Merge lp:~jameinel/bzr-builddeb/changelog-hook into lp:bzr-builddeb
- changelog-hook
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | not available | ||||
Proposed branch: | lp:~jameinel/bzr-builddeb/changelog-hook | ||||
Merge into: | lp:bzr-builddeb | ||||
Prerequisite: | lp:~jameinel/bzr-builddeb/use_loader | ||||
Diff against target: |
453 lines (+411/-2) 4 files modified
__init__.py (+20/-2) merge_changelog.py (+262/-0) tests/__init__.py (+1/-0) tests/test_merge_changelog.py (+128/-0) |
||||
To merge this branch: | bzr merge lp:~jameinel/bzr-builddeb/changelog-hook | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Westby | Approve | ||
Review via email: mp+18192@code.launchpad.net |
Commit message
Description of the change
John A Meinel (jameinel) wrote : | # |
Robert Collins (lifeless) wrote : | # |
top level stuff:
lets call it deb_changelog_
I don't know that merging 'changelog' is needed, as all the ubuntu package imports have debian/changelog, not 'changelog' as the filename.
I would make it enabled by default.
Finally, if we do do merges of 'changelog' we should check it /looks/ like a debian changelog and punt if it doesn't. Or use another heuristic like looking for a 'rules' file too.
Robert Collins (lifeless) wrote : | # |
Lets namespace the params.
also please file a bug saying how ugly that is in bzr core - we should fix that for 2.2 for sure, IMO. Or perhaps even for 2.1?
Other than that, my earlier comments and Scotts code which I have gone la-la-la to, its good.
Robert Collins (lifeless) wrote : | # |
Also as per discussion in rl, do deb_changelog_
- 405. By John A Meinel
-
Change over to using a list of filenames as the enablement.
Default to enabling the plugin for 'debian/changelog' only.
John A Meinel (jameinel) wrote : | # |
Updated as recommended.
1) Plugin now defaults to active, but only handling 'debian/changelog'
2) Setting the config variable:
deb_changelog
Empty will disable it, comma-separated values if you want to check multiple files (same as how news_merge does it.)
3) Changed variable naming, etc, as requested.
- 406. By Vincent Ladeuil
-
Get fixes from trunk
- 407. By Vincent Ladeuil
-
Use the shiny merge.Configura
bleFileMerger. This requires bzr-2.1but makes it even easier to define the per-file
merge hooks.Test updated accordingly.
- 408. By Vincent Ladeuil
-
Raise ImportError if we're not running at least bzr-2.1.0rc2.
James Westby (james-w) wrote : | # |
> This adds a per-file commit hook, which checks
> a) A Branch config called
> changelog_
> b) A file named 'changelog' or 'debian/changelog'
>
> If both hold true, and it perceives a conflict in the changelog file, it
> essentially runs Scott's merge_changelog code. This will always result in a
> conflict-free changelog file. The current algorithm is "prefer-mine". So if
> both changelogs have a section with a matching 'version', the text present in
> "this" are used.
That sounds wrong to me, why wouldn't it conflict those blocks?
> There are some new tests for this, but I've also manually tested merging
> lp:ubuntu/bzrtools @-r4.3.1 into -r 50. And it seemed to do a nice clean merge
> (without the plugin enabled it conflicted as expected.) As an example:
>
> bzr branch lp:ubuntu/bzrtools -r 4.3.1 bzrtools_upstream
> bzr branch lp:ubuntu/bzrtools -r 50 bzrtools_ubuntu
> cd bzrtools_ubuntu
> bzr merge ../bzrtools_
>
> (There are other conflicts, but *debian/changelog* is not one of them)
Great, thanks for your work on this.
29 + merge.Merger.
30 + 'merge_
31 + 'Changelog file merge')
We should put "Debian" in there somewhere. I can do that on merge.
Other than that this looks fine to me. I want to discuss the conflict
issue before merging though.
Thanks,
James
James Westby (james-w) wrote : | # |
> This adds a per-file commit hook, which checks
> a) A Branch config called
> changelog_
> b) A file named 'changelog' or 'debian/changelog'
>
> If both hold true, and it perceives a conflict in the changelog file, it
> essentially runs Scott's merge_changelog code. This will always result in a
> conflict-free changelog file. The current algorithm is "prefer-mine". So if
> both changelogs have a section with a matching 'version', the text present in
> "this" are used.
That sounds wrong to me, why wouldn't it conflict those blocks?
> There are some new tests for this, but I've also manually tested merging
> lp:ubuntu/bzrtools @-r4.3.1 into -r 50. And it seemed to do a nice clean merge
> (without the plugin enabled it conflicted as expected.) As an example:
>
> bzr branch lp:ubuntu/bzrtools -r 4.3.1 bzrtools_upstream
> bzr branch lp:ubuntu/bzrtools -r 50 bzrtools_ubuntu
> cd bzrtools_ubuntu
> bzr merge ../bzrtools_
>
> (There are other conflicts, but *debian/changelog* is not one of them)
Great, thanks for your work on this.
29 + merge.Merger.
30 + 'merge_
31 + 'Changelog file merge')
We should put "Debian" in there somewhere. I can do that on merge.
Other than that this looks fine to me. I want to discuss the conflict
issue before merging though.
Thanks,
James
James Westby (james-w) wrote : | # |
> This adds a per-file commit hook, which checks
> a) A Branch config called
> changelog_
> b) A file named 'changelog' or 'debian/changelog'
>
> If both hold true, and it perceives a conflict in the changelog file, it
> essentially runs Scott's merge_changelog code. This will always result in a
> conflict-free changelog file. The current algorithm is "prefer-mine". So if
> both changelogs have a section with a matching 'version', the text present in
> "this" are used.
That sounds wrong to me, why wouldn't it conflict those blocks?
> There are some new tests for this, but I've also manually tested merging
> lp:ubuntu/bzrtools @-r4.3.1 into -r 50. And it seemed to do a nice clean merge
> (without the plugin enabled it conflicted as expected.) As an example:
>
> bzr branch lp:ubuntu/bzrtools -r 4.3.1 bzrtools_upstream
> bzr branch lp:ubuntu/bzrtools -r 50 bzrtools_ubuntu
> cd bzrtools_ubuntu
> bzr merge ../bzrtools_
>
> (There are other conflicts, but *debian/changelog* is not one of them)
Great, thanks for your work on this.
29 + merge.Merger.
30 + 'merge_
31 + 'Changelog file merge')
We should put "Debian" in there somewhere. I can do that on merge.
Other than that this looks fine to me. I want to discuss the conflict
issue before merging though.
Thanks,
James
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
James Westby wrote:
> Review: Needs Information
>> This adds a per-file commit hook, which checks
>> a) A Branch config called
>> changelog_
>> b) A file named 'changelog' or 'debian/changelog'
>>
>> If both hold true, and it perceives a conflict in the changelog file, it
>> essentially runs Scott's merge_changelog code. This will always result in a
>> conflict-free changelog file. The current algorithm is "prefer-mine". So if
>> both changelogs have a section with a matching 'version', the text present in
>> "this" are used.
>
> That sounds wrong to me, why wouldn't it conflict those blocks?
So the main thing is that I took the code that Scott had provided (and
which I guess people had been using with decent success) and turned it
into a per-file merge hook.
I think in the long run, we can improve the code to change it from a
2-way merger into a 3-way merger. (So if both sides update things, we
can tell when one supersedes the base rather than having to conflict
just because there was a change on one side.)
However, I felt this would still be an improvement over the status quo,
and we could deal with bugs/improving things going forward.
>
>> There are some new tests for this, but I've also manually tested merging
>> lp:ubuntu/bzrtools @-r4.3.1 into -r 50. And it seemed to do a nice clean merge
>> (without the plugin enabled it conflicted as expected.) As an example:
>>
>> bzr branch lp:ubuntu/bzrtools -r 4.3.1 bzrtools_upstream
>> bzr branch lp:ubuntu/bzrtools -r 50 bzrtools_ubuntu
>> cd bzrtools_ubuntu
>> bzr merge ../bzrtools_
>>
>> (There are other conflicts, but *debian/changelog* is not one of them)
>
> Great, thanks for your work on this.
>
> 29 + merge.Merger.
> 30 + 'merge_
> 31 + 'Changelog file merge')
>
> We should put "Debian" in there somewhere. I can do that on merge.
>
'merge_
'changelog_
call it "Debian changelog file merge" if you wanted. I'm not sure if it
really matters.
> Other than that this looks fine to me. I want to discuss the conflict
> issue before merging though.
>
> Thanks,
>
> James
>
I think there is a lot that could be done. I'm not 100% sure about
Scott's parser, but it is independent of the per-file merge hook. Also,
I believe the other parts of the code depend on 'python-debian' for
doing stuff like Version, so we should probably grab that rather than
using his parser.
I think Jelmer said that python-debian has a lot more work in it to
handle really-old changelog files that pre-date the standardized
formatting. (Though really-old formats are most likely not going to be
part of the interesting merge sections.)
To elaborate a bit about the issues of a 2-way merger. If you have 2
changelog entries (1.0-1 and 1.1-1.) If one side introduces 1.2-1 and
updates 1.1-1 to fix a typo. And the other side introduces
1.1-1~ubuntu1. A 2-way merger can only tell that 1.1-1 is different on
both sides, and has to generate a conflict. A 3-way merger...
James Westby (james-w) wrote : | # |
> 'merge_
> 'changelog_
> call it "Debian changelog file merge" if you wanted. I'm not sure if it
> really matters.
Yeah, I meant the description, so that people don't ask about why
it isn't merging their non-Debian changelog.
> I think there is a lot that could be done. I'm not 100% sure about
> Scott's parser, but it is independent of the per-file merge hook. Also,
> I believe the other parts of the code depend on 'python-debian' for
> doing stuff like Version, so we should probably grab that rather than
> using his parser.
Version would be good to use.
> I think Jelmer said that python-debian has a lot more work in it to
> handle really-old changelog files that pre-date the standardized
> formatting. (Though really-old formats are most likely not going to be
> part of the interesting merge sections.)
I think that this approach may be better actually, parsing as little as
possible leaves less room for problems.
> To elaborate a bit about the issues of a 2-way merger. If you have 2
> changelog entries (1.0-1 and 1.1-1.) If one side introduces 1.2-1 and
> updates 1.1-1 to fix a typo. And the other side introduces
> 1.1-1~ubuntu1. A 2-way merger can only tell that 1.1-1 is different on
> both sides, and has to generate a conflict. A 3-way merger could tell
> that the 1.1-1 version in one side was the same as the version in the
> BASE, and thus can take just the newer 1.1-1.
>
> I think Scott chose "prefer-mine" because it was an easy answer, people
> don't often update old entries, and he didn't want to have to worry (or
> didn't think about) conflicts. It seemed that people had been using his
> code successfully for a while, so I figured it would be a reasonable
> starting point for this.
>
> I'd be willing to open some bugs on ways that the per-file merge hook
> could be improved, which we can try to get to as they seem to actually
> be needed.
I think that's a good compromise, please do this.
Thanks,
James
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
...
>>
>> I'd be willing to open some bugs on ways that the per-file merge hook
>> could be improved, which we can try to get to as they seem to actually
>> be needed.
>
> I think that's a good compromise, please do this.
>
> Thanks,
>
> James
>
https:/
(should use python-debian for parsing)
https:/
(should use a 3-way merge algorithm)
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkt
3bkAnigYN7crOHC
=E8Yw
-----END PGP SIGNATURE-----
Preview Diff
1 | === modified file '__init__.py' |
2 | --- __init__.py 2010-01-28 11:11:25 +0000 |
3 | +++ __init__.py 2010-01-29 14:09:14 +0000 |
4 | @@ -25,7 +25,11 @@ |
5 | |
6 | import os |
7 | |
8 | -from bzrlib import msgeditor |
9 | +import bzrlib |
10 | +from bzrlib import ( |
11 | + merge, |
12 | + msgeditor, |
13 | + ) |
14 | from bzrlib.commands import plugin_cmds |
15 | from bzrlib.directory_service import directories |
16 | |
17 | @@ -110,9 +114,23 @@ |
18 | "the commit message") |
19 | |
20 | |
21 | +if getattr(merge, 'ConfigurableFileMerger', None) is None: |
22 | + raise ImportError( |
23 | + 'need at least bzr 2.1.0rc2 (you use %r)', bzrlib.version_info) |
24 | +else: |
25 | + def changelog_merge_hook_factory(merger): |
26 | + from bzrlib.plugins.builddeb import merge_changelog |
27 | + return merge_changelog.ChangeLogFileMerge(merger) |
28 | + |
29 | + merge.Merger.hooks.install_named_hook( |
30 | + 'merge_file_content', changelog_merge_hook_factory, |
31 | + 'Changelog file merge') |
32 | + |
33 | + |
34 | try: |
35 | from bzrlib.revisionspec import revspec_registry |
36 | - revspec_registry.register_lazy("package:", "bzrlib.plugins.builddeb.revspec", "RevisionSpec_package") |
37 | + revspec_registry.register_lazy("package:", |
38 | + "bzrlib.plugins.builddeb.revspec", "RevisionSpec_package") |
39 | except ImportError: |
40 | from bzrlib.revisionspec import SPEC_TYPES |
41 | from bzrlib.plugins.builddeb.revspec import RevisionSpec_package |
42 | |
43 | === added file 'merge_changelog.py' |
44 | --- merge_changelog.py 1970-01-01 00:00:00 +0000 |
45 | +++ merge_changelog.py 2010-01-29 14:09:14 +0000 |
46 | @@ -0,0 +1,262 @@ |
47 | +#!/usr/bin/env python |
48 | +# -*- coding: utf-8 -*- |
49 | +# Copyright ? 2008 Canonical Ltd. |
50 | +# Author: Scott James Remnant <scott@ubuntu.com>. |
51 | +# Hacked up by: Bryce Harrington <bryce@ubuntu.com> |
52 | +# |
53 | +# This program is free software: you can redistribute it and/or modify |
54 | +# it under the terms of version 3 of the GNU General Public License as |
55 | +# published by the Free Software Foundation. |
56 | +# |
57 | +# This program is distributed in the hope that it will be useful, |
58 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
59 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
60 | +# GNU General Public License for more details. |
61 | +# |
62 | +# You should have received a copy of the GNU General Public License |
63 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
64 | + |
65 | +import re |
66 | + |
67 | +from bzrlib import ( |
68 | + merge, |
69 | + ) |
70 | + |
71 | +class ChangeLogFileMerge(merge.ConfigurableFileMerger): |
72 | + |
73 | + name_prefix = 'deb_changelog' |
74 | + default_files = ['debian/changelog'] |
75 | + |
76 | + def merge_text(self, params): |
77 | + return 'success', merge_changelog(params.this_lines, params.other_lines) |
78 | + |
79 | + |
80 | +######################################################################## |
81 | +# Changelog Management |
82 | +######################################################################## |
83 | + |
84 | +# Regular expression for top of debian/changelog |
85 | +CL_RE = re.compile(r'^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)((\s+[-0-9a-z]+)+)\;', |
86 | + re.IGNORECASE) |
87 | + |
88 | +def merge_changelog(left_changelog_lines, right_changelog_lines): |
89 | + """Merge a changelog file.""" |
90 | + |
91 | + left_cl = read_changelog(left_changelog_lines) |
92 | + right_cl = read_changelog(right_changelog_lines) |
93 | + |
94 | + content = [] |
95 | + # TODO: This is not a 3-way merge, but a 2-way merge |
96 | + # The resolution is currently 'if left and right have texts that have |
97 | + # the same "version" string, use left', aka "prefer-mine". |
98 | + # We could introduce BASE, and cause conflicts, or appropriately |
99 | + # resolve, etc. |
100 | + # Note also that this code is only invoked when there is a |
101 | + # left-and-right change, so merging a pure-right change will take all |
102 | + # changes. |
103 | + for right_ver, right_text in right_cl: |
104 | + while len(left_cl) and left_cl[0][0] > right_ver: |
105 | + (left_ver, left_text) = left_cl.pop(0) |
106 | + content.append(left_text) |
107 | + content.append('\n') |
108 | + |
109 | + while len(left_cl) and left_cl[0][0] == right_ver: |
110 | + (left_ver, left_text) = left_cl.pop(0) |
111 | + |
112 | + content.append(right_text) |
113 | + content.append('\n') |
114 | + |
115 | + for left_ver, left_text in left_cl: |
116 | + content.append(left_text) |
117 | + content.append('\n') |
118 | + |
119 | + return content |
120 | + |
121 | + |
122 | +def read_changelog(lines): |
123 | + """Return a parsed changelog file.""" |
124 | + entries = [] |
125 | + |
126 | + (ver, text) = (None, "") |
127 | + for line in lines: |
128 | + match = CL_RE.search(line) |
129 | + if match: |
130 | + try: |
131 | + ver = Version(match.group(2)) |
132 | + except ValueError: |
133 | + ver = None |
134 | + |
135 | + text += line |
136 | + elif line.startswith(" -- "): |
137 | + if ver is None: |
138 | + ver = Version("0") |
139 | + |
140 | + text += line |
141 | + entries.append((ver, text)) |
142 | + (ver, text) = (None, "") |
143 | + elif len(line.strip()) or ver is not None: |
144 | + text += line |
145 | + |
146 | + if len(text): |
147 | + entries.append((ver, text)) |
148 | + |
149 | + return entries |
150 | + |
151 | +######################################################################## |
152 | +# Version parsing code |
153 | +######################################################################## |
154 | +# Regular expressions make validating things easy |
155 | +valid_epoch = re.compile(r'^[0-9]+$') |
156 | +valid_upstream = re.compile(r'^[A-Za-z0-9+:.~-]*$') |
157 | +valid_revision = re.compile(r'^[A-Za-z0-9+.~]+$') |
158 | + |
159 | +# Character comparison table for upstream and revision components |
160 | +cmp_table = "~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:" |
161 | + |
162 | + |
163 | +class Version(object): |
164 | + """Debian version number. |
165 | + |
166 | + This class is designed to be reasonably transparent and allow you |
167 | + to write code like: |
168 | + |
169 | + | s.version >= '1.100-1' |
170 | + |
171 | + The comparison will be done according to Debian rules, so '1.2' will |
172 | + compare lower. |
173 | + |
174 | + Properties: |
175 | + epoch Epoch |
176 | + upstream Upstream version |
177 | + revision Debian/local revision |
178 | + """ |
179 | + |
180 | + def __init__(self, ver): |
181 | + """Parse a string or number into the three components.""" |
182 | + self.epoch = 0 |
183 | + self.upstream = None |
184 | + self.revision = None |
185 | + |
186 | + ver = str(ver) |
187 | + if not len(ver): |
188 | + raise ValueError |
189 | + |
190 | + # Epoch is component before first colon |
191 | + idx = ver.find(":") |
192 | + if idx != -1: |
193 | + self.epoch = ver[:idx] |
194 | + if not len(self.epoch): |
195 | + raise ValueError |
196 | + if not valid_epoch.search(self.epoch): |
197 | + raise ValueError |
198 | + ver = ver[idx+1:] |
199 | + |
200 | + # Revision is component after last hyphen |
201 | + idx = ver.rfind("-") |
202 | + if idx != -1: |
203 | + self.revision = ver[idx+1:] |
204 | + if not len(self.revision): |
205 | + raise ValueError |
206 | + if not valid_revision.search(self.revision): |
207 | + raise ValueError |
208 | + ver = ver[:idx] |
209 | + |
210 | + # Remaining component is upstream |
211 | + self.upstream = ver |
212 | + if not len(self.upstream): |
213 | + raise ValueError |
214 | + if not valid_upstream.search(self.upstream): |
215 | + raise ValueError |
216 | + |
217 | + self.epoch = int(self.epoch) |
218 | + |
219 | + def getWithoutEpoch(self): |
220 | + """Return the version without the epoch.""" |
221 | + str = self.upstream |
222 | + if self.revision is not None: |
223 | + str += "-%s" % (self.revision,) |
224 | + return str |
225 | + |
226 | + without_epoch = property(getWithoutEpoch) |
227 | + |
228 | + def __str__(self): |
229 | + """Return the class as a string for printing.""" |
230 | + str = "" |
231 | + if self.epoch > 0: |
232 | + str += "%d:" % (self.epoch,) |
233 | + str += self.upstream |
234 | + if self.revision is not None: |
235 | + str += "-%s" % (self.revision,) |
236 | + return str |
237 | + |
238 | + def __repr__(self): |
239 | + """Return a debugging representation of the object.""" |
240 | + return "<%s epoch: %d, upstream: %r, revision: %r>" \ |
241 | + % (self.__class__.__name__, self.epoch, |
242 | + self.upstream, self.revision) |
243 | + |
244 | + def __cmp__(self, other): |
245 | + """Compare two Version classes.""" |
246 | + other = Version(other) |
247 | + |
248 | + result = cmp(self.epoch, other.epoch) |
249 | + if result != 0: return result |
250 | + |
251 | + result = deb_cmp(self.upstream, other.upstream) |
252 | + if result != 0: return result |
253 | + |
254 | + result = deb_cmp(self.revision or "", other.revision or "") |
255 | + if result != 0: return result |
256 | + |
257 | + return 0 |
258 | + |
259 | + |
260 | +def strcut(str, idx, accept): |
261 | + """Cut characters from str that are entirely in accept.""" |
262 | + ret = "" |
263 | + while idx < len(str) and str[idx] in accept: |
264 | + ret += str[idx] |
265 | + idx += 1 |
266 | + |
267 | + return (ret, idx) |
268 | + |
269 | +def deb_order(str, idx): |
270 | + """Return the comparison order of two characters.""" |
271 | + if idx >= len(str): |
272 | + return 0 |
273 | + elif str[idx] == "~": |
274 | + return -1 |
275 | + else: |
276 | + return cmp_table.index(str[idx]) |
277 | + |
278 | +def deb_cmp_str(x, y): |
279 | + """Compare two strings in a deb version.""" |
280 | + idx = 0 |
281 | + while (idx < len(x)) or (idx < len(y)): |
282 | + result = deb_order(x, idx) - deb_order(y, idx) |
283 | + if result < 0: |
284 | + return -1 |
285 | + elif result > 0: |
286 | + return 1 |
287 | + |
288 | + idx += 1 |
289 | + |
290 | + return 0 |
291 | + |
292 | +def deb_cmp(x, y): |
293 | + """Implement the string comparison outlined by Debian policy.""" |
294 | + x_idx = y_idx = 0 |
295 | + while x_idx < len(x) or y_idx < len(y): |
296 | + # Compare strings |
297 | + (x_str, x_idx) = strcut(x, x_idx, cmp_table) |
298 | + (y_str, y_idx) = strcut(y, y_idx, cmp_table) |
299 | + result = deb_cmp_str(x_str, y_str) |
300 | + if result != 0: return result |
301 | + |
302 | + # Compare numbers |
303 | + (x_str, x_idx) = strcut(x, x_idx, "0123456789") |
304 | + (y_str, y_idx) = strcut(y, y_idx, "0123456789") |
305 | + result = cmp(int(x_str or "0"), int(y_str or "0")) |
306 | + if result != 0: return result |
307 | + |
308 | + return 0 |
309 | |
310 | === modified file 'tests/__init__.py' |
311 | --- tests/__init__.py 2010-01-29 09:30:27 +0000 |
312 | +++ tests/__init__.py 2010-01-29 14:09:14 +0000 |
313 | @@ -116,6 +116,7 @@ |
314 | 'test_config', |
315 | 'test_hooks', |
316 | 'test_import_dsc', |
317 | + 'test_merge_changelog', |
318 | 'test_merge_package', |
319 | 'test_merge_upstream', |
320 | 'test_repack_tarball_extra', |
321 | |
322 | === added file 'tests/test_merge_changelog.py' |
323 | --- tests/test_merge_changelog.py 1970-01-01 00:00:00 +0000 |
324 | +++ tests/test_merge_changelog.py 2010-01-29 14:09:14 +0000 |
325 | @@ -0,0 +1,128 @@ |
326 | +# Copyright (C) 2010 Canonical Ltd |
327 | +# |
328 | +# This file is part of bzr-builddeb. |
329 | +# |
330 | +# bzr-builddeb is free software; you can redistribute it and/or modify |
331 | +# it under the terms of the GNU General Public License as published by |
332 | +# the Free Software Foundation; either version 2 of the License, or |
333 | +# (at your option) any later version. |
334 | +# |
335 | +# bzr-builddeb is distributed in the hope that it will be useful, |
336 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
337 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
338 | +# GNU General Public License for more details. |
339 | +# |
340 | +# You should have received a copy of the GNU General Public License |
341 | +# along with bzr-builddeb; if not, write to the Free Software |
342 | +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
343 | +# |
344 | + |
345 | +"""Tests for the merge_changelog code.""" |
346 | + |
347 | +from bzrlib import ( |
348 | + memorytree, |
349 | + merge, |
350 | + tests, |
351 | + ) |
352 | +from bzrlib.plugins import builddeb |
353 | +from bzrlib.plugins.builddeb import merge_changelog |
354 | + |
355 | + |
356 | +class TestReadChangelog(tests.TestCase): |
357 | + |
358 | + def test_read_changelog(self): |
359 | + lines = """\ |
360 | +psuedo-prog (1.1.1-2) unstable; urgency=low |
361 | + |
362 | + * New upstream release. |
363 | + * Awesome bug fixes. |
364 | + |
365 | + -- Joe Foo <joe@example.com> Thu, 28 Jan 2010 10:45:44 +0000 |
366 | +""".splitlines(True) |
367 | + |
368 | + |
369 | + entries = merge_changelog.read_changelog(lines) |
370 | + self.assertEqual(1, len(entries)) |
371 | + |
372 | + |
373 | +class TestMergeChangelog(tests.TestCase): |
374 | + |
375 | + def assertMergeChangelog(self, expected_lines, this_lines, other_lines): |
376 | + merged_lines = merge_changelog.merge_changelog(this_lines, other_lines) |
377 | + self.assertEqualDiff(''.join(expected_lines), ''.join(merged_lines)) |
378 | + |
379 | + def test_merge_by_version(self): |
380 | + v_111_2 = """\ |
381 | +psuedo-prog (1.1.1-2) unstable; urgency=low |
382 | + |
383 | + * New upstream release. |
384 | + * Awesome bug fixes. |
385 | + |
386 | + -- Joe Foo <joe@example.com> Thu, 28 Jan 2010 10:45:44 +0000 |
387 | + |
388 | +""".splitlines(True) |
389 | + |
390 | + v_112_1 = """\ |
391 | +psuedo-prog (1.1.2-1) unstable; urgency=low |
392 | + |
393 | + * New upstream release. |
394 | + * No bug fixes :( |
395 | + |
396 | + -- Barry Foo <barry@example.com> Thu, 27 Jan 2010 10:45:44 +0000 |
397 | + |
398 | +""".splitlines(True) |
399 | + |
400 | + v_001_1 = """\ |
401 | +psuedo-prog (0.0.1-1) unstable; urgency=low |
402 | + |
403 | + * New project released!!!! |
404 | + * No bugs evar |
405 | + |
406 | + -- Barry Foo <barry@example.com> Thu, 27 Jan 2010 10:00:44 +0000 |
407 | + |
408 | +""".splitlines(True) |
409 | + |
410 | + this_lines = v_111_2 + v_001_1 |
411 | + other_lines = v_112_1 + v_001_1 |
412 | + expected_lines = v_112_1 + v_111_2 + v_001_1 |
413 | + self.assertMergeChangelog(expected_lines, this_lines, other_lines) |
414 | + self.assertMergeChangelog(expected_lines, other_lines, this_lines) |
415 | + |
416 | + |
417 | +class TestChangelogHook(tests.TestCaseWithMemoryTransport): |
418 | + |
419 | + def make_params(self): |
420 | + builder = self.make_branch_builder('source') |
421 | + builder.start_series() |
422 | + builder.build_snapshot('A', None, [ |
423 | + ('add', ('', 'TREE_ROOT', 'directory', None)), |
424 | + ('add', ('debian', 'deb-id', 'directory', None)), |
425 | + ('add', ('debian/changelog', 'c-id', 'file', '')), |
426 | + ('add', ('changelog', 'o-id', 'file', '')), |
427 | + ]) |
428 | + builder.finish_series() |
429 | + the_branch = builder.get_branch() |
430 | + |
431 | + tree = memorytree.MemoryTree.create_on_branch(the_branch) |
432 | + tree.lock_write() |
433 | + self.addCleanup(tree.unlock) |
434 | + |
435 | + class FakeMerger(object): |
436 | + def __init__(self, this_tree): |
437 | + self.this_tree = this_tree |
438 | + def get_lines(self, tree, file_id): |
439 | + return tree.get_file_lines(file_id) |
440 | + |
441 | + merger = FakeMerger(tree) |
442 | + params = merge.MergeHookParams(merger, 'c-id', None, 'file', 'file', |
443 | + 'this') |
444 | + return params, merger |
445 | + |
446 | + |
447 | + def test_changelog_merge_hook_successful(self): |
448 | + params, merger = self.make_params() |
449 | + params.other_lines = [''] |
450 | + file_merger = builddeb.changelog_merge_hook_factory(merger) |
451 | + result, new_content = file_merger.merge_text(params) |
452 | + self.assertEqual('success', result) |
453 | + # We ignore the new_content, as we test that elsewhere |
This adds a per-file commit hook, which checks hook_enabled = True
a) A Branch config called
changelog_
b) A file named 'changelog' or 'debian/changelog'
If both hold true, and it perceives a conflict in the changelog file, it essentially runs Scott's merge_changelog code. This will always result in a conflict-free changelog file. The current algorithm is "prefer-mine". So if both changelogs have a section with a matching 'version', the text present in "this" are used.
So if a Ubuntu Developer wants to enable this plugin, they can edit branch.conf or locations.conf (or bazaar.conf) to set 'changelog_ hook_enabled = True'. We can argue that just having 'bzr-builddeb' installed, would be sufficient to enable this for all branches when merging. (We could make the default True, and use the config to *disable* the plugin.)
There are some new tests for this, but I've also manually tested merging lp:ubuntu/bzrtools @-r4.3.1 into -r 50. And it seemed to do a nice clean merge (without the plugin enabled it conflicted as expected.) As an example:
bzr branch lp:ubuntu/bzrtools -r 4.3.1 bzrtools_upstream upstream
bzr branch lp:ubuntu/bzrtools -r 50 bzrtools_ubuntu
cd bzrtools_ubuntu
bzr merge ../bzrtools_
(There are other conflicts, but *debian/changelog* is not one of them)