Initial commit.

This commit is contained in:
James Smith 2023-01-26 07:43:08 +02:00
commit 6166b42b2e
6 changed files with 219 additions and 0 deletions

12
.flake8 Normal file
View File

@ -0,0 +1,12 @@
[flake8]
max-line-length = 120
exclude = .venv
# specify which checks to enable
select =
C, # complexity checks
E, # pep8 errors
F, # pyflakes fatals
W, # pep8 warnings
B, # bugbear checks for design issues (requires flake8-bugbear)
N # pep8 naming (requires pep8-naming)

50
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,50 @@
default_language_version:
python: python3.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-merge-conflict
- id: check-shebang-scripts-are-executable
- id: check-executables-have-shebangs
- 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
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
name: isort
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear==22.6.22',
'pep8-naming==0.13.0'
]
- repo: https://github.com/pycqa/pydocstyle
rev: 6.1.1
hooks:
- id: pydocstyle
exclude: 'setup.py|scratch/' # Because complaining about docstrings here is annoying.
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.991'
hooks:
- id: mypy
# Passing filenames to mypy can do odd things. See
# https://github.com/pre-commit/mirrors-mypy/issues/33.
# mypy.ini determines the set of files that will actually be checked.
pass_filenames: false
# The pre-commit hook passes some options, but we set options in mypy.ini.
args: []
# The pre-commit hook only has python, not pyi.
types: []
types_or: [python, pyi]

11
LICENCE Normal file
View File

@ -0,0 +1,11 @@
This program 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.
This program 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
this program. If not, see <https://www.gnu.org/licenses/>.

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# git-tidy
Have you ever come back to a git repo after a while, started developing, and
then realised you weren't on the main branch, or your branch was missing the
latest changes, so you ended up with merge conflicts?
Or have you ever finished working on a feature, completed a pull-request online,
and wanted an easy way to clean up your local repo from all the unused
references that have accumulated themselves?
Then `git-tidy` is for you!
This is a simple Python script which you can install by copying (or symlinking)
anywhere on your path (I recommend ~/bin/, but something like /usr/local/bin/
would also work).
Entering
```bash
git tidy
```
at the terminal will:
- Check out the default branch, usually `main`
- if this is not configured, it will try to figure out what it should be,
- Pull to make sure that you're up-to-date with the default branch,
- Delete all branches that have already been merged into the default, and,
- Delete all references which have been removed from the remote as well,
leaving you with a pristine, tidy repository in order to continue your
development.

104
git-tidy Executable file
View File

@ -0,0 +1,104 @@
#!/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"])

13
pyproject.toml Normal file
View File

@ -0,0 +1,13 @@
[tool.black]
line-length = 120
include = '\.pyi?$'
exclude ='''
/(
| \.git
| \.mypy_cache
)/
'''
[tool.isort]
profile = "black"
line_length = 120