diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2f7998c..7e1e23b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,7 +11,6 @@ repos:
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=lf]
- - id: no-commit-to-branch # without arguments, master/main will be protected.
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 22.10.0
diff --git a/git-tidy b/git-tidy
deleted file mode 100755
index def08e8..0000000
--- a/git-tidy
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python
-
-# git-tidy is free software: you can redistribute it and/or modify it under the terms of the GNU
-# General Public License as published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-
-# git-tidy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
-# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-# Public License for more details.
-
-# You should have received a copy of the GNU General Public License along with git-tidy. If not, see
-# .
-
-import subprocess
-import sys
-
-common_default_branches = [
- "main",
- "master",
- "devel",
- "dev",
-]
-
-# Check whether we're actually in a git repo before doing anything else.
-# TODO: Rename this function to something more obvious.
-def get_status():
- git_status = subprocess.run(
- ["git", "status"],
- capture_output=True, # we don't want to show the user everything
- )
- if git_status.returncode == 0:
- return True
-
- print(git_status.stderr.decode())
- return False
-
-
-def get_default_branch() -> str:
- # Try to see if there's a configuration already.
- sp = subprocess.run(["git", "config", "--get", "tidy.defaultbranch"], capture_output=True)
- if sp.returncode != 0:
- print("No default branch for git-tidy configured, will try to figure out which to use...")
- candidates = []
- branch_list = [
- branch.strip()
- for branch in subprocess.run(["git", "branch", "--list"], capture_output=True).stdout.decode().split("\n")
- if len(branch)
- ]
- # Check whether any branches match our list of common defaults.
- for branch in branch_list:
- # Remove the asterisk in front of the active branch name.
- if branch[0] == "*":
- branch = branch[2:]
-
- if branch in common_default_branches:
- candidates.append(branch)
-
- if len(candidates) == 1:
- # Only one branch name matches, so we'll go with that one.
- subprocess.run(["git", "config", "--add", "tidy.defaultbranch", candidates[0]])
- return candidates[0]
-
- # TODO: Handle the zero case.
-
- # If we don't have one or zero, which do we have?
- selection = -1
- while (selection < 0) or (selection >= len(candidates)):
- print(
- f"{'Several' if len(candidates) > 1 else 'No'} potential default branch candidates found. Please select one:"
- )
- for n, candidate in enumerate(candidates):
- print(f"{n}: {candidate}")
-
- # TODO: This breaks if the user is stupid and doesn't input an int.
- selection = int(input("Which to choose?"))
-
- return candidates[selection]
-
- # TODO: Might be nice to have a sanity-check that the configured branch is in fact actually on the list. It may just be easiest to do this by trying to switch to it.
- return sp.stdout.decode()
-
-
-if __name__ == "__main__":
- if not get_status():
- exit()
-
- default_branch = get_default_branch()
- print(f"Switching to {default_branch}")
- # Checkout the default branch.
- subprocess.run(["git", "switch", default_branch])
-
- subprocess.run(["git", "pull"]) # Make sure it's the most up-to-date version.
-
- # Check which branches are `--merged` and delete them.
- merged_branch_output = subprocess.run(["git", "branch", "--merged"], capture_output=True)
- merged_branches = [
- branch for branch in merged_branch_output.stdout.decode().split("\n") if len(branch)
- ] # Remove zero-length elements.
- for branch in merged_branches:
- if branch[0] != "*":
- subprocess.run(["git", "branch", "-d", branch.strip()])
-
- # Clear up dangling references to remote branches which are no longer.
- subprocess.run(["git", "fetch", "--all", "-p"])
diff --git a/git-tidy.py b/git-tidy.py
new file mode 100755
index 0000000..cc0ed67
--- /dev/null
+++ b/git-tidy.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+
+# git-tidy is free software: you can redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# git-tidy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+
+# You should have received a copy of the GNU General Public License along with git-tidy. If not, see
+# .
+"""git-tidy module."""
+
+import subprocess
+import sys
+from typing import List, Tuple, Union
+
+common_default_branches = [
+ "main",
+ "master",
+ "devel",
+ "dev",
+]
+
+
+def run(command: Union[List[str], str]) -> Tuple[int, str]:
+ """Run a subprocess.
+
+ The subprocess is checked for whether it returns successfully, and its
+ stdout or stderr is captured for the caller to make use of.
+
+ Parameters
+ ----------
+ command
+ The command to run as a subprocess.
+
+ Returns
+ -------
+ returncode
+ The (integer) return code from the completed subprocess.
+ stdout / stderr
+ In the case of a zero return code, the stdout from the subprocess is
+ returned. In all other cases, the stderr is returned. In both cases,
+ the bytes are converted to str and stripped of whitespace on either
+ side.
+ """
+ try:
+ process = subprocess.run(command, check=True, capture_output=True)
+ except subprocess.CalledProcessError as exception:
+ return exception.returncode, exception.stderr.decode().strip()
+
+ return process.returncode, process.stdout.decode().strip()
+
+
+def get_default_branch() -> str:
+ """Return the name of the repo's default branch."""
+ returncode, configured_default_branch = run(["git", "config", "--get", "--local", "tidy.defaultbranch"])
+ if returncode == 128:
+ # The --local flag caused this to fail because we're not in a git repository.
+ print(configured_default_branch, file=sys.stderr)
+ sys.exit(returncode)
+
+ # If we're in a repository, we need a list of branches, either as a
+ # sanity check that the configured branch is there, or to choose one
+ # if it's not configured.
+ _, branch_list_raw = run(["git", "branch", "--list"])
+ branch_list = [
+ branch.strip("*").strip() # Remove the indicator of current branch.
+ for branch in branch_list_raw.split("\n")
+ if len(branch) # Ignore empty lines.
+ ]
+
+ if returncode == 0: # The config entry was successfully returned.
+ # But it's worth checking that the branch exists nonetheless.
+ if configured_default_branch in branch_list:
+ return configured_default_branch
+
+ print(
+ f"{configured_default_branch} configured as defauly branch for tidying purposes, but it doesn't exist!",
+ file=sys.stderr,
+ )
+ # TODO: There is probably a clean way to fix this.
+ sys.exit(-1)
+
+ print("No default branch for git-tidy configured, will try to figure out which to use...")
+
+ candidates = []
+
+ # Check whether any branches match our list of common defaults.
+ for branch in branch_list:
+ if branch in common_default_branches:
+ candidates.append(branch)
+
+ if len(candidates) == 1:
+ # Only one branch name matches, so we'll go with that one.
+ run(["git", "config", "--add", "tidy.defaultbranch", candidates[0]])
+ return candidates[0]
+
+ if len(candidates) == 0:
+ print("No candidate branches!", file=sys.stderr)
+ sys.exit(-1)
+
+ # If we don't have one or zero, which do we have?
+ selection = -1
+ while (selection < 0) or (selection >= len(candidates)):
+ print(
+ f"{'Several' if len(candidates) > 1 else 'No'} potential default branch candidates found. "
+ "Please select one:"
+ )
+ for n, candidate in enumerate(candidates):
+ print(f"{n}: {candidate}")
+
+ # TODO: This breaks if the user is stupid and doesn't input an int.
+ selection = int(input("Which to choose?"))
+
+ return candidates[selection]
+
+
+if __name__ == "__main__":
+
+ default_branch = get_default_branch()
+ print(f"Switching to {default_branch}")
+ run(["git", "checkout", default_branch])
+
+ # Check which remote it's configured with.
+ _, remote = run(["git", "config", "--get", f"branch.{default_branch}.remote"])
+
+ print(f"{default_branch} is configured for remote {remote}")
+
+ # Get latest changes from remote, clear up unnecessary stuff.
+ run(["git", "fetch", "--all", "--prune"])
+ run(
+ ["git", "merge", "--ff-only", f"{remote}/{default_branch}", default_branch]
+ ) # TODO: Handle what happens if the ff fails.
+
+ # Check which branches are `--merged` and clean them up.
+ _, merged_branches_raw = run(["git", "branch", "--merged"])
+ merged_branches = [
+ branch for branch in merged_branches_raw.split("\n") if len(branch) # Remove zero-length elements.
+ ]
+ for branch in merged_branches:
+ if branch[0] != "*": # This time we want to explicitly exclude the checked-out branch.
+ subprocess.run(["git", "branch", "-d", branch.strip()], check=True)
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..b6bd15e
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,4 @@
+[mypy]
+python_version = 3.10
+ignore_missing_imports = True
+files = git-tidy.py