mirror of
https://codeberg.org/james-smith-za/git-tidy-python.git
synced 2026-01-15 19:29:25 +02:00
Flesh out pyproject.toml to allow properly installing
Mostly because I'd like to be able to install it with uv, so it's entirely for selfish reasons.
This commit is contained in:
parent
a0ce83fb72
commit
412444eb6e
2 changed files with 57 additions and 7 deletions
169
git_tidy.py
Normal file
169
git_tidy.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright 2023-2025 James Smith
|
||||
# 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 utility."""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
except ImportError:
|
||||
# Python < 3.8
|
||||
from importlib_metadata import version
|
||||
|
||||
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.
|
||||
|
||||
First try the config, if that doesn't work, check existing branches for
|
||||
some common defaults. If there aren't any or they're ambiguous, ask the
|
||||
user.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The name of the default branch with which we'll work for the rest of
|
||||
the exercise.
|
||||
"""
|
||||
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("fatal: not inside a repository", 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 default branch for tidying purposes, but it doesn't exist! "
|
||||
"Attempting to reconfigure....",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
else:
|
||||
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.
|
||||
print(f"Using branch {candidates[0]}...")
|
||||
run(["git", "config", "--add", "tidy.defaultbranch", candidates[0]])
|
||||
return candidates[0]
|
||||
|
||||
# If there's no clear default, let the user select which one to use:
|
||||
selection = -1
|
||||
while (selection < 0) or (selection >= len(branch_list)):
|
||||
print("Unable to determine default branch automatically. Please select one:")
|
||||
for n, branch in enumerate(branch_list):
|
||||
print(f"{n}: {branch}")
|
||||
|
||||
raw_input = input("Which to choose? ")
|
||||
try:
|
||||
selection = int(raw_input)
|
||||
except ValueError:
|
||||
continue
|
||||
print(selection)
|
||||
|
||||
return branch_list[selection]
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
parser = argparse.ArgumentParser(description="A time-saving device for a developer using git.")
|
||||
parser.add_argument("--version", action="version", version=f"git-tidy {version('git-tidy-python')}")
|
||||
parser.parse_args()
|
||||
|
||||
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 connected to {remote}")
|
||||
|
||||
# Get latest changes from remote, clear up unnecessary stuff.
|
||||
returncode, output = run(["git", "fetch", "--all", "--prune"])
|
||||
if returncode != 0: # possibly a problem in contacting the remote?
|
||||
print(output, file=sys.stderr)
|
||||
sys.exit(returncode)
|
||||
print(output) # I like to see what `git fetch` has accomplished.
|
||||
|
||||
returncode, output = run(["git", "merge", "--ff-only", f"{remote}/{default_branch}", default_branch])
|
||||
if returncode != 0: # Probably fast-forwarding won't work.
|
||||
print(output, file=sys.stderr)
|
||||
sys.exit(returncode)
|
||||
|
||||
# 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)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue