From 6166b42b2ea33b83ccbfcadf2f0b3d1c8519779e Mon Sep 17 00:00:00 2001 From: James Smith Date: Thu, 26 Jan 2023 07:43:08 +0200 Subject: [PATCH] Initial commit. --- .flake8 | 12 +++++ .pre-commit-config.yaml | 50 +++++++++++++++++++ LICENCE | 11 +++++ README.md | 29 +++++++++++ git-tidy | 104 ++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 13 +++++ 6 files changed, 219 insertions(+) create mode 100644 .flake8 create mode 100644 .pre-commit-config.yaml create mode 100644 LICENCE create mode 100644 README.md create mode 100755 git-tidy create mode 100644 pyproject.toml diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..d1a3835 --- /dev/null +++ b/.flake8 @@ -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) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..2f7998c --- /dev/null +++ b/.pre-commit-config.yaml @@ -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] diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..22dde48 --- /dev/null +++ b/LICENCE @@ -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 . diff --git a/README.md b/README.md new file mode 100644 index 0000000..d827bed --- /dev/null +++ b/README.md @@ -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. diff --git a/git-tidy b/git-tidy new file mode 100755 index 0000000..def08e8 --- /dev/null +++ b/git-tidy @@ -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 +# . + +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/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7c77a5d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +line-length = 120 +include = '\.pyi?$' +exclude =''' +/( + | \.git + | \.mypy_cache +)/ +''' + +[tool.isort] +profile = "black" +line_length = 120