22# Little script to make HISTORY.rst more easy to format properly, lots TODO
33# pull message down and embed, use arg parse, handle multiple, etc...
44import os
5+ import re
6+ import subprocess
57import sys
68import textwrap
79from urllib .parse import urljoin
2123PROJECT_API = f"https://api.github.com/repos/{ PROJECT_AUTHOR } /{ PROJECT_NAME } /"
2224
2325
26+ def get_last_release_tag ():
27+ """Get the last release tag based on the current version in __init__.py"""
28+ version = project .__version__
29+ # Remove .dev0 suffix if present
30+ if ".dev" in version :
31+ version = version .split (".dev" )[0 ]
32+
33+ # Parse version components
34+ parts = version .split ("." )
35+ if len (parts ) >= 3 :
36+ major , minor , patch = parts [:3 ]
37+ # Decrement patch version to get last release
38+ last_patch = max (0 , int (patch ) - 1 )
39+ return f"{ major } .{ minor } .{ last_patch } "
40+ return version
41+
42+
43+ def get_merge_commits_since_tag (tag ):
44+ """Get merge commits since the specified tag"""
45+ try :
46+ result = subprocess .run (
47+ ["git" , "log" , "--merges" , "--oneline" , f"{ tag } ..HEAD" ], capture_output = True , text = True , check = True
48+ )
49+ return result .stdout .strip ().split ("\n " ) if result .stdout .strip () else []
50+ except subprocess .CalledProcessError :
51+ return []
52+
53+
54+ def extract_pr_info_from_merge (merge_line ):
55+ """Extract PR number and author from merge commit message"""
56+ # Match pattern: "Merge pull request #1234 from author/branch"
57+ match = re .match (r"[a-f0-9]+\s+Merge pull request #(\d+) from ([^/]+)/" , merge_line )
58+ if match :
59+ pr_number = match .group (1 )
60+ author = match .group (2 )
61+ return pr_number , author
62+ return None , None
63+
64+
65+ def generate_acknowledgements ():
66+ """Generate acknowledgement lines for merge commits since last release"""
67+ tag = get_last_release_tag ()
68+ merge_commits = get_merge_commits_since_tag (tag )
69+
70+ acknowledgements = []
71+ for merge in merge_commits :
72+ if merge .strip ():
73+ pr_number , author = extract_pr_info_from_merge (merge )
74+ if pr_number and author :
75+ try :
76+ # Get PR details from GitHub API
77+ api_url = urljoin (PROJECT_API , f"pulls/{ pr_number } " )
78+ req = requests .get (api_url ).json ()
79+ title = req .get ("title" , "" )
80+ login = req ["user" ]["login" ]
81+
82+ # Format acknowledgement line
83+ title_clean = title .rstrip ("." )
84+ ack_line = f"* { title_clean } (thanks to `@{ login } `_). `Pull Request { pr_number } `_"
85+ acknowledgements .append (ack_line )
86+
87+ # Add GitHub link
88+ github_link = f".. _Pull Request { pr_number } : { PROJECT_URL } /pull/{ pr_number } "
89+ acknowledgements .append (github_link )
90+ except Exception as e :
91+ print (f"Error processing PR { pr_number } : { e } " , file = sys .stderr )
92+
93+ return acknowledgements
94+
95+
2496def main (argv ):
2597 history_path = os .path .join (PROJECT_DIRECTORY , "HISTORY.rst" )
2698 with open (history_path , encoding = "utf-8" ) as fh :
@@ -30,6 +102,37 @@ def extend(from_str, line):
30102 from_str += "\n "
31103 return history .replace (from_str , from_str + line + "\n " )
32104
105+ # Check if we should generate acknowledgements for merge commits
106+ if len (argv ) > 1 and argv [1 ] == "--acknowledgements" :
107+ acknowledgements = generate_acknowledgements ()
108+ if acknowledgements :
109+ print ("Generated acknowledgement lines:" )
110+
111+ # Find the unreleased section (current dev version)
112+ current_version = project .__version__
113+ unreleased_section_marker = f"---------------------\n { current_version } \n ---------------------"
114+
115+ for ack in acknowledgements :
116+ if ack .startswith ("*" ):
117+ print (ack )
118+ # Place acknowledgement lines in the unreleased section
119+ history = extend (unreleased_section_marker , ack )
120+ elif ack .startswith (".." ):
121+ print (ack )
122+ history = extend (".. github_links" , ack )
123+
124+ with open (history_path , "w" , encoding = "utf-8" ) as fh :
125+ fh .write (history )
126+ print (f"\n Acknowledgements added to { history_path } " )
127+ else :
128+ print ("No merge commits found since last release." )
129+ return
130+
131+ if len (argv ) < 2 :
132+ print ("Usage: python bootstrap_history.py <identifier> [message]" )
133+ print (" or: python bootstrap_history.py --acknowledgements" )
134+ return
135+
33136 ident = argv [1 ]
34137
35138 message = ""
0 commit comments