summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@google.com>2023-09-29 11:04:49 -0400
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-10-31 16:03:54 +0000
commitb32ccbb66bb16965ecb8b4e266c4e45186636c1b (patch)
tree1c1eda32af709f0cbf822de56f696ccd531ce6de
parentb99272c601bc5f466c3cfc782bb852c2c967ad27 (diff)
downloadgit-repo-b32ccbb66bb16965ecb8b4e266c4e45186636c1b.tar.gz
cleanup: Update codebase to expect Python 3.6
- Bump minimum version to Python 3.6. - Use f-strings in a lot of places. Change-Id: I2aa70197230fcec2eff8e7c8eb754f20c08075bb Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/389034 Tested-by: Jason R. Coombs <jaraco@google.com> Reviewed-by: Mike Frysinger <vapier@google.com> Commit-Queue: Jason R. Coombs <jaraco@google.com>
-rw-r--r--color.py2
-rw-r--r--editor.py4
-rw-r--r--git_command.py14
-rw-r--r--git_config.py20
-rw-r--r--git_trace2_event_log_base.py5
-rw-r--r--hooks.py11
-rwxr-xr-xmain.py7
-rw-r--r--manifest_xml.py36
-rw-r--r--platform_utils.py4
-rw-r--r--platform_utils_win32.py12
-rw-r--r--progress.py6
-rw-r--r--project.py69
-rwxr-xr-xrepo94
-rw-r--r--ssh.py2
-rw-r--r--subcmds/__init__.py4
-rw-r--r--subcmds/abandon.py4
-rw-r--r--subcmds/branches.py2
-rw-r--r--subcmds/checkout.py2
-rw-r--r--subcmds/diffmanifests.py24
-rw-r--r--subcmds/help.py2
-rw-r--r--subcmds/info.py2
-rw-r--r--subcmds/init.py2
-rw-r--r--subcmds/list.py2
-rw-r--r--subcmds/prune.py4
-rw-r--r--subcmds/start.py2
-rw-r--r--subcmds/sync.py2
-rw-r--r--subcmds/version.py35
-rw-r--r--tests/test_git_command.py7
-rw-r--r--tests/test_git_config.py2
-rw-r--r--tests/test_git_superproject.py2
-rw-r--r--tests/test_git_trace2_event_log.py2
-rw-r--r--tests/test_manifest_xml.py6
-rw-r--r--tests/test_project.py2
33 files changed, 169 insertions, 225 deletions
diff --git a/color.py b/color.py
index e3e2a5f3..77517156 100644
--- a/color.py
+++ b/color.py
@@ -194,7 +194,7 @@ class Coloring:
194 if not opt: 194 if not opt:
195 return _Color(fg, bg, attr) 195 return _Color(fg, bg, attr)
196 196
197 v = self._config.GetString("%s.%s" % (self._section, opt)) 197 v = self._config.GetString(f"{self._section}.{opt}")
198 if v is None: 198 if v is None:
199 return _Color(fg, bg, attr) 199 return _Color(fg, bg, attr)
200 200
diff --git a/editor.py b/editor.py
index 10ff158b..359cff93 100644
--- a/editor.py
+++ b/editor.py
@@ -104,9 +104,7 @@ least one of these before using this command.""", # noqa: E501
104 try: 104 try:
105 rc = subprocess.Popen(args, shell=shell).wait() 105 rc = subprocess.Popen(args, shell=shell).wait()
106 except OSError as e: 106 except OSError as e:
107 raise EditorError( 107 raise EditorError(f"editor failed, {str(e)}: {editor} {path}")
108 "editor failed, %s: %s %s" % (str(e), editor, path)
109 )
110 if rc != 0: 108 if rc != 0:
111 raise EditorError( 109 raise EditorError(
112 "editor failed with exit status %d: %s %s" 110 "editor failed with exit status %d: %s %s"
diff --git a/git_command.py b/git_command.py
index 0e256392..3c3869a2 100644
--- a/git_command.py
+++ b/git_command.py
@@ -196,12 +196,10 @@ class UserAgent:
196 def git(self): 196 def git(self):
197 """The UA when running git.""" 197 """The UA when running git."""
198 if self._git_ua is None: 198 if self._git_ua is None:
199 self._git_ua = "git/%s (%s) git-repo/%s" % ( 199 self._git_ua = (
200 git.version_tuple().full, 200 f"git/{git.version_tuple().full} ({self.os}) "
201 self.os, 201 f"git-repo/{RepoSourceVersion()}"
202 RepoSourceVersion(),
203 ) 202 )
204
205 return self._git_ua 203 return self._git_ua
206 204
207 205
@@ -216,7 +214,7 @@ def git_require(min_version, fail=False, msg=""):
216 need = ".".join(map(str, min_version)) 214 need = ".".join(map(str, min_version))
217 if msg: 215 if msg:
218 msg = " for " + msg 216 msg = " for " + msg
219 error_msg = "fatal: git %s or later required%s" % (need, msg) 217 error_msg = f"fatal: git {need} or later required{msg}"
220 logger.error(error_msg) 218 logger.error(error_msg)
221 raise GitRequireError(error_msg) 219 raise GitRequireError(error_msg)
222 return False 220 return False
@@ -243,7 +241,7 @@ def _build_env(
243 env["GIT_SSH"] = ssh_proxy.proxy 241 env["GIT_SSH"] = ssh_proxy.proxy
244 env["GIT_SSH_VARIANT"] = "ssh" 242 env["GIT_SSH_VARIANT"] = "ssh"
245 if "http_proxy" in env and "darwin" == sys.platform: 243 if "http_proxy" in env and "darwin" == sys.platform:
246 s = "'http.proxy=%s'" % (env["http_proxy"],) 244 s = f"'http.proxy={env['http_proxy']}'"
247 p = env.get("GIT_CONFIG_PARAMETERS") 245 p = env.get("GIT_CONFIG_PARAMETERS")
248 if p is not None: 246 if p is not None:
249 s = p + " " + s 247 s = p + " " + s
@@ -468,7 +466,7 @@ class GitCommand:
468 ) 466 )
469 except Exception as e: 467 except Exception as e:
470 raise GitPopenCommandError( 468 raise GitPopenCommandError(
471 message="%s: %s" % (command[1], e), 469 message=f"{command[1]}: {e}",
472 project=self.project.name if self.project else None, 470 project=self.project.name if self.project else None,
473 command_args=self.cmdv, 471 command_args=self.cmdv,
474 ) 472 )
diff --git a/git_config.py b/git_config.py
index 6aa8d855..68016fff 100644
--- a/git_config.py
+++ b/git_config.py
@@ -418,7 +418,7 @@ class GitConfig:
418 if p.Wait() == 0: 418 if p.Wait() == 0:
419 return p.stdout 419 return p.stdout
420 else: 420 else:
421 raise GitError("git config %s: %s" % (str(args), p.stderr)) 421 raise GitError(f"git config {str(args)}: {p.stderr}")
422 422
423 423
424class RepoConfig(GitConfig): 424class RepoConfig(GitConfig):
@@ -651,13 +651,11 @@ class Remote:
651 userEmail, host, port 651 userEmail, host, port
652 ) 652 )
653 except urllib.error.HTTPError as e: 653 except urllib.error.HTTPError as e:
654 raise UploadError("%s: %s" % (self.review, str(e))) 654 raise UploadError(f"{self.review}: {str(e)}")
655 except urllib.error.URLError as e: 655 except urllib.error.URLError as e:
656 raise UploadError("%s: %s" % (self.review, str(e))) 656 raise UploadError(f"{self.review}: {str(e)}")
657 except http.client.HTTPException as e: 657 except http.client.HTTPException as e:
658 raise UploadError( 658 raise UploadError(f"{self.review}: {e.__class__.__name__}")
659 "%s: %s" % (self.review, e.__class__.__name__)
660 )
661 659
662 REVIEW_CACHE[u] = self._review_url 660 REVIEW_CACHE[u] = self._review_url
663 return self._review_url + self.projectname 661 return self._review_url + self.projectname
@@ -666,7 +664,7 @@ class Remote:
666 username = self._config.GetString("review.%s.username" % self.review) 664 username = self._config.GetString("review.%s.username" % self.review)
667 if username is None: 665 if username is None:
668 username = userEmail.split("@")[0] 666 username = userEmail.split("@")[0]
669 return "ssh://%s@%s:%s/" % (username, host, port) 667 return f"ssh://{username}@{host}:{port}/"
670 668
671 def ToLocal(self, rev): 669 def ToLocal(self, rev):
672 """Convert a remote revision string to something we have locally.""" 670 """Convert a remote revision string to something we have locally."""
@@ -715,11 +713,11 @@ class Remote:
715 self._Set("fetch", list(map(str, self.fetch))) 713 self._Set("fetch", list(map(str, self.fetch)))
716 714
717 def _Set(self, key, value): 715 def _Set(self, key, value):
718 key = "remote.%s.%s" % (self.name, key) 716 key = f"remote.{self.name}.{key}"
719 return self._config.SetString(key, value) 717 return self._config.SetString(key, value)
720 718
721 def _Get(self, key, all_keys=False): 719 def _Get(self, key, all_keys=False):
722 key = "remote.%s.%s" % (self.name, key) 720 key = f"remote.{self.name}.{key}"
723 return self._config.GetString(key, all_keys=all_keys) 721 return self._config.GetString(key, all_keys=all_keys)
724 722
725 723
@@ -762,11 +760,11 @@ class Branch:
762 fd.write("\tmerge = %s\n" % self.merge) 760 fd.write("\tmerge = %s\n" % self.merge)
763 761
764 def _Set(self, key, value): 762 def _Set(self, key, value):
765 key = "branch.%s.%s" % (self.name, key) 763 key = f"branch.{self.name}.{key}"
766 return self._config.SetString(key, value) 764 return self._config.SetString(key, value)
767 765
768 def _Get(self, key, all_keys=False): 766 def _Get(self, key, all_keys=False):
769 key = "branch.%s.%s" % (self.name, key) 767 key = f"branch.{self.name}.{key}"
770 return self._config.GetString(key, all_keys=all_keys) 768 return self._config.GetString(key, all_keys=all_keys)
771 769
772 770
diff --git a/git_trace2_event_log_base.py b/git_trace2_event_log_base.py
index 7b51b753..f5424249 100644
--- a/git_trace2_event_log_base.py
+++ b/git_trace2_event_log_base.py
@@ -76,9 +76,8 @@ class BaseEventLog:
76 # Save both our sid component and the complete sid. 76 # Save both our sid component and the complete sid.
77 # We use our sid component (self._sid) as the unique filename prefix and 77 # We use our sid component (self._sid) as the unique filename prefix and
78 # the full sid (self._full_sid) in the log itself. 78 # the full sid (self._full_sid) in the log itself.
79 self._sid = "repo-%s-P%08x" % ( 79 self._sid = (
80 self.start.strftime("%Y%m%dT%H%M%SZ"), 80 f"repo-{self.start.strftime('%Y%m%dT%H%M%SZ')}-P{os.getpid():08x}"
81 os.getpid(),
82 ) 81 )
83 82
84 if add_init_count: 83 if add_init_count:
diff --git a/hooks.py b/hooks.py
index 6ded08b9..82bf7e36 100644
--- a/hooks.py
+++ b/hooks.py
@@ -180,7 +180,7 @@ class RepoHook:
180 abort_if_user_denies was passed to the consturctor. 180 abort_if_user_denies was passed to the consturctor.
181 """ 181 """
182 hooks_config = self._hooks_project.config 182 hooks_config = self._hooks_project.config
183 git_approval_key = "repo.hooks.%s.%s" % (self._hook_type, subkey) 183 git_approval_key = f"repo.hooks.{self._hook_type}.{subkey}"
184 184
185 # Get the last value that the user approved for this hook; may be None. 185 # Get the last value that the user approved for this hook; may be None.
186 old_val = hooks_config.GetString(git_approval_key) 186 old_val = hooks_config.GetString(git_approval_key)
@@ -193,7 +193,7 @@ class RepoHook:
193 else: 193 else:
194 # Give the user a reason why we're prompting, since they last 194 # Give the user a reason why we're prompting, since they last
195 # told us to "never ask again". 195 # told us to "never ask again".
196 prompt = "WARNING: %s\n\n" % (changed_prompt,) 196 prompt = f"WARNING: {changed_prompt}\n\n"
197 else: 197 else:
198 prompt = "" 198 prompt = ""
199 199
@@ -241,9 +241,8 @@ class RepoHook:
241 return self._CheckForHookApprovalHelper( 241 return self._CheckForHookApprovalHelper(
242 "approvedmanifest", 242 "approvedmanifest",
243 self._manifest_url, 243 self._manifest_url,
244 "Run hook scripts from %s" % (self._manifest_url,), 244 f"Run hook scripts from {self._manifest_url}",
245 "Manifest URL has changed since %s was allowed." 245 f"Manifest URL has changed since {self._hook_type} was allowed.",
246 % (self._hook_type,),
247 ) 246 )
248 247
249 def _CheckForHookApprovalHash(self): 248 def _CheckForHookApprovalHash(self):
@@ -262,7 +261,7 @@ class RepoHook:
262 "approvedhash", 261 "approvedhash",
263 self._GetHash(), 262 self._GetHash(),
264 prompt % (self._GetMustVerb(), self._script_fullpath), 263 prompt % (self._GetMustVerb(), self._script_fullpath),
265 "Scripts have changed since %s was allowed." % (self._hook_type,), 264 f"Scripts have changed since {self._hook_type} was allowed.",
266 ) 265 )
267 266
268 @staticmethod 267 @staticmethod
diff --git a/main.py b/main.py
index bd8d5135..604de76f 100755
--- a/main.py
+++ b/main.py
@@ -198,9 +198,8 @@ class _Repo:
198 if short: 198 if short:
199 commands = " ".join(sorted(self.commands)) 199 commands = " ".join(sorted(self.commands))
200 wrapped_commands = textwrap.wrap(commands, width=77) 200 wrapped_commands = textwrap.wrap(commands, width=77)
201 print( 201 help_commands = "".join(f"\n {x}" for x in wrapped_commands)
202 "Available commands:\n %s" % ("\n ".join(wrapped_commands),) 202 print(f"Available commands:{help_commands}")
203 )
204 print("\nRun `repo help <command>` for command-specific details.") 203 print("\nRun `repo help <command>` for command-specific details.")
205 print("Bug reports:", Wrapper().BUG_URL) 204 print("Bug reports:", Wrapper().BUG_URL)
206 else: 205 else:
@@ -236,7 +235,7 @@ class _Repo:
236 if name in self.commands: 235 if name in self.commands:
237 return name, [] 236 return name, []
238 237
239 key = "alias.%s" % (name,) 238 key = f"alias.{name}"
240 alias = RepoConfig.ForRepository(self.repodir).GetString(key) 239 alias = RepoConfig.ForRepository(self.repodir).GetString(key)
241 if alias is None: 240 if alias is None:
242 alias = RepoConfig.ForUser().GetString(key) 241 alias = RepoConfig.ForUser().GetString(key)
diff --git a/manifest_xml.py b/manifest_xml.py
index 97baed57..61b130cf 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -114,9 +114,7 @@ def XmlInt(node, attr, default=None):
114 try: 114 try:
115 return int(value) 115 return int(value)
116 except ValueError: 116 except ValueError:
117 raise ManifestParseError( 117 raise ManifestParseError(f'manifest: invalid {attr}="{value}" integer')
118 'manifest: invalid %s="%s" integer' % (attr, value)
119 )
120 118
121 119
122class _Default: 120class _Default:
@@ -810,7 +808,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
810 ret.setdefault(child.nodeName, []).append(element) 808 ret.setdefault(child.nodeName, []).append(element)
811 else: 809 else:
812 raise ManifestParseError( 810 raise ManifestParseError(
813 'Unhandled element "%s"' % (child.nodeName,) 811 f'Unhandled element "{child.nodeName}"'
814 ) 812 )
815 813
816 append_children(element, child) 814 append_children(element, child)
@@ -1258,12 +1256,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1258 try: 1256 try:
1259 root = xml.dom.minidom.parse(path) 1257 root = xml.dom.minidom.parse(path)
1260 except (OSError, xml.parsers.expat.ExpatError) as e: 1258 except (OSError, xml.parsers.expat.ExpatError) as e:
1261 raise ManifestParseError( 1259 raise ManifestParseError(f"error parsing manifest {path}: {e}")
1262 "error parsing manifest %s: %s" % (path, e)
1263 )
1264 1260
1265 if not root or not root.childNodes: 1261 if not root or not root.childNodes:
1266 raise ManifestParseError("no root node in %s" % (path,)) 1262 raise ManifestParseError(f"no root node in {path}")
1267 1263
1268 for manifest in root.childNodes: 1264 for manifest in root.childNodes:
1269 if ( 1265 if (
@@ -1272,7 +1268,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1272 ): 1268 ):
1273 break 1269 break
1274 else: 1270 else:
1275 raise ManifestParseError("no <manifest> in %s" % (path,)) 1271 raise ManifestParseError(f"no <manifest> in {path}")
1276 1272
1277 nodes = [] 1273 nodes = []
1278 for node in manifest.childNodes: 1274 for node in manifest.childNodes:
@@ -1282,7 +1278,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1282 msg = self._CheckLocalPath(name) 1278 msg = self._CheckLocalPath(name)
1283 if msg: 1279 if msg:
1284 raise ManifestInvalidPathError( 1280 raise ManifestInvalidPathError(
1285 '<include> invalid "name": %s: %s' % (name, msg) 1281 f'<include> invalid "name": {name}: {msg}'
1286 ) 1282 )
1287 include_groups = "" 1283 include_groups = ""
1288 if parent_groups: 1284 if parent_groups:
@@ -1314,7 +1310,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1314 raise 1310 raise
1315 except Exception as e: 1311 except Exception as e:
1316 raise ManifestParseError( 1312 raise ManifestParseError(
1317 "failed parsing included manifest %s: %s" % (name, e) 1313 f"failed parsing included manifest {name}: {e}"
1318 ) 1314 )
1319 else: 1315 else:
1320 if parent_groups and node.nodeName == "project": 1316 if parent_groups and node.nodeName == "project":
@@ -1765,13 +1761,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1765 msg = self._CheckLocalPath(name) 1761 msg = self._CheckLocalPath(name)
1766 if msg: 1762 if msg:
1767 raise ManifestInvalidPathError( 1763 raise ManifestInvalidPathError(
1768 '<submanifest> invalid "name": %s: %s' % (name, msg) 1764 f'<submanifest> invalid "name": {name}: {msg}'
1769 ) 1765 )
1770 else: 1766 else:
1771 msg = self._CheckLocalPath(path) 1767 msg = self._CheckLocalPath(path)
1772 if msg: 1768 if msg:
1773 raise ManifestInvalidPathError( 1769 raise ManifestInvalidPathError(
1774 '<submanifest> invalid "path": %s: %s' % (path, msg) 1770 f'<submanifest> invalid "path": {path}: {msg}'
1775 ) 1771 )
1776 1772
1777 submanifest = _XmlSubmanifest( 1773 submanifest = _XmlSubmanifest(
@@ -1806,7 +1802,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1806 msg = self._CheckLocalPath(name, dir_ok=True) 1802 msg = self._CheckLocalPath(name, dir_ok=True)
1807 if msg: 1803 if msg:
1808 raise ManifestInvalidPathError( 1804 raise ManifestInvalidPathError(
1809 '<project> invalid "name": %s: %s' % (name, msg) 1805 f'<project> invalid "name": {name}: {msg}'
1810 ) 1806 )
1811 if parent: 1807 if parent:
1812 name = self._JoinName(parent.name, name) 1808 name = self._JoinName(parent.name, name)
@@ -1816,7 +1812,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1816 remote = self._default.remote 1812 remote = self._default.remote
1817 if remote is None: 1813 if remote is None:
1818 raise ManifestParseError( 1814 raise ManifestParseError(
1819 "no remote for project %s within %s" % (name, self.manifestFile) 1815 f"no remote for project {name} within {self.manifestFile}"
1820 ) 1816 )
1821 1817
1822 revisionExpr = node.getAttribute("revision") or remote.revision 1818 revisionExpr = node.getAttribute("revision") or remote.revision
@@ -1837,7 +1833,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1837 msg = self._CheckLocalPath(path, dir_ok=True, cwd_dot_ok=True) 1833 msg = self._CheckLocalPath(path, dir_ok=True, cwd_dot_ok=True)
1838 if msg: 1834 if msg:
1839 raise ManifestInvalidPathError( 1835 raise ManifestInvalidPathError(
1840 '<project> invalid "path": %s: %s' % (path, msg) 1836 f'<project> invalid "path": {path}: {msg}'
1841 ) 1837 )
1842 1838
1843 rebase = XmlBool(node, "rebase", True) 1839 rebase = XmlBool(node, "rebase", True)
@@ -2094,7 +2090,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
2094 if not cwd_dot_ok or parts != ["."]: 2090 if not cwd_dot_ok or parts != ["."]:
2095 for part in set(parts): 2091 for part in set(parts):
2096 if part in {".", "..", ".git"} or part.startswith(".repo"): 2092 if part in {".", "..", ".git"} or part.startswith(".repo"):
2097 return "bad component: %s" % (part,) 2093 return f"bad component: {part}"
2098 2094
2099 if not dir_ok and resep.match(path[-1]): 2095 if not dir_ok and resep.match(path[-1]):
2100 return "dirs not allowed" 2096 return "dirs not allowed"
@@ -2130,7 +2126,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
2130 msg = cls._CheckLocalPath(dest) 2126 msg = cls._CheckLocalPath(dest)
2131 if msg: 2127 if msg:
2132 raise ManifestInvalidPathError( 2128 raise ManifestInvalidPathError(
2133 '<%s> invalid "dest": %s: %s' % (element, dest, msg) 2129 f'<{element}> invalid "dest": {dest}: {msg}'
2134 ) 2130 )
2135 2131
2136 # |src| is the file we read from or path we point to for symlinks. 2132 # |src| is the file we read from or path we point to for symlinks.
@@ -2141,7 +2137,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
2141 ) 2137 )
2142 if msg: 2138 if msg:
2143 raise ManifestInvalidPathError( 2139 raise ManifestInvalidPathError(
2144 '<%s> invalid "src": %s: %s' % (element, src, msg) 2140 f'<{element}> invalid "src": {src}: {msg}'
2145 ) 2141 )
2146 2142
2147 def _ParseCopyFile(self, project, node): 2143 def _ParseCopyFile(self, project, node):
@@ -2185,7 +2181,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
2185 v = self._remotes.get(name) 2181 v = self._remotes.get(name)
2186 if not v: 2182 if not v:
2187 raise ManifestParseError( 2183 raise ManifestParseError(
2188 "remote %s not defined in %s" % (name, self.manifestFile) 2184 f"remote {name} not defined in {self.manifestFile}"
2189 ) 2185 )
2190 return v 2186 return v
2191 2187
diff --git a/platform_utils.py b/platform_utils.py
index d720a07e..4cf994bc 100644
--- a/platform_utils.py
+++ b/platform_utils.py
@@ -57,8 +57,8 @@ def _validate_winpath(path):
57 if _winpath_is_valid(path): 57 if _winpath_is_valid(path):
58 return path 58 return path
59 raise ValueError( 59 raise ValueError(
60 'Path "{}" must be a relative path or an absolute ' 60 f'Path "{path}" must be a relative path or an absolute '
61 "path starting with a drive letter".format(path) 61 "path starting with a drive letter"
62 ) 62 )
63 63
64 64
diff --git a/platform_utils_win32.py b/platform_utils_win32.py
index 80a52639..f10d9d0a 100644
--- a/platform_utils_win32.py
+++ b/platform_utils_win32.py
@@ -186,9 +186,7 @@ def _create_symlink(source, link_name, dwFlags):
186 error_desc = FormatError(code).strip() 186 error_desc = FormatError(code).strip()
187 if code == ERROR_PRIVILEGE_NOT_HELD: 187 if code == ERROR_PRIVILEGE_NOT_HELD:
188 raise OSError(errno.EPERM, error_desc, link_name) 188 raise OSError(errno.EPERM, error_desc, link_name)
189 _raise_winerror( 189 _raise_winerror(code, f'Error creating symbolic link "{link_name}"')
190 code, 'Error creating symbolic link "{}"'.format(link_name)
191 )
192 190
193 191
194def islink(path): 192def islink(path):
@@ -210,7 +208,7 @@ def readlink(path):
210 ) 208 )
211 if reparse_point_handle == INVALID_HANDLE_VALUE: 209 if reparse_point_handle == INVALID_HANDLE_VALUE:
212 _raise_winerror( 210 _raise_winerror(
213 get_last_error(), 'Error opening symbolic link "{}"'.format(path) 211 get_last_error(), f'Error opening symbolic link "{path}"'
214 ) 212 )
215 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) 213 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
216 n_bytes_returned = DWORD() 214 n_bytes_returned = DWORD()
@@ -227,7 +225,7 @@ def readlink(path):
227 CloseHandle(reparse_point_handle) 225 CloseHandle(reparse_point_handle)
228 if not io_result: 226 if not io_result:
229 _raise_winerror( 227 _raise_winerror(
230 get_last_error(), 'Error reading symbolic link "{}"'.format(path) 228 get_last_error(), f'Error reading symbolic link "{path}"'
231 ) 229 )
232 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer) 230 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
233 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK: 231 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
@@ -236,11 +234,11 @@ def readlink(path):
236 return rdb.MountPointReparseBuffer.PrintName 234 return rdb.MountPointReparseBuffer.PrintName
237 # Unsupported reparse point type. 235 # Unsupported reparse point type.
238 _raise_winerror( 236 _raise_winerror(
239 ERROR_NOT_SUPPORTED, 'Error reading symbolic link "{}"'.format(path) 237 ERROR_NOT_SUPPORTED, f'Error reading symbolic link "{path}"'
240 ) 238 )
241 239
242 240
243def _raise_winerror(code, error_desc): 241def _raise_winerror(code, error_desc):
244 win_error_desc = FormatError(code).strip() 242 win_error_desc = FormatError(code).strip()
245 error_desc = "{0}: {1}".format(error_desc, win_error_desc) 243 error_desc = f"{error_desc}: {win_error_desc}"
246 raise WinError(code, error_desc) 244 raise WinError(code, error_desc)
diff --git a/progress.py b/progress.py
index 54eb8f93..290265c6 100644
--- a/progress.py
+++ b/progress.py
@@ -52,11 +52,11 @@ def duration_str(total):
52 uses microsecond resolution. This makes for noisy output. 52 uses microsecond resolution. This makes for noisy output.
53 """ 53 """
54 hours, mins, secs = convert_to_hms(total) 54 hours, mins, secs = convert_to_hms(total)
55 ret = "%.3fs" % (secs,) 55 ret = f"{secs:.3f}s"
56 if mins: 56 if mins:
57 ret = "%im%s" % (mins, ret) 57 ret = f"{mins}m{ret}"
58 if hours: 58 if hours:
59 ret = "%ih%s" % (hours, ret) 59 ret = f"{hours}h{ret}"
60 return ret 60 return ret
61 61
62 62
diff --git a/project.py b/project.py
index 7b78427d..069cc712 100644
--- a/project.py
+++ b/project.py
@@ -365,19 +365,19 @@ def _SafeExpandPath(base, subpath, skipfinal=False):
365 for part in components: 365 for part in components:
366 if part in {".", ".."}: 366 if part in {".", ".."}:
367 raise ManifestInvalidPathError( 367 raise ManifestInvalidPathError(
368 '%s: "%s" not allowed in paths' % (subpath, part) 368 f'{subpath}: "{part}" not allowed in paths'
369 ) 369 )
370 370
371 path = os.path.join(path, part) 371 path = os.path.join(path, part)
372 if platform_utils.islink(path): 372 if platform_utils.islink(path):
373 raise ManifestInvalidPathError( 373 raise ManifestInvalidPathError(
374 "%s: traversing symlinks not allow" % (path,) 374 f"{path}: traversing symlinks not allow"
375 ) 375 )
376 376
377 if os.path.exists(path): 377 if os.path.exists(path):
378 if not os.path.isfile(path) and not platform_utils.isdir(path): 378 if not os.path.isfile(path) and not platform_utils.isdir(path):
379 raise ManifestInvalidPathError( 379 raise ManifestInvalidPathError(
380 "%s: only regular files & directories allowed" % (path,) 380 f"{path}: only regular files & directories allowed"
381 ) 381 )
382 382
383 if skipfinal: 383 if skipfinal:
@@ -409,11 +409,11 @@ class _CopyFile:
409 409
410 if platform_utils.isdir(src): 410 if platform_utils.isdir(src):
411 raise ManifestInvalidPathError( 411 raise ManifestInvalidPathError(
412 "%s: copying from directory not supported" % (self.src,) 412 f"{self.src}: copying from directory not supported"
413 ) 413 )
414 if platform_utils.isdir(dest): 414 if platform_utils.isdir(dest):
415 raise ManifestInvalidPathError( 415 raise ManifestInvalidPathError(
416 "%s: copying to directory not allowed" % (self.dest,) 416 f"{self.dest}: copying to directory not allowed"
417 ) 417 )
418 418
419 # Copy file if it does not exist or is out of date. 419 # Copy file if it does not exist or is out of date.
@@ -957,15 +957,11 @@ class Project:
957 f_status = "-" 957 f_status = "-"
958 958
959 if i and i.src_path: 959 if i and i.src_path:
960 line = " %s%s\t%s => %s (%s%%)" % ( 960 line = (
961 i_status, 961 f" {i_status}{f_status}\t{i.src_path} => {p} ({i.level}%)"
962 f_status,
963 i.src_path,
964 p,
965 i.level,
966 ) 962 )
967 else: 963 else:
968 line = " %s%s\t%s" % (i_status, f_status, p) 964 line = f" {i_status}{f_status}\t{p}"
969 965
970 if i and not f: 966 if i and not f:
971 out.added("%s", line) 967 out.added("%s", line)
@@ -1157,7 +1153,7 @@ class Project:
1157 if dest_branch.startswith(R_HEADS): 1153 if dest_branch.startswith(R_HEADS):
1158 dest_branch = dest_branch[len(R_HEADS) :] 1154 dest_branch = dest_branch[len(R_HEADS) :]
1159 1155
1160 ref_spec = "%s:refs/for/%s" % (R_HEADS + branch.name, dest_branch) 1156 ref_spec = f"{R_HEADS + branch.name}:refs/for/{dest_branch}"
1161 opts = [] 1157 opts = []
1162 if auto_topic: 1158 if auto_topic:
1163 opts += ["topic=" + branch.name] 1159 opts += ["topic=" + branch.name]
@@ -1182,7 +1178,7 @@ class Project:
1182 GitCommand(self, cmd, bare=True, verify_command=True).Wait() 1178 GitCommand(self, cmd, bare=True, verify_command=True).Wait()
1183 1179
1184 if not dryrun: 1180 if not dryrun:
1185 msg = "posted to %s for %s" % (branch.remote.review, dest_branch) 1181 msg = f"posted to {branch.remote.review} for {dest_branch}"
1186 self.bare_git.UpdateRef( 1182 self.bare_git.UpdateRef(
1187 R_PUB + branch.name, R_HEADS + branch.name, message=msg 1183 R_PUB + branch.name, R_HEADS + branch.name, message=msg
1188 ) 1184 )
@@ -1444,7 +1440,7 @@ class Project:
1444 return self.bare_git.rev_list(self.revisionExpr, "-1")[0] 1440 return self.bare_git.rev_list(self.revisionExpr, "-1")[0]
1445 except GitError: 1441 except GitError:
1446 raise ManifestInvalidRevisionError( 1442 raise ManifestInvalidRevisionError(
1447 "revision %s in %s not found" % (self.revisionExpr, self.name) 1443 f"revision {self.revisionExpr} in {self.name} not found"
1448 ) 1444 )
1449 1445
1450 def GetRevisionId(self, all_refs=None): 1446 def GetRevisionId(self, all_refs=None):
@@ -1461,7 +1457,7 @@ class Project:
1461 return self.bare_git.rev_parse("--verify", "%s^0" % rev) 1457 return self.bare_git.rev_parse("--verify", "%s^0" % rev)
1462 except GitError: 1458 except GitError:
1463 raise ManifestInvalidRevisionError( 1459 raise ManifestInvalidRevisionError(
1464 "revision %s in %s not found" % (self.revisionExpr, self.name) 1460 f"revision {self.revisionExpr} in {self.name} not found"
1465 ) 1461 )
1466 1462
1467 def SetRevisionId(self, revisionId): 1463 def SetRevisionId(self, revisionId):
@@ -1773,9 +1769,7 @@ class Project:
1773 raise DeleteDirtyWorktreeError(msg, project=self) 1769 raise DeleteDirtyWorktreeError(msg, project=self)
1774 1770
1775 if not quiet: 1771 if not quiet:
1776 print( 1772 print(f"{self.RelPath(local=False)}: Deleting obsolete checkout.")
1777 "%s: Deleting obsolete checkout." % (self.RelPath(local=False),)
1778 )
1779 1773
1780 # Unlock and delink from the main worktree. We don't use git's worktree 1774 # Unlock and delink from the main worktree. We don't use git's worktree
1781 # remove because it will recursively delete projects -- we handle that 1775 # remove because it will recursively delete projects -- we handle that
@@ -1968,7 +1962,7 @@ class Project:
1968 # target branch, but otherwise take no other action. 1962 # target branch, but otherwise take no other action.
1969 _lwrite( 1963 _lwrite(
1970 self.work_git.GetDotgitPath(subpath=HEAD), 1964 self.work_git.GetDotgitPath(subpath=HEAD),
1971 "ref: %s%s\n" % (R_HEADS, name), 1965 f"ref: {R_HEADS}{name}\n",
1972 ) 1966 )
1973 return True 1967 return True
1974 1968
@@ -2277,7 +2271,7 @@ class Project:
2277 self.config.SetString("core.repositoryFormatVersion", str(version)) 2271 self.config.SetString("core.repositoryFormatVersion", str(version))
2278 2272
2279 # Enable the extension! 2273 # Enable the extension!
2280 self.config.SetString("extensions.%s" % (key,), value) 2274 self.config.SetString(f"extensions.{key}", value)
2281 2275
2282 def ResolveRemoteHead(self, name=None): 2276 def ResolveRemoteHead(self, name=None):
2283 """Find out what the default branch (HEAD) points to. 2277 """Find out what the default branch (HEAD) points to.
@@ -2447,7 +2441,7 @@ class Project:
2447 old_packed_lines = [] 2441 old_packed_lines = []
2448 2442
2449 for r in sorted(all_refs): 2443 for r in sorted(all_refs):
2450 line = "%s %s\n" % (all_refs[r], r) 2444 line = f"{all_refs[r]} {r}\n"
2451 tmp_packed_lines.append(line) 2445 tmp_packed_lines.append(line)
2452 if r not in tmp: 2446 if r not in tmp:
2453 old_packed_lines.append(line) 2447 old_packed_lines.append(line)
@@ -2617,7 +2611,7 @@ class Project:
2617 # one. 2611 # one.
2618 if not verbose and gitcmd.stdout: 2612 if not verbose and gitcmd.stdout:
2619 print( 2613 print(
2620 "\n%s:\n%s" % (self.name, gitcmd.stdout), 2614 f"\n{self.name}:\n{gitcmd.stdout}",
2621 end="", 2615 end="",
2622 file=output_redir, 2616 file=output_redir,
2623 ) 2617 )
@@ -2752,7 +2746,7 @@ class Project:
2752 proc = None 2746 proc = None
2753 with Trace("Fetching bundle: %s", " ".join(cmd)): 2747 with Trace("Fetching bundle: %s", " ".join(cmd)):
2754 if verbose: 2748 if verbose:
2755 print("%s: Downloading bundle: %s" % (self.name, srcUrl)) 2749 print(f"{self.name}: Downloading bundle: {srcUrl}")
2756 stdout = None if verbose else subprocess.PIPE 2750 stdout = None if verbose else subprocess.PIPE
2757 stderr = None if verbose else subprocess.STDOUT 2751 stderr = None if verbose else subprocess.STDOUT
2758 try: 2752 try:
@@ -2810,7 +2804,7 @@ class Project:
2810 if GitCommand(self, cmd).Wait() != 0: 2804 if GitCommand(self, cmd).Wait() != 0:
2811 if self._allrefs: 2805 if self._allrefs:
2812 raise GitError( 2806 raise GitError(
2813 "%s checkout %s " % (self.name, rev), project=self.name 2807 f"{self.name} checkout {rev} ", project=self.name
2814 ) 2808 )
2815 2809
2816 def _CherryPick(self, rev, ffonly=False, record_origin=False): 2810 def _CherryPick(self, rev, ffonly=False, record_origin=False):
@@ -2824,7 +2818,7 @@ class Project:
2824 if GitCommand(self, cmd).Wait() != 0: 2818 if GitCommand(self, cmd).Wait() != 0:
2825 if self._allrefs: 2819 if self._allrefs:
2826 raise GitError( 2820 raise GitError(
2827 "%s cherry-pick %s " % (self.name, rev), project=self.name 2821 f"{self.name} cherry-pick {rev} ", project=self.name
2828 ) 2822 )
2829 2823
2830 def _LsRemote(self, refs): 2824 def _LsRemote(self, refs):
@@ -2841,9 +2835,7 @@ class Project:
2841 cmd.append("--") 2835 cmd.append("--")
2842 if GitCommand(self, cmd).Wait() != 0: 2836 if GitCommand(self, cmd).Wait() != 0:
2843 if self._allrefs: 2837 if self._allrefs:
2844 raise GitError( 2838 raise GitError(f"{self.name} revert {rev} ", project=self.name)
2845 "%s revert %s " % (self.name, rev), project=self.name
2846 )
2847 2839
2848 def _ResetHard(self, rev, quiet=True): 2840 def _ResetHard(self, rev, quiet=True):
2849 cmd = ["reset", "--hard"] 2841 cmd = ["reset", "--hard"]
@@ -2852,7 +2844,7 @@ class Project:
2852 cmd.append(rev) 2844 cmd.append(rev)
2853 if GitCommand(self, cmd).Wait() != 0: 2845 if GitCommand(self, cmd).Wait() != 0:
2854 raise GitError( 2846 raise GitError(
2855 "%s reset --hard %s " % (self.name, rev), project=self.name 2847 f"{self.name} reset --hard {rev} ", project=self.name
2856 ) 2848 )
2857 2849
2858 def _SyncSubmodules(self, quiet=True): 2850 def _SyncSubmodules(self, quiet=True):
@@ -2871,18 +2863,14 @@ class Project:
2871 cmd.extend(["--onto", onto]) 2863 cmd.extend(["--onto", onto])
2872 cmd.append(upstream) 2864 cmd.append(upstream)
2873 if GitCommand(self, cmd).Wait() != 0: 2865 if GitCommand(self, cmd).Wait() != 0:
2874 raise GitError( 2866 raise GitError(f"{self.name} rebase {upstream} ", project=self.name)
2875 "%s rebase %s " % (self.name, upstream), project=self.name
2876 )
2877 2867
2878 def _FastForward(self, head, ffonly=False): 2868 def _FastForward(self, head, ffonly=False):
2879 cmd = ["merge", "--no-stat", head] 2869 cmd = ["merge", "--no-stat", head]
2880 if ffonly: 2870 if ffonly:
2881 cmd.append("--ff-only") 2871 cmd.append("--ff-only")
2882 if GitCommand(self, cmd).Wait() != 0: 2872 if GitCommand(self, cmd).Wait() != 0:
2883 raise GitError( 2873 raise GitError(f"{self.name} merge {head} ", project=self.name)
2884 "%s merge %s " % (self.name, head), project=self.name
2885 )
2886 2874
2887 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False): 2875 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
2888 init_git_dir = not os.path.exists(self.gitdir) 2876 init_git_dir = not os.path.exists(self.gitdir)
@@ -3171,8 +3159,9 @@ class Project:
3171 "--force-sync not enabled; cannot overwrite a local " 3159 "--force-sync not enabled; cannot overwrite a local "
3172 "work tree. If you're comfortable with the " 3160 "work tree. If you're comfortable with the "
3173 "possibility of losing the work tree's git metadata," 3161 "possibility of losing the work tree's git metadata,"
3174 " use `repo sync --force-sync {0}` to " 3162 " use "
3175 "proceed.".format(self.RelPath(local=False)), 3163 f"`repo sync --force-sync {self.RelPath(local=False)}` "
3164 "to proceed.",
3176 project=self.name, 3165 project=self.name,
3177 ) 3166 )
3178 3167
@@ -3686,12 +3675,12 @@ class Project:
3686 config = kwargs.pop("config", None) 3675 config = kwargs.pop("config", None)
3687 for k in kwargs: 3676 for k in kwargs:
3688 raise TypeError( 3677 raise TypeError(
3689 "%s() got an unexpected keyword argument %r" % (name, k) 3678 f"{name}() got an unexpected keyword argument {k!r}"
3690 ) 3679 )
3691 if config is not None: 3680 if config is not None:
3692 for k, v in config.items(): 3681 for k, v in config.items():
3693 cmdv.append("-c") 3682 cmdv.append("-c")
3694 cmdv.append("%s=%s" % (k, v)) 3683 cmdv.append(f"{k}={v}")
3695 cmdv.append(name) 3684 cmdv.append(name)
3696 cmdv.extend(args) 3685 cmdv.extend(args)
3697 p = GitCommand( 3686 p = GitCommand(
diff --git a/repo b/repo
index a9ae4fa7..27381421 100755
--- a/repo
+++ b/repo
@@ -33,7 +33,7 @@ import sys
33# bit more flexible with older systems. See that file for more details on the 33# bit more flexible with older systems. See that file for more details on the
34# versions we select. 34# versions we select.
35MIN_PYTHON_VERSION_SOFT = (3, 6) 35MIN_PYTHON_VERSION_SOFT = (3, 6)
36MIN_PYTHON_VERSION_HARD = (3, 5) 36MIN_PYTHON_VERSION_HARD = (3, 6)
37 37
38 38
39# Keep basic logic in sync with repo_trace.py. 39# Keep basic logic in sync with repo_trace.py.
@@ -96,7 +96,7 @@ def check_python_version():
96 # bridge the gap. This is the fallback anyways so perf isn't critical. 96 # bridge the gap. This is the fallback anyways so perf isn't critical.
97 min_major, min_minor = MIN_PYTHON_VERSION_SOFT 97 min_major, min_minor = MIN_PYTHON_VERSION_SOFT
98 for inc in range(0, 10): 98 for inc in range(0, 10):
99 reexec("python{}.{}".format(min_major, min_minor + inc)) 99 reexec(f"python{min_major}.{min_minor + inc}")
100 100
101 # Fallback to older versions if possible. 101 # Fallback to older versions if possible.
102 for inc in range( 102 for inc in range(
@@ -105,7 +105,7 @@ def check_python_version():
105 # Don't downgrade, and don't reexec ourselves (which would infinite loop). 105 # Don't downgrade, and don't reexec ourselves (which would infinite loop).
106 if (min_major, min_minor - inc) <= (major, minor): 106 if (min_major, min_minor - inc) <= (major, minor):
107 break 107 break
108 reexec("python{}.{}".format(min_major, min_minor - inc)) 108 reexec(f"python{min_major}.{min_minor - inc}")
109 109
110 # Try the generic Python 3 wrapper, but only if it's new enough. If it 110 # Try the generic Python 3 wrapper, but only if it's new enough. If it
111 # isn't, we want to just give up below and make the user resolve things. 111 # isn't, we want to just give up below and make the user resolve things.
@@ -566,8 +566,7 @@ def run_command(cmd, **kwargs):
566 return output.decode("utf-8") 566 return output.decode("utf-8")
567 except UnicodeError: 567 except UnicodeError:
568 print( 568 print(
569 "repo: warning: Invalid UTF-8 output:\ncmd: %r\n%r" 569 f"repo: warning: Invalid UTF-8 output:\ncmd: {cmd!r}\n{output}",
570 % (cmd, output),
571 file=sys.stderr, 570 file=sys.stderr,
572 ) 571 )
573 return output.decode("utf-8", "backslashreplace") 572 return output.decode("utf-8", "backslashreplace")
@@ -590,20 +589,17 @@ def run_command(cmd, **kwargs):
590 # If things failed, print useful debugging output. 589 # If things failed, print useful debugging output.
591 if check and ret.returncode: 590 if check and ret.returncode:
592 print( 591 print(
593 'repo: error: "%s" failed with exit status %s' 592 f'repo: error: "{cmd[0]}" failed with exit status {ret.returncode}',
594 % (cmd[0], ret.returncode),
595 file=sys.stderr,
596 )
597 print(
598 " cwd: %s\n cmd: %r" % (kwargs.get("cwd", os.getcwd()), cmd),
599 file=sys.stderr, 593 file=sys.stderr,
600 ) 594 )
595 cwd = kwargs.get("cwd", os.getcwd())
596 print(f" cwd: {cwd}\n cmd: {cmd!r}", file=sys.stderr)
601 597
602 def _print_output(name, output): 598 def _print_output(name, output):
603 if output: 599 if output:
604 print( 600 print(
605 " %s:\n >> %s" 601 f" {name}:"
606 % (name, "\n >> ".join(output.splitlines())), 602 + "".join(f"\n >> {x}" for x in output.splitlines()),
607 file=sys.stderr, 603 file=sys.stderr,
608 ) 604 )
609 605
@@ -719,7 +715,7 @@ def _Init(args, gitc_init=False):
719 except OSError as e: 715 except OSError as e:
720 if e.errno != errno.EEXIST: 716 if e.errno != errno.EEXIST:
721 print( 717 print(
722 "fatal: cannot make %s directory: %s" % (repodir, e.strerror), 718 f"fatal: cannot make {repodir} directory: {e.strerror}",
723 file=sys.stderr, 719 file=sys.stderr,
724 ) 720 )
725 # Don't raise CloneFailure; that would delete the 721 # Don't raise CloneFailure; that would delete the
@@ -817,7 +813,7 @@ def _CheckGitVersion():
817 if ver_act < MIN_GIT_VERSION: 813 if ver_act < MIN_GIT_VERSION:
818 need = ".".join(map(str, MIN_GIT_VERSION)) 814 need = ".".join(map(str, MIN_GIT_VERSION))
819 print( 815 print(
820 "fatal: git %s or later required; found %s" % (need, ver_act.full), 816 f"fatal: git {need} or later required; found {ver_act.full}",
821 file=sys.stderr, 817 file=sys.stderr,
822 ) 818 )
823 raise CloneFailure() 819 raise CloneFailure()
@@ -836,7 +832,8 @@ def SetGitTrace2ParentSid(env=None):
836 KEY = "GIT_TRACE2_PARENT_SID" 832 KEY = "GIT_TRACE2_PARENT_SID"
837 833
838 now = datetime.datetime.now(datetime.timezone.utc) 834 now = datetime.datetime.now(datetime.timezone.utc)
839 value = "repo-%s-P%08x" % (now.strftime("%Y%m%dT%H%M%SZ"), os.getpid()) 835 timestamp = now.strftime("%Y%m%dT%H%M%SZ")
836 value = f"repo-{timestamp}-P{os.getpid():08x}"
840 837
841 # If it's already set, then append ourselves. 838 # If it's already set, then append ourselves.
842 if KEY in env: 839 if KEY in env:
@@ -880,8 +877,7 @@ def SetupGnuPG(quiet):
880 except OSError as e: 877 except OSError as e:
881 if e.errno != errno.EEXIST: 878 if e.errno != errno.EEXIST:
882 print( 879 print(
883 "fatal: cannot make %s directory: %s" 880 f"fatal: cannot make {home_dot_repo} directory: {e.strerror}",
884 % (home_dot_repo, e.strerror),
885 file=sys.stderr, 881 file=sys.stderr,
886 ) 882 )
887 sys.exit(1) 883 sys.exit(1)
@@ -891,15 +887,15 @@ def SetupGnuPG(quiet):
891 except OSError as e: 887 except OSError as e:
892 if e.errno != errno.EEXIST: 888 if e.errno != errno.EEXIST:
893 print( 889 print(
894 "fatal: cannot make %s directory: %s" % (gpg_dir, e.strerror), 890 f"fatal: cannot make {gpg_dir} directory: {e.strerror}",
895 file=sys.stderr, 891 file=sys.stderr,
896 ) 892 )
897 sys.exit(1) 893 sys.exit(1)
898 894
899 if not quiet: 895 if not quiet:
900 print( 896 print(
901 "repo: Updating release signing keys to keyset ver %s" 897 "repo: Updating release signing keys to keyset ver "
902 % (".".join(str(x) for x in KEYRING_VERSION),) 898 + ".".join(str(x) for x in KEYRING_VERSION),
903 ) 899 )
904 # NB: We use --homedir (and cwd below) because some environments (Windows) do 900 # NB: We use --homedir (and cwd below) because some environments (Windows) do
905 # not correctly handle full native paths. We avoid the issue by changing to 901 # not correctly handle full native paths. We avoid the issue by changing to
@@ -951,7 +947,7 @@ def _GetRepoConfig(name):
951 return None 947 return None
952 else: 948 else:
953 print( 949 print(
954 "repo: error: git %s failed:\n%s" % (" ".join(cmd), ret.stderr), 950 f"repo: error: git {' '.join(cmd)} failed:\n{ret.stderr}",
955 file=sys.stderr, 951 file=sys.stderr,
956 ) 952 )
957 raise RunError() 953 raise RunError()
@@ -1064,7 +1060,7 @@ def _Clone(url, cwd, clone_bundle, quiet, verbose):
1064 os.mkdir(cwd) 1060 os.mkdir(cwd)
1065 except OSError as e: 1061 except OSError as e:
1066 print( 1062 print(
1067 "fatal: cannot make %s directory: %s" % (cwd, e.strerror), 1063 f"fatal: cannot make {cwd} directory: {e.strerror}",
1068 file=sys.stderr, 1064 file=sys.stderr,
1069 ) 1065 )
1070 raise CloneFailure() 1066 raise CloneFailure()
@@ -1104,7 +1100,7 @@ def resolve_repo_rev(cwd, committish):
1104 ret = run_git( 1100 ret = run_git(
1105 "rev-parse", 1101 "rev-parse",
1106 "--verify", 1102 "--verify",
1107 "%s^{commit}" % (committish,), 1103 f"{committish}^{{commit}}",
1108 cwd=cwd, 1104 cwd=cwd,
1109 check=False, 1105 check=False,
1110 ) 1106 )
@@ -1117,7 +1113,7 @@ def resolve_repo_rev(cwd, committish):
1117 rev = resolve("refs/remotes/origin/%s" % committish) 1113 rev = resolve("refs/remotes/origin/%s" % committish)
1118 if rev is None: 1114 if rev is None:
1119 print( 1115 print(
1120 'repo: error: unknown branch "%s"' % (committish,), 1116 f'repo: error: unknown branch "{committish}"',
1121 file=sys.stderr, 1117 file=sys.stderr,
1122 ) 1118 )
1123 raise CloneFailure() 1119 raise CloneFailure()
@@ -1130,7 +1126,8 @@ def resolve_repo_rev(cwd, committish):
1130 rev = resolve(remote_ref) 1126 rev = resolve(remote_ref)
1131 if rev is None: 1127 if rev is None:
1132 print( 1128 print(
1133 'repo: error: unknown tag "%s"' % (committish,), file=sys.stderr 1129 f'repo: error: unknown tag "{committish}"',
1130 file=sys.stderr,
1134 ) 1131 )
1135 raise CloneFailure() 1132 raise CloneFailure()
1136 return (remote_ref, rev) 1133 return (remote_ref, rev)
@@ -1138,12 +1135,12 @@ def resolve_repo_rev(cwd, committish):
1138 # See if it's a short branch name. 1135 # See if it's a short branch name.
1139 rev = resolve("refs/remotes/origin/%s" % committish) 1136 rev = resolve("refs/remotes/origin/%s" % committish)
1140 if rev: 1137 if rev:
1141 return ("refs/heads/%s" % (committish,), rev) 1138 return (f"refs/heads/{committish}", rev)
1142 1139
1143 # See if it's a tag. 1140 # See if it's a tag.
1144 rev = resolve("refs/tags/%s" % committish) 1141 rev = resolve(f"refs/tags/{committish}")
1145 if rev: 1142 if rev:
1146 return ("refs/tags/%s" % (committish,), rev) 1143 return (f"refs/tags/{committish}", rev)
1147 1144
1148 # See if it's a commit. 1145 # See if it's a commit.
1149 rev = resolve(committish) 1146 rev = resolve(committish)
@@ -1152,7 +1149,8 @@ def resolve_repo_rev(cwd, committish):
1152 1149
1153 # Give up! 1150 # Give up!
1154 print( 1151 print(
1155 'repo: error: unable to resolve "%s"' % (committish,), file=sys.stderr 1152 f'repo: error: unable to resolve "{committish}"',
1153 file=sys.stderr,
1156 ) 1154 )
1157 raise CloneFailure() 1155 raise CloneFailure()
1158 1156
@@ -1168,8 +1166,8 @@ def verify_rev(cwd, remote_ref, rev, quiet):
1168 if not quiet: 1166 if not quiet:
1169 print(file=sys.stderr) 1167 print(file=sys.stderr)
1170 print( 1168 print(
1171 "warning: '%s' is not signed; falling back to signed release '%s'" 1169 f"warning: '{remote_ref}' is not signed; "
1172 % (remote_ref, cur), 1170 f"falling back to signed release '{cur}'",
1173 file=sys.stderr, 1171 file=sys.stderr,
1174 ) 1172 )
1175 print(file=sys.stderr) 1173 print(file=sys.stderr)
@@ -1222,7 +1220,7 @@ def _ExpandAlias(name):
1222 if name in {"gitc-init", "help", "init"}: 1220 if name in {"gitc-init", "help", "init"}:
1223 return name, [] 1221 return name, []
1224 1222
1225 alias = _GetRepoConfig("alias.%s" % (name,)) 1223 alias = _GetRepoConfig(f"alias.{name}")
1226 if alias is None: 1224 if alias is None:
1227 return name, [] 1225 return name, []
1228 1226
@@ -1318,18 +1316,20 @@ class Requirements:
1318 hard_ver = tuple(self._get_hard_ver(pkg)) 1316 hard_ver = tuple(self._get_hard_ver(pkg))
1319 if curr_ver < hard_ver: 1317 if curr_ver < hard_ver:
1320 print( 1318 print(
1321 'repo: error: Your version of "%s" (%s) is unsupported; ' 1319 f'repo: error: Your version of "{pkg}" '
1322 "Please upgrade to at least version %s to continue." 1320 f"({self._format_ver(curr_ver)}) is unsupported; "
1323 % (pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)), 1321 "Please upgrade to at least version "
1322 f"{self._format_ver(soft_ver)} to continue.",
1324 file=sys.stderr, 1323 file=sys.stderr,
1325 ) 1324 )
1326 sys.exit(1) 1325 sys.exit(1)
1327 1326
1328 if curr_ver < soft_ver: 1327 if curr_ver < soft_ver:
1329 print( 1328 print(
1330 'repo: warning: Your version of "%s" (%s) is no longer supported; ' 1329 f'repo: error: Your version of "{pkg}" '
1331 "Please upgrade to at least version %s to avoid breakage." 1330 f"({self._format_ver(curr_ver)}) is no longer supported; "
1332 % (pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)), 1331 "Please upgrade to at least version "
1332 f"{self._format_ver(soft_ver)} to continue.",
1333 file=sys.stderr, 1333 file=sys.stderr,
1334 ) 1334 )
1335 1335
@@ -1390,20 +1390,18 @@ def _Help(args):
1390def _Version(): 1390def _Version():
1391 """Show version information.""" 1391 """Show version information."""
1392 print("<repo not installed>") 1392 print("<repo not installed>")
1393 print("repo launcher version %s" % (".".join(str(x) for x in VERSION),)) 1393 print(f"repo launcher version {'.'.join(str(x) for x in VERSION)}")
1394 print(" (from %s)" % (__file__,)) 1394 print(f" (from {__file__})")
1395 print("git %s" % (ParseGitVersion().full,)) 1395 print(f"git {ParseGitVersion().full}")
1396 print("Python %s" % sys.version) 1396 print(f"Python {sys.version}")
1397 uname = platform.uname() 1397 uname = platform.uname()
1398 if sys.version_info.major < 3: 1398 if sys.version_info.major < 3:
1399 # Python 3 returns a named tuple, but Python 2 is simpler. 1399 # Python 3 returns a named tuple, but Python 2 is simpler.
1400 print(uname) 1400 print(uname)
1401 else: 1401 else:
1402 print("OS %s %s (%s)" % (uname.system, uname.release, uname.version)) 1402 print(f"OS {uname.system} {uname.release} ({uname.version})")
1403 print( 1403 processor = uname.processor if uname.processor else "unknown"
1404 "CPU %s (%s)" 1404 print(f"CPU {uname.machine} ({processor})")
1405 % (uname.machine, uname.processor if uname.processor else "unknown")
1406 )
1407 print("Bug reports:", BUG_URL) 1405 print("Bug reports:", BUG_URL)
1408 sys.exit(0) 1406 sys.exit(0)
1409 1407
diff --git a/ssh.py b/ssh.py
index bb89fa1f..a8242790 100644
--- a/ssh.py
+++ b/ssh.py
@@ -165,7 +165,7 @@ class ProxyManager:
165 # Check to see whether we already think that the master is running; if 165 # Check to see whether we already think that the master is running; if
166 # we think it's already running, return right away. 166 # we think it's already running, return right away.
167 if port is not None: 167 if port is not None:
168 key = "%s:%s" % (host, port) 168 key = f"{host}:{port}"
169 else: 169 else:
170 key = host 170 key = host
171 171
diff --git a/subcmds/__init__.py b/subcmds/__init__.py
index 965ad0bb..83ec8470 100644
--- a/subcmds/__init__.py
+++ b/subcmds/__init__.py
@@ -37,9 +37,7 @@ for py in os.listdir(my_dir):
37 try: 37 try:
38 cmd = getattr(mod, clsn) 38 cmd = getattr(mod, clsn)
39 except AttributeError: 39 except AttributeError:
40 raise SyntaxError( 40 raise SyntaxError(f"{__name__}/{py} does not define class {clsn}")
41 "%s/%s does not define class %s" % (__name__, py, clsn)
42 )
43 41
44 name = name.replace("_", "-") 42 name = name.replace("_", "-")
45 cmd.NAME = name 43 cmd.NAME = name
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index f6c0c66c..e280d69e 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -117,7 +117,7 @@ It is equivalent to "git branch -D <branchname>".
117 all_projects, 117 all_projects,
118 callback=_ProcessResults, 118 callback=_ProcessResults,
119 output=Progress( 119 output=Progress(
120 "Abandon %s" % (nb,), len(all_projects), quiet=opt.quiet 120 f"Abandon {nb}", len(all_projects), quiet=opt.quiet
121 ), 121 ),
122 ) 122 )
123 123
@@ -152,4 +152,4 @@ It is equivalent to "git branch -D <branchname>".
152 _RelPath(p) for p in success[br] 152 _RelPath(p) for p in success[br]
153 ) 153 )
154 ) 154 )
155 print("%s%s| %s\n" % (br, " " * (width - len(br)), result)) 155 print(f"{br}{' ' * (width - len(br))}| {result}\n")
diff --git a/subcmds/branches.py b/subcmds/branches.py
index d9a190be..59b5cb28 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -174,7 +174,7 @@ is shown, then the branch appears in all projects.
174 if _RelPath(p) not in have: 174 if _RelPath(p) not in have:
175 paths.append(_RelPath(p)) 175 paths.append(_RelPath(p))
176 176
177 s = " %s %s" % (in_type, ", ".join(paths)) 177 s = f" {in_type} {', '.join(paths)}"
178 if not i.IsSplitCurrent and (width + 7 + len(s) < 80): 178 if not i.IsSplitCurrent and (width + 7 + len(s) < 80):
179 fmt = out.current if i.IsCurrent else fmt 179 fmt = out.current if i.IsCurrent else fmt
180 fmt(s) 180 fmt(s)
diff --git a/subcmds/checkout.py b/subcmds/checkout.py
index ea48263e..379bfa18 100644
--- a/subcmds/checkout.py
+++ b/subcmds/checkout.py
@@ -96,7 +96,7 @@ The command is equivalent to:
96 all_projects, 96 all_projects,
97 callback=_ProcessResults, 97 callback=_ProcessResults,
98 output=Progress( 98 output=Progress(
99 "Checkout %s" % (nb,), len(all_projects), quiet=opt.quiet 99 f"Checkout {nb}", len(all_projects), quiet=opt.quiet
100 ), 100 ),
101 ) 101 )
102 102
diff --git a/subcmds/diffmanifests.py b/subcmds/diffmanifests.py
index b446dbd8..88b697b6 100644
--- a/subcmds/diffmanifests.py
+++ b/subcmds/diffmanifests.py
@@ -87,25 +87,17 @@ synced and their revisions won't be found.
87 def _printRawDiff(self, diff, pretty_format=None, local=False): 87 def _printRawDiff(self, diff, pretty_format=None, local=False):
88 _RelPath = lambda p: p.RelPath(local=local) 88 _RelPath = lambda p: p.RelPath(local=local)
89 for project in diff["added"]: 89 for project in diff["added"]:
90 self.printText( 90 self.printText(f"A {_RelPath(project)} {project.revisionExpr}")
91 "A %s %s" % (_RelPath(project), project.revisionExpr)
92 )
93 self.out.nl() 91 self.out.nl()
94 92
95 for project in diff["removed"]: 93 for project in diff["removed"]:
96 self.printText( 94 self.printText(f"R {_RelPath(project)} {project.revisionExpr}")
97 "R %s %s" % (_RelPath(project), project.revisionExpr)
98 )
99 self.out.nl() 95 self.out.nl()
100 96
101 for project, otherProject in diff["changed"]: 97 for project, otherProject in diff["changed"]:
102 self.printText( 98 self.printText(
103 "C %s %s %s" 99 f"C {_RelPath(project)} {project.revisionExpr} "
104 % ( 100 f"{otherProject.revisionExpr}"
105 _RelPath(project),
106 project.revisionExpr,
107 otherProject.revisionExpr,
108 )
109 ) 101 )
110 self.out.nl() 102 self.out.nl()
111 self._printLogs( 103 self._printLogs(
@@ -118,12 +110,8 @@ synced and their revisions won't be found.
118 110
119 for project, otherProject in diff["unreachable"]: 111 for project, otherProject in diff["unreachable"]:
120 self.printText( 112 self.printText(
121 "U %s %s %s" 113 f"U {_RelPath(project)} {project.revisionExpr} "
122 % ( 114 f"{otherProject.revisionExpr}"
123 _RelPath(project),
124 project.revisionExpr,
125 otherProject.revisionExpr,
126 )
127 ) 115 )
128 self.out.nl() 116 self.out.nl()
129 117
diff --git a/subcmds/help.py b/subcmds/help.py
index a839131b..80040711 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -150,7 +150,7 @@ Displays detailed usage information about a command.
150 def _PrintAllCommandHelp(self): 150 def _PrintAllCommandHelp(self):
151 for name in sorted(all_commands): 151 for name in sorted(all_commands):
152 cmd = all_commands[name](manifest=self.manifest) 152 cmd = all_commands[name](manifest=self.manifest)
153 self._PrintCommandHelp(cmd, header_prefix="[%s] " % (name,)) 153 self._PrintCommandHelp(cmd, header_prefix=f"[{name}] ")
154 154
155 def _Options(self, p): 155 def _Options(self, p):
156 p.add_option( 156 p.add_option(
diff --git a/subcmds/info.py b/subcmds/info.py
index c24682c7..f637600e 100644
--- a/subcmds/info.py
+++ b/subcmds/info.py
@@ -248,7 +248,7 @@ class Info(PagedCommand):
248 248
249 for commit in commits: 249 for commit in commits:
250 split = commit.split() 250 split = commit.split()
251 self.text("{0:38}{1} ".format("", "-")) 251 self.text(f"{'':38}{'-'} ")
252 self.sha(split[0] + " ") 252 self.sha(split[0] + " ")
253 self.text(" ".join(split[1:])) 253 self.text(" ".join(split[1:]))
254 self.out.nl() 254 self.out.nl()
diff --git a/subcmds/init.py b/subcmds/init.py
index 9ac42d8e..44517877 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -215,7 +215,7 @@ to update the working directory files.
215 215
216 if not opt.quiet: 216 if not opt.quiet:
217 print() 217 print()
218 print("Your identity is: %s <%s>" % (name, email)) 218 print(f"Your identity is: {name} <{email}>")
219 print("is this correct [y/N]? ", end="", flush=True) 219 print("is this correct [y/N]? ", end="", flush=True)
220 a = sys.stdin.readline().strip().lower() 220 a = sys.stdin.readline().strip().lower()
221 if a in ("yes", "y", "t", "true"): 221 if a in ("yes", "y", "t", "true"):
diff --git a/subcmds/list.py b/subcmds/list.py
index fba6a4dc..4338e1c9 100644
--- a/subcmds/list.py
+++ b/subcmds/list.py
@@ -131,7 +131,7 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
131 elif opt.path_only and not opt.name_only: 131 elif opt.path_only and not opt.name_only:
132 lines.append("%s" % (_getpath(project))) 132 lines.append("%s" % (_getpath(project)))
133 else: 133 else:
134 lines.append("%s : %s" % (_getpath(project), project.name)) 134 lines.append(f"{_getpath(project)} : {project.name}")
135 135
136 if lines: 136 if lines:
137 lines.sort() 137 lines.sort()
diff --git a/subcmds/prune.py b/subcmds/prune.py
index f18471f3..f99082a4 100644
--- a/subcmds/prune.py
+++ b/subcmds/prune.py
@@ -83,9 +83,7 @@ class Prune(PagedCommand):
83 ) 83 )
84 84
85 if not branch.base_exists: 85 if not branch.base_exists:
86 print( 86 print(f"(ignoring: tracking branch is gone: {branch.base})")
87 "(ignoring: tracking branch is gone: %s)" % (branch.base,)
88 )
89 else: 87 else:
90 commits = branch.commits 88 commits = branch.commits
91 date = branch.date 89 date = branch.date
diff --git a/subcmds/start.py b/subcmds/start.py
index fd177f9e..56008f42 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -130,7 +130,7 @@ revision specified in the manifest.
130 all_projects, 130 all_projects,
131 callback=_ProcessResults, 131 callback=_ProcessResults,
132 output=Progress( 132 output=Progress(
133 "Starting %s" % (nb,), len(all_projects), quiet=opt.quiet 133 f"Starting {nb}", len(all_projects), quiet=opt.quiet
134 ), 134 ),
135 ) 135 )
136 136
diff --git a/subcmds/sync.py b/subcmds/sync.py
index a0a0be9a..02c1d3ae 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -1394,7 +1394,7 @@ later is required to fix a server side protocol bug.
1394 1394
1395 if username and password: 1395 if username and password:
1396 manifest_server = manifest_server.replace( 1396 manifest_server = manifest_server.replace(
1397 "://", "://%s:%s@" % (username, password), 1 1397 "://", f"://{username}:{password}@", 1
1398 ) 1398 )
1399 1399
1400 transport = PersistentTransport(manifest_server) 1400 transport = PersistentTransport(manifest_server)
diff --git a/subcmds/version.py b/subcmds/version.py
index 71a03608..5c817f17 100644
--- a/subcmds/version.py
+++ b/subcmds/version.py
@@ -42,35 +42,28 @@ class Version(Command, MirrorSafeCommand):
42 # These might not be the same. Report them both. 42 # These might not be the same. Report them both.
43 src_ver = RepoSourceVersion() 43 src_ver = RepoSourceVersion()
44 rp_ver = rp.bare_git.describe(HEAD) 44 rp_ver = rp.bare_git.describe(HEAD)
45 print("repo version %s" % rp_ver) 45 print(f"repo version {rp_ver}")
46 print(" (from %s)" % rem.url) 46 print(f" (from {rem.url})")
47 print(" (tracking %s)" % branch.merge) 47 print(f" (tracking {branch.merge})")
48 print(" (%s)" % rp.bare_git.log("-1", "--format=%cD", HEAD)) 48 print(f" ({rp.bare_git.log('-1', '--format=%cD', HEAD)})")
49 49
50 if self.wrapper_path is not None: 50 if self.wrapper_path is not None:
51 print("repo launcher version %s" % self.wrapper_version) 51 print(f"repo launcher version {self.wrapper_version}")
52 print(" (from %s)" % self.wrapper_path) 52 print(f" (from {self.wrapper_path})")
53 53
54 if src_ver != rp_ver: 54 if src_ver != rp_ver:
55 print(" (currently at %s)" % src_ver) 55 print(f" (currently at {src_ver})")
56 56
57 print("repo User-Agent %s" % user_agent.repo) 57 print(f"repo User-Agent {user_agent.repo}")
58 print("git %s" % git.version_tuple().full) 58 print(f"git {git.version_tuple().full}")
59 print("git User-Agent %s" % user_agent.git) 59 print(f"git User-Agent {user_agent.git}")
60 print("Python %s" % sys.version) 60 print(f"Python {sys.version}")
61 uname = platform.uname() 61 uname = platform.uname()
62 if sys.version_info.major < 3: 62 if sys.version_info.major < 3:
63 # Python 3 returns a named tuple, but Python 2 is simpler. 63 # Python 3 returns a named tuple, but Python 2 is simpler.
64 print(uname) 64 print(uname)
65 else: 65 else:
66 print( 66 print(f"OS {uname.system} {uname.release} ({uname.version})")
67 "OS %s %s (%s)" % (uname.system, uname.release, uname.version) 67 processor = uname.processor if uname.processor else "unknown"
68 ) 68 print(f"CPU {uname.machine} ({processor})")
69 print(
70 "CPU %s (%s)"
71 % (
72 uname.machine,
73 uname.processor if uname.processor else "unknown",
74 )
75 )
76 print("Bug reports:", Wrapper().BUG_URL) 69 print("Bug reports:", Wrapper().BUG_URL)
diff --git a/tests/test_git_command.py b/tests/test_git_command.py
index 7c108ccd..ffee023b 100644
--- a/tests/test_git_command.py
+++ b/tests/test_git_command.py
@@ -19,12 +19,7 @@ import os
19import re 19import re
20import subprocess 20import subprocess
21import unittest 21import unittest
22 22from unittest import mock
23
24try:
25 from unittest import mock
26except ImportError:
27 import mock
28 23
29import git_command 24import git_command
30import wrapper 25import wrapper
diff --git a/tests/test_git_config.py b/tests/test_git_config.py
index a44dca0f..cf6e7793 100644
--- a/tests/test_git_config.py
+++ b/tests/test_git_config.py
@@ -100,7 +100,7 @@ class GitConfigReadOnlyTests(unittest.TestCase):
100 ("intg", 10737418240), 100 ("intg", 10737418240),
101 ) 101 )
102 for key, value in TESTS: 102 for key, value in TESTS:
103 self.assertEqual(value, self.config.GetInt("section.%s" % (key,))) 103 self.assertEqual(value, self.config.GetInt(f"section.{key}"))
104 104
105 105
106class GitConfigReadWriteTests(unittest.TestCase): 106class GitConfigReadWriteTests(unittest.TestCase):
diff --git a/tests/test_git_superproject.py b/tests/test_git_superproject.py
index 478ebca7..4e66521b 100644
--- a/tests/test_git_superproject.py
+++ b/tests/test_git_superproject.py
@@ -34,7 +34,7 @@ class SuperprojectTestCase(unittest.TestCase):
34 PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID" 34 PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID"
35 PARENT_SID_VALUE = "parent_sid" 35 PARENT_SID_VALUE = "parent_sid"
36 SELF_SID_REGEX = r"repo-\d+T\d+Z-.*" 36 SELF_SID_REGEX = r"repo-\d+T\d+Z-.*"
37 FULL_SID_REGEX = r"^%s/%s" % (PARENT_SID_VALUE, SELF_SID_REGEX) 37 FULL_SID_REGEX = rf"^{PARENT_SID_VALUE}/{SELF_SID_REGEX}"
38 38
39 def setUp(self): 39 def setUp(self):
40 """Set up superproject every time.""" 40 """Set up superproject every time."""
diff --git a/tests/test_git_trace2_event_log.py b/tests/test_git_trace2_event_log.py
index d8e963dd..4658a793 100644
--- a/tests/test_git_trace2_event_log.py
+++ b/tests/test_git_trace2_event_log.py
@@ -61,7 +61,7 @@ class EventLogTestCase(unittest.TestCase):
61 PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID" 61 PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID"
62 PARENT_SID_VALUE = "parent_sid" 62 PARENT_SID_VALUE = "parent_sid"
63 SELF_SID_REGEX = r"repo-\d+T\d+Z-.*" 63 SELF_SID_REGEX = r"repo-\d+T\d+Z-.*"
64 FULL_SID_REGEX = r"^%s/%s" % (PARENT_SID_VALUE, SELF_SID_REGEX) 64 FULL_SID_REGEX = rf"^{PARENT_SID_VALUE}/{SELF_SID_REGEX}"
65 65
66 def setUp(self): 66 def setUp(self):
67 """Load the event_log module every time.""" 67 """Load the event_log module every time."""
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py
index bd255dcc..3fcf09fa 100644
--- a/tests/test_manifest_xml.py
+++ b/tests/test_manifest_xml.py
@@ -198,13 +198,13 @@ class ValueTests(unittest.TestCase):
198 def test_bool_true(self): 198 def test_bool_true(self):
199 """Check XmlBool true values.""" 199 """Check XmlBool true values."""
200 for value in ("yes", "true", "1"): 200 for value in ("yes", "true", "1"):
201 node = self._get_node('<node a="%s"/>' % (value,)) 201 node = self._get_node(f'<node a="{value}"/>')
202 self.assertTrue(manifest_xml.XmlBool(node, "a")) 202 self.assertTrue(manifest_xml.XmlBool(node, "a"))
203 203
204 def test_bool_false(self): 204 def test_bool_false(self):
205 """Check XmlBool false values.""" 205 """Check XmlBool false values."""
206 for value in ("no", "false", "0"): 206 for value in ("no", "false", "0"):
207 node = self._get_node('<node a="%s"/>' % (value,)) 207 node = self._get_node(f'<node a="{value}"/>')
208 self.assertFalse(manifest_xml.XmlBool(node, "a")) 208 self.assertFalse(manifest_xml.XmlBool(node, "a"))
209 209
210 def test_int_default(self): 210 def test_int_default(self):
@@ -220,7 +220,7 @@ class ValueTests(unittest.TestCase):
220 def test_int_good(self): 220 def test_int_good(self):
221 """Check XmlInt numeric handling.""" 221 """Check XmlInt numeric handling."""
222 for value in (-1, 0, 1, 50000): 222 for value in (-1, 0, 1, 50000):
223 node = self._get_node('<node a="%s"/>' % (value,)) 223 node = self._get_node(f'<node a="{value}"/>')
224 self.assertEqual(value, manifest_xml.XmlInt(node, "a")) 224 self.assertEqual(value, manifest_xml.XmlInt(node, "a"))
225 225
226 def test_int_invalid(self): 226 def test_int_invalid(self):
diff --git a/tests/test_project.py b/tests/test_project.py
index 83cfe0a4..6dc071bd 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -151,7 +151,7 @@ class CopyLinkTestCase(unittest.TestCase):
151 # "". 151 # "".
152 break 152 break
153 result = os.path.exists(path) 153 result = os.path.exists(path)
154 msg.append("\tos.path.exists(%s): %s" % (path, result)) 154 msg.append(f"\tos.path.exists({path}): {result}")
155 if result: 155 if result:
156 msg.append("\tcontents: %r" % os.listdir(path)) 156 msg.append("\tcontents: %r" % os.listdir(path))
157 break 157 break