Skip to content

Instantly share code, notes, and snippets.

@ceNobiteElf
Last active August 30, 2022 07:07
Show Gist options
  • Save ceNobiteElf/0ae696f59e34f979f1582fa1b056f0c4 to your computer and use it in GitHub Desktop.
Save ceNobiteElf/0ae696f59e34f979f1582fa1b056f0c4 to your computer and use it in GitHub Desktop.
Prints out a list of stale and/or incorrectly named branches grouped by author, so you can go shout at them.

Requirements

Usage

The script has a number of parameters (all optional), simply run the following to get a list of these:

python3 gitshout.py -h

If you run this without any parameters it will assume that the current working directory is a local git repository, otherwise a repo path should be specified using -r:

python3 gitshout.py -r /Users/cenobiteelf/repos/example_repo

There are 2 parameters that take regex patterns as input (-b and -i), the first is for determining whether a branch has a valid name based on your conventions or standards and the second one is for ignoring certain branches, like main or develop. The usage of these will look something like this:

python3 gitshout.py -b "^(origin\/)?(hotfix|feature|release)(\/[-\w\d.]*)*$"

Output Example

Below is an example of what will be printed to the console when you run this. Do note that in the actual output the lines are coloured by error type.

John Smith
  origin/feature/JIRA-323_addFormButtons (updated 163 days ago)
  origin/feature/JIRA-200_documentUploads (updated 131 days ago)
  origin/bugfix/bugFixes (invalid name)
#!/usr/bin/env python
# GitShout v1.0.0
# Copyright (c) 2022 Con Momberg
import argparse
import re
from collections import defaultdict, namedtuple
from datetime import date
from git import Repo
Remote = namedtuple("Remote", "reference commit")
Error = namedtuple("Error", "message color")
def positive_int(raw_value):
int_value = int(raw_value)
if (int_value <= 0):
raise argparse.ArgumentTypeError(f"Age must be a positive integer, provided value was {int_value}")
return int_value
parser = argparse.ArgumentParser(description = "Analyses a local git repository, highlting stale and non-conforming branches per author so you can go shout at them.")
parser.add_argument("-r", "--repo", dest = "repo_path", default = ".", help = "The path to the git repository to analyse")
parser.add_argument("-b", "--valid-branch-pattern", dest = "valid_branch_pattern", default = r"^(origin\/)?(feature|hotfix|release)(\/[-\w\d.]*)*$", help = "Regex pattern used to validate that branches are named correctly")
parser.add_argument("-i", "--ignored-branch-pattern", dest = "ignored_branch_pattern", default = r"^(origin\/)?(develop|master|main|HEAD)$", help = "Regex pattern for branches that should be ignored")
parser.add_argument("-a", "--stale-age", dest = "stale_age", default = 20, type = positive_int, help = "Age (in days) before a branch is considered stale")
args = parser.parse_args()
valid_branch_pattern = re.compile(args.valid_branch_pattern)
ignored_branch_pattern = re.compile(args.ignored_branch_pattern)
def can_ignore(reference):
return ignored_branch_pattern.match(reference.name) is not None
def validate(remote):
errors = []
commit_age = (date.today() - remote.commit.authored_datetime.date()).days
if commit_age >= args.stale_age:
errors.append(Error(f"updated {commit_age} {'day' if commit_age == 1 else 'days'} ago", "\033[93m"))
if valid_branch_pattern.match(remote.reference.name) is None:
errors.append(Error("invalid name", "\033[91m"))
return errors
# Get a list of all remotes from the local git repo that don't match the ignored branch pattern, sorted by last commit date
repo = Repo(args.repo_path)
all_remotes = sorted({ Remote(ref, repo.commit(ref)) for ref in filter(lambda ref: not can_ignore(ref), repo.remote().refs) }, key = lambda remote: remote.commit.authored_datetime)
# Group remotes by author, but only include them if they have an error
grouped_output = defaultdict(list)
for remote in all_remotes:
errors = validate(remote)
if errors:
message = f"{errors[0].color}{remote.reference.name} ({', '.join(map(lambda error: error.message, errors))})"
grouped_output[remote.commit.author.name].append(message)
# Print output with pretty colours applied
if grouped_output:
for key, messages in sorted(grouped_output.items(), key = lambda item: item[0].lower()):
print(f"\033[1m\033[97m{key}")
for message in messages:
print(f"\t{message}")
else:
print("\033[92mYay! No issues found!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment