]>
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
38 fixes_re
= re
.compile(r
"Fixes\:? #(\d+)")
39 # labels is the list of relevant labels defined for github.com/ceph/ceph
40 labels
= ['bluestore', 'build/ops', 'cephfs', 'common', 'core', 'mgr',
41 'mon', 'performance', 'pybind', 'rdma', 'rgw', 'rbd', 'tests',
43 merge_re
= re
.compile("Merge pull request #(\d+).*")
44 # prefixes is the list of commit description prefixes we recognize
45 prefixes
= ['bluestore', 'build/ops', 'cephfs', 'cephx', 'cli', 'cmake',
46 'common', 'core', 'crush', 'doc', 'fs', 'librados', 'librbd',
47 'log', 'mds', 'mgr', 'mon', 'msg', 'objecter', 'osd', 'pybind',
48 'rbd', 'rbd-mirror', 'rbd-nbd', 'rgw', 'tests', 'tools']
49 signed_off_re
= re
.compile("Signed-off-by: (.+) <")
50 tracker_re
= re
.compile("http://tracker.ceph.com/issues/(\d+)")
51 tracker_uri
= "http://tracker.ceph.com/issues/{0}.json"
54 def get_original_issue(issue
, verbose
):
55 r
= requests
.get(tracker_uri
.format(issue
),
56 params
={"include": "relations"}).json()
58 # looking up for the original issue only makes sense
59 # when dealing with an issue in the Backport tracker
60 if r
["issue"]["tracker"]["name"] != "Backport":
62 print ("http://tracker.ceph.com/issues/" + issue
+
63 " is from the tracker " + r
["issue"]["tracker"]["name"] +
64 ", do not look for the original issue")
67 # if a Backport issue does not have a relation, keep it
68 if "relations" not in r
["issue"]:
70 print ("http://tracker.ceph.com/issues/" + issue
+
71 " has no relations, do not look for the original issue")
75 str(i
['issue_id']) for i
in r
["issue"]["relations"]
76 if i
["relation_type"] == "copied_to"
79 if len(copied_to
) > 1:
81 print ("ERROR: http://tracker.ceph.com/issues/" + issue
+
82 " has more than one Copied To relation")
85 print ("http://tracker.ceph.com/issues/" + issue
+
86 " is the backport of http://tracker.ceph.com/issues/" +
91 print ("http://tracker.ceph.com/issues/" + issue
+
92 " has no copied_to relations; do not look for the" +
97 def split_component(title
, gh
, number
):
98 title_re
= '(' + '|'.join(prefixes
) + ')(:.*)'
99 match
= re
.match(title_re
, title
)
101 return match
.group(1)+match
.group(2)
103 issue
= gh
.repos("ceph")("ceph").issues(number
).get()
104 issue_labels
= {it
['name'] for it
in issue
['labels']}
105 if 'documentation' in issue_labels
:
106 return 'doc: ' + title
107 item
= labels
.intersection(issue_labels
)
109 return ",".join(item
) + ': ' + title
111 return 'UNKNOWN: ' + title
114 def make_release_notes(gh
, repo
, ref
, plaintext
, verbose
, strict
, use_tags
):
120 for commit
in repo
.iter_commits(ref
, merges
=True):
121 merge
= merge_re
.match(commit
.summary
)
123 number
= merge
.group(1)
124 print ("Considering PR#" + number
)
125 # do not pick up ceph/ceph-qa-suite.git PRs
126 if int(number
) < 1311:
127 print ("Ignoring low-numbered PR, probably picked up from"
128 " ceph/ceph-qa-suite.git")
130 pr
= gh
.repos("ceph")("ceph").pulls(number
).get()
133 message_lines
= commit
.message
.split('\n')
134 if not strict
and len(message_lines
) > 1:
136 for line
in message_lines
[1:]:
137 if 'Reviewed-by' in line
:
144 duplicates_pr_title
= lines
[0] == pr
['title'].strip()
145 if duplicates_pr_title
:
147 assert len(lines
) > 0, "missing message content"
149 # assume that a single line means the intention is to
150 # re-write the PR title
154 message
= " " + "\n ".join(lines
)
157 issues
= fixes_re
.findall(pr
['body']) + tracker_re
.findall(
162 for c
in repo
.iter_commits(
163 "{sha1}^1..{sha1}^2".format(sha1
=commit
.hexsha
)
165 for author
in re
.findall(
166 "Signed-off-by:\s*(.*?)\s*<", c
.message
169 issues
.extend(fixes_re
.findall(c
.message
) +
170 tracker_re
.findall(c
.message
))
172 author
= ", ".join(authors
.keys())
174 author
= commit
.parents
[-1].author
.name
176 if strict
and not issues
:
177 print ("ERROR: https://github.com/ceph/ceph/pull/" +
178 str(number
) + " has no associated issue")
183 '^(?:hammer|infernalis|jewel|kraken):\s+(' +
187 match
= re
.match(title_re
, title
)
189 print ("ERROR: https://github.com/ceph/ceph/pull/" +
190 str(number
) + " title " + title
.encode("utf-8") +
191 " does not match " + title_re
)
193 title
= match
.group(1) + match
.group(2)
195 title
= split_component(title
, gh
, number
)
197 title
= title
.strip(' \t\n\r\f\v\.\,\;\:\-\=')
198 # escape asterisks, which is used by reStructuredTextrst for inline
200 title
= title
.replace('*', '\*')
201 pr2info
[number
] = (author
, title
, message
)
203 for issue
in set(issues
):
205 issue
= get_original_issue(issue
, verbose
)
206 issue2prs
.setdefault(issue
, set([])).add(number
)
207 pr2issues
.setdefault(number
, set([])).add(issue
)
208 sys
.stdout
.write('.')
210 print (" done collecting merges.")
213 for (issue
, prs
) in issue2prs
.iteritems():
215 print (">>>>>>> " + str(len(prs
)) + " pr for issue " +
216 issue
+ " " + str(prs
))
218 for (pr
, (author
, title
, message
)) in sorted(
219 pr2info
.iteritems(), key
=lambda (k
, v
): (v
[2], v
[1])
223 issues
= map(lambda issue
: '#' + str(issue
), pr2issues
[pr
])
225 issues
= map(lambda issue
: (
226 '`issue#{issue} <http://tracker.ceph.com/issues/{issue}>`_'
227 ).format(issue
=issue
), pr2issues
[pr
]
229 issues
= ", ".join(issues
) + ", "
233 print ("* {title} ({issues}{author})".format(
234 title
=title
.encode("utf-8"),
236 author
=author
.encode("utf-8")
242 "* {title} ({issues}`pr#{pr} <"
243 "https://github.com/ceph/ceph/pull/{pr}"
246 title
=title
.encode("utf-8"),
248 author
=author
.encode("utf-8"), pr
=pr
255 if __name__
== "__main__":
257 Make ceph release notes for a given revision. Eg usage:
259 $ ceph-release-notes -r tags/v0.87..origin/giant \
260 $(git rev-parse --show-toplevel)
262 It is recommended to set the github env. token in order to avoid
263 hitting the api rate limits.
266 parser
= argparse
.ArgumentParser(
268 formatter_class
=argparse
.RawTextHelpFormatter
271 parser
.add_argument("--rev", "-r",
272 help="git revision range for creating release notes")
273 parser
.add_argument("--text", "-t",
274 action
='store_true', default
=None,
275 help="output plain text only, no links")
276 parser
.add_argument("--verbose", "-v",
277 action
='store_true', default
=None,
279 parser
.add_argument("--strict",
280 action
='store_true', default
=None,
281 help="strict, recommended only for backport releases")
282 parser
.add_argument("repo", metavar
="repo",
283 help="path to ceph git repo")
286 default
=os
.getenv("GITHUB_ACCESS_TOKEN"),
287 help="Github Access Token ($GITHUB_ACCESS_TOKEN otherwise)",
289 parser
.add_argument("--use-tags", default
=False,
290 help="Use github tags to guess the component")
292 args
= parser
.parse_args()
294 access_token
=args
.token
)