Instantly share code, notes, and snippets.
Created
June 7, 2021 18:33
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save theodore86/b62c8ea1380f19abb639b4bc116151a9 to your computer and use it in GitHub Desktop.
Python script to delete git tags older than X days
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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