More tinkering. Lots of refactoring to get it to do what I want.

This commit is contained in:
James Smith 2023-01-28 22:09:50 +02:00
parent 6166b42b2e
commit 01775362b3
4 changed files with 148 additions and 105 deletions

View File

@ -11,7 +11,6 @@ repos:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: mixed-line-ending - id: mixed-line-ending
args: [--fix=lf] args: [--fix=lf]
- id: no-commit-to-branch # without arguments, master/main will be protected.
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.10.0 rev: 22.10.0

104
git-tidy
View File

@ -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
# <https://www.gnu.org/licenses/>.
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"])

144
git-tidy.py Executable file
View File

@ -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
# <https://www.gnu.org/licenses/>.
"""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)

4
mypy.ini Normal file
View File

@ -0,0 +1,4 @@
[mypy]
python_version = 3.10
ignore_missing_imports = True
files = git-tidy.py