Merge lp:~jameinel/bzr-builddeb/changelog-parser into lp:bzr-builddeb

Proposed by John A Meinel
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jameinel/bzr-builddeb/changelog-parser
Merge into: lp:bzr-builddeb
Diff against target: 609 lines (+271/-269)
2 files modified
merge_changelog.py (+104/-222)
tests/test_merge_changelog.py (+167/-47)
To merge this branch: bzr merge lp:~jameinel/bzr-builddeb/changelog-parser
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+18557@code.launchpad.net
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

This removes the guts of the Changelog parsing code that I brought in, and replaces it by just importing debian_bundle.changelog and having that do the parsing.

I don't really like depending on cl._blocks, but since import_dsc is doing so, I assumed that was the best we could do.

The tests all still pass, and the parsing is going to be as accurate as python-debian's parsing, which is what import_dsc uses anyway.

402. By John A Meinel

Make sure that the blocks are in sorted order before we do anything else.

403. By John A Meinel

Fix bug #516060, implement 3-way changelog merge.

This uses a fairly crude 3-way changelog merge algorithm, but doesn't yet
introduce conflicts. That will be next, as it requires an api bump.
If left != right, then we check if the version is in base, and if
one side is identical to base, then we pick the other.
This doesn't:
a) Conflict when left != right != base
b) Allow deleting entries (present in base & this but not other, for instance)
etc.

404. By John A Meinel

Conflict when appropriate.

We could try to do a textual merge, but it is just easier to
conflict on the whole region. Mostly because merge3.Merge3 is missing
a decent api for telling whether there was a conflict region :(.

Revision history for this message
John A Meinel (jameinel) wrote :

I just updated this branch. I was going to submit a separate proposal, but I did find a bug here, and just fixed it while doing the rest.

Anyway, this now does 3-way merging of the blocks. The basic logic is:

1) Find all blocks in THIS, OTHER and BASE
2) For all blocks in THIS and OTHER, include them in the output in sorted order.
3) If THIS and OTHER both contain a block (matched by Version) with different content, compare against the BASE content
  a) If THIS == BASE, chose OTHER
  b) if OTHER == BASE, chose THIS
  c) If BASE is not present, or BASE is different from both, conflict

What this is missing:

1) If THIS or OTHER deletes a block it will be restored. (I assume that if a version is in THIS or OTHER then you always want it in the output)
2) 3c could use merge3.Merge3() to try to do a textual merge of the conflicted region.

I can open bugs on those two bits if you want.

405. By John A Meinel

Simplify the conflict logic slightly.

Revision history for this message
Robert Collins (lifeless) wrote :

On Wed, 2010-02-03 at 22:19 +0000, John A Meinel wrote:
>
> What this is missing:
>
> 1) If THIS or OTHER deletes a block it will be restored. (I assume
> that if a version is in THIS or OTHER then you always want it in the
> output)
> 2) 3c could use merge3.Merge3() to try to do a textual merge of the
> conflicted region.
>
> I can open bugs on those two bits if you want.
--

please do

-Rob

406. By John A Meinel

Trying to trigger the invalid code, only to find bugs in python-debian :(

Revision history for this message
Robert Collins (lifeless) wrote :

 review: approve

On Wed, 2010-02-03 at 20:19 +0000, John A Meinel wrote:
>
> + elif right_block is None:
> + next_block = left_block
> + left_block = step(left_blocks)
> ...

> + elif left_block.version > right_block.version:
> + # left comes first
> + next_block = left_block
> + left_block = step(left_blocks)

The None and > cases are the same - I think it would be shorter to group
them.

elif right_block_is None or left_block.version > right_block.version:

-Rob

review: Approve
Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Robert Collins wrote:
> Review: Approve
> review: approve
>
> On Wed, 2010-02-03 at 20:19 +0000, John A Meinel wrote:
>> + elif right_block is None:
>> + next_block = left_block
>> + left_block = step(left_blocks)
>> ...
>
>> + elif left_block.version > right_block.version:
>> + # left comes first
>> + next_block = left_block
>> + left_block = step(left_blocks)
>
>
> The None and > cases are the same - I think it would be shorter to group
> them.
>
> elif right_block_is None or left_block.version > right_block.version:
>
> -Rob
>
>

I believe I've updated to that style in more recent patches. But I'm
also trying to address some of James's concerns about always being
active. I'm trying to get Strict handling and fallback to regular
processing working (only to find that Strict isn't properly triggering
in some edge cases... :(

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAktq63wACgkQJdeBCYSNAAOBmwCg1EmjxckXoDq5QlGLAln6P9Rz
50gAn02X+OR5lx/dWL/QmrAfSCtr4f7r
=271q
-----END PGP SIGNATURE-----

407. By John A Meinel

Sort out some more details for the 3-way merge code.
The main problem I was running into was that the constructor always suppressed parse failures.

Revision history for this message
James Westby (james-w) wrote :

I think what I would like to do is use strict=True and then let
merge3 do the merge if we can't parse it. That way we won't drop
text if we can't parse it.

Thanks,

James

Revision history for this message
James Westby (james-w) wrote :

Oh, it already has code for that, but I don't understand the comment

 # BASE lines don't end up in the output, so we allow strict=False

Would it also be good to note to the user when we tried to merge but
it failed due to parsing?

Thanks,

James

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

James Westby wrote:
> I think what I would like to do is use strict=True and then let
> merge3 do the merge if we can't parse it. That way we won't drop
> text if we can't parse it.
>
> Thanks,
>
> James
>

That is what I ended up with. I don't know if the current diff shows that.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkt1na4ACgkQJdeBCYSNAAMTQgCfYfcKCebNDyQPtpkwkCrInf0S
Bh0Anit1wIn+m7J24Gr7U1p/sBsfQsQS
=h8Ig
-----END PGP SIGNATURE-----

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'merge_changelog.py'
--- merge_changelog.py 2010-01-29 10:51:45 +0000
+++ merge_changelog.py 2010-02-04 16:34:14 +0000
@@ -20,243 +20,125 @@
2020
21from bzrlib import (21from bzrlib import (
22 merge,22 merge,
23 merge3,
23 )24 )
2425
26from debian_bundle import changelog
27
25class ChangeLogFileMerge(merge.ConfigurableFileMerger):28class ChangeLogFileMerge(merge.ConfigurableFileMerger):
2629
27 name_prefix = 'deb_changelog'30 name_prefix = 'deb_changelog'
28 default_files = ['debian/changelog']31 default_files = ['debian/changelog']
2932
30 def merge_text(self, params):33 def merge_text(self, params):
31 return 'success', merge_changelog(params.this_lines, params.other_lines)34 return merge_changelog(params.this_lines, params.other_lines,
3235 params.base_lines)
3336
34########################################################################
35# Changelog Management
36########################################################################
3737
38# Regular expression for top of debian/changelog38# Regular expression for top of debian/changelog
39CL_RE = re.compile(r'^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)((\s+[-0-9a-z]+)+)\;',39CL_RE = re.compile(r'^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)((\s+[-0-9a-z]+)+)\;',
40 re.IGNORECASE)40 re.IGNORECASE)
4141
42def merge_changelog(left_changelog_lines, right_changelog_lines):42def merge_changelog(this_lines, other_lines, base_lines=[]):
43 """Merge a changelog file."""43 """Merge a changelog file."""
4444
45 left_cl = read_changelog(left_changelog_lines)45 try:
46 right_cl = read_changelog(right_changelog_lines)46 left_cl = read_changelog(this_lines)
47 right_cl = read_changelog(other_lines)
48 # BASE lines don't end up in the output, so we allow strict=False
49 base_cl = read_changelog(base_lines, strict=False)
50 except changelog.ChangelogParseError:
51 return ('not_applicable', None)
4752
48 content = []53 content = []
49 # TODO: This is not a 3-way merge, but a 2-way merge54 def step(iterator):
50 # The resolution is currently 'if left and right have texts that have55 try:
51 # the same "version" string, use left', aka "prefer-mine".56 return iterator.next()
52 # We could introduce BASE, and cause conflicts, or appropriately57 except StopIteration:
53 # resolve, etc.58 return None
54 # Note also that this code is only invoked when there is a59 left_blocks = dict((b.version, b) for b in left_cl._blocks)
55 # left-and-right change, so merging a pure-right change will take all60 right_blocks = dict((b.version, b) for b in right_cl._blocks)
56 # changes.61 # Unfortunately, while version objects implement __eq__ they *don't*
57 for right_ver, right_text in right_cl:62 # implement __hash__, which means we can't do dict lookups properly, so
58 while len(left_cl) and left_cl[0][0] > right_ver:63 # instead, we fall back on the version string instead of the object.
59 (left_ver, left_text) = left_cl.pop(0)64 # Make sure never to try to use right_version in left_blocks because of
60 content.append(left_text)65 # this.
61 content.append('\n')66 # We lazily parse the base data, in case we never need it
6267 base_blocks = dict((b.version.full_version, b) for b in base_cl._blocks)
63 while len(left_cl) and left_cl[0][0] == right_ver:68 left_order = iter(sorted(left_blocks.keys(), reverse=True))
64 (left_ver, left_text) = left_cl.pop(0)69 right_order = iter(sorted(right_blocks.keys(), reverse=True))
6570 left_version = step(left_order)
66 content.append(right_text)71 right_version = step(right_order)
67 content.append('\n')72
6873 # TODO: Do we want to support the ability to delete a section? We could do
69 for left_ver, left_text in left_cl:74 # a first-pass algorithm that checks the versions in base versus the
70 content.append(left_text)75 # versions in this and other, to determine what versions should be in
71 content.append('\n')76 # the output. For now, we just assume that if a version is present in
72 77 # any of this or other, then we want it in the output.
73 return content78 conflict_status = 'success'
7479
7580 while left_version is not None or right_version is not None:
76def read_changelog(lines):81 if (left_version is None or
82 (right_version is not None and right_version > left_version)):
83 next_content = str(right_blocks[right_version])
84 right_version = step(right_order)
85 elif (right_version is None or
86 (left_version is not None and left_version > right_version)):
87 next_content = str(left_blocks[left_version])
88 left_version = step(left_order)
89 else:
90 assert left_version == right_version
91 # Same version, step both
92 # TODO: Conflict if left_version != right
93 # Note: See above comment why we can't use
94 # right_blocks[left_version] even though they *should* be
95 # equivalent
96 left_content = str(left_blocks[left_version])
97 right_content = str(right_blocks[right_version])
98 if left_content == right_content:
99 # Identical content
100 next_content = left_content
101 else:
102 # Sides disagree, compare with base
103 base_content = str(base_blocks.get(left_version.full_version,
104 ''))
105 if left_content == base_content:
106 next_content = right_content
107 elif right_content == base_content:
108 next_content = left_content
109 else:
110 # TODO: We could use merge3.Merge3 to try a line-based
111 # textual merge on the content. However, for now I'm
112 # just going to conflict on the whole region
113 # Conflict names taken from merge.py
114 next_content = ('<<<<<<< TREE\n'
115 + left_content
116 + '=======\n'
117 + right_content
118 + '>>>>>>> MERGE-SOURCE\n'
119 )
120 conflict_status = 'conflicted'
121 next_block = left_blocks[left_version]
122 left_version = step(left_order)
123 right_version = step(right_order)
124 content.append(next_content)
125
126 return conflict_status, content
127
128
129def read_changelog(lines, strict=True):
77 """Return a parsed changelog file."""130 """Return a parsed changelog file."""
78 entries = []131 # Note: There appears to be a bug in Changelog if you pass it an iterable
79132 # of lines (like a file obj, or a list of lines). Specifically, it
80 (ver, text) = (None, "")133 # does not strip trailing newlines, and it adds ones back in, so you
81 for line in lines:134 # get doubled blank lines... :(
82 match = CL_RE.search(line)135 # So we just ''.join() the lines and don't worry about it
83 if match:136 # Note: There is also a bug that the Changelog constructor suppresses parse
84 try:137 # errors, so we want to always call parse_changelog separately
85 ver = Version(match.group(2))138 content = ''.join(lines)
86 except ValueError:139 cl = changelog.Changelog()
87 ver = None140 if content:
88141 # We get a warning if we try to parse an empty changelog file, which in
89 text += line142 # strict mode is an error, so only parse when we have content
90 elif line.startswith(" -- "):143 cl.parse_changelog(content, strict=strict)
91 if ver is None:144 return cl
92 ver = Version("0")
93
94 text += line
95 entries.append((ver, text))
96 (ver, text) = (None, "")
97 elif len(line.strip()) or ver is not None:
98 text += line
99
100 if len(text):
101 entries.append((ver, text))
102
103 return entries
104
105########################################################################
106# Version parsing code
107########################################################################
108# Regular expressions make validating things easy
109valid_epoch = re.compile(r'^[0-9]+$')
110valid_upstream = re.compile(r'^[A-Za-z0-9+:.~-]*$')
111valid_revision = re.compile(r'^[A-Za-z0-9+.~]+$')
112
113# Character comparison table for upstream and revision components
114cmp_table = "~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:"
115
116
117class Version(object):
118 """Debian version number.
119
120 This class is designed to be reasonably transparent and allow you
121 to write code like:
122
123 | s.version >= '1.100-1'
124
125 The comparison will be done according to Debian rules, so '1.2' will
126 compare lower.
127
128 Properties:
129 epoch Epoch
130 upstream Upstream version
131 revision Debian/local revision
132 """
133
134 def __init__(self, ver):
135 """Parse a string or number into the three components."""
136 self.epoch = 0
137 self.upstream = None
138 self.revision = None
139
140 ver = str(ver)
141 if not len(ver):
142 raise ValueError
143
144 # Epoch is component before first colon
145 idx = ver.find(":")
146 if idx != -1:
147 self.epoch = ver[:idx]
148 if not len(self.epoch):
149 raise ValueError
150 if not valid_epoch.search(self.epoch):
151 raise ValueError
152 ver = ver[idx+1:]
153
154 # Revision is component after last hyphen
155 idx = ver.rfind("-")
156 if idx != -1:
157 self.revision = ver[idx+1:]
158 if not len(self.revision):
159 raise ValueError
160 if not valid_revision.search(self.revision):
161 raise ValueError
162 ver = ver[:idx]
163
164 # Remaining component is upstream
165 self.upstream = ver
166 if not len(self.upstream):
167 raise ValueError
168 if not valid_upstream.search(self.upstream):
169 raise ValueError
170
171 self.epoch = int(self.epoch)
172
173 def getWithoutEpoch(self):
174 """Return the version without the epoch."""
175 str = self.upstream
176 if self.revision is not None:
177 str += "-%s" % (self.revision,)
178 return str
179
180 without_epoch = property(getWithoutEpoch)
181
182 def __str__(self):
183 """Return the class as a string for printing."""
184 str = ""
185 if self.epoch > 0:
186 str += "%d:" % (self.epoch,)
187 str += self.upstream
188 if self.revision is not None:
189 str += "-%s" % (self.revision,)
190 return str
191
192 def __repr__(self):
193 """Return a debugging representation of the object."""
194 return "<%s epoch: %d, upstream: %r, revision: %r>" \
195 % (self.__class__.__name__, self.epoch,
196 self.upstream, self.revision)
197
198 def __cmp__(self, other):
199 """Compare two Version classes."""
200 other = Version(other)
201
202 result = cmp(self.epoch, other.epoch)
203 if result != 0: return result
204
205 result = deb_cmp(self.upstream, other.upstream)
206 if result != 0: return result
207
208 result = deb_cmp(self.revision or "", other.revision or "")
209 if result != 0: return result
210
211 return 0
212
213
214def strcut(str, idx, accept):
215 """Cut characters from str that are entirely in accept."""
216 ret = ""
217 while idx < len(str) and str[idx] in accept:
218 ret += str[idx]
219 idx += 1
220
221 return (ret, idx)
222
223def deb_order(str, idx):
224 """Return the comparison order of two characters."""
225 if idx >= len(str):
226 return 0
227 elif str[idx] == "~":
228 return -1
229 else:
230 return cmp_table.index(str[idx])
231
232def deb_cmp_str(x, y):
233 """Compare two strings in a deb version."""
234 idx = 0
235 while (idx < len(x)) or (idx < len(y)):
236 result = deb_order(x, idx) - deb_order(y, idx)
237 if result < 0:
238 return -1
239 elif result > 0:
240 return 1
241
242 idx += 1
243
244 return 0
245
246def deb_cmp(x, y):
247 """Implement the string comparison outlined by Debian policy."""
248 x_idx = y_idx = 0
249 while x_idx < len(x) or y_idx < len(y):
250 # Compare strings
251 (x_str, x_idx) = strcut(x, x_idx, cmp_table)
252 (y_str, y_idx) = strcut(y, y_idx, cmp_table)
253 result = deb_cmp_str(x_str, y_str)
254 if result != 0: return result
255
256 # Compare numbers
257 (x_str, x_idx) = strcut(x, x_idx, "0123456789")
258 (y_str, y_idx) = strcut(y, y_idx, "0123456789")
259 result = cmp(int(x_str or "0"), int(y_str or "0"))
260 if result != 0: return result
261
262 return 0
263145
=== modified file 'tests/test_merge_changelog.py'
--- tests/test_merge_changelog.py 2010-01-29 10:51:45 +0000
+++ tests/test_merge_changelog.py 2010-02-04 16:34:14 +0000
@@ -1,5 +1,5 @@
1# Copyright (C) 2010 Canonical Ltd1# Copyright (C) 2010 Canonical Ltd
2# 2#
3# This file is part of bzr-builddeb.3# This file is part of bzr-builddeb.
4#4#
5# bzr-builddeb is free software; you can redistribute it and/or modify5# bzr-builddeb is free software; you can redistribute it and/or modify
@@ -19,6 +19,10 @@
1919
20"""Tests for the merge_changelog code."""20"""Tests for the merge_changelog code."""
2121
22import warnings
23
24from debian_bundle import changelog
25
22from bzrlib import (26from bzrlib import (
23 memorytree,27 memorytree,
24 merge,28 merge,
@@ -28,66 +32,181 @@
28from bzrlib.plugins.builddeb import merge_changelog32from bzrlib.plugins.builddeb import merge_changelog
2933
3034
35v_111_2 = """\
36psuedo-prog (1.1.1-2) unstable; urgency=low
37
38 * New upstream release.
39 * Awesome bug fixes.
40
41 -- Joe Foo <joe@example.com> Thu, 28 Jan 2010 10:45:44 +0000
42
43""".splitlines(True)
44
45
46v_111_2b = """\
47psuedo-prog (1.1.1-2) unstable; urgency=low
48
49 * New upstream release.
50 * Awesome bug fixes.
51 * But more is better
52
53 -- Joe Foo <joe@example.com> Thu, 28 Jan 2010 10:45:44 +0000
54
55""".splitlines(True)
56
57
58v_111_2c = """\
59psuedo-prog (1.1.1-2) unstable; urgency=low
60
61 * New upstream release.
62 * Yet another content for 1.1.1-2
63
64 -- Joe Foo <joe@example.com> Thu, 28 Jan 2010 10:45:44 +0000
65
66""".splitlines(True)
67
68
69v_112_1 = """\
70psuedo-prog (1.1.2-1) unstable; urgency=low
71
72 * New upstream release.
73 * No bug fixes :(
74
75 -- Barry Foo <barry@example.com> Thu, 27 Jan 2010 10:45:44 +0000
76
77""".splitlines(True)
78
79
80v_001_1 = """\
81psuedo-prog (0.0.1-1) unstable; urgency=low
82
83 * New project released!!!!
84 * No bugs evar
85
86 -- Barry Foo <barry@example.com> Thu, 27 Jan 2010 10:00:44 +0000
87
88""".splitlines(True)
89
90
31class TestReadChangelog(tests.TestCase):91class TestReadChangelog(tests.TestCase):
3292
33 def test_read_changelog(self):93 def test_read_changelog(self):
34 lines = """\94 cl = merge_changelog.read_changelog(v_112_1)
35psuedo-prog (1.1.1-2) unstable; urgency=low95 self.assertEqual(1, len(cl._blocks))
3696
37 * New upstream release.97
38 * Awesome bug fixes.
39
40 -- Joe Foo <joe@example.com> Thu, 28 Jan 2010 10:45:44 +0000
41""".splitlines(True)
42
43
44 entries = merge_changelog.read_changelog(lines)
45 self.assertEqual(1, len(entries))
46
47
48class TestMergeChangelog(tests.TestCase):98class TestMergeChangelog(tests.TestCase):
4999
50 def assertMergeChangelog(self, expected_lines, this_lines, other_lines):100 def assertMergeChangelog(self, expected_lines, this_lines, other_lines,
51 merged_lines = merge_changelog.merge_changelog(this_lines, other_lines)101 base_lines=[], conflicted=False):
102 status, merged_lines = merge_changelog.merge_changelog(
103 this_lines, other_lines, base_lines)
104 if conflicted:
105 self.assertEqual('conflicted', status)
106 else:
107 self.assertEqual('success', status)
52 self.assertEqualDiff(''.join(expected_lines), ''.join(merged_lines))108 self.assertEqualDiff(''.join(expected_lines), ''.join(merged_lines))
53109
54 def test_merge_by_version(self):110 def test_merge_by_version(self):
55 v_111_2 = """\
56psuedo-prog (1.1.1-2) unstable; urgency=low
57
58 * New upstream release.
59 * Awesome bug fixes.
60
61 -- Joe Foo <joe@example.com> Thu, 28 Jan 2010 10:45:44 +0000
62
63""".splitlines(True)
64
65 v_112_1 = """\
66psuedo-prog (1.1.2-1) unstable; urgency=low
67
68 * New upstream release.
69 * No bug fixes :(
70
71 -- Barry Foo <barry@example.com> Thu, 27 Jan 2010 10:45:44 +0000
72
73""".splitlines(True)
74
75 v_001_1 = """\
76psuedo-prog (0.0.1-1) unstable; urgency=low
77
78 * New project released!!!!
79 * No bugs evar
80
81 -- Barry Foo <barry@example.com> Thu, 27 Jan 2010 10:00:44 +0000
82
83""".splitlines(True)
84
85 this_lines = v_111_2 + v_001_1111 this_lines = v_111_2 + v_001_1
86 other_lines = v_112_1 + v_001_1112 other_lines = v_112_1 + v_001_1
87 expected_lines = v_112_1 + v_111_2 + v_001_1113 expected_lines = v_112_1 + v_111_2 + v_001_1
88 self.assertMergeChangelog(expected_lines, this_lines, other_lines)114 self.assertMergeChangelog(expected_lines, this_lines, other_lines)
89 self.assertMergeChangelog(expected_lines, other_lines, this_lines)115 self.assertMergeChangelog(expected_lines, other_lines, this_lines)
90116
117 def test_this_shorter(self):
118 self.assertMergeChangelog(v_112_1 + v_111_2 + v_001_1,
119 this_lines=v_111_2,
120 other_lines=v_112_1 + v_001_1,
121 base_lines=[])
122 self.assertMergeChangelog(v_112_1 + v_111_2 + v_001_1,
123 this_lines=v_001_1,
124 other_lines=v_112_1 + v_111_2,
125 base_lines=[])
126
127 def test_other_shorter(self):
128 self.assertMergeChangelog(v_112_1 + v_111_2 + v_001_1,
129 this_lines=v_112_1 + v_001_1,
130 other_lines=v_111_2,
131 base_lines=[])
132 self.assertMergeChangelog(v_112_1 + v_111_2 + v_001_1,
133 this_lines=v_112_1 + v_111_2,
134 other_lines=v_001_1,
135 base_lines=[])
136
137 def test_unsorted(self):
138 # Passing in an improperly sorted text should result in a properly
139 # sorted one
140 self.assertMergeChangelog(v_111_2 + v_001_1,
141 this_lines = v_001_1 + v_111_2,
142 other_lines = [],
143 base_lines = [])
144
145 def test_3way_merge(self):
146 # Check that if one of THIS or OTHER matches BASE, then we select the
147 # other content
148 self.assertMergeChangelog(expected_lines=v_111_2,
149 this_lines=v_111_2, other_lines=v_111_2b,
150 base_lines=v_111_2b)
151 self.assertMergeChangelog(expected_lines=v_111_2b,
152 this_lines=v_111_2, other_lines=v_111_2b,
153 base_lines=v_111_2)
154
155 def test_3way_conflicted(self):
156 self.assertMergeChangelog(
157 expected_lines=['<<<<<<< TREE\n']
158 + v_111_2b
159 + ['=======\n']
160 + v_111_2c
161 + ['>>>>>>> MERGE-SOURCE\n'],
162 this_lines=v_111_2b, other_lines=v_111_2c,
163 base_lines=v_111_2,
164 conflicted=True)
165 self.assertMergeChangelog(
166 expected_lines=['<<<<<<< TREE\n']
167 + v_111_2b
168 + ['=======\n']
169 + v_111_2c
170 + ['>>>>>>> MERGE-SOURCE\n'],
171 this_lines=v_111_2b, other_lines=v_111_2c,
172 base_lines=[],
173 conflicted=True)
174
175 def test_not_valid_changelog(self):
176 invalid_changelog = """\
177psuedo-prog (1.1.1-2) unstable; urgency=low
178
179 * New upstream release.
180 * Awesome bug fixes.
181
182 -- Thu, 28 Jan 2010 10:45:44 +0000
183
184""".splitlines(True)
185 # Missing the author and we don't have allow_missing_author set
186 cl = changelog.Changelog()
187 self.assertRaises(changelog.ChangelogParseError,
188 cl.parse_changelog, ''.join(invalid_changelog), strict=True)
189 # If strict parsing fails, don't try to do special merging
190 self.assertEqual(('not_applicable', None),
191 merge_changelog.merge_changelog(invalid_changelog, v_111_2,
192 v_111_2))
193 self.assertEqual(('not_applicable', None),
194 merge_changelog.merge_changelog(v_111_2, invalid_changelog,
195 v_111_2))
196 # We are non-strict about parsing BASE, because its contents are not
197 # included in the output.
198 # This triggers a warning, but we don't want to clutter the test run
199 cur_filters = warnings.filters[:]
200 warnings.simplefilter('ignore', UserWarning)
201 try:
202 self.assertMergeChangelog(v_112_1 + v_111_2,
203 this_lines=v_111_2,
204 other_lines=v_112_1,
205 base_lines=invalid_changelog,
206 )
207 finally:
208 warnings.filters = cur_filters[:]
209
91210
92class TestChangelogHook(tests.TestCaseWithMemoryTransport):211class TestChangelogHook(tests.TestCaseWithMemoryTransport):
93212
@@ -122,6 +241,7 @@
122 def test_changelog_merge_hook_successful(self):241 def test_changelog_merge_hook_successful(self):
123 params, merger = self.make_params()242 params, merger = self.make_params()
124 params.other_lines = ['']243 params.other_lines = ['']
244 params.base_lines = ['']
125 file_merger = builddeb.changelog_merge_hook_factory(merger)245 file_merger = builddeb.changelog_merge_hook_factory(merger)
126 result, new_content = file_merger.merge_text(params)246 result, new_content = file_merger.merge_text(params)
127 self.assertEqual('success', result)247 self.assertEqual('success', result)

Subscribers

People subscribed via source and target branches