]>
git.proxmox.com Git - ceph.git/blob - ceph/src/script/ceph-release-notes
2 # Originally modified from A. Israel's script seen at
3 # https://gist.github.com/aisrael/b2b78d9dfdd176a232b9
4 """To run this script first install the dependencies
9 pip install githubpy GitPython requests
11 Generate a github access token; this is needed as the anonymous access
12 to Github's API will easily hit the limit even with a single invocation.
14 https://help.github.com/articles/creating-an-access-token-for-command-line-use/
16 Next either set the github token as an env variable
17 `GITHUB_ACCESS_TOKEN` or alternatively invoke the script with
22 ceph-release-notes -r tags/v0.87..origin/giant \
23 $(git rev-parse --show-toplevel)
27 from __future__
import print_function
39 fixes_re
= re
.compile(r
"Fixes\:? #(\d+)")
40 reviewed_by_re
= re
.compile(r
"Rev(.*)By", re
.IGNORECASE
)
41 # labels is the list of relevant labels defined for github.com/ceph/ceph
42 labels
= {'bluestore', 'build/ops', 'cephfs', 'common', 'core', 'mgr',
43 'mon', 'performance', 'pybind', 'rdma', 'rgw', 'rbd', 'tests',
45 merge_re
= re
.compile("Merge (pull request|PR) #(\d+).*")
46 # prefixes is the list of commit description prefixes we recognize
47 prefixes
= ['bluestore', 'build/ops', 'cephfs', 'cephx', 'cli', 'cmake',
48 'common', 'core', 'crush', 'doc', 'fs', 'librados', 'librbd',
49 'log', 'mds', 'mgr', 'mon', 'msg', 'objecter', 'osd', 'pybind',
50 'rbd', 'rbd-mirror', 'rbd-nbd', 'rgw', 'tests', 'tools']
51 signed_off_re
= re
.compile("Signed-off-by: (.+) <")
52 tracker_re
= re
.compile("http://tracker.ceph.com/issues/(\d+)")
53 rst_link_re
= re
.compile(r
"([a-zA-Z0-9])_(\W)")
54 release_re
= re
.compile(r
"^(nautilus|octopus|pacific|quincy):\s*")
56 tracker_uri
= "http://tracker.ceph.com/issues/{0}.json"
59 def get_original_issue(issue
, verbose
):
60 r
= requests
.get(tracker_uri
.format(issue
),
61 params
={"include": "relations"}).json()
63 # looking up for the original issue only makes sense
64 # when dealing with an issue in the Backport tracker
65 if r
["issue"]["tracker"]["name"] != "Backport":
67 print ("http://tracker.ceph.com/issues/" + issue
+
68 " is from the tracker " + r
["issue"]["tracker"]["name"] +
69 ", do not look for the original issue")
72 # if a Backport issue does not have a relation, keep it
73 if "relations" not in r
["issue"]:
75 print ("http://tracker.ceph.com/issues/" + issue
+
76 " has no relations, do not look for the original issue")
80 str(i
['issue_id']) for i
in r
["issue"]["relations"]
81 if i
["relation_type"] == "copied_to"
84 if len(copied_to
) > 1:
86 print ("ERROR: http://tracker.ceph.com/issues/" + issue
+
87 " has more than one Copied To relation")
90 print ("http://tracker.ceph.com/issues/" + issue
+
91 " is the backport of http://tracker.ceph.com/issues/" +
96 print ("http://tracker.ceph.com/issues/" + issue
+
97 " has no copied_to relations; do not look for the" +
102 def split_component(title
, gh
, number
):
103 title_re
= '(' + '|'.join(prefixes
) + ')(:.*)'
104 match
= re
.match(title_re
, title
)
106 return match
.group(1)+match
.group(2)
108 issue
= gh
.repos("ceph")("ceph").issues(number
).get()
109 issue_labels
= {it
['name'] for it
in issue
['labels']}
110 if 'documentation' in issue_labels
:
111 return 'doc: ' + title
112 item
= set(prefixes
).intersection(issue_labels
)
114 return ",".join(sorted(item
)) + ': ' + title
116 return 'UNKNOWN: ' + title
118 def _title_message(commit
, pr
, strict
):
120 message_lines
= commit
.message
.split('\n')
121 if strict
or len(message_lines
) < 1:
124 for line
in message_lines
[1:]:
125 if reviewed_by_re
.match(line
):
132 duplicates_pr_title
= lines
[0] == pr
['title'].strip()
133 if duplicates_pr_title
:
135 assert len(lines
) > 0, "missing message content"
137 # assume that a single line means the intention is to
138 # re-write the PR title
139 return (lines
[0], None)
140 elif len(lines
) < 3 and 'refs/pull' in lines
[0]:
141 # assume the intent was rewriting the title and something like
142 # ptl-tool was used to generate the merge message
143 return (lines
[1], None)
144 message
= " " + "\n ".join(lines
)
145 return (title
, message
)
147 def make_release_notes(gh
, repo
, ref
, plaintext
, html
, markdown
, verbose
, strict
, use_tags
, include_pr_messages
):
153 for commit
in repo
.iter_commits(ref
, merges
=True):
154 merge
= merge_re
.match(commit
.summary
)
157 number
= merge
.group(2)
158 print ("Considering PR#" + number
)
159 # do not pick up ceph/ceph-qa-suite.git PRs
160 if int(number
) < 1311:
161 print ("Ignoring low-numbered PR, probably picked up from"
162 " ceph/ceph-qa-suite.git")
167 while attempts
< retries
:
169 pr
= gh
.repos("ceph")("ceph").pulls(number
).get()
172 if attempts
< retries
:
174 sleep_time
= 2 * attempts
175 print(f
"Failed to fetch PR {number}, sleeping for {sleep_time} seconds")
176 time
.sleep(sleep_time
)
178 print(f
"Could not fetch PR {number} in {retries} tries.")
180 (title
, message
) = _title_message(commit
, pr
, strict
)
183 issues
= fixes_re
.findall(pr
['body']) + tracker_re
.findall(
188 for c
in repo
.iter_commits(
189 "{sha1}^1..{sha1}^2".format(sha1
=commit
.hexsha
)
191 for author
in re
.findall(
192 "Signed-off-by:\s*(.*?)\s*<", c
.message
195 issues
.extend(fixes_re
.findall(c
.message
) +
196 tracker_re
.findall(c
.message
))
198 author
= ", ".join(authors
.keys())
200 author
= commit
.parents
[-1].author
.name
202 if strict
and not issues
:
203 print ("ERROR: https://github.com/ceph/ceph/pull/" +
204 str(number
) + " has no associated issue")
209 '^(?:nautilus|octopus|pacific|quincy):\s+(' +
213 match
= re
.match(title_re
, title
)
215 print ("ERROR: https://github.com/ceph/ceph/pull/" +
216 str(number
) + " title " + title
+
217 " does not match " + title_re
)
219 title
= match
.group(1) + match
.group(2)
221 title
= split_component(title
, gh
, number
)
223 title
= title
.strip(' \t\n\r\f\v\.\,\;\:\-\=')
224 # escape asterisks, which is used by reStructuredTextrst for inline
226 title
= title
.replace('*', '\*')
227 # and escape the underscores for noting a link
228 title
= rst_link_re
.sub(r
'\1\_\2', title
)
229 # remove release prefix for backports
230 title
= release_re
.sub('', title
)
231 pr2info
[number
] = (author
, title
, message
)
233 for issue
in set(issues
):
235 issue
= get_original_issue(issue
, verbose
)
236 issue2prs
.setdefault(issue
, set([])).add(number
)
237 pr2issues
.setdefault(number
, set([])).add(issue
)
238 sys
.stdout
.write('.')
240 print (" done collecting merges.")
243 for (issue
, prs
) in issue2prs
.items():
245 print (">>>>>>> " + str(len(prs
)) + " pr for issue " +
246 issue
+ " " + str(prs
))
248 for (pr
, (author
, title
, message
)) in sorted(
249 pr2info
.items(), key
=lambda title
: title
[1][1].lower()
253 issues
= map(lambda issue
: '#' + str(issue
), pr2issues
[pr
])
255 issues
= map(lambda issue
: (
256 '<a href="http://tracker.ceph.com/issues/{issue}">issue#{issue}</a>'
257 ).format(issue
=issue
), pr2issues
[pr
]
260 issues
= map(lambda issue
: (
261 '[issue#{issue}](http://tracker.ceph.com/issues/{issue})'
262 ).format(issue
=issue
), pr2issues
[pr
]
265 issues
= map(lambda issue
: (
266 '`issue#{issue} <http://tracker.ceph.com/issues/{issue}>`_'
267 ).format(issue
=issue
), pr2issues
[pr
]
269 issues
= ", ".join(issues
) + ", "
273 print ("* {title} ({issues}{author})".format(
282 "<li><p>{title} ({issues}<a href=\""
283 "https://github.com/ceph/ceph/pull/{pr}\""
284 ">pr#{pr}</a>, {author})</p></li>"
292 markdown_title
= title
.replace('_', '\_').replace('.', '<span></span>.')
293 print ("- {title} ({issues}[pr#{pr}](https://github.com/ceph/ceph/pull/{pr}), {author})\n".format(
294 title
=markdown_title
,
302 "* {title} ({issues}`pr#{pr} <"
303 "https://github.com/ceph/ceph/pull/{pr}"
311 if include_pr_messages
and message
:
315 if __name__
== "__main__":
317 Make ceph release notes for a given revision. Eg usage:
319 $ ceph-release-notes -r tags/v0.87..origin/giant \
320 $(git rev-parse --show-toplevel)
322 It is recommended to set the github env. token in order to avoid
323 hitting the api rate limits.
326 parser
= argparse
.ArgumentParser(
328 formatter_class
=argparse
.RawTextHelpFormatter
331 parser
.add_argument("--rev", "-r",
332 help="git revision range for creating release notes")
333 parser
.add_argument("--text", "-t",
334 action
='store_true', default
=None,
335 help="output plain text only, no links")
336 parser
.add_argument("--html",
337 action
='store_true', default
=None,
338 help="output html format for (old wordpress) website blog")
339 parser
.add_argument("--markdown",
340 action
='store_true', default
=None,
341 help="output markdown format for new ceph.io blog")
342 parser
.add_argument("--verbose", "-v",
343 action
='store_true', default
=None,
345 parser
.add_argument("--strict",
346 action
='store_true', default
=None,
347 help="strict, recommended only for backport releases")
348 parser
.add_argument("repo", metavar
="repo",
349 help="path to ceph git repo")
352 default
=os
.getenv("GITHUB_ACCESS_TOKEN"),
353 help="Github Access Token ($GITHUB_ACCESS_TOKEN otherwise)",
355 parser
.add_argument("--use-tags", default
=False,
356 help="Use github tags to guess the component")
357 parser
.add_argument("--include-pr-messages", default
=False, action
='store_true',
358 help="Include full PR message in addition to PR title, if available")
360 args
= parser
.parse_args()
362 access_token
=args
.token
)
374 args
.include_pr_messages