##// END OF EJS Templates
releasenotes: add custom admonitions support for release notes...
Rishabh Madan -
r33572:9a944e90 default
parent child Browse files
Show More
@@ -1,441 +1,465 b''
1 1 # Copyright 2017-present Gregory Szorc <gregory.szorc@gmail.com>
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 """generate release notes from commit messages (EXPERIMENTAL)
7 7
8 8 It is common to maintain files detailing changes in a project between
9 9 releases. Maintaining these files can be difficult and time consuming.
10 10 The :hg:`releasenotes` command provided by this extension makes the
11 11 process simpler by automating it.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import errno
17 17 import re
18 18 import sys
19 19 import textwrap
20 20
21 21 from mercurial.i18n import _
22 22 from mercurial import (
23 config,
23 24 error,
24 25 minirst,
25 26 registrar,
26 27 scmutil,
28 util,
27 29 )
28 30
29 31 cmdtable = {}
30 32 command = registrar.command(cmdtable)
31 33
32 34 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
33 35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
34 36 # be specifying the version(s) of Mercurial they are tested with, or
35 37 # leave the attribute unspecified.
36 38 testedwith = 'ships-with-hg-core'
37 39
38 40 DEFAULT_SECTIONS = [
39 41 ('feature', _('New Features')),
40 42 ('bc', _('Backwards Compatibility Changes')),
41 43 ('fix', _('Bug Fixes')),
42 44 ('perf', _('Performance Improvements')),
43 45 ('api', _('API Changes')),
44 46 ]
45 47
46 48 RE_DIRECTIVE = re.compile('^\.\. ([a-zA-Z0-9_]+)::\s*([^$]+)?$')
47 49
48 50 BULLET_SECTION = _('Other Changes')
49 51
50 52 class parsedreleasenotes(object):
51 53 def __init__(self):
52 54 self.sections = {}
53 55
54 56 def __contains__(self, section):
55 57 return section in self.sections
56 58
57 59 def __iter__(self):
58 60 return iter(sorted(self.sections))
59 61
60 62 def addtitleditem(self, section, title, paragraphs):
61 63 """Add a titled release note entry."""
62 64 self.sections.setdefault(section, ([], []))
63 65 self.sections[section][0].append((title, paragraphs))
64 66
65 67 def addnontitleditem(self, section, paragraphs):
66 68 """Adds a non-titled release note entry.
67 69
68 70 Will be rendered as a bullet point.
69 71 """
70 72 self.sections.setdefault(section, ([], []))
71 73 self.sections[section][1].append(paragraphs)
72 74
73 75 def titledforsection(self, section):
74 76 """Returns titled entries in a section.
75 77
76 78 Returns a list of (title, paragraphs) tuples describing sub-sections.
77 79 """
78 80 return self.sections.get(section, ([], []))[0]
79 81
80 82 def nontitledforsection(self, section):
81 83 """Returns non-titled, bulleted paragraphs in a section."""
82 84 return self.sections.get(section, ([], []))[1]
83 85
84 86 def hastitledinsection(self, section, title):
85 87 return any(t[0] == title for t in self.titledforsection(section))
86 88
87 89 def merge(self, ui, other):
88 90 """Merge another instance into this one.
89 91
90 92 This is used to combine multiple sources of release notes together.
91 93 """
92 94 for section in other:
93 95 for title, paragraphs in other.titledforsection(section):
94 96 if self.hastitledinsection(section, title):
95 97 # TODO prompt for resolution if different and running in
96 98 # interactive mode.
97 99 ui.write(_('%s already exists in %s section; ignoring\n') %
98 100 (title, section))
99 101 continue
100 102
101 103 # TODO perform similarity comparison and try to match against
102 104 # existing.
103 105 self.addtitleditem(section, title, paragraphs)
104 106
105 107 for paragraphs in other.nontitledforsection(section):
106 108 if paragraphs in self.nontitledforsection(section):
107 109 continue
108 110
109 111 # TODO perform similarily comparison and try to match against
110 112 # existing.
111 113 self.addnontitleditem(section, paragraphs)
112 114
113 115 class releasenotessections(object):
114 def __init__(self, ui):
115 # TODO support defining custom sections from config.
116 self._sections = list(DEFAULT_SECTIONS)
116 def __init__(self, ui, repo=None):
117 if repo:
118 sections = util.sortdict(DEFAULT_SECTIONS)
119 custom_sections = getcustomadmonitions(repo)
120 if custom_sections:
121 sections.update(custom_sections)
122 self._sections = list(sections.iteritems())
123 else:
124 self._sections = list(DEFAULT_SECTIONS)
117 125
118 126 def __iter__(self):
119 127 return iter(self._sections)
120 128
121 129 def names(self):
122 130 return [t[0] for t in self._sections]
123 131
124 132 def sectionfromtitle(self, title):
125 133 for name, value in self._sections:
126 134 if value == title:
127 135 return name
128 136
129 137 return None
130 138
139 def getcustomadmonitions(repo):
140 ctx = repo['.']
141 p = config.config()
142
143 def read(f, sections=None, remap=None):
144 if f in ctx:
145 data = ctx[f].data()
146 p.parse(f, data, sections, remap, read)
147 else:
148 raise error.Abort(_(".hgreleasenotes file \'%s\' not found") %
149 repo.pathto(f))
150
151 if '.hgreleasenotes' in ctx:
152 read('.hgreleasenotes')
153 return p['sections']
154
131 155 def parsenotesfromrevisions(repo, directives, revs):
132 156 notes = parsedreleasenotes()
133 157
134 158 for rev in revs:
135 159 ctx = repo[rev]
136 160
137 161 blocks, pruned = minirst.parse(ctx.description(),
138 162 admonitions=directives)
139 163
140 164 for i, block in enumerate(blocks):
141 165 if block['type'] != 'admonition':
142 166 continue
143 167
144 168 directive = block['admonitiontitle']
145 169 title = block['lines'][0].strip() if block['lines'] else None
146 170
147 171 if i + 1 == len(blocks):
148 172 raise error.Abort(_('release notes directive %s lacks content')
149 173 % directive)
150 174
151 175 # Now search ahead and find all paragraphs attached to this
152 176 # admonition.
153 177 paragraphs = []
154 178 for j in range(i + 1, len(blocks)):
155 179 pblock = blocks[j]
156 180
157 181 # Margin blocks may appear between paragraphs. Ignore them.
158 182 if pblock['type'] == 'margin':
159 183 continue
160 184
161 185 if pblock['type'] != 'paragraph':
162 186 raise error.Abort(_('unexpected block in release notes '
163 187 'directive %s') % directive)
164 188
165 189 if pblock['indent'] > 0:
166 190 paragraphs.append(pblock['lines'])
167 191 else:
168 192 break
169 193
170 194 # TODO consider using title as paragraph for more concise notes.
171 195 if not paragraphs:
172 196 raise error.Abort(_('could not find content for release note '
173 197 '%s') % directive)
174 198
175 199 if title:
176 200 notes.addtitleditem(directive, title, paragraphs)
177 201 else:
178 202 notes.addnontitleditem(directive, paragraphs)
179 203
180 204 return notes
181 205
182 206 def parsereleasenotesfile(sections, text):
183 207 """Parse text content containing generated release notes."""
184 208 notes = parsedreleasenotes()
185 209
186 210 blocks = minirst.parse(text)[0]
187 211
188 212 def gatherparagraphsbullets(offset, title=False):
189 213 notefragment = []
190 214
191 215 for i in range(offset + 1, len(blocks)):
192 216 block = blocks[i]
193 217
194 218 if block['type'] == 'margin':
195 219 continue
196 220 elif block['type'] == 'section':
197 221 break
198 222 elif block['type'] == 'bullet':
199 223 if block['indent'] != 0:
200 224 raise error.Abort(_('indented bullet lists not supported'))
201 225 if title:
202 226 lines = [l[1:].strip() for l in block['lines']]
203 227 notefragment.append(lines)
204 228 continue
205 229 else:
206 230 lines = [[l[1:].strip() for l in block['lines']]]
207 231
208 232 for block in blocks[i + 1:]:
209 233 if block['type'] in ('bullet', 'section'):
210 234 break
211 235 if block['type'] == 'paragraph':
212 236 lines.append(block['lines'])
213 237 notefragment.append(lines)
214 238 continue
215 239 elif block['type'] != 'paragraph':
216 240 raise error.Abort(_('unexpected block type in release notes: '
217 241 '%s') % block['type'])
218 242 if title:
219 243 notefragment.append(block['lines'])
220 244
221 245 return notefragment
222 246
223 247 currentsection = None
224 248 for i, block in enumerate(blocks):
225 249 if block['type'] != 'section':
226 250 continue
227 251
228 252 title = block['lines'][0]
229 253
230 254 # TODO the parsing around paragraphs and bullet points needs some
231 255 # work.
232 256 if block['underline'] == '=': # main section
233 257 name = sections.sectionfromtitle(title)
234 258 if not name:
235 259 raise error.Abort(_('unknown release notes section: %s') %
236 260 title)
237 261
238 262 currentsection = name
239 263 bullet_points = gatherparagraphsbullets(i)
240 264 if bullet_points:
241 265 for para in bullet_points:
242 266 notes.addnontitleditem(currentsection, para)
243 267
244 268 elif block['underline'] == '-': # sub-section
245 269 if title == BULLET_SECTION:
246 270 bullet_points = gatherparagraphsbullets(i)
247 271 for para in bullet_points:
248 272 notes.addnontitleditem(currentsection, para)
249 273 else:
250 274 paragraphs = gatherparagraphsbullets(i, True)
251 275 notes.addtitleditem(currentsection, title, paragraphs)
252 276 else:
253 277 raise error.Abort(_('unsupported section type for %s') % title)
254 278
255 279 return notes
256 280
257 281 def serializenotes(sections, notes):
258 282 """Serialize release notes from parsed fragments and notes.
259 283
260 284 This function essentially takes the output of ``parsenotesfromrevisions()``
261 285 and ``parserelnotesfile()`` and produces output combining the 2.
262 286 """
263 287 lines = []
264 288
265 289 for sectionname, sectiontitle in sections:
266 290 if sectionname not in notes:
267 291 continue
268 292
269 293 lines.append(sectiontitle)
270 294 lines.append('=' * len(sectiontitle))
271 295 lines.append('')
272 296
273 297 # First pass to emit sub-sections.
274 298 for title, paragraphs in notes.titledforsection(sectionname):
275 299 lines.append(title)
276 300 lines.append('-' * len(title))
277 301 lines.append('')
278 302
279 303 wrapper = textwrap.TextWrapper(width=78)
280 304 for i, para in enumerate(paragraphs):
281 305 if i:
282 306 lines.append('')
283 307 lines.extend(wrapper.wrap(' '.join(para)))
284 308
285 309 lines.append('')
286 310
287 311 # Second pass to emit bullet list items.
288 312
289 313 # If the section has titled and non-titled items, we can't
290 314 # simply emit the bullet list because it would appear to come
291 315 # from the last title/section. So, we emit a new sub-section
292 316 # for the non-titled items.
293 317 nontitled = notes.nontitledforsection(sectionname)
294 318 if notes.titledforsection(sectionname) and nontitled:
295 319 # TODO make configurable.
296 320 lines.append(BULLET_SECTION)
297 321 lines.append('-' * len(BULLET_SECTION))
298 322 lines.append('')
299 323
300 324 for paragraphs in nontitled:
301 325 wrapper = textwrap.TextWrapper(initial_indent='* ',
302 326 subsequent_indent=' ',
303 327 width=78)
304 328 lines.extend(wrapper.wrap(' '.join(paragraphs[0])))
305 329
306 330 wrapper = textwrap.TextWrapper(initial_indent=' ',
307 331 subsequent_indent=' ',
308 332 width=78)
309 333 for para in paragraphs[1:]:
310 334 lines.append('')
311 335 lines.extend(wrapper.wrap(' '.join(para)))
312 336
313 337 lines.append('')
314 338
315 339 if lines[-1]:
316 340 lines.append('')
317 341
318 342 return '\n'.join(lines)
319 343
320 344 @command('releasenotes',
321 345 [('r', 'rev', '', _('revisions to process for release notes'), _('REV'))],
322 346 _('[-r REV] FILE'))
323 347 def releasenotes(ui, repo, file_, rev=None):
324 348 """parse release notes from commit messages into an output file
325 349
326 350 Given an output file and set of revisions, this command will parse commit
327 351 messages for release notes then add them to the output file.
328 352
329 353 Release notes are defined in commit messages as ReStructuredText
330 354 directives. These have the form::
331 355
332 356 .. directive:: title
333 357
334 358 content
335 359
336 360 Each ``directive`` maps to an output section in a generated release notes
337 361 file, which itself is ReStructuredText. For example, the ``.. feature::``
338 362 directive would map to a ``New Features`` section.
339 363
340 364 Release note directives can be either short-form or long-form. In short-
341 365 form, ``title`` is omitted and the release note is rendered as a bullet
342 366 list. In long form, a sub-section with the title ``title`` is added to the
343 367 section.
344 368
345 369 The ``FILE`` argument controls the output file to write gathered release
346 370 notes to. The format of the file is::
347 371
348 372 Section 1
349 373 =========
350 374
351 375 ...
352 376
353 377 Section 2
354 378 =========
355 379
356 380 ...
357 381
358 382 Only sections with defined release notes are emitted.
359 383
360 384 If a section only has short-form notes, it will consist of bullet list::
361 385
362 386 Section
363 387 =======
364 388
365 389 * Release note 1
366 390 * Release note 2
367 391
368 392 If a section has long-form notes, sub-sections will be emitted::
369 393
370 394 Section
371 395 =======
372 396
373 397 Note 1 Title
374 398 ------------
375 399
376 400 Description of the first long-form note.
377 401
378 402 Note 2 Title
379 403 ------------
380 404
381 405 Description of the second long-form note.
382 406
383 407 If the ``FILE`` argument points to an existing file, that file will be
384 408 parsed for release notes having the format that would be generated by this
385 409 command. The notes from the processed commit messages will be *merged*
386 410 into this parsed set.
387 411
388 412 During release notes merging:
389 413
390 414 * Duplicate items are automatically ignored
391 415 * Items that are different are automatically ignored if the similarity is
392 416 greater than a threshold.
393 417
394 418 This means that the release notes file can be updated independently from
395 419 this command and changes should not be lost when running this command on
396 420 that file. A particular use case for this is to tweak the wording of a
397 421 release note after it has been added to the release notes file.
398 422 """
399 sections = releasenotessections(ui)
423 sections = releasenotessections(ui, repo)
400 424
401 425 revs = scmutil.revrange(repo, [rev or 'not public()'])
402 426 incoming = parsenotesfromrevisions(repo, sections.names(), revs)
403 427
404 428 try:
405 429 with open(file_, 'rb') as fh:
406 430 notes = parsereleasenotesfile(sections, fh.read())
407 431 except IOError as e:
408 432 if e.errno != errno.ENOENT:
409 433 raise
410 434
411 435 notes = parsedreleasenotes()
412 436
413 437 notes.merge(ui, incoming)
414 438
415 439 with open(file_, 'wb') as fh:
416 440 fh.write(serializenotes(sections, notes))
417 441
418 442 @command('debugparsereleasenotes', norepo=True)
419 def debugparsereleasenotes(ui, path):
443 def debugparsereleasenotes(ui, path, repo=None):
420 444 """parse release notes and print resulting data structure"""
421 445 if path == '-':
422 446 text = sys.stdin.read()
423 447 else:
424 448 with open(path, 'rb') as fh:
425 449 text = fh.read()
426 450
427 sections = releasenotessections(ui)
451 sections = releasenotessections(ui, repo)
428 452
429 453 notes = parsereleasenotesfile(sections, text)
430 454
431 455 for section in notes:
432 456 ui.write(_('section: %s\n') % section)
433 457 for title, paragraphs in notes.titledforsection(section):
434 458 ui.write(_(' subsection: %s\n') % title)
435 459 for para in paragraphs:
436 460 ui.write(_(' paragraph: %s\n') % ' '.join(para))
437 461
438 462 for paragraphs in notes.nontitledforsection(section):
439 463 ui.write(_(' bullet point:\n'))
440 464 for para in paragraphs:
441 465 ui.write(_(' paragraph: %s\n') % ' '.join(para))
@@ -1,326 +1,378 b''
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [extensions]
3 3 > releasenotes=
4 4 > EOF
5 5
6 6 $ hg init simple-repo
7 7 $ cd simple-repo
8 8
9 9 A fix with a single line results in a bullet point in the appropriate section
10 10
11 11 $ touch fix1
12 12 $ hg -q commit -A -l - << EOF
13 13 > single line fix
14 14 >
15 15 > .. fix::
16 16 >
17 17 > Simple fix with a single line content entry.
18 18 > EOF
19 19
20 20 $ hg releasenotes -r . $TESTTMP/relnotes-single-line
21 21
22 22 $ cat $TESTTMP/relnotes-single-line
23 23 Bug Fixes
24 24 =========
25 25
26 26 * Simple fix with a single line content entry.
27 27
28 28 A fix with multiple lines is handled correctly
29 29
30 30 $ touch fix2
31 31 $ hg -q commit -A -l - << EOF
32 32 > multi line fix
33 33 >
34 34 > .. fix::
35 35 >
36 36 > First line of fix entry.
37 37 > A line after it without a space.
38 38 >
39 39 > A new paragraph in the fix entry. And this is a really long line. It goes on for a while.
40 40 > And it wraps around to a new paragraph.
41 41 > EOF
42 42
43 43 $ hg releasenotes -r . $TESTTMP/relnotes-multi-line
44 44 $ cat $TESTTMP/relnotes-multi-line
45 45 Bug Fixes
46 46 =========
47 47
48 48 * First line of fix entry. A line after it without a space.
49 49
50 50 A new paragraph in the fix entry. And this is a really long line. It goes on
51 51 for a while. And it wraps around to a new paragraph.
52 52
53 53 A release note with a title results in a sub-section being written
54 54
55 55 $ touch fix3
56 56 $ hg -q commit -A -l - << EOF
57 57 > fix with title
58 58 >
59 59 > .. fix:: Fix Title
60 60 >
61 61 > First line of fix with title.
62 62 >
63 63 > Another paragraph of fix with title. But this is a paragraph
64 64 > with multiple lines.
65 65 > EOF
66 66
67 67 $ hg releasenotes -r . $TESTTMP/relnotes-fix-with-title
68 68 $ cat $TESTTMP/relnotes-fix-with-title
69 69 Bug Fixes
70 70 =========
71 71
72 72 Fix Title
73 73 ---------
74 74
75 75 First line of fix with title.
76 76
77 77 Another paragraph of fix with title. But this is a paragraph with multiple
78 78 lines.
79 79
80 80 $ cd ..
81 81
82 82 Formatting of multiple bullet points works
83 83
84 84 $ hg init multiple-bullets
85 85 $ cd multiple-bullets
86 86 $ touch fix1
87 87 $ hg -q commit -A -l - << EOF
88 88 > commit 1
89 89 >
90 90 > .. fix::
91 91 >
92 92 > first fix
93 93 > EOF
94 94
95 95 $ touch fix2
96 96 $ hg -q commit -A -l - << EOF
97 97 > commit 2
98 98 >
99 99 > .. fix::
100 100 >
101 101 > second fix
102 102 >
103 103 > Second paragraph of second fix.
104 104 > EOF
105 105
106 106 $ touch fix3
107 107 $ hg -q commit -A -l - << EOF
108 108 > commit 3
109 109 >
110 110 > .. fix::
111 111 >
112 112 > third fix
113 113 > EOF
114 114
115 115 $ hg releasenotes -r 'all()' $TESTTMP/relnotes-multiple-bullets
116 116 $ cat $TESTTMP/relnotes-multiple-bullets
117 117 Bug Fixes
118 118 =========
119 119
120 120 * first fix
121 121
122 122 * second fix
123 123
124 124 Second paragraph of second fix.
125 125
126 126 * third fix
127 127
128 128 $ cd ..
129 129
130 130 Formatting of multiple sections works
131 131
132 132 $ hg init multiple-sections
133 133 $ cd multiple-sections
134 134 $ touch fix1
135 135 $ hg -q commit -A -l - << EOF
136 136 > commit 1
137 137 >
138 138 > .. fix::
139 139 >
140 140 > first fix
141 141 > EOF
142 142
143 143 $ touch feature1
144 144 $ hg -q commit -A -l - << EOF
145 145 > commit 2
146 146 >
147 147 > .. feature::
148 148 >
149 149 > description of the new feature
150 150 > EOF
151 151
152 152 $ touch fix2
153 153 $ hg -q commit -A -l - << EOF
154 154 > commit 3
155 155 >
156 156 > .. fix::
157 157 >
158 158 > second fix
159 159 > EOF
160 160
161 161 $ hg releasenotes -r 'all()' $TESTTMP/relnotes-multiple-sections
162 162 $ cat $TESTTMP/relnotes-multiple-sections
163 163 New Features
164 164 ============
165 165
166 166 * description of the new feature
167 167
168 168 Bug Fixes
169 169 =========
170 170
171 171 * first fix
172 172
173 173 * second fix
174 174
175 175 $ cd ..
176 176
177 177 Section with subsections and bullets
178 178
179 179 $ hg init multiple-subsections
180 180 $ cd multiple-subsections
181 181
182 182 $ touch fix1
183 183 $ hg -q commit -A -l - << EOF
184 184 > commit 1
185 185 >
186 186 > .. fix:: Title of First Fix
187 187 >
188 188 > First paragraph of first fix.
189 189 >
190 190 > Second paragraph of first fix.
191 191 > EOF
192 192
193 193 $ touch fix2
194 194 $ hg -q commit -A -l - << EOF
195 195 > commit 2
196 196 >
197 197 > .. fix:: Title of Second Fix
198 198 >
199 199 > First paragraph of second fix.
200 200 >
201 201 > Second paragraph of second fix.
202 202 > EOF
203 203
204 204 $ hg releasenotes -r 'all()' $TESTTMP/relnotes-multiple-subsections
205 205 $ cat $TESTTMP/relnotes-multiple-subsections
206 206 Bug Fixes
207 207 =========
208 208
209 209 Title of First Fix
210 210 ------------------
211 211
212 212 First paragraph of first fix.
213 213
214 214 Second paragraph of first fix.
215 215
216 216 Title of Second Fix
217 217 -------------------
218 218
219 219 First paragraph of second fix.
220 220
221 221 Second paragraph of second fix.
222 222
223 223 Now add bullet points to sections having sub-sections
224 224
225 225 $ touch fix3
226 226 $ hg -q commit -A -l - << EOF
227 227 > commit 3
228 228 >
229 229 > .. fix::
230 230 >
231 231 > Short summary of fix 3
232 232 > EOF
233 233
234 234 $ hg releasenotes -r 'all()' $TESTTMP/relnotes-multiple-subsections-with-bullets
235 235 $ cat $TESTTMP/relnotes-multiple-subsections-with-bullets
236 236 Bug Fixes
237 237 =========
238 238
239 239 Title of First Fix
240 240 ------------------
241 241
242 242 First paragraph of first fix.
243 243
244 244 Second paragraph of first fix.
245 245
246 246 Title of Second Fix
247 247 -------------------
248 248
249 249 First paragraph of second fix.
250 250
251 251 Second paragraph of second fix.
252 252
253 253 Other Changes
254 254 -------------
255 255
256 256 * Short summary of fix 3
257 257
258 $ cd ..
259
258 260 Multiple 'Other Changes' sub-sections for every section
259 261
260 262 $ hg init multiple-otherchanges
261 263 $ cd multiple-otherchanges
262 264
263 265 $ touch fix1
264 266 $ hg -q commit -A -l - << EOF
265 267 > commit 1
266 268 >
267 269 > .. fix:: Title of First Fix
268 270 >
269 271 > First paragraph of fix 1.
270 272 > EOF
271 273
272 274 $ touch feature1
273 275 $ hg -q commit -A -l - << EOF
274 276 > commit 2
275 277 >
276 278 > .. feature:: Title of First Feature
277 279 >
278 280 > First paragraph of feature 1.
279 281 > EOF
280 282
281 283 $ touch feature2
282 284 $ hg -q commit -A -l - << EOF
283 285 > commit 3
284 286 >
285 287 > .. feature::
286 288 >
287 289 > Short summary of feature 2.
288 290 > EOF
289 291
290 292 $ touch fix2
291 293 $ hg -q commit -A -l - << EOF
292 294 > commit 4
293 295 >
294 296 > .. fix::
295 297 >
296 298 > Short summary of fix 2
297 299 > EOF
298 300
299 301 $ hg releasenotes -r 'all()' $TESTTMP/relnotes-multiple-otherchanges
300 302 $ cat $TESTTMP/relnotes-multiple-otherchanges
301 303 New Features
302 304 ============
303 305
304 306 Title of First Feature
305 307 ----------------------
306 308
307 309 First paragraph of feature 1.
308 310
309 311 Other Changes
310 312 -------------
311 313
312 314 * Short summary of feature 2.
313 315
314 316 Bug Fixes
315 317 =========
316 318
317 319 Title of First Fix
318 320 ------------------
319 321
320 322 First paragraph of fix 1.
321 323
322 324 Other Changes
323 325 -------------
324 326
325 327 * Short summary of fix 2
326 328
329 $ cd ..
330
331 Using custom sections in notes
332
333 $ hg init custom-section
334 $ cd custom-section
335 $ cat >> .hgreleasenotes << EOF
336 > [sections]
337 > testsection=Name of Section
338 > EOF
339
340 $ touch a
341 $ hg -q commit -A -l - << EOF
342 > commit 1
343 >
344 > .. testsection::
345 >
346 > First paragraph under this admonition.
347 > EOF
348
349 $ hg releasenotes -r . $TESTTMP/relnotes-custom-section
350 $ cat $TESTTMP/relnotes-custom-section
351 Name of Section
352 ===============
353
354 * First paragraph under this admonition.
355
356 Overriding default sections (For eg. by default feature = New Features)
357
358 $ cat >> .hgreleasenotes << EOF
359 > [sections]
360 > feature=Feature Additions
361 > EOF
362
363 $ touch b
364 $ hg -q commit -A -l - << EOF
365 > commit 2
366 >
367 > .. feature::
368 >
369 > Adds a new feature.
370 > EOF
371
372 $ hg releasenotes -r . $TESTTMP/relnotes-override-section
373 $ cat $TESTTMP/relnotes-override-section
374 Feature Additions
375 =================
376
377 * Adds a new feature.
378
General Comments 0
You need to be logged in to leave comments. Login now