2
2
# Little script to make HISTORY.rst more easy to format properly, lots TODO
3
3
# pull message down and embed, use arg parse, handle multiple, etc...
4
4
import os
5
+ import re
6
+ import subprocess
5
7
import sys
6
8
import textwrap
7
9
from urllib .parse import urljoin
21
23
PROJECT_API = f"https://api.github.com/repos/{ PROJECT_AUTHOR } /{ PROJECT_NAME } /"
22
24
23
25
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
+
24
96
def main (argv ):
25
97
history_path = os .path .join (PROJECT_DIRECTORY , "HISTORY.rst" )
26
98
with open (history_path , encoding = "utf-8" ) as fh :
@@ -30,6 +102,37 @@ def extend(from_str, line):
30
102
from_str += "\n "
31
103
return history .replace (from_str , from_str + line + "\n " )
32
104
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
+
33
136
ident = argv [1 ]
34
137
35
138
message = ""
0 commit comments