240 |
241 |
241 if '.hgreleasenotes' in ctx: |
242 if '.hgreleasenotes' in ctx: |
242 read('.hgreleasenotes') |
243 read('.hgreleasenotes') |
243 return p['sections'] |
244 return p['sections'] |
244 |
245 |
|
246 def checkadmonitions(ui, repo, directives, revs): |
|
247 """ |
|
248 Checks the commit messages for admonitions and their validity. |
|
249 |
|
250 .. abcd:: |
|
251 |
|
252 First paragraph under this admonition |
|
253 |
|
254 For this commit message, using `hg releasenotes -r . --check` |
|
255 returns: Invalid admonition 'abcd' present in changeset 3ea92981e103 |
|
256 |
|
257 As admonition 'abcd' is neither present in default nor custom admonitions |
|
258 """ |
|
259 for rev in revs: |
|
260 ctx = repo[rev] |
|
261 admonition = re.search(RE_DIRECTIVE, ctx.description()) |
|
262 if admonition: |
|
263 if admonition.group(1) in directives: |
|
264 continue |
|
265 else: |
|
266 ui.write(_("Invalid admonition '%s' present in changeset %s" |
|
267 "\n") % (admonition.group(1), ctx.hex()[:12])) |
|
268 sim = lambda x: difflib.SequenceMatcher(None, |
|
269 admonition.group(1), x).ratio() |
|
270 |
|
271 similar = [s for s in directives if sim(s) > 0.6] |
|
272 if len(similar) == 1: |
|
273 ui.write(_("(did you mean %s?)\n") % similar[0]) |
|
274 elif similar: |
|
275 ss = ", ".join(sorted(similar)) |
|
276 ui.write(_("(did you mean one of %s?)\n") % ss) |
|
277 |
245 def parsenotesfromrevisions(repo, directives, revs): |
278 def parsenotesfromrevisions(repo, directives, revs): |
246 notes = parsedreleasenotes() |
279 notes = parsedreleasenotes() |
247 |
280 |
248 for rev in revs: |
281 for rev in revs: |
249 ctx = repo[rev] |
282 ctx = repo[rev] |
430 lines.append('') |
463 lines.append('') |
431 |
464 |
432 return '\n'.join(lines) |
465 return '\n'.join(lines) |
433 |
466 |
434 @command('releasenotes', |
467 @command('releasenotes', |
435 [('r', 'rev', '', _('revisions to process for release notes'), _('REV'))], |
468 [('r', 'rev', '', _('revisions to process for release notes'), _('REV')), |
436 _('[-r REV] FILE')) |
469 ('c', 'check', False, _('checks for validity of admonitions (if any)'), |
437 def releasenotes(ui, repo, file_, rev=None): |
470 _('REV'))], |
|
471 _('hg releasenotes [-r REV] [-c] FILE')) |
|
472 def releasenotes(ui, repo, file_=None, **opts): |
438 """parse release notes from commit messages into an output file |
473 """parse release notes from commit messages into an output file |
439 |
474 |
440 Given an output file and set of revisions, this command will parse commit |
475 Given an output file and set of revisions, this command will parse commit |
441 messages for release notes then add them to the output file. |
476 messages for release notes then add them to the output file. |
442 |
477 |
509 this command and changes should not be lost when running this command on |
544 this command and changes should not be lost when running this command on |
510 that file. A particular use case for this is to tweak the wording of a |
545 that file. A particular use case for this is to tweak the wording of a |
511 release note after it has been added to the release notes file. |
546 release note after it has been added to the release notes file. |
512 """ |
547 """ |
513 sections = releasenotessections(ui, repo) |
548 sections = releasenotessections(ui, repo) |
|
549 rev = opts.get('rev') |
514 |
550 |
515 revs = scmutil.revrange(repo, [rev or 'not public()']) |
551 revs = scmutil.revrange(repo, [rev or 'not public()']) |
|
552 if opts.get('check'): |
|
553 return checkadmonitions(ui, repo, sections.names(), revs) |
|
554 |
516 incoming = parsenotesfromrevisions(repo, sections.names(), revs) |
555 incoming = parsenotesfromrevisions(repo, sections.names(), revs) |
517 |
556 |
518 try: |
557 try: |
519 with open(file_, 'rb') as fh: |
558 with open(file_, 'rb') as fh: |
520 notes = parsereleasenotesfile(sections, fh.read()) |
559 notes = parsereleasenotesfile(sections, fh.read()) |