scripts/draft-release.sh: improve changelog script (#6508)

- Improved commit message parsing for better format handling
- Added argument validation and user prompts for interactive mode
- Implemented enhanced error handling and logging
This commit is contained in:
Igor 2023-11-22 20:26:03 +02:00 committed by GitHub
parent 3c2596e3e2
commit fb894b3720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,69 +1,164 @@
#!/usr/bin/env python
# Example of usage: changelog.py <old-release-tag> <new-release-tag>
import argparse
import collections
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"
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):
# Execute command to show diff without cherry-picks
cmd = "git cherry {} {} -v | grep '^+'".format(from_ref, to_ref)
ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = ps.communicate()[0]
return output.decode()
"""
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 = {SATELLITE: [], MULTINODE: [], STORAGENODE: [], TEST: [], UPLINK: [], GENERAL: []}
section_dict = {s: [] for s in Section}
# Sorting and populating the dictionary d with commit hash and message
for commit in commits.splitlines():
if TEST.lower() in commit[42:].split(":")[0]:
section[TEST].append(generate_line(commit))
elif MULTINODE.lower() in commit[42:].split(":")[0]:
section[MULTINODE].append(generate_line(commit))
elif STORAGENODE.lower() in commit[42:].split(":")[0]:
section[STORAGENODE].append(generate_line(commit))
elif UPLINK.lower() in commit[42:].split(":")[0]:
section[UPLINK].append(generate_line(commit))
elif SATELLITE.lower() in commit[42:].split(":")[0]:
section[SATELLITE].append(generate_line(commit))
else:
section[GENERAL].append(generate_line(commit))
categorize_commit(commit, section_dict)
for title, lines in section_dict.items():
if lines:
changelog += f'### {title.value}\n' + ''.join(lines)
for title in collections.OrderedDict(sorted(section.items())):
if section[title]:
changelog += ('### {}\n'.format(title))
for line in section[title]:
changelog += line
return changelog
def generate_line(commit):
return "- {}{} \n".format(GITHUB_LINK.format(commit[2:9]), commit[42:])
"""
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():
p = argparse.ArgumentParser(description=(
"generate changelog sorted by topics."))
p.add_argument("from_ref", help="the ref to show the path from")
p.add_argument("to_ref", help="the ref to show the path to")
args = p.parse_args()
commits = git_ref_field(args.from_ref, args.to_ref)
"""
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")
print(generate_changelog(commits))
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()