Skip to content

Instantly share code, notes, and snippets.

@theodore86
Created June 7, 2021 18:33
Show Gist options
  • Select an option

  • Save theodore86/b62c8ea1380f19abb639b4bc116151a9 to your computer and use it in GitHub Desktop.

Select an option

Save theodore86/b62c8ea1380f19abb639b4bc116151a9 to your computer and use it in GitHub Desktop.
Python script to delete git tags older than X days
#!/usr/bin/env python
"""
Delete Git repository tags older than X days.
"""
import argparse
import sys
import os
import collections
from datetime import datetime, timedelta
import subprocess
import re
import logging
import shlex
class Error(Exception):
""" Any script error """
class GitCommandError(Error):
""" Git execution errors """
class ArgumentError(Error):
""" Argument parsing errors """
def execute_command(command):
try:
output = subprocess.check_output(
shlex.split(command)
)
except (OSError, subprocess.CalledProcessError) as e:
raise GitCommandError(e)
return get_decoded_lines(output)
def get_decoded_lines(output, encoding='utf-8', errors='ignore'):
return output.decode(
encoding=encoding, errors=errors
).splitlines()
def create_tags(lines):
Tag = collections.namedtuple('Tag', 'name date')
return [Tag(*line.split()) for line in lines]
def get_formatted_tags(tags, sep=' '):
return sep.join([
tag.name for tag in tags
])
def exclude_lines(lines, pattern=None, flags=None):
if pattern is not None:
return [
line for line in lines
if not re.search(pattern, line, flags=flags)
]
return lines
def get_tags_older_than(tags, days=1):
date_limit = (
datetime.now() - timedelta(days=days)
)
return [
tag
for tag in tags
if datetime.strptime(tag.date, '%Y-%m-%d') < date_limit
]
def get_logger(name=None, level=logging.INFO):
logger = logging.getLogger(name or __name__)
handler = logging.StreamHandler(sys.stdout)
template = logging.Formatter('[%(levelname)s]: %(message)s')
handler.setFormatter(template)
logger.addHandler(handler)
logger.setLevel(level)
return logger
class ArgumentParser(argparse.ArgumentParser):
""" Override error to handle ArgumentParser exceptions """
def _error_message(self, message):
return os.linesep.join(
[message or '', self.format_usage()]
)
def error(self, message):
_message = self._error_message(message)
raise ArgumentError(
'{}: {}'.format(self.prog, _message)
)
def parse_arguments(argv):
parser = ArgumentParser(
description=__doc__,
formatter_class=argparse.RawTextHelpFormatter,
usage='python delete_tags.py --days 5 [--exclude ^Rel.*] [--dryrun]'
)
parser.add_argument(
'-d', '--days', dest='days',
type=int, required=True,
help='Delete tags older than X days'
)
parser.add_argument(
'-e', '--exclude', dest='exclude',
type=str, default=None,
help='Regex pattern to exclude specific tag names'
)
parser.add_argument(
'--dryrun', dest='dryrun', action='store_true', default=None,
help='Display the selected tags without deleting them'
)
return parser.parse_args(argv)
def main(argv=None, logger=None):
logger = logger or get_logger()
try:
args = parse_arguments(argv)
execute_command('git fetch --prune --prune-tags')
output = execute_command(
'git for-each-ref --sort=taggerdate '
'--format "%(refname:short) %(taggerdate:short)" '
'refs/tags'
)
lines = exclude_lines(output, pattern=args.exclude, flags=re.M)
tags = create_tags(lines)
tags = get_tags_older_than(tags, days=args.days)
if args.dryrun and tags:
logger.info(
'Git Tags to be deleted:%s%s',
os.linesep, get_formatted_tags(tags, os.linesep)
)
elif tags:
logger.warning(
'Git Tags will be permantly deleted:%s%s',
os.linesep, get_formatted_tags(tags, os.linesep)
)
execute_command(
'git push --delete origin {}'
.format(
get_formatted_tags(tags)
)
)
execute_command(
'git tag -d {}'
.format(
get_formatted_tags(tags)
)
)
else:
logger.info(
'No tags to delete older than "%s" days',
args.days
)
except Error as e:
logger.error(e)
return 2
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment