Merge lp:~qbzr-dev/qbzr/commit_data into lp:~qbzr-dev/qbzr/trunk
- commit_data
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | not available | ||||||||
Proposed branch: | lp:~qbzr-dev/qbzr/commit_data | ||||||||
Merge into: | lp:~qbzr-dev/qbzr/trunk | ||||||||
Diff against target: | None lines | ||||||||
To merge this branch: | bzr merge lp:~qbzr-dev/qbzr/commit_data | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary van der Merwe | Approve | ||
Vincent Ladeuil | Pending | ||
Review via email: mp+10040@code.launchpad.net |
Commit message
Description of the change
Alexander Belchenko (bialix) wrote : | # |
Alexander Belchenko (bialix) wrote : | # |
I've finished this work. Now this branch supports [commit_data] section in branch.conf with following data:
* message
* bugs
* old_revid - old tip (uncommitted revision)
* new_revid - new tip (after uncommit)
[old_revid, new_revid) -- is uncommitted chain.
Code for support commit_data has been reworked to be as much universal as possible, so in the future it could be reused for other clients. Code for decoding bug urls back to bug numbers or bug ids extracted to separate bugs.py module and also can be reused by other clients. There is enough unittest IMO, but of course there is might be room for improvements.
It works fine in my manual testing. Actually I'm starting to dogfood it.
I'll wait couple of days for review and then planning to land it if nobody will object.
Alexander Belchenko (bialix) wrote : | # |
Vincent: if you'll have a chance to look at the final code -- it will be great. I've addressed some of your recent feedback.
Alexander Belchenko (bialix) wrote : | # |
Oh, actually I have to Resubmit this proposal to refresh the diff. Please, use recent code from my branch for review.
Gary van der Merwe (garyvdm) wrote : | # |
I think that this work is excellent. I approve.
How ever I would like for Vicent to also have a look, as he may have some input on how we make things common with gtk. Maybe we can have a common uncommit hook.
I would also like to see that we look for a commit message from the msgeditor commit_
Preview Diff
1 | === modified file '__init__.py' |
2 | --- __init__.py 2009-08-11 18:52:11 +0000 |
3 | +++ __init__.py 2009-08-12 16:08:39 +0000 |
4 | @@ -101,13 +101,15 @@ |
5 | |
6 | |
7 | def post_uncommit_hook(local, master, old_revno, old_tip, new_revno, hook_new_tip): |
8 | + from bzrlib.plugins.qbzr.lib.commit_data import CommitData |
9 | branch = local or master |
10 | - message = branch.repository.get_revision(old_tip).message |
11 | - config = branch.get_config() |
12 | - config.set_user_option('qbzr_commit_message', message.strip()) |
13 | + ci_data = CommitData(branch=branch) |
14 | + ci_data.set_data_on_uncommit(old_tip, hook_new_tip) |
15 | + ci_data.save() |
16 | |
17 | from bzrlib.branch import Branch |
18 | -Branch.hooks.install_named_hook('post_uncommit', post_uncommit_hook, 'Remember uncomitted message for qcommit') |
19 | +Branch.hooks.install_named_hook('post_uncommit', post_uncommit_hook, |
20 | + 'Remember uncomitted revision data for qcommit') |
21 | |
22 | |
23 | def load_tests(basic_tests, module, loader): |
24 | |
25 | === modified file 'lib/commit.py' |
26 | --- lib/commit.py 2009-08-11 18:47:59 +0000 |
27 | +++ lib/commit.py 2009-08-12 16:08:39 +0000 |
28 | @@ -24,6 +24,7 @@ |
29 | |
30 | from bzrlib.plugins.qbzr.lib.spellcheck import SpellCheckHighlighter, SpellChecker |
31 | from bzrlib.plugins.qbzr.lib.autocomplete import get_wordlist_builder |
32 | +from bzrlib.plugins.qbzr.lib.commit_data import CommitData |
33 | from bzrlib.plugins.qbzr.lib.diff import ( |
34 | DiffButtons, |
35 | show_diff, |
36 | @@ -216,6 +217,9 @@ |
37 | dialog = dialog, |
38 | parent = parent) |
39 | self.tree = tree |
40 | + self.ci_data = CommitData(tree=tree) |
41 | + self.ci_data.load() |
42 | + |
43 | self.is_bound = bool(tree.branch.get_bound_location()) |
44 | self.has_pending_merges = len(tree.get_parent_ids())>1 |
45 | |
46 | @@ -301,7 +305,7 @@ |
47 | spell_highlighter = SpellCheckHighlighter(self.message.document(), |
48 | spell_checker) |
49 | |
50 | - self.restore_message() |
51 | + self.restore_commit_data() |
52 | if message: |
53 | self.message.setText(message) |
54 | grid.addWidget(self.message, 0, 0, 1, 2) |
55 | @@ -406,7 +410,7 @@ |
56 | |
57 | # Try to be smart: if there is no saved message |
58 | # then set focus on Edit Area; otherwise on OK button. |
59 | - if self.get_saved_message(): |
60 | + if self.ci_data['message']: |
61 | self.buttonbox.setFocus() |
62 | else: |
63 | self.message.setFocus() |
64 | @@ -517,34 +521,32 @@ |
65 | self.custom_author = self.author.text() |
66 | self.author.setText(self.default_author) |
67 | |
68 | - def get_saved_message(self): |
69 | - config = self.tree.branch.get_config()._get_branch_data_config() |
70 | - return config.get_user_option('qbzr_commit_message') |
71 | - |
72 | - def restore_message(self): |
73 | - message = self.get_saved_message() |
74 | + def restore_commit_data(self): |
75 | + message = self.ci_data['message'] |
76 | if message: |
77 | self.message.setText(message) |
78 | |
79 | - def save_message(self): |
80 | - if self.tree.branch.control_files.get_physical_lock_status() or \ |
81 | - self.tree.branch.is_locked(): |
82 | + def save_commit_data(self): |
83 | + if (self.tree.branch.control_files.get_physical_lock_status() |
84 | + or self.tree.branch.is_locked()): |
85 | + # XXX maybe show this in a GUI MessageBox (information box)??? |
86 | from bzrlib.trace import warning |
87 | - warning("Cannot save commit message because the branch is locked.") |
88 | - else: |
89 | - message = unicode(self.message.toPlainText()) |
90 | - config = self.tree.branch.get_config() |
91 | - if message.strip(): |
92 | - config.set_user_option('qbzr_commit_message', message) |
93 | - else: |
94 | - if config.get_user_option('qbzr_commit_message'): |
95 | - # FIXME this should delete the config entry, not just set it to '' |
96 | - config.set_user_option('qbzr_commit_message', '') |
97 | + warning("Cannot save commit data because the branch is locked.") |
98 | + return |
99 | + message = unicode(self.message.toPlainText()).strip() |
100 | + ci_data = CommitData(tree=self.tree) |
101 | + ci_data.set_data(message=message) |
102 | + ci_data.save() |
103 | |
104 | - def clear_saved_message(self): |
105 | - config = self.tree.branch.get_config() |
106 | - # FIXME this should delete the config entry, not just set it to '' |
107 | - config.set_user_option('qbzr_commit_message', '') |
108 | + def wipe_commit_data(self): |
109 | + if (self.tree.branch.control_files.get_physical_lock_status() |
110 | + or self.tree.branch.is_locked()): |
111 | + # XXX maybe show this in a GUI MessageBox (information box)??? |
112 | + from bzrlib.trace import warning |
113 | + warning("Cannot wipe commit data because the branch is locked.") |
114 | + return |
115 | + ci_data = CommitData(tree=self.tree) |
116 | + ci_data.wipe() |
117 | |
118 | def do_start(self): |
119 | args = ["commit"] |
120 | @@ -635,19 +637,19 @@ |
121 | fmodel = self.filelist.tree_filter_model |
122 | fmodel.setFilter(fmodel.UNVERSIONED, state) |
123 | |
124 | - def _save_or_clear_message(self): |
125 | + def _save_or_wipe_commit_data(self): |
126 | if not self.process_widget.is_running(): |
127 | if self.process_widget.finished: |
128 | - self.clear_saved_message() |
129 | + self.wipe_commit_data() |
130 | else: |
131 | - self.save_message() |
132 | + self.save_commit_data() |
133 | |
134 | def closeEvent(self, event): |
135 | - self._save_or_clear_message() |
136 | + self._save_or_wipe_commit_data() |
137 | return SubProcessDialog.closeEvent(self, event) |
138 | |
139 | def reject(self): |
140 | - self._save_or_clear_message() |
141 | + self._save_or_wipe_commit_data() |
142 | return SubProcessDialog.reject(self) |
143 | |
144 | def update_branch_groupbox(self): |
145 | |
146 | === added file 'lib/commit_data.py' |
147 | --- lib/commit_data.py 1970-01-01 00:00:00 +0000 |
148 | +++ lib/commit_data.py 2009-08-12 16:08:39 +0000 |
149 | @@ -0,0 +1,144 @@ |
150 | +# -*- coding: utf-8 -*- |
151 | +# |
152 | +# QBzr - Qt frontend to Bazaar commands |
153 | +# Copyright (C) 2009 Alexander Belchenko |
154 | +# |
155 | +# This program is free software; you can redistribute it and/or |
156 | +# modify it under the terms of the GNU General Public License |
157 | +# as published by the Free Software Foundation; either version 2 |
158 | +# of the License, or (at your option) any later version. |
159 | +# |
160 | +# This program is distributed in the hope that it will be useful, |
161 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
162 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
163 | +# GNU General Public License for more details. |
164 | +# |
165 | +# You should have received a copy of the GNU General Public License |
166 | +# along with this program; if not, write to the Free Software |
167 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
168 | + |
169 | +"""Commit data save/restore support.""" |
170 | + |
171 | + |
172 | +class CommitData(object): |
173 | + """Class to manipulate with commit data. |
174 | + |
175 | + Hold the data as dictionary and provide dict-like interface. |
176 | + All strings saved internally as unicode. |
177 | + |
178 | + Known items for data dict: |
179 | + message: main commit message. |
180 | + bugs: list of 'id:number' strings for fixed bugs |
181 | + authors: list of strings with name(s) of patch author(s) |
182 | + file_message: dict for per-file commit messages (MySQL), |
183 | + keys in this dict are filenames/fileids, |
184 | + values are specific commit messages. |
185 | + old_revid: old tip revid (before uncommit) |
186 | + new_revid: new tip revid (after uncommit) |
187 | + |
188 | + [bialix 20090812] Data saved in branch.conf in [commit_data] section |
189 | + as plain dict. |
190 | + """ |
191 | + |
192 | + def __init__(self, branch=None, tree=None): |
193 | + """Initialize data object attached to some tree. |
194 | + @param tree: working tree object for commit/uncommit. |
195 | + @param branch: branch object for commit/uncommit. |
196 | + """ |
197 | + self._tree = tree |
198 | + self._branch = branch |
199 | + self._data = {} |
200 | + |
201 | + def __nonzero__(self): |
202 | + """Check if there is some data actually. |
203 | + @return: True if data dictionary is not empty. |
204 | + """ |
205 | + return bool(self._data) |
206 | + |
207 | + def __getitem__(self, key): |
208 | + """Read the value of specified key via dict-like interface, e.g. a[key]. |
209 | + @param key: key in data dictionary. |
210 | + @return: value or None if there is no such key. |
211 | + """ |
212 | + return self._data.get(key) |
213 | + |
214 | + def __setitem__(self, key, value): |
215 | + """Set new value for specified key.""" |
216 | + self._data[key] = value |
217 | + |
218 | + def __delitem__(self, key): |
219 | + """Delete key from dictionary.""" |
220 | + del self._data[key] |
221 | + |
222 | + def as_dict(self): |
223 | + return self._data.copy() |
224 | + |
225 | + def set_data(self, data=None, **kw): |
226 | + """Set new data to dictionary (e.g. to save data from commit dialog). |
227 | + @param data: dictionary with new data. |
228 | + @param kw: pairs name=value to insert. |
229 | + """ |
230 | + if data: |
231 | + self._data.update(data) |
232 | + for key, value in kw.iteritems(): |
233 | + self._data[key] = value |
234 | + |
235 | + def set_data_on_uncommit(self, old_revid, new_revid): |
236 | + """Set data from post_uncommit hook. |
237 | + @param old_revid: old tip revid (before uncommit) |
238 | + @param new_revid: new tip revid (after uncommit). Could be None. |
239 | + """ |
240 | + branch = self._get_branch() |
241 | + revision = branch.repository.get_revision(old_revid) |
242 | + # remember revids |
243 | + self._data['old_revid'] = old_revid |
244 | + if new_revid is None: |
245 | + from bzrlib.revision import NULL_REVISION |
246 | + new_revid = NULL_REVISION |
247 | + self._data['new_revid'] = new_revid |
248 | + # set data from revision |
249 | + self._data['message'] = revision.message |
250 | + |
251 | + def load(self): |
252 | + """Load saved data from branch/tree.""" |
253 | + branch = self._get_branch() |
254 | + config = branch.get_config() |
255 | + data = config.get_user_option('commit_data') |
256 | + if data: |
257 | + self.set_data(data) |
258 | + else: |
259 | + # backward compatibility |
260 | + old_data = config.get_user_option('qbzr_commit_message') |
261 | + if old_data: |
262 | + self.set_data(message=old_data) |
263 | + |
264 | + def save(self): |
265 | + """Save data to the branch/tree.""" |
266 | + # XXX save should wipe if self._data is empty |
267 | + branch = self._get_branch() |
268 | + config = branch.get_config() |
269 | + config.set_user_option('commit_data', self._data) |
270 | + # clear old data |
271 | + if config.get_user_option('qbzr_commit_message'): |
272 | + config.set_user_option('qbzr_commit_message', '') |
273 | + |
274 | + def wipe(self): |
275 | + """Delete saved data from branch/tree config.""" |
276 | + branch = self._get_branch() |
277 | + config = branch.get_config() |
278 | + config.set_user_option('commit_data', {}) |
279 | + # clear old data |
280 | + if config.get_user_option('qbzr_commit_message'): |
281 | + config.set_user_option('qbzr_commit_message', '') |
282 | + |
283 | + def _get_branch(self): |
284 | + """Return branch object if either branch or tree was specified on init. |
285 | + Raise BzrInternalError otherwise. |
286 | + """ |
287 | + if self._branch: |
288 | + return self._branch |
289 | + if self._tree: |
290 | + return self._tree.branch |
291 | + # too bad |
292 | + from bzrlib import errors |
293 | + raise errors.BzrInternalError("CommitData has no saved branch or tree.") |
294 | |
295 | === added file 'lib/tests/Makefile' |
296 | --- lib/tests/Makefile 1970-01-01 00:00:00 +0000 |
297 | +++ lib/tests/Makefile 2009-08-12 14:05:52 +0000 |
298 | @@ -0,0 +1,2 @@ |
299 | +test: |
300 | + $(MAKE) -C ../../ test |
301 | |
302 | === modified file 'lib/tests/__init__.py' |
303 | --- lib/tests/__init__.py 2009-07-09 23:27:53 +0000 |
304 | +++ lib/tests/__init__.py 2009-08-12 09:44:09 +0000 |
305 | @@ -26,6 +26,7 @@ |
306 | 'mock', |
307 | 'test_autocomplete', |
308 | #'test_diffview', - broken by API changes |
309 | + 'test_commit_data', |
310 | 'test_extra_isignored', |
311 | 'test_extra_isversioned', |
312 | 'test_i18n', |
313 | |
314 | === added file 'lib/tests/test_commit_data.py' |
315 | --- lib/tests/test_commit_data.py 1970-01-01 00:00:00 +0000 |
316 | +++ lib/tests/test_commit_data.py 2009-08-12 15:27:56 +0000 |
317 | @@ -0,0 +1,151 @@ |
318 | +# -*- coding: utf-8 -*- |
319 | +# |
320 | +# QBzr - Qt frontend to Bazaar commands |
321 | +# Copyright (C) 2009 Alexander Belchenko |
322 | +# |
323 | +# This program is free software; you can redistribute it and/or |
324 | +# modify it under the terms of the GNU General Public License |
325 | +# as published by the Free Software Foundation; either version 2 |
326 | +# of the License, or (at your option) any later version. |
327 | +# |
328 | +# This program is distributed in the hope that it will be useful, |
329 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
330 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
331 | +# GNU General Public License for more details. |
332 | +# |
333 | +# You should have received a copy of the GNU General Public License |
334 | +# along with this program; if not, write to the Free Software |
335 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
336 | + |
337 | +"""Tests for commit data object and operations.""" |
338 | + |
339 | +from bzrlib.tests import TestCase, TestCaseWithTransport |
340 | +from bzrlib.plugins.qbzr.lib.commit_data import ( |
341 | + CommitData, |
342 | + ) |
343 | + |
344 | + |
345 | +class TestCommitDataBase(TestCase): |
346 | + |
347 | + def test_empty(self): |
348 | + d = CommitData() |
349 | + self.assertFalse(bool(d)) |
350 | + self.assertEqual(None, d['message']) |
351 | + self.assertEqual({}, d.as_dict()) |
352 | + |
353 | + def test_set_data(self): |
354 | + d = CommitData() |
355 | + d.set_data({'message': 'foo bar'}) |
356 | + self.assertTrue(bool(d)) |
357 | + self.assertEqual('foo bar', d['message']) |
358 | + self.assertEqual({'message': 'foo bar'}, d.as_dict()) |
359 | + # |
360 | + d = CommitData() |
361 | + d.set_data(message='foo bar') |
362 | + self.assertTrue(bool(d)) |
363 | + self.assertEqual('foo bar', d['message']) |
364 | + self.assertEqual({'message': 'foo bar'}, d.as_dict()) |
365 | + # |
366 | + d = CommitData() |
367 | + d.set_data({'fixes': 'lp:123456'}, message='foo bar') |
368 | + self.assertTrue(bool(d)) |
369 | + self.assertEqual('foo bar', d['message']) |
370 | + self.assertEqual({'message': 'foo bar', |
371 | + 'fixes': 'lp:123456', |
372 | + }, d.as_dict()) |
373 | + |
374 | + |
375 | +class TestCommitDataWithTree(TestCaseWithTransport): |
376 | + |
377 | + def test_set_data_on_uncommit(self): |
378 | + wt = self.make_branch_and_tree('.') |
379 | + revid1 = wt.commit(message='1') |
380 | + # imitate uncommit in branch with only one revision |
381 | + d = CommitData(branch=wt.branch) |
382 | + d.set_data_on_uncommit(revid1, None) |
383 | + self.assertEqual({'message': '1', |
384 | + 'old_revid': revid1, |
385 | + 'new_revid': 'null:', |
386 | + }, d.as_dict()) |
387 | + # |
388 | + revid2 = wt.commit(message='2') |
389 | + # imitate uncommit in branch with several revisions |
390 | + d = CommitData(branch=wt.branch) |
391 | + d.set_data_on_uncommit(revid2, revid1) |
392 | + self.assertEqual({'message': '2', |
393 | + 'old_revid': revid2, |
394 | + 'new_revid': revid1, |
395 | + }, d.as_dict()) |
396 | + |
397 | + def test_io(self): |
398 | + wt = self.make_branch_and_tree('.') |
399 | + # load nothing |
400 | + d = CommitData(tree=wt) |
401 | + d.load() |
402 | + self.assertEqual({}, d.as_dict()) |
403 | + # |
404 | + # save data |
405 | + d = CommitData(tree=wt) |
406 | + d.set_data(message='spam', old_revid='foo', new_revid='bar') |
407 | + d.save() |
408 | + # check branch.conf |
409 | + cfg = wt.branch.get_config() |
410 | + self.assertEqual({'message': 'spam', |
411 | + 'old_revid': 'foo', |
412 | + 'new_revid': 'bar', |
413 | + }, cfg.get_user_option('commit_data')) |
414 | + # |
415 | + # load data later |
416 | + d = CommitData(tree=wt) |
417 | + d.load() |
418 | + self.assertEqual({'message': 'spam', |
419 | + 'old_revid': 'foo', |
420 | + 'new_revid': 'bar', |
421 | + }, d.as_dict()) |
422 | + # |
423 | + # wipe the data in the end |
424 | + d = CommitData(tree=wt) |
425 | + d.wipe() |
426 | + # check branch.conf |
427 | + cfg = wt.branch.get_config() |
428 | + self.assertEqual({}, cfg.get_user_option('commit_data')) |
429 | + |
430 | + def test_io_old_data_transition(self): |
431 | + # we should handle old data (i.e. qbzr_commit_message) gracefully |
432 | + wt = self.make_branch_and_tree('.') |
433 | + cfg = wt.branch.get_config() |
434 | + cfg.set_user_option('qbzr_commit_message', 'spam') |
435 | + # load |
436 | + d = CommitData(tree=wt) |
437 | + d.load() |
438 | + self.assertEqual({'message': 'spam', |
439 | + }, d.as_dict()) |
440 | + # |
441 | + # if here both old and new then prefer new |
442 | + cfg.set_user_option('commit_data', {'foo': 'bar'}) |
443 | + d = CommitData(tree=wt) |
444 | + d.load() |
445 | + self.assertEqual({'foo': 'bar', |
446 | + }, d.as_dict()) |
447 | + # |
448 | + # on save we should clear old data |
449 | + d = CommitData(tree=wt) |
450 | + d.set_data(message='eggs', old_revid='foo', new_revid='bar') |
451 | + d.save() |
452 | + # check branch.conf |
453 | + cfg = wt.branch.get_config() |
454 | + self.assertEqual('', cfg.get_user_option('qbzr_commit_message')) |
455 | + self.assertEqual({'message': 'eggs', |
456 | + 'old_revid': 'foo', |
457 | + 'new_revid': 'bar', |
458 | + }, cfg.get_user_option('commit_data')) |
459 | + # |
460 | + # on wipe we should clear old data too |
461 | + cfg = wt.branch.get_config() |
462 | + cfg.set_user_option('qbzr_commit_message', 'spam') |
463 | + d = CommitData(tree=wt) |
464 | + d.wipe() |
465 | + # check branch.conf |
466 | + cfg = wt.branch.get_config() |
467 | + self.assertEqual('', cfg.get_user_option('qbzr_commit_message')) |
468 | + self.assertEqual({}, cfg.get_user_option('commit_data')) |
Major refactoring of our internal code that saves/restore/ uncommit commit message. Now it becomes more generic and we can easily extend CommitData to save/restore bugfixes ids etc.
The next step is actually implement support for save/restore bugfixes ids data.