storj/scripts/changelog.py

165 lines
4.7 KiB
Python
Raw Permalink Normal View History

import argparse
import logging
import os
import subprocess
import sys
from enum import Enum
# Setting up basic logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class Section(Enum):
GENERAL = "General"
MULTINODE = "Multinode"
SATELLITE = "Satellite"
STORAGENODE = "Storagenode"
TEST = "Test"
UPLINK = "Uplink"
GITHUB_LINK = "[{0}](https://github.com/storj/storj/commit/{0})"
def git_ref_field(from_ref, to_ref):
"""
Executes a git command to find the difference in commits between two references.
Assumes 'from_ref' and 'to_ref' are valid Git references.
Args:
from_ref (str): The source reference.
to_ref (str): The target reference.
Returns:
str: A string containing the git commit differences.
"""
cmd = ["git", "cherry", from_ref, to_ref, "-v"]
try:
result = subprocess.run(cmd, text=True, capture_output=True, check=True)
return result.stdout
except subprocess.CalledProcessError as e:
logging.error(f"Error executing git command: {e.stderr}")
raise
def validate_git_refs(from_ref, to_ref):
"""
Validates the provided Git references.
Args:
from_ref (str): The source reference.
to_ref (str): The target reference.
Returns:
bool: True if references are valid, False otherwise.
"""
for ref in [from_ref, to_ref]:
result = subprocess.run(["git", "rev-parse", "--verify", ref], text=True, capture_output=True)
if result.returncode != 0:
logging.error(f"Invalid Git reference: {ref}")
return False
return True
def categorize_commit(commit, section_dict):
"""
Categorizes a single commit into the appropriate section.
Handles unexpected commit formats by logging a warning and defaulting to the GENERAL section.
Args:
commit (str): A git commit message.
section_dict (dict): Dictionary of sections.
Returns:
None
"""
try:
commit_category = commit[42:].split(":")[0].lower()
for category in section_dict:
if category.name.lower() in commit_category:
section_dict[category].append(generate_line(commit))
return
section_dict[Section.GENERAL].append(generate_line(commit))
except IndexError:
logging.warning(f"Unexpected commit format: {commit}")
section_dict[Section.GENERAL].append(generate_line(commit))
def generate_changelog(commits):
"""
Generates a formatted changelog from a string of commits.
Args:
commits (str): A string containing git commit messages.
Returns:
str: The formatted changelog.
"""
if not commits:
return "No new commits found or error occurred."
changelog = "# Changelog\n"
section_dict = {s: [] for s in Section}
for commit in commits.splitlines():
categorize_commit(commit, section_dict)
for title, lines in section_dict.items():
if lines:
changelog += f'### {title.value}\n' + ''.join(lines)
return changelog
def generate_line(commit):
"""
Formats a single commit line for the changelog.
Args:
commit (str): A git commit message.
Returns:
str: The formatted commit line.
"""
return f"- {GITHUB_LINK.format(commit[2:9])} {commit[42:]}\n"
def prompt_for_refs(args):
"""
Prompts user for 'from_ref' and 'to_ref' if not provided.
Args:
args: Parsed command-line arguments.
Returns:
None
"""
if not args.from_ref:
args.from_ref = input("Enter the starting Git reference (from_ref): ")
if not args.to_ref:
args.to_ref = input("Enter the ending Git reference (to_ref): ")
def main():
"""
Main function to parse arguments, validate them, and print the changelog.
If run interactively, prompts the user for input.
"""
parser = argparse.ArgumentParser(description="Generate a sorted changelog from Git commits.")
parser.add_argument("from_ref", nargs='?', help="The ref to show the path from")
parser.add_argument("to_ref", nargs='?', help="The ref to show the path to")
args = parser.parse_args()
# Check if the script is running interactively
if os.isatty(sys.stdin.fileno()):
prompt_for_refs(args)
if not (args.from_ref and args.to_ref) or not validate_git_refs(args.from_ref, args.to_ref):
parser.print_help()
sys.exit(1)
try:
commits = git_ref_field(args.from_ref, args.to_ref)
changelog = generate_changelog(commits)
print(changelog)
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
if __name__ == "__main__":
main()