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:
parent
3c2596e3e2
commit
fb894b3720
@ -1,69 +1,164 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Example of usage: changelog.py <old-release-tag> <new-release-tag>
|
|
||||||
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import collections
|
import logging
|
||||||
|
import os
|
||||||
import subprocess
|
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})"
|
GITHUB_LINK = "[{0}](https://github.com/storj/storj/commit/{0})"
|
||||||
|
|
||||||
|
|
||||||
def git_ref_field(from_ref, to_ref):
|
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)
|
Executes a git command to find the difference in commits between two references.
|
||||||
ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
Assumes 'from_ref' and 'to_ref' are valid Git references.
|
||||||
output = ps.communicate()[0]
|
|
||||||
return output.decode()
|
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):
|
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"
|
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():
|
for commit in commits.splitlines():
|
||||||
if TEST.lower() in commit[42:].split(":")[0]:
|
categorize_commit(commit, section_dict)
|
||||||
section[TEST].append(generate_line(commit))
|
|
||||||
elif MULTINODE.lower() in commit[42:].split(":")[0]:
|
for title, lines in section_dict.items():
|
||||||
section[MULTINODE].append(generate_line(commit))
|
if lines:
|
||||||
elif STORAGENODE.lower() in commit[42:].split(":")[0]:
|
changelog += f'### {title.value}\n' + ''.join(lines)
|
||||||
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))
|
|
||||||
|
|
||||||
for title in collections.OrderedDict(sorted(section.items())):
|
|
||||||
if section[title]:
|
|
||||||
changelog += ('### {}\n'.format(title))
|
|
||||||
for line in section[title]:
|
|
||||||
changelog += line
|
|
||||||
return changelog
|
return changelog
|
||||||
|
|
||||||
|
|
||||||
def generate_line(commit):
|
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():
|
def main():
|
||||||
p = argparse.ArgumentParser(description=(
|
"""
|
||||||
"generate changelog sorted by topics."))
|
Main function to parse arguments, validate them, and print the changelog.
|
||||||
p.add_argument("from_ref", help="the ref to show the path from")
|
If run interactively, prompts the user for input.
|
||||||
p.add_argument("to_ref", help="the ref to show the path to")
|
"""
|
||||||
args = p.parse_args()
|
parser = argparse.ArgumentParser(description="Generate a sorted changelog from Git commits.")
|
||||||
commits = git_ref_field(args.from_ref, args.to_ref)
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
Loading…
Reference in New Issue
Block a user