Merge lp:~jelmer/brz/bundle-commitfromnews into lp:brz

Proposed by Jelmer Vernooij
Status: Merged
Merged at revision: 6739
Proposed branch: lp:~jelmer/brz/bundle-commitfromnews
Merge into: lp:brz
Diff against target: 457 lines (+420/-0)
6 files modified
breezy/plugins/commitfromnews/__init__.py (+86/-0)
breezy/plugins/commitfromnews/committemplate.py (+110/-0)
breezy/plugins/commitfromnews/tests/__init__.py (+29/-0)
breezy/plugins/commitfromnews/tests/test_committemplate.py (+155/-0)
breezy/plugins/commitfromnews/tests/test_msgeditor.py (+36/-0)
doc/en/release-notes/brz-3.0.txt (+4/-0)
To merge this branch: bzr merge lp:~jelmer/brz/bundle-commitfromnews
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+327941@code.launchpad.net

Commit message

Bundle the bzr-commitfromnews plugin.

Description of the change

Bundle the bzr-commitfromnews plugin.

I've changed the behaviour slightly so you have to set the "commit.template_from_files" setting to a shell glob rather than it being enabled by default.

To use with bzr:

echo "commit.template_from_files = doc/en/release-notes/*.txt" >> .bzr/branch/branch.conf

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Bundling seems fine, couple of issues inline.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'breezy/plugins/commitfromnews'
2=== added file 'breezy/plugins/commitfromnews/__init__.py'
3--- breezy/plugins/commitfromnews/__init__.py 1970-01-01 00:00:00 +0000
4+++ breezy/plugins/commitfromnews/__init__.py 2017-07-23 22:07:30 +0000
5@@ -0,0 +1,86 @@
6+# Copyright (C) 2010 Canonical Ltd
7+#
8+# This program is free software; you can redistribute it and/or modify
9+# it under the terms of the GNU General Public License as published by
10+# the Free Software Foundation; either version 2 of the License, or
11+# (at your option) any later version.
12+#
13+# This program is distributed in the hope that it will be useful,
14+# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+# GNU General Public License for more details.
17+#
18+# You should have received a copy of the GNU General Public License
19+# along with this program; if not, write to the Free Software
20+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
22+"""bzr-commitfromnews - make commit messages from the changes in a NEWS file.
23+
24+commitfromnews is enabled by default when installed.
25+
26+To use, set the ``commit.template_from_files`` setting to a path and
27+just do a commit where the NEWS file for your project has a new section
28+added without providing a message to commit.
29+
30+E.g.::
31+ $ echo "commit.template_from_files = NEWS" >> .bzr/branch/branch.conf
32+ $ echo "\n* new thing\n" >> NEWS
33+ $ bzr commit
34+ # editor pops open to let you tweak the message, and it starts with
35+ "* new thing" as the message to edit.
36+
37+commitfromnews attempts to create a sensible default commit message by
38+including sections from a NEWS or ChangeLog file.
39+"""
40+
41+from __future__ import absolute_import
42+
43+from ... import hooks
44+from ...config import (
45+ option_registry,
46+ ListOption,
47+ )
48+
49+option_registry.register(
50+ ListOption('commit.template_from_files', default=[], help="""\
51+List of fnmatch(2)-style shell file patterns to use when creating commit
52+templates.
53+"""))
54+
55+
56+def commit_template(commit, message):
57+ """Create a commit message for commit based on changes in the tree."""
58+ config_stack = commit.work_tree.get_config_stack()
59+ filespec = config_stack.get('commit.template_from_files')
60+ if filespec:
61+ from .committemplate import CommitTemplate
62+ template = CommitTemplate(commit, message, filespec)
63+ return template.make()
64+ return message
65+
66+
67+def load_tests(loader, basic_tests, pattern):
68+ testmod_names = [
69+ 'tests',
70+ ]
71+ basic_tests.addTest(loader.loadTestsFromModuleNames(
72+ ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
73+ return basic_tests
74+
75+
76+_registered = False
77+
78+
79+def register():
80+ """Register the plugin."""
81+ global _registered
82+ # Does not check registered because only tests call this, and they are
83+ # isolated.
84+ _registered = True
85+ hooks.install_lazy_named_hook(
86+ 'breezy.msgeditor', 'hooks',
87+ 'commit_message_template',
88+ commit_template, 'commitfromnews template')
89+
90+
91+register()
92
93=== added file 'breezy/plugins/commitfromnews/committemplate.py'
94--- breezy/plugins/commitfromnews/committemplate.py 1970-01-01 00:00:00 +0000
95+++ breezy/plugins/commitfromnews/committemplate.py 2017-07-23 22:07:30 +0000
96@@ -0,0 +1,110 @@
97+# Copyright (C) 2010 Canonical Ltd
98+#
99+# This program is free software; you can redistribute it and/or modify
100+# it under the terms of the GNU General Public License as published by
101+# the Free Software Foundation; either version 2 of the License, or
102+# (at your option) any later version.
103+#
104+# This program is distributed in the hope that it will be useful,
105+# but WITHOUT ANY WARRANTY; without even the implied warranty of
106+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
107+# GNU General Public License for more details.
108+#
109+# You should have received a copy of the GNU General Public License
110+# along with this program; if not, write to the Free Software
111+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
112+
113+"""Logic to create commit templates."""
114+
115+from __future__ import absolute_import
116+
117+from ... import bugtracker, osutils, patiencediff
118+import re
119+
120+_BUG_MATCH = re.compile(r'lp:(\d+)')
121+
122+
123+class CommitTemplate(object):
124+
125+ def __init__(self, commit, message, file_matches):
126+ """Create a commit template for commit with initial message message.
127+
128+ :param commit: A Commit object for the in progress commit.
129+ :param message: The current message (which may be None).
130+ :param file_matches: Check whether file matches
131+ """
132+ self.commit = commit
133+ self.message = message
134+ self.file_matches = file_matches
135+
136+ def make(self):
137+ """Make the template.
138+
139+ If NEWS is missing or not not modified, the original template is
140+ returned unaltered. Otherwise the changes from NEWS are concatenated
141+ with whatever message was provided to __init__.
142+ """
143+ delta = self.commit.builder.get_basis_delta()
144+ found_old_path = None
145+ found_entry = None
146+ for old_path, new_path, fileid, entry in delta:
147+ if self.file_matches(new_path):
148+ found_entry = entry
149+ found_old_path = old_path
150+ break
151+ if not found_entry:
152+ return self.message
153+ if found_old_path is None:
154+ # New file
155+ _, new_chunks = list(
156+ self.commit.builder.repository.iter_files_bytes(
157+ [(found_entry.file_id, found_entry.revision, None)]))[0]
158+ content = ''.join(new_chunks)
159+ return self.merge_message(content)
160+ else:
161+ # Get a diff. XXX Is this hookable? I thought it was, can't find it
162+ # though.... add DiffTree.diff_factories. Sadly thats not at the
163+ # right level: we want to identify the changed lines, not have the
164+ # final diff: because we want to grab the sections for regions
165+ # changed in new version of the file. So for now a direct diff
166+ # using patiencediff is done.
167+ old_revision = self.commit.basis_tree.get_file_revision(
168+ found_entry.file_id)
169+ needed = [(found_entry.file_id, found_entry.revision, 'new'),
170+ (found_entry.file_id, old_revision, 'old')]
171+ contents = self.commit.builder.repository.iter_files_bytes(needed)
172+ lines = {}
173+ for name, chunks in contents:
174+ lines[name] = osutils.chunks_to_lines(chunks)
175+ new = lines['new']
176+ sequence_matcher = patiencediff.PatienceSequenceMatcher(
177+ None, lines['old'], new)
178+ new_lines = []
179+ for group in sequence_matcher.get_opcodes():
180+ tag, i1, i2, j1, j2 = group
181+ if tag == 'equal':
182+ continue
183+ if tag == 'delete':
184+ continue
185+ new_lines.extend(new[j1:j2])
186+ if not self.commit.revprops.get('bugs'):
187+ # TODO: Allow the user to configure the bug tracker to use
188+ # rather than hardcoding Launchpad.
189+ bt = bugtracker.tracker_registry.get('launchpad')
190+ bugids = []
191+ for line in new_lines:
192+ bugids.extend(_BUG_MATCH.findall(line))
193+ self.commit.revprops['bugs'] = \
194+ bugtracker.encode_fixes_bug_urls(
195+ [bt.get_bug_url(bugid) for bugid in bugids])
196+ return self.merge_message(''.join(new_lines))
197+
198+ def merge_message(self, new_message):
199+ """Merge new_message with self.message.
200+
201+ :param new_message: A string message to merge with self.message.
202+ :return: A string with the merged messages.
203+ """
204+ if self.message is None:
205+ return new_message
206+ return self.message + new_message
207
208=== added directory 'breezy/plugins/commitfromnews/tests'
209=== added file 'breezy/plugins/commitfromnews/tests/__init__.py'
210--- breezy/plugins/commitfromnews/tests/__init__.py 1970-01-01 00:00:00 +0000
211+++ breezy/plugins/commitfromnews/tests/__init__.py 2017-07-23 22:07:30 +0000
212@@ -0,0 +1,29 @@
213+# Copyright (C) 2010 Canonical Ltd
214+#
215+# This program is free software; you can redistribute it and/or modify
216+# it under the terms of the GNU General Public License as published by
217+# the Free Software Foundation; either version 2 of the License, or
218+# (at your option) any later version.
219+#
220+# This program is distributed in the hope that it will be useful,
221+# but WITHOUT ANY WARRANTY; without even the implied warranty of
222+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
223+# GNU General Public License for more details.
224+#
225+# You should have received a copy of the GNU General Public License
226+# along with this program; if not, write to the Free Software
227+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
228+
229+"""Tests for commitfromnews."""
230+
231+from __future__ import absolute_import
232+
233+def load_tests(loader, basic_tests, pattern):
234+ testmod_names = [
235+ 'test_committemplate',
236+ 'test_msgeditor',
237+ ]
238+ basic_tests.addTest(loader.loadTestsFromModuleNames(
239+ ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
240+ return basic_tests
241+
242
243=== added file 'breezy/plugins/commitfromnews/tests/test_committemplate.py'
244--- breezy/plugins/commitfromnews/tests/test_committemplate.py 1970-01-01 00:00:00 +0000
245+++ breezy/plugins/commitfromnews/tests/test_committemplate.py 2017-07-23 22:07:30 +0000
246@@ -0,0 +1,155 @@
247+# Copyright (C) 2010 Canonical Ltd
248+#
249+# This program is free software; you can redistribute it and/or modify
250+# it under the terms of the GNU General Public License as published by
251+# the Free Software Foundation; either version 2 of the License, or
252+# (at your option) any later version.
253+#
254+# This program is distributed in the hope that it will be useful,
255+# but WITHOUT ANY WARRANTY; without even the implied warranty of
256+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
257+# GNU General Public License for more details.
258+#
259+# You should have received a copy of the GNU General Public License
260+# along with this program; if not, write to the Free Software
261+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
262+
263+"""Tests for the commit template creation."""
264+
265+from __future__ import absolute_import
266+
267+from ... import commitfromnews
268+from .... import msgeditor
269+from ....tests import TestCaseWithTransport
270+
271+INITIAL_NEWS_CONTENT = """----------------------------
272+commitfromnews release notes
273+----------------------------
274+
275+NEXT (In development)
276+---------------------
277+
278+IMPROVEMENTS
279+~~~~~~~~~~~~
280+
281+* Created plugin, basic functionality of looking for NEWS and including the
282+ NEWS diff.
283+"""
284+
285+
286+class TestCommitTemplate(TestCaseWithTransport):
287+
288+ def capture_template(self, commit, message):
289+ self.commits.append(commit)
290+ self.messages.append(message)
291+ if message is None:
292+ message = 'let this commit succeed I command thee.'
293+ return message
294+
295+ def setup_capture(self):
296+ commitfromnews.register()
297+ msgeditor.hooks.install_named_hook('commit_message_template',
298+ self.capture_template, 'commitfromnews test template')
299+ self.messages = []
300+ self.commits = []
301+
302+ def test_initial(self):
303+ self.setup_capture()
304+ builder = self.make_branch_builder('test')
305+ builder.start_series()
306+ builder.build_snapshot('BASE-id', None,
307+ [('add', ('', None, 'directory', None)),
308+ ('add', ('foo', 'foo-id', 'file', 'a\nb\nc\nd\ne\n')),
309+ ],
310+ message_callback=msgeditor.generate_commit_message_template)
311+ builder.finish_series()
312+ self.assertEqual([None], self.messages)
313+
314+ def test_added_NEWS(self):
315+ self.setup_capture()
316+ builder = self.make_branch_builder('test')
317+ builder.start_series()
318+ content = INITIAL_NEWS_CONTENT
319+ builder.build_snapshot('BASE-id', None,
320+ [('add', ('', None, 'directory', None)),
321+ ('add', ('NEWS', 'foo-id', 'file', content)),
322+ ],
323+ message_callback=msgeditor.generate_commit_message_template)
324+ builder.finish_series()
325+ self.assertEqual([content], self.messages)
326+
327+ def test_changed_NEWS(self):
328+ self.setup_capture()
329+ builder = self.make_branch_builder('test')
330+ builder.start_series()
331+ orig_content = INITIAL_NEWS_CONTENT
332+ mod_content = """----------------------------
333+commitfromnews release notes
334+----------------------------
335+
336+NEXT (In development)
337+---------------------
338+
339+IMPROVEMENTS
340+~~~~~~~~~~~~
341+
342+* Added a new change to the system.
343+
344+* Created plugin, basic functionality of looking for NEWS and including the
345+ NEWS diff.
346+"""
347+ change_content = """* Added a new change to the system.
348+
349+"""
350+ builder.build_snapshot('BASE-id', None,
351+ [('add', ('', None, 'directory', None)),
352+ ('add', ('NEWS', 'foo-id', 'file', orig_content)),
353+ ])
354+ builder.build_snapshot(None, None,
355+ [('modify', ('foo-id', mod_content)),
356+ ],
357+ message_callback=msgeditor.generate_commit_message_template)
358+ builder.finish_series()
359+ self.assertEqual([change_content], self.messages)
360+
361+ def test_fix_bug(self):
362+ self.setup_capture()
363+ builder = self.make_branch_builder('test')
364+ builder.start_series()
365+ orig_content = INITIAL_NEWS_CONTENT
366+ mod_content = """----------------------------
367+commitfromnews release notes
368+----------------------------
369+
370+NEXT (In development)
371+---------------------
372+
373+IMPROVEMENTS
374+~~~~~~~~~~~~
375+
376+* Created plugin, basic functionality of looking for NEWS and including the
377+ NEWS diff.
378+
379+* Fixed a horrible bug. (lp:523423)
380+
381+"""
382+ change_content = """
383+* Fixed a horrible bug. (lp:523423)
384+
385+"""
386+ builder.build_snapshot('BASE-id', None,
387+ [('add', ('', None, 'directory', None)),
388+ ('add', ('NEWS', 'foo-id', 'file', orig_content)),
389+ ])
390+ builder.build_snapshot(None, None,
391+ [('modify', ('foo-id', mod_content)),
392+ ],
393+ message_callback=msgeditor.generate_commit_message_template)
394+ builder.finish_series()
395+ self.assertEqual([change_content], self.messages)
396+ self.assertEqual(1, len(self.commits))
397+ self.assertEquals('https://launchpad.net/bugs/523423 fixed',
398+ self.commits[0].revprops['bugs'])
399+
400+ def _todo_test_passes_messages_through(self):
401+ pass
402
403=== added file 'breezy/plugins/commitfromnews/tests/test_msgeditor.py'
404--- breezy/plugins/commitfromnews/tests/test_msgeditor.py 1970-01-01 00:00:00 +0000
405+++ breezy/plugins/commitfromnews/tests/test_msgeditor.py 2017-07-23 22:07:30 +0000
406@@ -0,0 +1,36 @@
407+# Copyright (C) 2010 Canonical Ltd
408+#
409+# This program is free software; you can redistribute it and/or modify
410+# it under the terms of the GNU General Public License as published by
411+# the Free Software Foundation; either version 2 of the License, or
412+# (at your option) any later version.
413+#
414+# This program is distributed in the hope that it will be useful,
415+# but WITHOUT ANY WARRANTY; without even the implied warranty of
416+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
417+# GNU General Public License for more details.
418+#
419+# You should have received a copy of the GNU General Public License
420+# along with this program; if not, write to the Free Software
421+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
422+
423+"""Tests for msg editor interactions.."""
424+
425+from __future__ import absolute_import
426+
427+from ... import commitfromnews
428+from .... import msgeditor
429+from ....tests import TestCaseWithTransport
430+
431+
432+class TestRegisters(TestCaseWithTransport):
433+
434+ def test_registered_at_import(self):
435+ self.assertTrue(commitfromnews._registered)
436+
437+ def test_register_registers_for_commit_message_template(self):
438+ commitfromnews._registered = False
439+ # Registers only within the plugin
440+ commitfromnews.register()
441+ self.assertLength(1, msgeditor.hooks['commit_message_template'])
442+ self.assertTrue(commitfromnews._registered)
443
444=== modified file 'doc/en/release-notes/brz-3.0.txt'
445--- doc/en/release-notes/brz-3.0.txt 2017-07-20 00:00:04 +0000
446+++ doc/en/release-notes/brz-3.0.txt 2017-07-23 22:07:30 +0000
447@@ -86,6 +86,10 @@
448 * The 'fetch-ghosts' command is now bundled with brz.
449 Imported from bzrtools by Aaron Bentley. (Jelmer Vernooij)
450
451+ * The 'commitfromnews' plugin is now bundled and
452+ can be enabled by setting ``commit.template_from_files = NEWS``.
453+ (Jelmer Vernooij)
454+
455 * The functionality from ``bzr-guess`` is now merged into Breezy.
456 It will provide suggestions if the user typoes a command.
457 (Jelmer Vernooij)

Subscribers

People subscribed via source and target branches