Merge lp:~al-maisan/bzr-builddeb/merge-package into lp:~bzr-builddeb-hackers/bzr-builddeb/trunk-old

Proposed by Muharem Hrnjadovic
Status: Merged
Merged at revision: not available
Proposed branch: lp:~al-maisan/bzr-builddeb/merge-package
Merge into: lp:~bzr-builddeb-hackers/bzr-builddeb/trunk-old
Diff against target: None lines
To merge this branch: bzr merge lp:~al-maisan/bzr-builddeb/merge-package
Reviewer Review Type Date Requested Status
James Westby Approve
Review via email: mp+10369@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Hello James,

this branch implements the 'merge-package' command as specified here: http://pastebin.com/f7214e464.

Please take a look and let me know what you think.

391. By Muharem Hrnjadovic

More test clean-ups.

Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Hmm .. some more test code clean-ups .. please see the attached
incremental diff.

Best regards

--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC

1=== modified file 'merge_package.py'
2--- merge_package.py 2009-08-19 09:19:38 +0000
3+++ merge_package.py 2009-08-19 10:06:11 +0000
4@@ -89,7 +89,8 @@
5 def _upstream_version_data(source, target):
6 """Most recent upstream versions/revision IDs of the merge source/target.
7
8- Please note: both packaing branches must have been read-locked beforehand.
9+ Please note: both packaging branches must have been read-locked
10+ beforehand.
11
12 :param source: The merge source branch.
13 :param target: The merge target branch.
14
15=== modified file 'tests/test_merge_package.py'
16--- tests/test_merge_package.py 2009-08-19 09:44:38 +0000
17+++ tests/test_merge_package.py 2009-08-19 10:08:00 +0000
18@@ -19,13 +19,10 @@
19 # along with bzr-builddeb; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
22-import os
23-import random
24 import string
25 import unittest
26
27 from bzrlib.errors import ConflictsInTree
28-from bzrlib.merge import WeaveMerger
29 from bzrlib.tests import TestCaseWithTransport
30
31 from bzrlib.plugins.builddeb import merge_package as MP
32@@ -474,8 +471,7 @@
33 for version, paths, utree, urevid in vdata:
34 msg = ''
35 if utree is not None:
36- tree.merge_from_branch(
37- utree.branch, to_revision=urevid, merge_type=WeaveMerger)
38+ tree.merge_from_branch(utree.branch, to_revision=urevid)
39 utree.branch.tags.merge_to(tree.branch.tags)
40 if urevid is not None:
41 msg += 'Merged tree %s|%s. ' % (tree_nick(utree), urevid)
42@@ -491,7 +487,6 @@
43
44
45 if __name__ == '__main__':
46- # unittest.main()
47 suite = unittest.TestLoader().loadTestsFromTestCase(MergePackageTests)
48 unittest.TextTestRunner(verbosity=2).run(suite)
49
Revision history for this message
James Westby (james-w) wrote :
Download full text (12.6 KiB)

> Hello James,
>
> this branch implements the 'merge-package' command as specified here:
> http://pastebin.com/f7214e464.
>
> Please take a look and let me know what you think.

=== modified file '__init__.py'
--- __init__.py 2009-07-26 15:51:02 +0000
+++ __init__.py 2009-08-06 09:59:34 +0000
@@ -39,7 +39,8 @@
         "merge_upstream": ["mu"],
         "import_dsc": [],
         "bd_do": [],
- "mark_uploaded": []
+ "mark_uploaded": [],
+ "merge_package": ["mp"]
         }

 for command, aliases in commands.iteritems():

I fear this alias will conflict with something in the future (merge-proposal
perhaps), perhaps it is better to leave it off for now and let people set
it as they like.

=== modified file 'cmds.py'
--- cmds.py 2009-07-26 18:21:49 +0000
+++ cmds.py 2009-08-19 09:06:47 +0000
@@ -870,6 +871,39 @@
             t.unlock()

+class cmd_merge_package(Command):
+ """Merges source packaging branch into target packaging branch.
+
+ This will first check whether the upstream branches have diverged.
+
+ If that's the case an attempt will be made to fix the upstream ancestry
+ so that the user only needs to deal wth packaging branch merge issues.
+
+ In the opposite case a normal merge will be performed.
+ """
+ takes_args = ['source']
+
+ def run(self, source):
+ source_branch = target_branch = None
+ # Get the target branch.
+ try:
+ tree = WorkingTree.open_containing('.')[0]
+ target_branch = tree.branch
+ except NotBranchError:
+ raise BzrCommandError(
+ "There is no tree to merge the source branch in to")
+ # Get the source branch.
+ try:
+ source_branch = Branch.open(source)
+ except NotBranchError:
+ raise BzrCommandError("Invalid source branch URL?")
+
+ fix_ancestry_as_needed(tree, source_branch)
+
+ # Merge source packaging branch in to the target packaging branch.
+ tree.merge_from_branch(source_branch)
+
+
 class cmd_test_builddeb(Command):
     """Run the builddeb test suite"""

@@ -880,4 +914,3 @@
         passed = selftest(test_suite_factory=test_suite)
         # invert for shell exit code rules
         return not passed
-

=== modified file 'import_dsc.py'
--- import_dsc.py 2009-07-26 16:44:17 +0000
+++ import_dsc.py 2009-08-19 08:58:47 +0000
@@ -1570,7 +1570,7 @@
         finally:
             shutil.rmtree(tempdir)

- def _extract_upstream_tree(self, upstream_tip, basedir):
+ def extract_upstream_tree(self, upstream_tip, basedir):
         # Extract that to a tempdir so we can get a working
         # tree for it.
         # TODO: should stack rather than trying to use the repository,
@@ -1582,6 +1582,13 @@
         self.upstream_tree = dir_to.open_workingtree()
         self.upstream_branch = self.upstream_tree.branch

+ def _extract_upstream_tree(self, upstream_tip, basedir):
+ # This method is now being used outside this module and hence
+ # not really private any longer.
+ # TODO: obsolete/remove this method and start using
+ # extract_upstream_tree() instead.
+ self.extract_upstre...

review: Needs Resubmitting
392. By Muharem Hrnjadovic

Enhancements stemming from James' review comments, round 1

393. By Muharem Hrnjadovic

Minor fix.

394. By Muharem Hrnjadovic

Enhancements stemming from James' review comments, round 2

395. By Muharem Hrnjadovic

Enhancements stemming from James' review comments, round 3

396. By Muharem Hrnjadovic

Enhancements stemming from James' review comments, round 4

397. By Muharem Hrnjadovic

Enhancements stemming from James' review comments, round 5

398. By Muharem Hrnjadovic

Enhancements stemming from James' review comments, round 6

399. By Muharem Hrnjadovic

Enhancements stemming from James' review comments, round 7

400. By Muharem Hrnjadovic

Minor fix.

401. By Muharem Hrnjadovic

Minor test code fix.

402. By Muharem Hrnjadovic

Added test that checks the ancestry pre- and post-fix.

403. By Muharem Hrnjadovic

Added a more stringent test that checks the ancestry pre- and post-fix.

Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :
Download full text (15.2 KiB)

James Westby wrote:

Hello James,

thank you very much for reviewing the branch at hand. I have revised it
to accommodate most of your suggestions.

Please have a look at the attached incremental/full diffs as well as at
my in-line responses below.

You are obviously free to track the changes made in the course of the
review here:
bzr+ssh://bazaar.launchpad.net/~al-maisan/bzr-builddeb/merge-package

> Review: Resubmit
>> Hello James,
>>
>> this branch implements the 'merge-package' command as specified here:
>> http://pastebin.com/f7214e464.
>>
>> Please take a look and let me know what you think.
>
>
> === modified file '__init__.py'
> --- __init__.py 2009-07-26 15:51:02 +0000
> +++ __init__.py 2009-08-06 09:59:34 +0000
> @@ -39,7 +39,8 @@
> "merge_upstream": ["mu"],
> "import_dsc": [],
> "bd_do": [],
> - "mark_uploaded": []
> + "mark_uploaded": [],
> + "merge_package": ["mp"]
> }
>
> for command, aliases in commands.iteritems():
>
>
> I fear this alias will conflict with something in the future (merge-proposal
> perhaps), perhaps it is better to leave it off for now and let people set
> it as they like.

Good point, alias removed.

> === modified file 'cmds.py'
> --- cmds.py 2009-07-26 18:21:49 +0000
> +++ cmds.py 2009-08-19 09:06:47 +0000
> @@ -870,6 +871,39 @@
> t.unlock()
>
>
> +class cmd_merge_package(Command):
> + """Merges source packaging branch into target packaging branch.
> +
> + This will first check whether the upstream branches have diverged.
> +
> + If that's the case an attempt will be made to fix the upstream ancestry
> + so that the user only needs to deal wth packaging branch merge issues.
> +
> + In the opposite case a normal merge will be performed.
> + """
> + takes_args = ['source']
> +
> + def run(self, source):
> + source_branch = target_branch = None
> + # Get the target branch.
> + try:
> + tree = WorkingTree.open_containing('.')[0]
> + target_branch = tree.branch
> + except NotBranchError:
> + raise BzrCommandError(
> + "There is no tree to merge the source branch in to")
> + # Get the source branch.
> + try:
> + source_branch = Branch.open(source)
> + except NotBranchError:
> + raise BzrCommandError("Invalid source branch URL?")
> +
> + fix_ancestry_as_needed(tree, source_branch)
> +
> + # Merge source packaging branch in to the target packaging branch.
> + tree.merge_from_branch(source_branch)
> +
> +
> class cmd_test_builddeb(Command):
> """Run the builddeb test suite"""
>
> @@ -880,4 +914,3 @@
> passed = selftest(test_suite_factory=test_suite)
> # invert for shell exit code rules
> return not passed
> -
>
> === modified file 'import_dsc.py'
> --- import_dsc.py 2009-07-26 16:44:17 +0000
> +++ import_dsc.py 2009-08-19 08:58:47 +0000
> @@ -1570,7 +1570,7 @@
> finally:
> shutil.rmtree(tempdir)
>
> - def _extract_upstream_tree(self, upstream_tip, basedir):
> + def extract_upstream_tree(self, upstream_tip, base...

1=== modified file '__init__.py'
2--- __init__.py 2009-08-06 09:59:34 +0000
3+++ __init__.py 2009-08-19 11:39:16 +0000
4@@ -40,7 +40,7 @@
5 "import_dsc": [],
6 "bd_do": [],
7 "mark_uploaded": [],
8- "merge_package": ["mp"]
9+ "merge_package": []
10 }
11
12 for command, aliases in commands.iteritems():
13
14=== modified file 'errors.py'
15--- errors.py 2009-04-16 09:30:49 +0000
16+++ errors.py 2009-08-19 14:08:39 +0000
17@@ -175,3 +175,22 @@
18
19 def __init__(self, error):
20 BzrError.__init__(self, error=error)
21+
22+
23+class SharedUpstreamConflictsWithTargetPackaging(BzrError):
24+ _fmt = ('''\
25+ The "merge-package" command has detected diverged upstream
26+ branches for the merge source and target. A shared upstream
27+ revision was constructed to remedy the problem.
28+
29+ However, merging the shared upstream revision into the merge
30+ target resulted in conflicts.
31+
32+ Please proceed as follows:
33+
34+ 1 - Resolve the current merge conflicts in the merge target
35+ directory and commit the changes.
36+ 2 - Perform a plain "bzr merge <source-packaging-branch>"
37+ command, resolve any ensuing packaging branch conflicts
38+ and commit once satisfied with the changes.
39+ ''')
40
41=== modified file 'import_dsc.py'
42--- import_dsc.py 2009-08-19 08:58:47 +0000
43+++ import_dsc.py 2009-08-19 11:43:09 +0000
44@@ -1575,6 +1575,7 @@
45 # tree for it.
46 # TODO: should stack rather than trying to use the repository,
47 # as that will be more efficient.
48+ # TODO: remove the _extract_upstream_tree alias below.
49 to_location = os.path.join(basedir, "upstream")
50 dir_to = self.branch.bzrdir.sprout(to_location,
51 revision_id=upstream_tip,
52@@ -1582,12 +1583,7 @@
53 self.upstream_tree = dir_to.open_workingtree()
54 self.upstream_branch = self.upstream_tree.branch
55
56- def _extract_upstream_tree(self, upstream_tip, basedir):
57- # This method is now being used outside this module and hence
58- # not really private any longer.
59- # TODO: obsolete/remove this method and start using
60- # extract_upstream_tree() instead.
61- self.extract_upstream_tree(upstream_tip, basedir)
62+ _extract_upstream_tree = extract_upstream_tree
63
64 def _create_empty_upstream_tree(self, basedir):
65 to_location = os.path.join(basedir, "upstream")
66@@ -1622,11 +1618,11 @@
67 shutil.rmtree(tempdir)
68 raise
69
70- def _revid_of_upstream_version_from_branch(self, version):
71 """The private method below will go away eventually."""
72 return self.revid_of_upstream_version_from_branch(version)
73
74 def revid_of_upstream_version_from_branch(self, version):
75+ # TODO: remove the _revid_of_upstream_version_from_branch alias below.
76 assert isinstance(version, str)
77 tag_name = self.upstream_tag_name(version)
78 if self._has_version(self.branch, tag_name):
79@@ -1640,6 +1636,8 @@
80 tag_name = self.upstream_tag_name(version)
81 return self.branch.tags.lookup_tag(tag_name)
82
83+ _revid_of_upstream_version_from_branch = revid_of_upstream_version_from_branch
84+
85 def merge_upstream(self, tarball_filename, version, previous_version,
86 upstream_branch=None, upstream_revision=None, merge_type=None):
87 assert self.upstream_branch is None, \
88
89=== modified file 'merge_package.py'
90--- merge_package.py 2009-08-19 09:19:38 +0000
91+++ merge_package.py 2009-08-19 16:31:25 +0000
92@@ -30,37 +30,7 @@
93 from bzrlib import errors
94
95 from bzrlib.plugins.builddeb.import_dsc import DistributionBranch
96-
97-
98-class WrongBranchType(errors.BzrError):
99- _fmt = "The merge target is not a packaging branch."
100-
101-
102-class InvalidChangelogFormat(errors.BzrError):
103- _fmt = "The debian/changelog is empty or not in valid format."
104-
105-
106-class SourceUpstreamConflictsWithTargetPackaging(errors.BzrError):
107- _fmt = (
108- "The source upstream branch conflicts with "
109- "the target packaging branch")
110-
111-
112-def _read_file(branch, path):
113- """Get content of file for given `branch` and `path.
114-
115- :param branch: A Branch object containing the file of interest.
116- :param path: The path of the file to read.
117- """
118- try:
119- tree = branch.basis_tree()
120- tree.lock_read()
121- content = tree.get_file_text(tree.path2id(path))
122- tree.unlock()
123- except errors.NoSuchId:
124- raise WrongBranchType()
125-
126- return content
127+from bzrlib.plugins.builddeb.util import find_changelog
128
129
130 def _latest_version(branch):
131@@ -68,28 +38,16 @@
132
133 :param branch: A Branch object containing the source upload of interest.
134 """
135- upload_version = ''
136- changelog = _read_file(branch, "debian/changelog")
137-
138- for line in changelog.splitlines():
139- # Look for the top-level changelog stanza, extract the
140- # upload version from it and break on success.
141- match = re.search('^.+\(([^)]+)\).*$', line)
142- if match is not None:
143- (upload_version,) = match.groups(1)
144- break
145-
146- upload_version = upload_version.strip()
147- if len(upload_version) <= 0:
148- raise InvalidChangelogFormat()
149-
150- return Version(upload_version)
151+ changelog, _ignore = find_changelog(branch.basis_tree(), False)
152+
153+ return changelog.version
154
155
156 def _upstream_version_data(source, target):
157 """Most recent upstream versions/revision IDs of the merge source/target.
158
159- Please note: both packaing branches must have been read-locked beforehand.
160+ Please note: both packaging branches must have been read-locked
161+ beforehand.
162
163 :param source: The merge source branch.
164 :param target: The merge target branch.
165@@ -150,49 +108,48 @@
166 t_upstream_reverted = False
167 target = tree.branch
168
169+ source.lock_read()
170 try:
171- source.lock_read()
172- target.lock_read()
173- upstream_vdata = _upstream_version_data(source, target)
174- # Did the upstream branches of the merge source and target diverge?
175- revids = [vdata[1] for vdata in upstream_vdata]
176- graph = source.repository.get_graph(target.repository)
177- upstreams_diverged = (len(graph.heads(revids)) > 1)
178+ tree.lock_read()
179+ try:
180+ # "Unpack" the upstream versions and revision ids for the merge
181+ # source and target branch respectively.
182+ [(us_ver, us_revid), (ut_ver, ut_revid)] = _upstream_version_data(source, target)
183+
184+ # Did the upstream branches of the merge source/target diverge?
185+ graph = source.repository.get_graph(target.repository)
186+ upstreams_diverged = (len(graph.heads([us_revid, ut_revid])) > 1)
187+ finally:
188+ tree.unlock()
189 finally:
190 source.unlock()
191- target.unlock()
192
193 if not upstreams_diverged:
194 return (upstreams_diverged, t_upstream_reverted)
195
196- # "Unpack" the upstream versions and revision ids for the merge source and
197- # target branch respectively.
198- [(usource_v, usource_revid), (utarget_v, utarget_revid)] = upstream_vdata
199-
200 # Instantiate a `DistributionBranch` object for the merge target
201 # (packaging) branch.
202 db = DistributionBranch(tree.branch, tree.branch)
203 tempdir = tempfile.mkdtemp(dir=os.path.join(tree.basedir, '..'))
204
205 # Extract the merge target's upstream tree into a temporary directory.
206- db.extract_upstream_tree(utarget_revid, tempdir)
207+ db.extract_upstream_tree(ut_revid, tempdir)
208 tmp_target_upstream_tree = db.upstream_tree
209
210 # Merge upstream branch tips to obtain a shared upstream parent. This
211 # will add revision K (see graph above) to a temporary merge target
212 # upstream tree.
213+ tmp_target_upstream_tree.lock_write()
214 try:
215- tmp_target_upstream_tree.lock_write()
216-
217- if usource_v > utarget_v:
218+ if us_ver > ut_ver:
219 # The source upstream tree is more recent and the temporary
220 # target tree needs to be reshaped to match it.
221 tmp_target_upstream_tree.revert(
222- None, source.repository.revision_tree(usource_revid))
223+ None, source.repository.revision_tree(us_revid))
224 t_upstream_reverted = True
225
226 tmp_target_upstream_tree.set_parent_ids(
227- (utarget_revid, usource_revid))
228+ (ut_revid, us_revid))
229
230 tmp_target_upstream_tree.commit(
231 'Consolidated upstream tree for merging into target branch')
232@@ -201,13 +158,13 @@
233
234 # Merge shared upstream parent into the target merge branch. This creates
235 # revison L in the digram above.
236+ tree.lock_write()
237 try:
238- tree.lock_write()
239- try:
240- tree.merge_from_branch(tmp_target_upstream_tree.branch)
241- tree.commit('Merging source packaging branch in to target.')
242- except ConflictsInTree:
243- raise SourceUpstreamConflictsWithTargetPackaging()
244+ conflicts = tree.merge_from_branch(tmp_target_upstream_tree.branch)
245+ if conflicts > 0:
246+ raise errors.SharedUpstreamConflictsWithTargetPackaging()
247+ else:
248+ tree.commit('Merging shared upstream rev in to target branch.')
249 finally:
250 tree.unlock()
251
252
253=== modified file 'tests/test_merge_package.py'
254--- tests/test_merge_package.py 2009-08-19 09:44:38 +0000
255+++ tests/test_merge_package.py 2009-08-21 12:40:48 +0000
256@@ -19,16 +19,16 @@
257 # along with bzr-builddeb; if not, write to the Free Software
258 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
259
260-import os
261-import random
262 import string
263 import unittest
264
265+from debian_bundle.changelog import Version
266+
267 from bzrlib.errors import ConflictsInTree
268-from bzrlib.merge import WeaveMerger
269 from bzrlib.tests import TestCaseWithTransport
270
271 from bzrlib.plugins.builddeb import merge_package as MP
272+from bzrlib.plugins.builddeb.import_dsc import DistributionBranch
273
274 _Debian_changelog = '''\
275 ipsec-tools (%s) unstable; urgency=high
276@@ -52,29 +52,31 @@
277 def _prepend_log(text, path):
278 content = open(path).read()
279 fh = open(path, 'wb')
280- fh.write(text+content)
281- fh.close()
282+ try:
283+ fh.write(text+content)
284+ finally:
285+ fh.close()
286
287
288 class MergePackageTests(TestCaseWithTransport):
289
290 def test_latest_upstream_versions(self):
291 """Check correctness of upstream version computation."""
292- ubup_o, debp_n = self._setup_debian_upstrem_newer()
293+ ubup_o, debp_n, _ubuu, _debu = self._setup_debian_upstream_newer()
294 # Ubuntu upstream.
295 self.assertEquals(
296- MP._latest_version(ubup_o.branch).upstream_version, '1.1.2')
297+ MP._latest_version(ubup_o).upstream_version, '1.1.2')
298 # Debian upstream.
299 self.assertEquals(
300- MP._latest_version(debp_n.branch).upstream_version, '2.0')
301+ MP._latest_version(debp_n).upstream_version, '2.0')
302
303 ubuntup, debianp = self._setup_upstreams_not_diverged()
304 # Ubuntu upstream.
305 self.assertEquals(
306- MP._latest_version(ubuntup.branch).upstream_version, '1.4')
307+ MP._latest_version(ubuntup).upstream_version, '1.4')
308 # Debian upstream.
309 self.assertEquals(
310- MP._latest_version(debianp.branch).upstream_version, '2.2')
311+ MP._latest_version(debianp).upstream_version, '2.2')
312
313 def test_debian_upstream_newer(self):
314 """Diverging upstreams (debian newer) don't cause merge conflicts.
315@@ -88,23 +90,39 @@
316 The upstream conflict will be resolved by fix_ancestry_as_needed().
317 Please note that the debian ancestry is more recent.
318 """
319- ubup_o, debp_n = self._setup_debian_upstrem_newer()
320+ ubup, debp, ubuu, debu = self._setup_debian_upstream_newer()
321
322 # Attempt a plain merge first.
323- conflicts = ubup_o.merge_from_branch(
324- debp_n.branch, to_revision=self.revid_debp_n_C)
325+ conflicts = ubup.merge_from_branch(
326+ debp.branch, to_revision=self.revid_debp_n_C)
327
328 # There are two conflicts in the 'c' and the 'debian/changelog' files
329 # respectively.
330 self.assertEquals(conflicts, 2)
331- conflict_paths = sorted([c.path for c in ubup_o.conflicts()])
332+ conflict_paths = sorted([c.path for c in ubup.conflicts()])
333 self.assertEquals(conflict_paths, [u'c.moved', u'debian/changelog'])
334
335 # Undo the failed merge.
336- ubup_o.revert()
337+ ubup.revert()
338+
339+ # Check the versions present in the tree with the fixed ancestry.
340+ v3 = "1.1.2"
341+ v4 = "2.0"
342+ db1 = DistributionBranch(ubup.branch, ubup.branch)
343+ self.assertEqual(db1.has_upstream_version(v3), True)
344+ # This version is in the diverged debian upstream tree and will
345+ # hence not be present in the target ubuntu packaging branch.
346+ self.assertEqual(db1.has_upstream_version(v4), False)
347+
348+ # The ubuntu upstream branch tip.
349+ ubuu_tip = ubuu.branch.revision_history()[-1]
350+ # The debian upstream branch tip.
351+ debu_tip = debu.branch.revision_history()[-1]
352+ # The ubuntu packaging branch tip.
353+ ubup_tip_pre_fix = ubup.branch.revision_history()[-1]
354
355 # The first conflict is resolved by calling fix_ancestry_as_needed().
356- upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup_o, debp_n.branch)
357+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup, debp.branch)
358
359 # The ancestry did diverge and needed to be fixed.
360 self.assertEquals(upstreams_diverged, True)
361@@ -112,13 +130,35 @@
362 # source upstream branch since the latter was more recent.
363 self.assertEquals(t_upstream_reverted, True)
364
365+ # Check the versions present in the tree with the fixed ancestry.
366+ db2 = DistributionBranch(ubup.branch, ubup.branch)
367+ self.assertEqual(db2.has_upstream_version(v3), True)
368+ # The ancestry has been fixed and the missing debian upstream
369+ # version should now be present in the target ubuntu packaging
370+ # branch.
371+ self.assertEqual(db2.has_upstream_version(v4), True)
372+
373+ # Now let's take a look at the fixed ubuntu packaging branch.
374+ ubup_tip_post_fix = ubup.branch.revision_history()[-1]
375+ ubup_parents_post_fix = ubup.branch.repository.revision_tree(ubup_tip_post_fix).get_parent_ids()
376+
377+ # The tip of the fixed ubuntu packaging branch has 2 parents.
378+ self.assertEquals(len(ubup_parents_post_fix), 2)
379+
380+ # The left parent is the packaging branch tip before fixing.
381+ self.assertEquals(ubup_parents_post_fix[0], ubup_tip_pre_fix)
382+
383+ # The right parent is derived from a merge
384+ ubup_parents_sharedupstream = ubup.branch.repository.revision_tree(ubup_parents_post_fix[1]).get_parent_ids()
385+ self.assertEquals(ubup_parents_sharedupstream, [ubuu_tip, debu_tip])
386+
387 # Try merging again.
388- conflicts = ubup_o.merge_from_branch(
389- debp_n.branch, to_revision=self.revid_debp_n_C)
390+ conflicts = ubup.merge_from_branch(
391+ debp.branch, to_revision=self.revid_debp_n_C)
392
393 # And, voila, only the packaging branch conflict remains.
394 self.assertEquals(conflicts, 1)
395- conflict_paths = sorted([c.path for c in ubup_o.conflicts()])
396+ conflict_paths = sorted([c.path for c in ubup.conflicts()])
397 self.assertEquals(conflict_paths, [u'debian/changelog'])
398
399 def test_debian_upstream_older(self):
400@@ -208,9 +248,9 @@
401 conflict_paths = sorted([c.path for c in ubuntup.conflicts()])
402 self.assertEquals(conflict_paths, [u'debian/changelog'])
403
404- def _setup_debian_upstrem_newer(self):
405+ def _setup_debian_upstream_newer(self):
406 """
407- Set up the following test configuration (debian upstrem newer).
408+ Set up the following test configuration (debian upstream newer).
409
410 debian-upstream ,------------------H
411 A-----------B \
412@@ -280,11 +320,11 @@
413 self._setup_branch(name, vdata, ubup_o, 'u')
414
415 # Return the ubuntu and the debian packaging branches.
416- return (ubup_o, debp_n)
417+ return (ubup_o, debp_n, ubuu_o, debu_n)
418
419 def _setup_debian_upstream_older(self):
420 """
421- Set up the following test configuration (debian upstrem older).
422+ Set up the following test configuration (debian upstream older).
423
424 debian-upstream ,----H-------------.
425 A-----------B \
426@@ -434,6 +474,9 @@
427 if tree is None:
428 tree = self.make_branch_and_tree(name)
429
430+ tree.lock_write()
431+ self.addCleanup(tree.unlock)
432+
433 def revid_name(vid):
434 return 'revid_%s_%s' % (name.replace('-', '_'), vid)
435
436@@ -464,7 +507,7 @@
437 cle = changelog(version, vid)
438 p = '%s/work/%s/debian/changelog' % (self.test_base_dir, name)
439 _prepend_log(cle, p)
440- revid = tree.commit('%s: %s' % (vid, msg), rev_id='%s-%s' % (name, vid))
441+ revid = tree.commit('%s: %s' % (vid, msg))
442 setattr(self, revid_name(vid), revid)
443 tree.branch.tags.set_tag(version, revid)
444
445@@ -474,8 +517,7 @@
446 for version, paths, utree, urevid in vdata:
447 msg = ''
448 if utree is not None:
449- tree.merge_from_branch(
450- utree.branch, to_revision=urevid, merge_type=WeaveMerger)
451+ tree.merge_from_branch(utree.branch, to_revision=urevid)
452 utree.branch.tags.merge_to(tree.branch.tags)
453 if urevid is not None:
454 msg += 'Merged tree %s|%s. ' % (tree_nick(utree), urevid)
455@@ -491,7 +533,5 @@
456
457
458 if __name__ == '__main__':
459- # unittest.main()
460 suite = unittest.TestLoader().loadTestsFromTestCase(MergePackageTests)
461 unittest.TextTestRunner(verbosity=2).run(suite)
462-
1=== modified file '__init__.py'
2--- __init__.py 2009-07-26 15:51:02 +0000
3+++ __init__.py 2009-08-19 11:39:16 +0000
4@@ -39,7 +39,8 @@
5 "merge_upstream": ["mu"],
6 "import_dsc": [],
7 "bd_do": [],
8- "mark_uploaded": []
9+ "mark_uploaded": [],
10+ "merge_package": []
11 }
12
13 for command, aliases in commands.iteritems():
14
15=== modified file 'cmds.py'
16--- cmds.py 2009-07-26 18:21:49 +0000
17+++ cmds.py 2009-08-19 09:01:30 +0000
18@@ -69,6 +69,7 @@
19 DscCache,
20 DscComp,
21 )
22+from bzrlib.plugins.builddeb.merge_package import fix_ancestry_as_needed
23 from bzrlib.plugins.builddeb.source_distiller import (
24 FullSourceDistiller,
25 MergeModeDistiller,
26@@ -719,9 +720,9 @@
27 "the previous upstream version, %s, in the "
28 "branch: %s" % (last_version,
29 db.upstream_tag_name(last_version)))
30- upstream_tip = db._revid_of_upstream_version_from_branch(
31+ upstream_tip = db.revid_of_upstream_version_from_branch(
32 last_version)
33- db._extract_upstream_tree(upstream_tip, tempdir)
34+ db.extract_upstream_tree(upstream_tip, tempdir)
35 else:
36 db._create_empty_upstream_tree(tempdir)
37 self.import_many(db, files_list, orig_target)
38@@ -870,6 +871,39 @@
39 t.unlock()
40
41
42+class cmd_merge_package(Command):
43+ """Merges source packaging branch into target packaging branch.
44+
45+ This will first check whether the upstream branches have diverged.
46+
47+ If that's the case an attempt will be made to fix the upstream ancestry
48+ so that the user only needs to deal wth packaging branch merge issues.
49+
50+ In the opposite case a normal merge will be performed.
51+ """
52+ takes_args = ['source']
53+
54+ def run(self, source):
55+ source_branch = target_branch = None
56+ # Get the target branch.
57+ try:
58+ tree = WorkingTree.open_containing('.')[0]
59+ target_branch = tree.branch
60+ except NotBranchError:
61+ raise BzrCommandError(
62+ "There is no tree to merge the source branch in to")
63+ # Get the source branch.
64+ try:
65+ source_branch = Branch.open(source)
66+ except NotBranchError:
67+ raise BzrCommandError("Invalid source branch URL?")
68+
69+ fix_ancestry_as_needed(tree, source_branch)
70+
71+ # Merge source packaging branch in to the target packaging branch.
72+ tree.merge_from_branch(source_branch)
73+
74+
75 class cmd_test_builddeb(Command):
76 """Run the builddeb test suite"""
77
78@@ -880,4 +914,3 @@
79 passed = selftest(test_suite_factory=test_suite)
80 # invert for shell exit code rules
81 return not passed
82-
83
84=== modified file 'errors.py'
85--- errors.py 2009-04-16 09:30:49 +0000
86+++ errors.py 2009-08-19 14:08:39 +0000
87@@ -175,3 +175,22 @@
88
89 def __init__(self, error):
90 BzrError.__init__(self, error=error)
91+
92+
93+class SharedUpstreamConflictsWithTargetPackaging(BzrError):
94+ _fmt = ('''\
95+ The "merge-package" command has detected diverged upstream
96+ branches for the merge source and target. A shared upstream
97+ revision was constructed to remedy the problem.
98+
99+ However, merging the shared upstream revision into the merge
100+ target resulted in conflicts.
101+
102+ Please proceed as follows:
103+
104+ 1 - Resolve the current merge conflicts in the merge target
105+ directory and commit the changes.
106+ 2 - Perform a plain "bzr merge <source-packaging-branch>"
107+ command, resolve any ensuing packaging branch conflicts
108+ and commit once satisfied with the changes.
109+ ''')
110
111=== modified file 'import_dsc.py'
112--- import_dsc.py 2009-07-26 16:44:17 +0000
113+++ import_dsc.py 2009-08-19 11:43:09 +0000
114@@ -1570,11 +1570,12 @@
115 finally:
116 shutil.rmtree(tempdir)
117
118- def _extract_upstream_tree(self, upstream_tip, basedir):
119+ def extract_upstream_tree(self, upstream_tip, basedir):
120 # Extract that to a tempdir so we can get a working
121 # tree for it.
122 # TODO: should stack rather than trying to use the repository,
123 # as that will be more efficient.
124+ # TODO: remove the _extract_upstream_tree alias below.
125 to_location = os.path.join(basedir, "upstream")
126 dir_to = self.branch.bzrdir.sprout(to_location,
127 revision_id=upstream_tip,
128@@ -1582,6 +1583,8 @@
129 self.upstream_tree = dir_to.open_workingtree()
130 self.upstream_branch = self.upstream_tree.branch
131
132+ _extract_upstream_tree = extract_upstream_tree
133+
134 def _create_empty_upstream_tree(self, basedir):
135 to_location = os.path.join(basedir, "upstream")
136 to_transport = get_transport(to_location)
137@@ -1615,7 +1618,11 @@
138 shutil.rmtree(tempdir)
139 raise
140
141- def _revid_of_upstream_version_from_branch(self, version):
142+ """The private method below will go away eventually."""
143+ return self.revid_of_upstream_version_from_branch(version)
144+
145+ def revid_of_upstream_version_from_branch(self, version):
146+ # TODO: remove the _revid_of_upstream_version_from_branch alias below.
147 assert isinstance(version, str)
148 tag_name = self.upstream_tag_name(version)
149 if self._has_version(self.branch, tag_name):
150@@ -1629,6 +1636,8 @@
151 tag_name = self.upstream_tag_name(version)
152 return self.branch.tags.lookup_tag(tag_name)
153
154+ _revid_of_upstream_version_from_branch = revid_of_upstream_version_from_branch
155+
156 def merge_upstream(self, tarball_filename, version, previous_version,
157 upstream_branch=None, upstream_revision=None, merge_type=None):
158 assert self.upstream_branch is None, \
159@@ -1639,14 +1648,14 @@
160 if previous_version is not None:
161 if self.has_upstream_version_in_packaging_branch(
162 previous_version.upstream_version):
163- upstream_tip = self._revid_of_upstream_version_from_branch(
164+ upstream_tip = self.revid_of_upstream_version_from_branch(
165 previous_version.upstream_version)
166- self._extract_upstream_tree(upstream_tip, tempdir)
167+ self.extract_upstream_tree(upstream_tip, tempdir)
168 elif (upstream_branch is not None and
169 previous_upstream_revision is not None):
170 upstream_tip = RevisionSpec.from_string(previous_upstream_revision).as_revision_id(upstream_branch)
171 assert isinstance(upstream_tip, str)
172- self._extract_upstream_tree(upstream_tip, tempdir)
173+ self.extract_upstream_tree(upstream_tip, tempdir)
174 else:
175 raise BzrCommandError("Unable to find the tag for the "
176 "previous upstream version, %s, in the branch: "
177
178=== added file 'merge_package.py'
179--- merge_package.py 1970-01-01 00:00:00 +0000
180+++ merge_package.py 2009-08-19 16:31:25 +0000
181@@ -0,0 +1,171 @@
182+# merge_package.py -- The plugin for bzr
183+# Copyright (C) 2009 Canonical Ltd.
184+#
185+# :Author: Muharem Hrnjadovic <muharem@ubuntu.com>
186+#
187+# This file is part of bzr-builddeb.
188+#
189+# bzr-builddeb is free software; you can redistribute it and/or modify
190+# it under the terms of the GNU General Public License as published by
191+# the Free Software Foundation; either version 2 of the License, or
192+# (at your option) any later version.
193+#
194+# bzr-builddeb is distributed in the hope that it will be useful,
195+# but WITHOUT ANY WARRANTY; without even the implied warranty of
196+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
197+# GNU General Public License for more details.
198+#
199+# You should have received a copy of the GNU General Public License
200+# along with bzr-builddeb; if not, write to the Free Software
201+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
202+#
203+
204+import os
205+import re
206+import sys
207+import tempfile
208+
209+from debian_bundle.changelog import Version
210+
211+from bzrlib import errors
212+
213+from bzrlib.plugins.builddeb.import_dsc import DistributionBranch
214+from bzrlib.plugins.builddeb.util import find_changelog
215+
216+
217+def _latest_version(branch):
218+ """Version of the most recent source package upload in the given `branch`.
219+
220+ :param branch: A Branch object containing the source upload of interest.
221+ """
222+ changelog, _ignore = find_changelog(branch.basis_tree(), False)
223+
224+ return changelog.version
225+
226+
227+def _upstream_version_data(source, target):
228+ """Most recent upstream versions/revision IDs of the merge source/target.
229+
230+ Please note: both packaging branches must have been read-locked
231+ beforehand.
232+
233+ :param source: The merge source branch.
234+ :param target: The merge target branch.
235+ """
236+ results = list()
237+ for branch in (source, target):
238+ db = DistributionBranch(branch, branch)
239+ uver = _latest_version(branch).upstream_version
240+ results.append((uver, db.revid_of_upstream_version_from_branch(uver)))
241+
242+ return results
243+
244+
245+def fix_ancestry_as_needed(tree, source):
246+ """Manipulate the merge target's ancestry to avoid upstream conflicts.
247+
248+ Merging J->I given the following ancestry tree is likely to result in
249+ upstream merge conflicts:
250+
251+ debian-upstream ,------------------H
252+ A-----------B \
253+ ubuntu-upstream \ \`-------G \
254+ \ \ \ \
255+ debian-packaging \ ,---------D--------\-----------J
256+ C \ \
257+ ubuntu-packaging `----E------F--------I
258+
259+ Here there was a new upstream release (G) that Ubuntu packaged (I), and
260+ then another one that Debian packaged, skipping G, at H and J.
261+
262+ Now, the way to solve this is to introduce the missing link.
263+
264+ debian-upstream ,------------------H------.
265+ A-----------B \ \
266+ ubuntu-upstream \ \`-------G-----------\------K
267+ \ \ \ \
268+ debian-packaging \ ,---------D--------\-----------J
269+ C \ \
270+ ubuntu-packaging `----E------F--------I
271+
272+ at K, which isn't a real merge, as we just use the tree from H, but add
273+ G as a parent and then we merge that in to Ubuntu.
274+
275+ debian-upstream ,------------------H------.
276+ A-----------B \ \
277+ ubuntu-upstream \ \`-------G-----------\------K
278+ \ \ \ \ \
279+ debian-packaging \ ,---------D--------\-----------J \
280+ C \ \ \
281+ ubuntu-packaging `----E------F--------I------------------L
282+
283+ At this point we can merge J->L to merge the Debian and Ubuntu changes.
284+
285+ :param tree: The `WorkingTree` of the merge target branch.
286+ :param source: The merge source (packaging) branch.
287+ """
288+ upstreams_diverged = False
289+ t_upstream_reverted = False
290+ target = tree.branch
291+
292+ source.lock_read()
293+ try:
294+ tree.lock_read()
295+ try:
296+ # "Unpack" the upstream versions and revision ids for the merge
297+ # source and target branch respectively.
298+ [(us_ver, us_revid), (ut_ver, ut_revid)] = _upstream_version_data(source, target)
299+
300+ # Did the upstream branches of the merge source/target diverge?
301+ graph = source.repository.get_graph(target.repository)
302+ upstreams_diverged = (len(graph.heads([us_revid, ut_revid])) > 1)
303+ finally:
304+ tree.unlock()
305+ finally:
306+ source.unlock()
307+
308+ if not upstreams_diverged:
309+ return (upstreams_diverged, t_upstream_reverted)
310+
311+ # Instantiate a `DistributionBranch` object for the merge target
312+ # (packaging) branch.
313+ db = DistributionBranch(tree.branch, tree.branch)
314+ tempdir = tempfile.mkdtemp(dir=os.path.join(tree.basedir, '..'))
315+
316+ # Extract the merge target's upstream tree into a temporary directory.
317+ db.extract_upstream_tree(ut_revid, tempdir)
318+ tmp_target_upstream_tree = db.upstream_tree
319+
320+ # Merge upstream branch tips to obtain a shared upstream parent. This
321+ # will add revision K (see graph above) to a temporary merge target
322+ # upstream tree.
323+ tmp_target_upstream_tree.lock_write()
324+ try:
325+ if us_ver > ut_ver:
326+ # The source upstream tree is more recent and the temporary
327+ # target tree needs to be reshaped to match it.
328+ tmp_target_upstream_tree.revert(
329+ None, source.repository.revision_tree(us_revid))
330+ t_upstream_reverted = True
331+
332+ tmp_target_upstream_tree.set_parent_ids(
333+ (ut_revid, us_revid))
334+
335+ tmp_target_upstream_tree.commit(
336+ 'Consolidated upstream tree for merging into target branch')
337+ finally:
338+ tmp_target_upstream_tree.unlock()
339+
340+ # Merge shared upstream parent into the target merge branch. This creates
341+ # revison L in the digram above.
342+ tree.lock_write()
343+ try:
344+ conflicts = tree.merge_from_branch(tmp_target_upstream_tree.branch)
345+ if conflicts > 0:
346+ raise errors.SharedUpstreamConflictsWithTargetPackaging()
347+ else:
348+ tree.commit('Merging shared upstream rev in to target branch.')
349+ finally:
350+ tree.unlock()
351+
352+ return (upstreams_diverged, t_upstream_reverted)
353
354=== modified file 'tests/__init__.py'
355--- tests/__init__.py 2009-07-04 20:45:01 +0000
356+++ tests/__init__.py 2009-08-19 09:55:06 +0000
357@@ -118,6 +118,7 @@
358 'test_config',
359 'test_hooks',
360 'test_import_dsc',
361+ 'test_merge_package',
362 'test_merge_upstream',
363 'test_repack_tarball_extra',
364 'test_revspec',
365
366=== added file 'tests/test_merge_package.py'
367--- tests/test_merge_package.py 1970-01-01 00:00:00 +0000
368+++ tests/test_merge_package.py 2009-08-21 12:40:48 +0000
369@@ -0,0 +1,537 @@
370+#!/usr/bin/env python
371+# -*- coding: iso-8859-15 -*-
372+# test_merge_package.py -- Merge packaging branches, fix ancestry as needed.
373+# Copyright (C) 2008 Canonical Ltd.
374+#
375+# This file is part of bzr-builddeb.
376+#
377+# bzr-builddeb is free software; you can redistribute it and/or modify
378+# it under the terms of the GNU General Public License as published by
379+# the Free Software Foundation; either version 2 of the License, or
380+# (at your option) any later version.
381+#
382+# bzr-builddeb is distributed in the hope that it will be useful,
383+# but WITHOUT ANY WARRANTY; without even the implied warranty of
384+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
385+# GNU General Public License for more details.
386+#
387+# You should have received a copy of the GNU General Public License
388+# along with bzr-builddeb; if not, write to the Free Software
389+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
390+
391+import string
392+import unittest
393+
394+from debian_bundle.changelog import Version
395+
396+from bzrlib.errors import ConflictsInTree
397+from bzrlib.tests import TestCaseWithTransport
398+
399+from bzrlib.plugins.builddeb import merge_package as MP
400+from bzrlib.plugins.builddeb.import_dsc import DistributionBranch
401+
402+_Debian_changelog = '''\
403+ipsec-tools (%s) unstable; urgency=high
404+
405+ * debian packaging -- %s
406+
407+ -- Nico Golde <nion@debian.org> Tue, %02d May 2009 13:26:14 +0200
408+
409+'''
410+
411+_Ubuntu_changelog = '''\
412+ipsec-tools (%s) karmic; urgency=low
413+
414+ * ubuntu packaging -- %s
415+
416+ -- Jamie Strandboge <jamie@ubuntu.com> Fri, %02d Jul 2009 13:24:17 -0500
417+
418+'''
419+
420+
421+def _prepend_log(text, path):
422+ content = open(path).read()
423+ fh = open(path, 'wb')
424+ try:
425+ fh.write(text+content)
426+ finally:
427+ fh.close()
428+
429+
430+class MergePackageTests(TestCaseWithTransport):
431+
432+ def test_latest_upstream_versions(self):
433+ """Check correctness of upstream version computation."""
434+ ubup_o, debp_n, _ubuu, _debu = self._setup_debian_upstream_newer()
435+ # Ubuntu upstream.
436+ self.assertEquals(
437+ MP._latest_version(ubup_o).upstream_version, '1.1.2')
438+ # Debian upstream.
439+ self.assertEquals(
440+ MP._latest_version(debp_n).upstream_version, '2.0')
441+
442+ ubuntup, debianp = self._setup_upstreams_not_diverged()
443+ # Ubuntu upstream.
444+ self.assertEquals(
445+ MP._latest_version(ubuntup).upstream_version, '1.4')
446+ # Debian upstream.
447+ self.assertEquals(
448+ MP._latest_version(debianp).upstream_version, '2.2')
449+
450+ def test_debian_upstream_newer(self):
451+ """Diverging upstreams (debian newer) don't cause merge conflicts.
452+
453+ The debian and ubuntu upstream branches will differ with regard to
454+ the content of the file 'c'.
455+
456+ Furthermore the respective packaging branches will have a text
457+ conflict in 'debian/changelog'.
458+
459+ The upstream conflict will be resolved by fix_ancestry_as_needed().
460+ Please note that the debian ancestry is more recent.
461+ """
462+ ubup, debp, ubuu, debu = self._setup_debian_upstream_newer()
463+
464+ # Attempt a plain merge first.
465+ conflicts = ubup.merge_from_branch(
466+ debp.branch, to_revision=self.revid_debp_n_C)
467+
468+ # There are two conflicts in the 'c' and the 'debian/changelog' files
469+ # respectively.
470+ self.assertEquals(conflicts, 2)
471+ conflict_paths = sorted([c.path for c in ubup.conflicts()])
472+ self.assertEquals(conflict_paths, [u'c.moved', u'debian/changelog'])
473+
474+ # Undo the failed merge.
475+ ubup.revert()
476+
477+ # Check the versions present in the tree with the fixed ancestry.
478+ v3 = "1.1.2"
479+ v4 = "2.0"
480+ db1 = DistributionBranch(ubup.branch, ubup.branch)
481+ self.assertEqual(db1.has_upstream_version(v3), True)
482+ # This version is in the diverged debian upstream tree and will
483+ # hence not be present in the target ubuntu packaging branch.
484+ self.assertEqual(db1.has_upstream_version(v4), False)
485+
486+ # The ubuntu upstream branch tip.
487+ ubuu_tip = ubuu.branch.revision_history()[-1]
488+ # The debian upstream branch tip.
489+ debu_tip = debu.branch.revision_history()[-1]
490+ # The ubuntu packaging branch tip.
491+ ubup_tip_pre_fix = ubup.branch.revision_history()[-1]
492+
493+ # The first conflict is resolved by calling fix_ancestry_as_needed().
494+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup, debp.branch)
495+
496+ # The ancestry did diverge and needed to be fixed.
497+ self.assertEquals(upstreams_diverged, True)
498+ # The (temporary) target upstream branch had to be reverted to the
499+ # source upstream branch since the latter was more recent.
500+ self.assertEquals(t_upstream_reverted, True)
501+
502+ # Check the versions present in the tree with the fixed ancestry.
503+ db2 = DistributionBranch(ubup.branch, ubup.branch)
504+ self.assertEqual(db2.has_upstream_version(v3), True)
505+ # The ancestry has been fixed and the missing debian upstream
506+ # version should now be present in the target ubuntu packaging
507+ # branch.
508+ self.assertEqual(db2.has_upstream_version(v4), True)
509+
510+ # Now let's take a look at the fixed ubuntu packaging branch.
511+ ubup_tip_post_fix = ubup.branch.revision_history()[-1]
512+ ubup_parents_post_fix = ubup.branch.repository.revision_tree(ubup_tip_post_fix).get_parent_ids()
513+
514+ # The tip of the fixed ubuntu packaging branch has 2 parents.
515+ self.assertEquals(len(ubup_parents_post_fix), 2)
516+
517+ # The left parent is the packaging branch tip before fixing.
518+ self.assertEquals(ubup_parents_post_fix[0], ubup_tip_pre_fix)
519+
520+ # The right parent is derived from a merge
521+ ubup_parents_sharedupstream = ubup.branch.repository.revision_tree(ubup_parents_post_fix[1]).get_parent_ids()
522+ self.assertEquals(ubup_parents_sharedupstream, [ubuu_tip, debu_tip])
523+
524+ # Try merging again.
525+ conflicts = ubup.merge_from_branch(
526+ debp.branch, to_revision=self.revid_debp_n_C)
527+
528+ # And, voila, only the packaging branch conflict remains.
529+ self.assertEquals(conflicts, 1)
530+ conflict_paths = sorted([c.path for c in ubup.conflicts()])
531+ self.assertEquals(conflict_paths, [u'debian/changelog'])
532+
533+ def test_debian_upstream_older(self):
534+ """Diverging upstreams (debian older) don't cause merge conflicts.
535+
536+ The debian and ubuntu upstream branches will differ with regard to
537+ the content of the file 'c'.
538+
539+ Furthermore the respective packaging branches will have a text
540+ conflict in 'debian/changelog'.
541+
542+ The upstream conflict will be resolved by fix_ancestry_as_needed().
543+ Please note that the debian ancestry is older in this case.
544+ """
545+ ubup_n, debp_o = self._setup_debian_upstream_older()
546+
547+ # Attempt a plain merge first.
548+ conflicts = ubup_n.merge_from_branch(
549+ debp_o.branch, to_revision=self.revid_debp_o_C)
550+
551+ # There are two conflicts in the 'c' and the 'debian/changelog' files
552+ # respectively.
553+ self.assertEquals(conflicts, 2)
554+ conflict_paths = sorted([c.path for c in ubup_n.conflicts()])
555+ self.assertEquals(conflict_paths, [u'c.moved', u'debian/changelog'])
556+
557+ # Undo the failed merge.
558+ ubup_n.revert()
559+
560+ # The first conflict is resolved by calling fix_ancestry_as_needed().
561+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup_n, debp_o.branch)
562+
563+ # The ancestry did diverge and needed to be fixed.
564+ self.assertEquals(upstreams_diverged, True)
565+ # The target upstream branch was more recent in this case and hence
566+ # was not reverted to the source upstream branch.
567+ self.assertEquals(t_upstream_reverted, False)
568+
569+ # Try merging again.
570+ conflicts = ubup_n.merge_from_branch(
571+ debp_o.branch, to_revision=self.revid_debp_o_C)
572+
573+ # And, voila, only the packaging branch conflict remains.
574+ self.assertEquals(conflicts, 1)
575+ conflict_paths = sorted([c.path for c in ubup_n.conflicts()])
576+ self.assertEquals(conflict_paths, [u'debian/changelog'])
577+
578+ def test_upstreams_not_diverged(self):
579+ """Non-diverging upstreams result in a normal merge.
580+
581+ The debian and ubuntu upstream branches will not have diverged
582+ this time.
583+
584+ The packaging branches will have a conflict in 'debian/changelog'.
585+ fix_ancestry_as_needed() will return as soon as establishing that
586+ the upstreams have not diverged.
587+ """
588+ ubuntup, debianp = self._setup_upstreams_not_diverged()
589+
590+ # Attempt a plain merge first.
591+ conflicts = ubuntup.merge_from_branch(
592+ debianp.branch, to_revision=self.revid_debianp_C)
593+
594+ # There is only a conflict in the 'debian/changelog' file.
595+ self.assertEquals(conflicts, 1)
596+ conflict_paths = sorted([c.path for c in ubuntup.conflicts()])
597+ self.assertEquals(conflict_paths, [u'debian/changelog'])
598+
599+ # Undo the failed merge.
600+ ubuntup.revert()
601+
602+ # The conflict is *not* resolved by calling fix_ancestry_as_needed().
603+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubuntup, debianp.branch)
604+
605+ # The ancestry did *not* diverge.
606+ self.assertEquals(upstreams_diverged, False)
607+ # The upstreams have not diverged, hence no need to fix/revert
608+ # either of them.
609+ self.assertEquals(t_upstream_reverted, False)
610+
611+ # Try merging again.
612+ conflicts = ubuntup.merge_from_branch(
613+ debianp.branch, to_revision=self.revid_debianp_C)
614+
615+ # The packaging branch conflict we saw above is still there.
616+ self.assertEquals(conflicts, 1)
617+ conflict_paths = sorted([c.path for c in ubuntup.conflicts()])
618+ self.assertEquals(conflict_paths, [u'debian/changelog'])
619+
620+ def _setup_debian_upstream_newer(self):
621+ """
622+ Set up the following test configuration (debian upstream newer).
623+
624+ debian-upstream ,------------------H
625+ A-----------B \
626+ ubuntu-upstream \ \`-------G \
627+ \ \ \ \
628+ debian-packaging \ ,---------D--------\-----------J
629+ C \
630+ ubuntu-packaging `----E---------------I
631+
632+ where:
633+ - A = 1.0
634+ - B = 1.1
635+ - H = 2.0
636+
637+ - G = 1.1.2
638+
639+ - C = 1.0-1
640+ - D = 1.1-1
641+ - J = 2.0-1
642+
643+ - E = 1.0-1ubuntu1
644+ - I = 1.1.2-0ubuntu1
645+
646+ Please note that the debian and ubuntu branches will have a conflict
647+ with respect to the file 'c'.
648+ """
649+ # Set up the debian upstream branch.
650+ name = 'debu-n'
651+ vdata = [
652+ ('upstream-1.0', ('a',), None, None),
653+ ('upstream-1.1', ('b',), None, None),
654+ ('upstream-2.0', ('c',), None, None),
655+ ]
656+ debu_n = self._setup_branch(name, vdata)
657+
658+ # Set up the debian packaging branch.
659+ name = 'debp-n'
660+ debp_n = self.make_branch_and_tree(name)
661+ debp_n.pull(debu_n.branch, stop_revision=self.revid_debu_n_A)
662+
663+ vdata = [
664+ ('1.0-1', ('debian/', 'debian/changelog'), None, None),
665+ ('1.1-1', ('o',), debu_n, self.revid_debu_n_B),
666+ ('2.0-1', ('p',), debu_n, self.revid_debu_n_C),
667+ ]
668+ self._setup_branch(name, vdata, debp_n, 'd')
669+
670+ # Set up the ubuntu upstream branch.
671+ name = 'ubuu-o'
672+ ubuu_o = debu_n.bzrdir.sprout(
673+ name, revision_id=self.revid_debu_n_B).open_workingtree()
674+
675+ vdata = [
676+ ('upstream-1.1.2', ('c',), None, None),
677+ ]
678+ self._setup_branch(name, vdata, ubuu_o)
679+
680+ # Set up the ubuntu packaging branch.
681+ name = 'ubup-o'
682+ ubup_o = debu_n.bzrdir.sprout(
683+ name, revision_id=self.revid_debu_n_A).open_workingtree()
684+
685+ vdata = [
686+ ('1.0-1ubuntu1', (), debp_n, self.revid_debp_n_A),
687+ ('1.1.2-0ubuntu1', (), ubuu_o, self.revid_ubuu_o_A),
688+ ]
689+ self._setup_branch(name, vdata, ubup_o, 'u')
690+
691+ # Return the ubuntu and the debian packaging branches.
692+ return (ubup_o, debp_n, ubuu_o, debu_n)
693+
694+ def _setup_debian_upstream_older(self):
695+ """
696+ Set up the following test configuration (debian upstream older).
697+
698+ debian-upstream ,----H-------------.
699+ A-----------B \
700+ ubuntu-upstream \ \`-----------G \
701+ \ \ \ \
702+ debian-packaging \ ,---------D------------\-------J
703+ C \
704+ ubuntu-packaging `----E-------------------I
705+
706+ where:
707+ - A = 1.0
708+ - B = 1.1
709+ - H = 1.1.3
710+
711+ - G = 2.1
712+
713+ - C = 1.0-1
714+ - D = 1.1-1
715+ - J = 1.1.3-1
716+
717+ - E = 1.0-1ubuntu1
718+ - I = 2.1-0ubuntu1
719+
720+ Please note that the debian and ubuntu branches will have a conflict
721+ with respect to the file 'c'.
722+ """
723+ # Set up the debian upstream branch.
724+ name = 'debu-o'
725+ vdata = [
726+ ('upstream-1.0', ('a',), None, None),
727+ ('upstream-1.1', ('b',), None, None),
728+ ('upstream-1.1.3', ('c',), None, None),
729+ ]
730+ debu_o = self._setup_branch(name, vdata)
731+
732+ # Set up the debian packaging branch.
733+ name = 'debp-o'
734+ debp_o = self.make_branch_and_tree(name)
735+ debp_o.pull(debu_o.branch, stop_revision=self.revid_debu_o_A)
736+
737+ vdata = [
738+ ('1.0-1', ('debian/', 'debian/changelog'), None, None),
739+ ('1.1-1', ('o',), debu_o, self.revid_debu_o_B),
740+ ('1.1.3-1', ('p',), debu_o, self.revid_debu_o_C),
741+ ]
742+ self._setup_branch(name, vdata, debp_o, 'd')
743+
744+ # Set up the ubuntu upstream branch.
745+ name = 'ubuu-n'
746+ ubuu_n = debu_o.bzrdir.sprout(
747+ name, revision_id=self.revid_debu_o_B).open_workingtree()
748+
749+ vdata = [
750+ ('upstream-2.1', ('c',), None, None),
751+ ]
752+ self._setup_branch(name, vdata, ubuu_n)
753+
754+ # Set up the ubuntu packaging branch.
755+ name = 'ubup-n'
756+ ubup_n = debu_o.bzrdir.sprout(
757+ name, revision_id=self.revid_debu_o_A).open_workingtree()
758+
759+ vdata = [
760+ ('1.0-1ubuntu1', (), debp_o, self.revid_debp_o_A),
761+ ('2.1-0ubuntu1', (), ubuu_n, self.revid_ubuu_n_A),
762+ ]
763+ self._setup_branch(name, vdata, ubup_n, 'u')
764+
765+ # Return the ubuntu and the debian packaging branches.
766+ return (ubup_n, debp_o)
767+
768+ def _setup_upstreams_not_diverged(self):
769+ """
770+ Set up a test configuration where the usptreams have not diverged.
771+
772+ debian-upstream .-----G
773+ A-----------B-----H \
774+ ubuntu-upstream \ \ \ \
775+ \ \ \ \
776+ debian-packaging \ ,---------D-----\-------J
777+ C \
778+ ubuntu-packaging `----E------------I
779+
780+ where:
781+ - A = 1.0
782+ - B = 1.1
783+ - H = 1.4
784+
785+ - G = 2.2
786+
787+ - C = 1.0-1
788+ - D = 1.1-1
789+ - J = 2.2-1
790+
791+ - E = 1.0-1ubuntu1
792+ - I = 1.4-0ubuntu1
793+
794+ Please note that there's only one shared upstream branch in this case.
795+ """
796+ # Set up the upstream branch.
797+ name = 'upstream'
798+ vdata = [
799+ ('upstream-1.0', ('a',), None, None),
800+ ('upstream-1.1', ('b',), None, None),
801+ ('upstream-1.4', ('c',), None, None),
802+ ]
803+ upstream = self._setup_branch(name, vdata)
804+
805+ # Set up the debian upstream branch.
806+ name = 'dupstream'
807+ dupstream = upstream.bzrdir.sprout(name).open_workingtree()
808+ vdata = [
809+ ('upstream-2.2', (), None, None),
810+ ]
811+ dupstream = self._setup_branch(name, vdata, dupstream)
812+
813+ # Set up the debian packaging branch.
814+ name = 'debianp'
815+ debianp = self.make_branch_and_tree(name)
816+ debianp.pull(dupstream.branch, stop_revision=self.revid_upstream_A)
817+
818+ vdata = [
819+ ('1.0-1', ('debian/', 'debian/changelog'), None, None),
820+ ('1.1-1', ('o',), dupstream, self.revid_upstream_B),
821+ ('2.2-1', ('p',), dupstream, self.revid_dupstream_A),
822+ ]
823+ self._setup_branch(name, vdata, debianp, 'd')
824+
825+ # Set up the ubuntu packaging branch.
826+ name = 'ubuntup'
827+ ubuntup = upstream.bzrdir.sprout(
828+ name, revision_id=self.revid_upstream_A).open_workingtree()
829+
830+ vdata = [
831+ ('1.0-1ubuntu1', (), debianp, self.revid_debianp_A),
832+ ('1.4-0ubuntu1', (), upstream, self.revid_upstream_C),
833+ ]
834+ self._setup_branch(name, vdata, ubuntup, 'u')
835+
836+ # Return the ubuntu and the debian packaging branches.
837+ return (ubuntup, debianp)
838+
839+ def _setup_branch(self, name, vdata, tree=None, log_format=None):
840+ vids = list(string.ascii_uppercase)
841+ days = range(len(string.ascii_uppercase))
842+
843+ if tree is None:
844+ tree = self.make_branch_and_tree(name)
845+
846+ tree.lock_write()
847+ self.addCleanup(tree.unlock)
848+
849+ def revid_name(vid):
850+ return 'revid_%s_%s' % (name.replace('-', '_'), vid)
851+
852+ def add_paths(paths):
853+ qpaths = ['%s/%s' % (name, path) for path in paths]
854+ self.build_tree(qpaths)
855+ tree.add(paths)
856+
857+ def changelog(vdata, vid):
858+ result = ''
859+ day = days.pop(0)
860+ if isinstance(vdata, tuple):
861+ uver, dver = vdata[:2]
862+ ucle = _Ubuntu_changelog % (uver, vid, day)
863+ dcle = _Debian_changelog % (dver, vid, day)
864+ result = ucle + dcle
865+ else:
866+ if log_format == 'u':
867+ result = _Ubuntu_changelog % (vdata, vid, day)
868+ elif log_format == 'd':
869+ result = _Debian_changelog % (vdata, vid, day)
870+
871+ return result
872+
873+ def commit(msg, version):
874+ vid = vids.pop(0)
875+ if log_format is not None:
876+ cle = changelog(version, vid)
877+ p = '%s/work/%s/debian/changelog' % (self.test_base_dir, name)
878+ _prepend_log(cle, p)
879+ revid = tree.commit('%s: %s' % (vid, msg))
880+ setattr(self, revid_name(vid), revid)
881+ tree.branch.tags.set_tag(version, revid)
882+
883+ def tree_nick(tree):
884+ return str(tree)[1:-1].split('/')[-1]
885+
886+ for version, paths, utree, urevid in vdata:
887+ msg = ''
888+ if utree is not None:
889+ tree.merge_from_branch(utree.branch, to_revision=urevid)
890+ utree.branch.tags.merge_to(tree.branch.tags)
891+ if urevid is not None:
892+ msg += 'Merged tree %s|%s. ' % (tree_nick(utree), urevid)
893+ else:
894+ msg += 'Merged tree %s. ' % utree
895+ if paths is not None:
896+ add_paths(paths)
897+ msg += 'Added paths: %s. ' % str(paths)
898+
899+ commit(msg, version)
900+
901+ return tree
902+
903+
904+if __name__ == '__main__':
905+ suite = unittest.TestLoader().loadTestsFromTestCase(MergePackageTests)
906+ unittest.TextTestRunner(verbosity=2).run(suite)
907
908=== modified file 'upstream.py'
909--- upstream.py 2009-07-26 18:21:49 +0000
910+++ upstream.py 2009-08-06 08:40:05 +0000
911@@ -79,7 +79,7 @@
912 db = DistributionBranch(self.branch, None, tree=self.tree)
913 if not db.has_upstream_version_in_packaging_branch(version):
914 raise PackageVersionNotPresent(package, version, self)
915- revid = db._revid_of_upstream_version_from_branch(version)
916+ revid = db.revid_of_upstream_version_from_branch(version)
917 if not db.has_pristine_tar_delta(revid):
918 raise PackageVersionNotPresent(package, version, self)
919 info("Using pristine-tar to reconstruct the needed tarball.")
Revision history for this message
James Westby (james-w) wrote :

Excerpts from Muharem Hrnjadovic's message of Fri Aug 21 12:51:08 UTC 2009:
> > This is the big question I have left though. How does the user continue
> > after this? There should be a clear way to continue once they have committed,
> > and the exception should tell them how to proceed.
>
> How about this?
>
> {{{
> The "merge-package" command has detected diverged upstream
> branches for the merge source and target. A shared upstream
> revision was constructed to remedy the problem.
>
> However, merging the shared upstream revision into the merge
> target resulted in conflicts.
>
> Please proceed as follows:
>
> 1 - Resolve the current merge conflicts in the merge target
> directory and commit the changes.
> 2 - Perform a plain "bzr merge <source-packaging-branch>"
> command, resolve any ensuing packaging branch conflicts
> and commit once satisfied with the changes.
> }}}

For starters this is bit verbose, we can't really format it like that
in an error message, so it needs to be a couple of sentences.

I don't think they need to know that a new "shared upstream" was
created, you can just say that there are conflicts but they are
not finished.

Should we direct them to "bzr merge-package" again? It becomes
equivalent, but I think it's better to not confuse them with
when they can use "bzr merge" and when they can't.

You don't need to tell them what to do after they've run that again,
it can just tell them when it is done.

Would it be worth saying that if they don't like what they see
they can just "bzr revert" to get back to where they started?

Looking at the incremental diff the major issue I see remaining
is that you lock and unlock the tree, only to lock it again.
Lock it once, and hold that lock until you don't reference the
tree or anything that makes use of it any more.

Writing this mail also interested me in whether you ever get any
conflicts in the case when merging the faked upstream merge
in to the packaging branch in the case where your upstream
version was newer than the other upstream version.

Thanks,

James

404. By Muharem Hrnjadovic

Target branch is now being locked once only; James' review comments, round 7

405. By Muharem Hrnjadovic

Enhanced error message for case where merging the shared upstreams revision results in conflicts; James' review comments, round 8

406. By Muharem Hrnjadovic

Minor fix.

407. By Muharem Hrnjadovic

Slightly cleaned-up test.

Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

James Westby wrote:
> Excerpts from Muharem Hrnjadovic's message of Fri Aug 21 12:51:08 UTC 2009:
>>> This is the big question I have left though. How does the user continue
>>> after this? There should be a clear way to continue once they have committed,
>>> and the exception should tell them how to proceed.
>> How about this?
>>
>> {{{
>> The "merge-package" command has detected diverged upstream
>> branches for the merge source and target. A shared upstream
>> revision was constructed to remedy the problem.
>>
>> However, merging the shared upstream revision into the merge
>> target resulted in conflicts.
>>
>> Please proceed as follows:
>>
>> 1 - Resolve the current merge conflicts in the merge target
>> directory and commit the changes.
>> 2 - Perform a plain "bzr merge <source-packaging-branch>"
>> command, resolve any ensuing packaging branch conflicts
>> and commit once satisfied with the changes.
>> }}}
>
> For starters this is bit verbose, we can't really format it like that
> in an error message, so it needs to be a couple of sentences.
>
> I don't think they need to know that a new "shared upstream" was
> created, you can just say that there are conflicts but they are
> not finished.
>
> Should we direct them to "bzr merge-package" again? It becomes
> equivalent, but I think it's better to not confuse them with
> when they can use "bzr merge" and when they can't.
>
> You don't need to tell them what to do after they've run that again,
> it can just tell them when it is done.
>
> Would it be worth saying that if they don't like what they see
> they can just "bzr revert" to get back to where they started?

Hello James,

thank you very much for your suggestions above. They are all good. I
hope the following text is more suitable.

{{{
The upstream branches for the merge source and target have diverged.
Unfortunately, the attempt to fix this problem resulted in conflicts.
Please resolve these and re-run the "merge-package" command to finish.
Alternatively, you can restore the original merge target state by
running "bzr revert".
}}}

>
> Looking at the incremental diff the major issue I see remaining
> is that you lock and unlock the tree, only to lock it again.
> Lock it once, and hold that lock until you don't reference the
> tree or anything that makes use of it any more.

Done.

> Writing this mail also interested me in whether you ever get any
> conflicts in the case when merging the faked upstream merge
> in to the packaging branch in the case where your upstream
> version was newer than the other upstream version.

That would be the test_debian_upstream_older() test, and, no, I did not
get any conflicts.

Please find the incremental diff enclosed.

Best regards

--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC

1=== modified file 'cmds.py'
2--- cmds.py 2009-08-19 09:06:47 +0000
3+++ cmds.py 2009-08-24 10:28:15 +0000
4@@ -901,7 +901,13 @@
5 fix_ancestry_as_needed(tree, source_branch)
6
7 # Merge source packaging branch in to the target packaging branch.
8- tree.merge_from_branch(source_branch)
9+ conflicts = tree.merge_from_branch(source_branch)
10+ if conflicts > 0:
11+ info('The merge resulted in %s conflicts. Please resolve these '
12+ 'and commit the changes with "bzr commit".' % conflicts)
13+ else:
14+ info('The merge resulted in no conflicts. You may commit the '
15+ 'changes by running "bzr commit".')
16
17
18 class cmd_test_builddeb(Command):
19
20=== modified file 'errors.py'
21--- errors.py 2009-08-19 14:09:53 +0000
22+++ errors.py 2009-08-24 10:36:04 +0000
23@@ -179,18 +179,5 @@
24
25 class SharedUpstreamConflictsWithTargetPackaging(BzrError):
26 _fmt = ('''\
27- The "merge-package" command has detected diverged upstream
28- branches for the merge source and target. A shared upstream
29- revision was constructed to remedy the problem.
30-
31- However, merging the shared upstream revision into the merge
32- target resulted in conflicts.
33-
34- Please proceed as follows:
35-
36- 1 - Resolve the current merge conflicts in the merge target
37- directory and commit the changes.
38- 2 - Perform a plain "bzr merge <source-packaging-branch>"
39- command, resolve any ensuing packaging branch conflicts
40- and commit once satisfied with the changes.
41- ''')
42+The upstream branches for the merge source and target have diverged. Unfortunately, the attempt to fix this problem resulted in conflicts. Please resolve these and re-run the "merge-package" command to finish.
43+Alternatively, you can restore the original merge target state by running "bzr revert".''')
44
45=== modified file 'merge_package.py'
46--- merge_package.py 2009-08-20 07:39:39 +0000
47+++ merge_package.py 2009-08-24 10:22:13 +0000
48@@ -110,7 +110,7 @@
49
50 source.lock_read()
51 try:
52- tree.lock_read()
53+ tree.lock_write()
54 try:
55 # "Unpack" the upstream versions and revision ids for the merge
56 # source and target branch respectively.
57@@ -119,53 +119,50 @@
58 # Did the upstream branches of the merge source/target diverge?
59 graph = source.repository.get_graph(target.repository)
60 upstreams_diverged = (len(graph.heads([us_revid, ut_revid])) > 1)
61+
62+ # No, we're done!
63+ if not upstreams_diverged:
64+ return (upstreams_diverged, t_upstream_reverted)
65+
66+ # Instantiate a `DistributionBranch` object for the merge target
67+ # (packaging) branch.
68+ db = DistributionBranch(tree.branch, tree.branch)
69+ tempdir = tempfile.mkdtemp(dir=os.path.join(tree.basedir, '..'))
70+
71+ # Extract the merge target's upstream tree into a temporary
72+ # directory.
73+ db.extract_upstream_tree(ut_revid, tempdir)
74+ tmp_target_utree = db.upstream_tree
75+
76+ # Merge upstream branch tips to obtain a shared upstream parent.
77+ # This will add revision K (see graph above) to a temporary merge
78+ # target upstream tree.
79+ tmp_target_utree.lock_write()
80+ try:
81+ if us_ver > ut_ver:
82+ # The source upstream tree is more recent and the
83+ # temporary target tree needs to be reshaped to match it.
84+ tmp_target_utree.revert(
85+ None, source.repository.revision_tree(us_revid))
86+ t_upstream_reverted = True
87+
88+ tmp_target_utree.set_parent_ids((ut_revid, us_revid))
89+ tmp_target_utree.commit(
90+ 'Prepared upstream tree for merging into target branch.')
91+ finally:
92+ tmp_target_utree.unlock()
93+
94+ # Merge shared upstream parent into the target merge branch. This
95+ # creates revison L in the digram above.
96+ conflicts = tree.merge_from_branch(tmp_target_utree.branch)
97+ if conflicts > 0:
98+ raise errors.SharedUpstreamConflictsWithTargetPackaging()
99+ else:
100+ tree.commit('Merging shared upstream rev into target branch.')
101+
102 finally:
103 tree.unlock()
104 finally:
105 source.unlock()
106
107- if not upstreams_diverged:
108- return (upstreams_diverged, t_upstream_reverted)
109-
110- # Instantiate a `DistributionBranch` object for the merge target
111- # (packaging) branch.
112- db = DistributionBranch(tree.branch, tree.branch)
113- tempdir = tempfile.mkdtemp(dir=os.path.join(tree.basedir, '..'))
114-
115- # Extract the merge target's upstream tree into a temporary directory.
116- db.extract_upstream_tree(ut_revid, tempdir)
117- tmp_target_upstream_tree = db.upstream_tree
118-
119- # Merge upstream branch tips to obtain a shared upstream parent. This
120- # will add revision K (see graph above) to a temporary merge target
121- # upstream tree.
122- tmp_target_upstream_tree.lock_write()
123- try:
124- if us_ver > ut_ver:
125- # The source upstream tree is more recent and the temporary
126- # target tree needs to be reshaped to match it.
127- tmp_target_upstream_tree.revert(
128- None, source.repository.revision_tree(us_revid))
129- t_upstream_reverted = True
130-
131- tmp_target_upstream_tree.set_parent_ids(
132- (ut_revid, us_revid))
133-
134- tmp_target_upstream_tree.commit(
135- 'Consolidated upstream tree for merging into target branch')
136- finally:
137- tmp_target_upstream_tree.unlock()
138-
139- # Merge shared upstream parent into the target merge branch. This creates
140- # revison L in the digram above.
141- tree.lock_write()
142- try:
143- conflicts = tree.merge_from_branch(tmp_target_upstream_tree.branch)
144- if conflicts > 0:
145- raise errors.SharedUpstreamConflictsWithTargetPackaging()
146- else:
147- tree.commit('Merging shared upstream rev in to target branch.')
148- finally:
149- tree.unlock()
150-
151 return (upstreams_diverged, t_upstream_reverted)
152
153=== modified file 'tests/test_merge_package.py'
154--- tests/test_merge_package.py 2009-08-21 12:41:34 +0000
155+++ tests/test_merge_package.py 2009-08-24 10:44:31 +0000
156@@ -173,23 +173,23 @@
157 The upstream conflict will be resolved by fix_ancestry_as_needed().
158 Please note that the debian ancestry is older in this case.
159 """
160- ubup_n, debp_o = self._setup_debian_upstream_older()
161+ ubup, debp, _ubuu, _debu = self._setup_debian_upstream_older()
162
163 # Attempt a plain merge first.
164- conflicts = ubup_n.merge_from_branch(
165- debp_o.branch, to_revision=self.revid_debp_o_C)
166+ conflicts = ubup.merge_from_branch(
167+ debp.branch, to_revision=self.revid_debp_o_C)
168
169 # There are two conflicts in the 'c' and the 'debian/changelog' files
170 # respectively.
171 self.assertEquals(conflicts, 2)
172- conflict_paths = sorted([c.path for c in ubup_n.conflicts()])
173+ conflict_paths = sorted([c.path for c in ubup.conflicts()])
174 self.assertEquals(conflict_paths, [u'c.moved', u'debian/changelog'])
175
176 # Undo the failed merge.
177- ubup_n.revert()
178+ ubup.revert()
179
180 # The first conflict is resolved by calling fix_ancestry_as_needed().
181- upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup_n, debp_o.branch)
182+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup, debp.branch)
183
184 # The ancestry did diverge and needed to be fixed.
185 self.assertEquals(upstreams_diverged, True)
186@@ -198,12 +198,12 @@
187 self.assertEquals(t_upstream_reverted, False)
188
189 # Try merging again.
190- conflicts = ubup_n.merge_from_branch(
191- debp_o.branch, to_revision=self.revid_debp_o_C)
192+ conflicts = ubup.merge_from_branch(
193+ debp.branch, to_revision=self.revid_debp_o_C)
194
195 # And, voila, only the packaging branch conflict remains.
196 self.assertEquals(conflicts, 1)
197- conflict_paths = sorted([c.path for c in ubup_n.conflicts()])
198+ conflict_paths = sorted([c.path for c in ubup.conflicts()])
199 self.assertEquals(conflict_paths, [u'debian/changelog'])
200
201 def test_upstreams_not_diverged(self):
202@@ -394,7 +394,7 @@
203 self._setup_branch(name, vdata, ubup_n, 'u')
204
205 # Return the ubuntu and the debian packaging branches.
206- return (ubup_n, debp_o)
207+ return (ubup_n, debp_o, ubuu_n, debu_o)
208
209 def _setup_upstreams_not_diverged(self):
210 """
Revision history for this message
James Westby (james-w) wrote :

Excerpts from Muharem Hrnjadovic's message of Mon Aug 24 10:57:08 UTC 2009:
> thank you very much for your suggestions above. They are all good. I
> hope the following text is more suitable.
>
> {{{
> The upstream branches for the merge source and target have diverged.
> Unfortunately, the attempt to fix this problem resulted in conflicts.
> Please resolve these and re-run the "merge-package" command to finish.
> Alternatively, you can restore the original merge target state by
> running "bzr revert".
> }}}

That's better. I think "resolve these and commit" is more explicit, and
I would prefer something other than "original merge target state", as
that sounds a bit jargonish, perhaps 'Alternatively, until you commit
you can use "bzr revert" to restore the state of the unmerged branch.'

It's not as clear what is going on, but I think that makes it easier
to understand :-)

> > Writing this mail also interested me in whether you ever get any
> > conflicts in the case when merging the faked upstream merge
> > in to the packaging branch in the case where your upstream
> > version was newer than the other upstream version.
>
> That would be the test_debian_upstream_older() test, and, no, I did not
> get any conflicts.

Sure, there's one test, and I wouldn't expect any conflicts in that
test, I was thinking more of testing other situations though. Not
as unit tests, but one-off testing to investigate whether you can
provoke conflicts. If it is possible then we may want to investigate
ways to avoid that.

> Please find the incremental diff enclosed.

I'll review this shortly.

Thanks,

James

408. By Muharem Hrnjadovic

Clarified error message in accordance with James' suggestion.

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

Excerpts from Muharem Hrnjadovic's message of Mon Aug 24 10:57:08 UTC 2009:
> Please find the incremental diff enclosed.

My only quibble with this is the wrapping of the exception message. Please
make it wrap at <80 chars. You can make it resolve to a single string though.

   _fmt = ('...............................................'
           '...............................................'
           '...............................................')

Then I think we should be good to go.

There is documentation needed, but we can get this landed first.

Thanks,

James

409. By Muharem Hrnjadovic

Better formating of error string.

Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

James Westby wrote:
> Excerpts from Muharem Hrnjadovic's message of Mon Aug 24 10:57:08 UTC 2009:
>> Please find the incremental diff enclosed.
>
> My only quibble with this is the wrapping of the exception message. Please
> make it wrap at <80 chars. You can make it resolve to a single string though.
>
> _fmt = ('...............................................'
> '...............................................'
> '...............................................')
>
> Then I think we should be good to go.

done.

> There is documentation needed, but we can get this landed first.

I see. Please point me to an example if available.

Best regards

--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC

Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

James Westby wrote:
> Excerpts from Muharem Hrnjadovic's message of Mon Aug 24 10:57:08 UTC 2009:
>> thank you very much for your suggestions above. They are all good. I
>> hope the following text is more suitable.
>>
>> {{{
>> The upstream branches for the merge source and target have diverged.
>> Unfortunately, the attempt to fix this problem resulted in conflicts.
>> Please resolve these and re-run the "merge-package" command to finish.
>> Alternatively, you can restore the original merge target state by
>> running "bzr revert".
>> }}}
>
> That's better. I think "resolve these and commit" is more explicit, and
> I would prefer something other than "original merge target state", as
> that sounds a bit jargonish, perhaps 'Alternatively, until you commit
> you can use "bzr revert" to restore the state of the unmerged branch.'
>
> It's not as clear what is going on, but I think that makes it easier
> to understand :-)

Thank you very much for your help with this :)

>>> Writing this mail also interested me in whether you ever get any
>>> conflicts in the case when merging the faked upstream merge
>>> in to the packaging branch in the case where your upstream
>>> version was newer than the other upstream version.
>> That would be the test_debian_upstream_older() test, and, no, I did not
>> get any conflicts.
>
> Sure, there's one test, and I wouldn't expect any conflicts in that
> test, I was thinking more of testing other situations though. Not
> as unit tests, but one-off testing to investigate whether you can
> provoke conflicts. If it is possible then we may want to investigate
> ways to avoid that.

I can think of a way to set up such a situation. There would need to
be a file that's shared and modified in all four branches: source
upstream, source packaging, target upstream and target packaging.
Furthermore the target upstream branch would have to be more recent one.

I will try to come up with this.

[..]

Best regards

--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC

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

On Mon Aug 24 16:30:19 UTC 2009 Muharem Hrnjadovic wrote:
> > There is documentation needed, but we can get this landed first.
>
> I see. Please point me to an example if available.

Check the doc/user_manual/ directory. We will also want to put a quick
recipe type thing on https://wiki.ubuntu.com/DistributedDevelopment/Documentation
as well.

One thing jumped to mind as well, perhaps there are some options of "merge"
we should support in "merge-package"? We can add these later if needed.

Thanks,

James

Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

James Westby wrote:
> On Mon Aug 24 16:30:19 UTC 2009 Muharem Hrnjadovic wrote:
>>> There is documentation needed, but we can get this landed first.
>> I see. Please point me to an example if available.
>
> Check the doc/user_manual/ directory. We will also want to put a quick
> recipe type thing on https://wiki.ubuntu.com/DistributedDevelopment/Documentation
> as well.

Thanks, I will take a look.

> One thing jumped to mind as well, perhaps there are some options of "merge"
> we should support in "merge-package"? We can add these later if needed.

Hey, that's a good idea :)

Best regards

--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC

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

Looks good, thanks for all your work.

James

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '__init__.py'
2--- __init__.py 2009-07-26 15:51:02 +0000
3+++ __init__.py 2009-08-06 09:59:34 +0000
4@@ -39,7 +39,8 @@
5 "merge_upstream": ["mu"],
6 "import_dsc": [],
7 "bd_do": [],
8- "mark_uploaded": []
9+ "mark_uploaded": [],
10+ "merge_package": ["mp"]
11 }
12
13 for command, aliases in commands.iteritems():
14
15=== modified file 'cmds.py'
16--- cmds.py 2009-07-26 18:21:49 +0000
17+++ cmds.py 2009-08-19 09:06:47 +0000
18@@ -69,6 +69,7 @@
19 DscCache,
20 DscComp,
21 )
22+from bzrlib.plugins.builddeb.merge_package import fix_ancestry_as_needed
23 from bzrlib.plugins.builddeb.source_distiller import (
24 FullSourceDistiller,
25 MergeModeDistiller,
26@@ -719,9 +720,9 @@
27 "the previous upstream version, %s, in the "
28 "branch: %s" % (last_version,
29 db.upstream_tag_name(last_version)))
30- upstream_tip = db._revid_of_upstream_version_from_branch(
31+ upstream_tip = db.revid_of_upstream_version_from_branch(
32 last_version)
33- db._extract_upstream_tree(upstream_tip, tempdir)
34+ db.extract_upstream_tree(upstream_tip, tempdir)
35 else:
36 db._create_empty_upstream_tree(tempdir)
37 self.import_many(db, files_list, orig_target)
38@@ -870,6 +871,39 @@
39 t.unlock()
40
41
42+class cmd_merge_package(Command):
43+ """Merges source packaging branch into target packaging branch.
44+
45+ This will first check whether the upstream branches have diverged.
46+
47+ If that's the case an attempt will be made to fix the upstream ancestry
48+ so that the user only needs to deal wth packaging branch merge issues.
49+
50+ In the opposite case a normal merge will be performed.
51+ """
52+ takes_args = ['source']
53+
54+ def run(self, source):
55+ source_branch = target_branch = None
56+ # Get the target branch.
57+ try:
58+ tree = WorkingTree.open_containing('.')[0]
59+ target_branch = tree.branch
60+ except NotBranchError:
61+ raise BzrCommandError(
62+ "There is no tree to merge the source branch in to")
63+ # Get the source branch.
64+ try:
65+ source_branch = Branch.open(source)
66+ except NotBranchError:
67+ raise BzrCommandError("Invalid source branch URL?")
68+
69+ fix_ancestry_as_needed(tree, source_branch)
70+
71+ # Merge source packaging branch in to the target packaging branch.
72+ tree.merge_from_branch(source_branch)
73+
74+
75 class cmd_test_builddeb(Command):
76 """Run the builddeb test suite"""
77
78@@ -880,4 +914,3 @@
79 passed = selftest(test_suite_factory=test_suite)
80 # invert for shell exit code rules
81 return not passed
82-
83
84=== modified file 'import_dsc.py'
85--- import_dsc.py 2009-07-26 16:44:17 +0000
86+++ import_dsc.py 2009-08-19 08:58:47 +0000
87@@ -1570,7 +1570,7 @@
88 finally:
89 shutil.rmtree(tempdir)
90
91- def _extract_upstream_tree(self, upstream_tip, basedir):
92+ def extract_upstream_tree(self, upstream_tip, basedir):
93 # Extract that to a tempdir so we can get a working
94 # tree for it.
95 # TODO: should stack rather than trying to use the repository,
96@@ -1582,6 +1582,13 @@
97 self.upstream_tree = dir_to.open_workingtree()
98 self.upstream_branch = self.upstream_tree.branch
99
100+ def _extract_upstream_tree(self, upstream_tip, basedir):
101+ # This method is now being used outside this module and hence
102+ # not really private any longer.
103+ # TODO: obsolete/remove this method and start using
104+ # extract_upstream_tree() instead.
105+ self.extract_upstream_tree(upstream_tip, basedir)
106+
107 def _create_empty_upstream_tree(self, basedir):
108 to_location = os.path.join(basedir, "upstream")
109 to_transport = get_transport(to_location)
110@@ -1616,6 +1623,10 @@
111 raise
112
113 def _revid_of_upstream_version_from_branch(self, version):
114+ """The private method below will go away eventually."""
115+ return self.revid_of_upstream_version_from_branch(version)
116+
117+ def revid_of_upstream_version_from_branch(self, version):
118 assert isinstance(version, str)
119 tag_name = self.upstream_tag_name(version)
120 if self._has_version(self.branch, tag_name):
121@@ -1639,14 +1650,14 @@
122 if previous_version is not None:
123 if self.has_upstream_version_in_packaging_branch(
124 previous_version.upstream_version):
125- upstream_tip = self._revid_of_upstream_version_from_branch(
126+ upstream_tip = self.revid_of_upstream_version_from_branch(
127 previous_version.upstream_version)
128- self._extract_upstream_tree(upstream_tip, tempdir)
129+ self.extract_upstream_tree(upstream_tip, tempdir)
130 elif (upstream_branch is not None and
131 previous_upstream_revision is not None):
132 upstream_tip = RevisionSpec.from_string(previous_upstream_revision).as_revision_id(upstream_branch)
133 assert isinstance(upstream_tip, str)
134- self._extract_upstream_tree(upstream_tip, tempdir)
135+ self.extract_upstream_tree(upstream_tip, tempdir)
136 else:
137 raise BzrCommandError("Unable to find the tag for the "
138 "previous upstream version, %s, in the branch: "
139
140=== added file 'merge_package.py'
141--- merge_package.py 1970-01-01 00:00:00 +0000
142+++ merge_package.py 2009-08-19 09:19:38 +0000
143@@ -0,0 +1,214 @@
144+# merge_package.py -- The plugin for bzr
145+# Copyright (C) 2009 Canonical Ltd.
146+#
147+# :Author: Muharem Hrnjadovic <muharem@ubuntu.com>
148+#
149+# This file is part of bzr-builddeb.
150+#
151+# bzr-builddeb is free software; you can redistribute it and/or modify
152+# it under the terms of the GNU General Public License as published by
153+# the Free Software Foundation; either version 2 of the License, or
154+# (at your option) any later version.
155+#
156+# bzr-builddeb is distributed in the hope that it will be useful,
157+# but WITHOUT ANY WARRANTY; without even the implied warranty of
158+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
159+# GNU General Public License for more details.
160+#
161+# You should have received a copy of the GNU General Public License
162+# along with bzr-builddeb; if not, write to the Free Software
163+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
164+#
165+
166+import os
167+import re
168+import sys
169+import tempfile
170+
171+from debian_bundle.changelog import Version
172+
173+from bzrlib import errors
174+
175+from bzrlib.plugins.builddeb.import_dsc import DistributionBranch
176+
177+
178+class WrongBranchType(errors.BzrError):
179+ _fmt = "The merge target is not a packaging branch."
180+
181+
182+class InvalidChangelogFormat(errors.BzrError):
183+ _fmt = "The debian/changelog is empty or not in valid format."
184+
185+
186+class SourceUpstreamConflictsWithTargetPackaging(errors.BzrError):
187+ _fmt = (
188+ "The source upstream branch conflicts with "
189+ "the target packaging branch")
190+
191+
192+def _read_file(branch, path):
193+ """Get content of file for given `branch` and `path.
194+
195+ :param branch: A Branch object containing the file of interest.
196+ :param path: The path of the file to read.
197+ """
198+ try:
199+ tree = branch.basis_tree()
200+ tree.lock_read()
201+ content = tree.get_file_text(tree.path2id(path))
202+ tree.unlock()
203+ except errors.NoSuchId:
204+ raise WrongBranchType()
205+
206+ return content
207+
208+
209+def _latest_version(branch):
210+ """Version of the most recent source package upload in the given `branch`.
211+
212+ :param branch: A Branch object containing the source upload of interest.
213+ """
214+ upload_version = ''
215+ changelog = _read_file(branch, "debian/changelog")
216+
217+ for line in changelog.splitlines():
218+ # Look for the top-level changelog stanza, extract the
219+ # upload version from it and break on success.
220+ match = re.search('^.+\(([^)]+)\).*$', line)
221+ if match is not None:
222+ (upload_version,) = match.groups(1)
223+ break
224+
225+ upload_version = upload_version.strip()
226+ if len(upload_version) <= 0:
227+ raise InvalidChangelogFormat()
228+
229+ return Version(upload_version)
230+
231+
232+def _upstream_version_data(source, target):
233+ """Most recent upstream versions/revision IDs of the merge source/target.
234+
235+ Please note: both packaing branches must have been read-locked beforehand.
236+
237+ :param source: The merge source branch.
238+ :param target: The merge target branch.
239+ """
240+ results = list()
241+ for branch in (source, target):
242+ db = DistributionBranch(branch, branch)
243+ uver = _latest_version(branch).upstream_version
244+ results.append((uver, db.revid_of_upstream_version_from_branch(uver)))
245+
246+ return results
247+
248+
249+def fix_ancestry_as_needed(tree, source):
250+ """Manipulate the merge target's ancestry to avoid upstream conflicts.
251+
252+ Merging J->I given the following ancestry tree is likely to result in
253+ upstream merge conflicts:
254+
255+ debian-upstream ,------------------H
256+ A-----------B \
257+ ubuntu-upstream \ \`-------G \
258+ \ \ \ \
259+ debian-packaging \ ,---------D--------\-----------J
260+ C \ \
261+ ubuntu-packaging `----E------F--------I
262+
263+ Here there was a new upstream release (G) that Ubuntu packaged (I), and
264+ then another one that Debian packaged, skipping G, at H and J.
265+
266+ Now, the way to solve this is to introduce the missing link.
267+
268+ debian-upstream ,------------------H------.
269+ A-----------B \ \
270+ ubuntu-upstream \ \`-------G-----------\------K
271+ \ \ \ \
272+ debian-packaging \ ,---------D--------\-----------J
273+ C \ \
274+ ubuntu-packaging `----E------F--------I
275+
276+ at K, which isn't a real merge, as we just use the tree from H, but add
277+ G as a parent and then we merge that in to Ubuntu.
278+
279+ debian-upstream ,------------------H------.
280+ A-----------B \ \
281+ ubuntu-upstream \ \`-------G-----------\------K
282+ \ \ \ \ \
283+ debian-packaging \ ,---------D--------\-----------J \
284+ C \ \ \
285+ ubuntu-packaging `----E------F--------I------------------L
286+
287+ At this point we can merge J->L to merge the Debian and Ubuntu changes.
288+
289+ :param tree: The `WorkingTree` of the merge target branch.
290+ :param source: The merge source (packaging) branch.
291+ """
292+ upstreams_diverged = False
293+ t_upstream_reverted = False
294+ target = tree.branch
295+
296+ try:
297+ source.lock_read()
298+ target.lock_read()
299+ upstream_vdata = _upstream_version_data(source, target)
300+ # Did the upstream branches of the merge source and target diverge?
301+ revids = [vdata[1] for vdata in upstream_vdata]
302+ graph = source.repository.get_graph(target.repository)
303+ upstreams_diverged = (len(graph.heads(revids)) > 1)
304+ finally:
305+ source.unlock()
306+ target.unlock()
307+
308+ if not upstreams_diverged:
309+ return (upstreams_diverged, t_upstream_reverted)
310+
311+ # "Unpack" the upstream versions and revision ids for the merge source and
312+ # target branch respectively.
313+ [(usource_v, usource_revid), (utarget_v, utarget_revid)] = upstream_vdata
314+
315+ # Instantiate a `DistributionBranch` object for the merge target
316+ # (packaging) branch.
317+ db = DistributionBranch(tree.branch, tree.branch)
318+ tempdir = tempfile.mkdtemp(dir=os.path.join(tree.basedir, '..'))
319+
320+ # Extract the merge target's upstream tree into a temporary directory.
321+ db.extract_upstream_tree(utarget_revid, tempdir)
322+ tmp_target_upstream_tree = db.upstream_tree
323+
324+ # Merge upstream branch tips to obtain a shared upstream parent. This
325+ # will add revision K (see graph above) to a temporary merge target
326+ # upstream tree.
327+ try:
328+ tmp_target_upstream_tree.lock_write()
329+
330+ if usource_v > utarget_v:
331+ # The source upstream tree is more recent and the temporary
332+ # target tree needs to be reshaped to match it.
333+ tmp_target_upstream_tree.revert(
334+ None, source.repository.revision_tree(usource_revid))
335+ t_upstream_reverted = True
336+
337+ tmp_target_upstream_tree.set_parent_ids(
338+ (utarget_revid, usource_revid))
339+
340+ tmp_target_upstream_tree.commit(
341+ 'Consolidated upstream tree for merging into target branch')
342+ finally:
343+ tmp_target_upstream_tree.unlock()
344+
345+ # Merge shared upstream parent into the target merge branch. This creates
346+ # revison L in the digram above.
347+ try:
348+ tree.lock_write()
349+ try:
350+ tree.merge_from_branch(tmp_target_upstream_tree.branch)
351+ tree.commit('Merging source packaging branch in to target.')
352+ except ConflictsInTree:
353+ raise SourceUpstreamConflictsWithTargetPackaging()
354+ finally:
355+ tree.unlock()
356+
357+ return (upstreams_diverged, t_upstream_reverted)
358
359=== modified file 'tests/__init__.py'
360--- tests/__init__.py 2009-07-04 20:45:01 +0000
361+++ tests/__init__.py 2009-08-19 09:56:50 +0000
362@@ -118,6 +118,7 @@
363 'test_config',
364 'test_hooks',
365 'test_import_dsc',
366+ 'test_merge_package',
367 'test_merge_upstream',
368 'test_repack_tarball_extra',
369 'test_revspec',
370
371=== added file 'tests/test_merge_package.py'
372--- tests/test_merge_package.py 1970-01-01 00:00:00 +0000
373+++ tests/test_merge_package.py 2009-08-19 09:44:38 +0000
374@@ -0,0 +1,497 @@
375+#!/usr/bin/env python
376+# -*- coding: iso-8859-15 -*-
377+# test_merge_package.py -- Merge packaging branches, fix ancestry as needed.
378+# Copyright (C) 2008 Canonical Ltd.
379+#
380+# This file is part of bzr-builddeb.
381+#
382+# bzr-builddeb is free software; you can redistribute it and/or modify
383+# it under the terms of the GNU General Public License as published by
384+# the Free Software Foundation; either version 2 of the License, or
385+# (at your option) any later version.
386+#
387+# bzr-builddeb is distributed in the hope that it will be useful,
388+# but WITHOUT ANY WARRANTY; without even the implied warranty of
389+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
390+# GNU General Public License for more details.
391+#
392+# You should have received a copy of the GNU General Public License
393+# along with bzr-builddeb; if not, write to the Free Software
394+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
395+
396+import os
397+import random
398+import string
399+import unittest
400+
401+from bzrlib.errors import ConflictsInTree
402+from bzrlib.merge import WeaveMerger
403+from bzrlib.tests import TestCaseWithTransport
404+
405+from bzrlib.plugins.builddeb import merge_package as MP
406+
407+_Debian_changelog = '''\
408+ipsec-tools (%s) unstable; urgency=high
409+
410+ * debian packaging -- %s
411+
412+ -- Nico Golde <nion@debian.org> Tue, %02d May 2009 13:26:14 +0200
413+
414+'''
415+
416+_Ubuntu_changelog = '''\
417+ipsec-tools (%s) karmic; urgency=low
418+
419+ * ubuntu packaging -- %s
420+
421+ -- Jamie Strandboge <jamie@ubuntu.com> Fri, %02d Jul 2009 13:24:17 -0500
422+
423+'''
424+
425+
426+def _prepend_log(text, path):
427+ content = open(path).read()
428+ fh = open(path, 'wb')
429+ fh.write(text+content)
430+ fh.close()
431+
432+
433+class MergePackageTests(TestCaseWithTransport):
434+
435+ def test_latest_upstream_versions(self):
436+ """Check correctness of upstream version computation."""
437+ ubup_o, debp_n = self._setup_debian_upstrem_newer()
438+ # Ubuntu upstream.
439+ self.assertEquals(
440+ MP._latest_version(ubup_o.branch).upstream_version, '1.1.2')
441+ # Debian upstream.
442+ self.assertEquals(
443+ MP._latest_version(debp_n.branch).upstream_version, '2.0')
444+
445+ ubuntup, debianp = self._setup_upstreams_not_diverged()
446+ # Ubuntu upstream.
447+ self.assertEquals(
448+ MP._latest_version(ubuntup.branch).upstream_version, '1.4')
449+ # Debian upstream.
450+ self.assertEquals(
451+ MP._latest_version(debianp.branch).upstream_version, '2.2')
452+
453+ def test_debian_upstream_newer(self):
454+ """Diverging upstreams (debian newer) don't cause merge conflicts.
455+
456+ The debian and ubuntu upstream branches will differ with regard to
457+ the content of the file 'c'.
458+
459+ Furthermore the respective packaging branches will have a text
460+ conflict in 'debian/changelog'.
461+
462+ The upstream conflict will be resolved by fix_ancestry_as_needed().
463+ Please note that the debian ancestry is more recent.
464+ """
465+ ubup_o, debp_n = self._setup_debian_upstrem_newer()
466+
467+ # Attempt a plain merge first.
468+ conflicts = ubup_o.merge_from_branch(
469+ debp_n.branch, to_revision=self.revid_debp_n_C)
470+
471+ # There are two conflicts in the 'c' and the 'debian/changelog' files
472+ # respectively.
473+ self.assertEquals(conflicts, 2)
474+ conflict_paths = sorted([c.path for c in ubup_o.conflicts()])
475+ self.assertEquals(conflict_paths, [u'c.moved', u'debian/changelog'])
476+
477+ # Undo the failed merge.
478+ ubup_o.revert()
479+
480+ # The first conflict is resolved by calling fix_ancestry_as_needed().
481+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup_o, debp_n.branch)
482+
483+ # The ancestry did diverge and needed to be fixed.
484+ self.assertEquals(upstreams_diverged, True)
485+ # The (temporary) target upstream branch had to be reverted to the
486+ # source upstream branch since the latter was more recent.
487+ self.assertEquals(t_upstream_reverted, True)
488+
489+ # Try merging again.
490+ conflicts = ubup_o.merge_from_branch(
491+ debp_n.branch, to_revision=self.revid_debp_n_C)
492+
493+ # And, voila, only the packaging branch conflict remains.
494+ self.assertEquals(conflicts, 1)
495+ conflict_paths = sorted([c.path for c in ubup_o.conflicts()])
496+ self.assertEquals(conflict_paths, [u'debian/changelog'])
497+
498+ def test_debian_upstream_older(self):
499+ """Diverging upstreams (debian older) don't cause merge conflicts.
500+
501+ The debian and ubuntu upstream branches will differ with regard to
502+ the content of the file 'c'.
503+
504+ Furthermore the respective packaging branches will have a text
505+ conflict in 'debian/changelog'.
506+
507+ The upstream conflict will be resolved by fix_ancestry_as_needed().
508+ Please note that the debian ancestry is older in this case.
509+ """
510+ ubup_n, debp_o = self._setup_debian_upstream_older()
511+
512+ # Attempt a plain merge first.
513+ conflicts = ubup_n.merge_from_branch(
514+ debp_o.branch, to_revision=self.revid_debp_o_C)
515+
516+ # There are two conflicts in the 'c' and the 'debian/changelog' files
517+ # respectively.
518+ self.assertEquals(conflicts, 2)
519+ conflict_paths = sorted([c.path for c in ubup_n.conflicts()])
520+ self.assertEquals(conflict_paths, [u'c.moved', u'debian/changelog'])
521+
522+ # Undo the failed merge.
523+ ubup_n.revert()
524+
525+ # The first conflict is resolved by calling fix_ancestry_as_needed().
526+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubup_n, debp_o.branch)
527+
528+ # The ancestry did diverge and needed to be fixed.
529+ self.assertEquals(upstreams_diverged, True)
530+ # The target upstream branch was more recent in this case and hence
531+ # was not reverted to the source upstream branch.
532+ self.assertEquals(t_upstream_reverted, False)
533+
534+ # Try merging again.
535+ conflicts = ubup_n.merge_from_branch(
536+ debp_o.branch, to_revision=self.revid_debp_o_C)
537+
538+ # And, voila, only the packaging branch conflict remains.
539+ self.assertEquals(conflicts, 1)
540+ conflict_paths = sorted([c.path for c in ubup_n.conflicts()])
541+ self.assertEquals(conflict_paths, [u'debian/changelog'])
542+
543+ def test_upstreams_not_diverged(self):
544+ """Non-diverging upstreams result in a normal merge.
545+
546+ The debian and ubuntu upstream branches will not have diverged
547+ this time.
548+
549+ The packaging branches will have a conflict in 'debian/changelog'.
550+ fix_ancestry_as_needed() will return as soon as establishing that
551+ the upstreams have not diverged.
552+ """
553+ ubuntup, debianp = self._setup_upstreams_not_diverged()
554+
555+ # Attempt a plain merge first.
556+ conflicts = ubuntup.merge_from_branch(
557+ debianp.branch, to_revision=self.revid_debianp_C)
558+
559+ # There is only a conflict in the 'debian/changelog' file.
560+ self.assertEquals(conflicts, 1)
561+ conflict_paths = sorted([c.path for c in ubuntup.conflicts()])
562+ self.assertEquals(conflict_paths, [u'debian/changelog'])
563+
564+ # Undo the failed merge.
565+ ubuntup.revert()
566+
567+ # The conflict is *not* resolved by calling fix_ancestry_as_needed().
568+ upstreams_diverged, t_upstream_reverted = MP.fix_ancestry_as_needed(ubuntup, debianp.branch)
569+
570+ # The ancestry did *not* diverge.
571+ self.assertEquals(upstreams_diverged, False)
572+ # The upstreams have not diverged, hence no need to fix/revert
573+ # either of them.
574+ self.assertEquals(t_upstream_reverted, False)
575+
576+ # Try merging again.
577+ conflicts = ubuntup.merge_from_branch(
578+ debianp.branch, to_revision=self.revid_debianp_C)
579+
580+ # The packaging branch conflict we saw above is still there.
581+ self.assertEquals(conflicts, 1)
582+ conflict_paths = sorted([c.path for c in ubuntup.conflicts()])
583+ self.assertEquals(conflict_paths, [u'debian/changelog'])
584+
585+ def _setup_debian_upstrem_newer(self):
586+ """
587+ Set up the following test configuration (debian upstrem newer).
588+
589+ debian-upstream ,------------------H
590+ A-----------B \
591+ ubuntu-upstream \ \`-------G \
592+ \ \ \ \
593+ debian-packaging \ ,---------D--------\-----------J
594+ C \
595+ ubuntu-packaging `----E---------------I
596+
597+ where:
598+ - A = 1.0
599+ - B = 1.1
600+ - H = 2.0
601+
602+ - G = 1.1.2
603+
604+ - C = 1.0-1
605+ - D = 1.1-1
606+ - J = 2.0-1
607+
608+ - E = 1.0-1ubuntu1
609+ - I = 1.1.2-0ubuntu1
610+
611+ Please note that the debian and ubuntu branches will have a conflict
612+ with respect to the file 'c'.
613+ """
614+ # Set up the debian upstream branch.
615+ name = 'debu-n'
616+ vdata = [
617+ ('upstream-1.0', ('a',), None, None),
618+ ('upstream-1.1', ('b',), None, None),
619+ ('upstream-2.0', ('c',), None, None),
620+ ]
621+ debu_n = self._setup_branch(name, vdata)
622+
623+ # Set up the debian packaging branch.
624+ name = 'debp-n'
625+ debp_n = self.make_branch_and_tree(name)
626+ debp_n.pull(debu_n.branch, stop_revision=self.revid_debu_n_A)
627+
628+ vdata = [
629+ ('1.0-1', ('debian/', 'debian/changelog'), None, None),
630+ ('1.1-1', ('o',), debu_n, self.revid_debu_n_B),
631+ ('2.0-1', ('p',), debu_n, self.revid_debu_n_C),
632+ ]
633+ self._setup_branch(name, vdata, debp_n, 'd')
634+
635+ # Set up the ubuntu upstream branch.
636+ name = 'ubuu-o'
637+ ubuu_o = debu_n.bzrdir.sprout(
638+ name, revision_id=self.revid_debu_n_B).open_workingtree()
639+
640+ vdata = [
641+ ('upstream-1.1.2', ('c',), None, None),
642+ ]
643+ self._setup_branch(name, vdata, ubuu_o)
644+
645+ # Set up the ubuntu packaging branch.
646+ name = 'ubup-o'
647+ ubup_o = debu_n.bzrdir.sprout(
648+ name, revision_id=self.revid_debu_n_A).open_workingtree()
649+
650+ vdata = [
651+ ('1.0-1ubuntu1', (), debp_n, self.revid_debp_n_A),
652+ ('1.1.2-0ubuntu1', (), ubuu_o, self.revid_ubuu_o_A),
653+ ]
654+ self._setup_branch(name, vdata, ubup_o, 'u')
655+
656+ # Return the ubuntu and the debian packaging branches.
657+ return (ubup_o, debp_n)
658+
659+ def _setup_debian_upstream_older(self):
660+ """
661+ Set up the following test configuration (debian upstrem older).
662+
663+ debian-upstream ,----H-------------.
664+ A-----------B \
665+ ubuntu-upstream \ \`-----------G \
666+ \ \ \ \
667+ debian-packaging \ ,---------D------------\-------J
668+ C \
669+ ubuntu-packaging `----E-------------------I
670+
671+ where:
672+ - A = 1.0
673+ - B = 1.1
674+ - H = 1.1.3
675+
676+ - G = 2.1
677+
678+ - C = 1.0-1
679+ - D = 1.1-1
680+ - J = 1.1.3-1
681+
682+ - E = 1.0-1ubuntu1
683+ - I = 2.1-0ubuntu1
684+
685+ Please note that the debian and ubuntu branches will have a conflict
686+ with respect to the file 'c'.
687+ """
688+ # Set up the debian upstream branch.
689+ name = 'debu-o'
690+ vdata = [
691+ ('upstream-1.0', ('a',), None, None),
692+ ('upstream-1.1', ('b',), None, None),
693+ ('upstream-1.1.3', ('c',), None, None),
694+ ]
695+ debu_o = self._setup_branch(name, vdata)
696+
697+ # Set up the debian packaging branch.
698+ name = 'debp-o'
699+ debp_o = self.make_branch_and_tree(name)
700+ debp_o.pull(debu_o.branch, stop_revision=self.revid_debu_o_A)
701+
702+ vdata = [
703+ ('1.0-1', ('debian/', 'debian/changelog'), None, None),
704+ ('1.1-1', ('o',), debu_o, self.revid_debu_o_B),
705+ ('1.1.3-1', ('p',), debu_o, self.revid_debu_o_C),
706+ ]
707+ self._setup_branch(name, vdata, debp_o, 'd')
708+
709+ # Set up the ubuntu upstream branch.
710+ name = 'ubuu-n'
711+ ubuu_n = debu_o.bzrdir.sprout(
712+ name, revision_id=self.revid_debu_o_B).open_workingtree()
713+
714+ vdata = [
715+ ('upstream-2.1', ('c',), None, None),
716+ ]
717+ self._setup_branch(name, vdata, ubuu_n)
718+
719+ # Set up the ubuntu packaging branch.
720+ name = 'ubup-n'
721+ ubup_n = debu_o.bzrdir.sprout(
722+ name, revision_id=self.revid_debu_o_A).open_workingtree()
723+
724+ vdata = [
725+ ('1.0-1ubuntu1', (), debp_o, self.revid_debp_o_A),
726+ ('2.1-0ubuntu1', (), ubuu_n, self.revid_ubuu_n_A),
727+ ]
728+ self._setup_branch(name, vdata, ubup_n, 'u')
729+
730+ # Return the ubuntu and the debian packaging branches.
731+ return (ubup_n, debp_o)
732+
733+ def _setup_upstreams_not_diverged(self):
734+ """
735+ Set up a test configuration where the usptreams have not diverged.
736+
737+ debian-upstream .-----G
738+ A-----------B-----H \
739+ ubuntu-upstream \ \ \ \
740+ \ \ \ \
741+ debian-packaging \ ,---------D-----\-------J
742+ C \
743+ ubuntu-packaging `----E------------I
744+
745+ where:
746+ - A = 1.0
747+ - B = 1.1
748+ - H = 1.4
749+
750+ - G = 2.2
751+
752+ - C = 1.0-1
753+ - D = 1.1-1
754+ - J = 2.2-1
755+
756+ - E = 1.0-1ubuntu1
757+ - I = 1.4-0ubuntu1
758+
759+ Please note that there's only one shared upstream branch in this case.
760+ """
761+ # Set up the upstream branch.
762+ name = 'upstream'
763+ vdata = [
764+ ('upstream-1.0', ('a',), None, None),
765+ ('upstream-1.1', ('b',), None, None),
766+ ('upstream-1.4', ('c',), None, None),
767+ ]
768+ upstream = self._setup_branch(name, vdata)
769+
770+ # Set up the debian upstream branch.
771+ name = 'dupstream'
772+ dupstream = upstream.bzrdir.sprout(name).open_workingtree()
773+ vdata = [
774+ ('upstream-2.2', (), None, None),
775+ ]
776+ dupstream = self._setup_branch(name, vdata, dupstream)
777+
778+ # Set up the debian packaging branch.
779+ name = 'debianp'
780+ debianp = self.make_branch_and_tree(name)
781+ debianp.pull(dupstream.branch, stop_revision=self.revid_upstream_A)
782+
783+ vdata = [
784+ ('1.0-1', ('debian/', 'debian/changelog'), None, None),
785+ ('1.1-1', ('o',), dupstream, self.revid_upstream_B),
786+ ('2.2-1', ('p',), dupstream, self.revid_dupstream_A),
787+ ]
788+ self._setup_branch(name, vdata, debianp, 'd')
789+
790+ # Set up the ubuntu packaging branch.
791+ name = 'ubuntup'
792+ ubuntup = upstream.bzrdir.sprout(
793+ name, revision_id=self.revid_upstream_A).open_workingtree()
794+
795+ vdata = [
796+ ('1.0-1ubuntu1', (), debianp, self.revid_debianp_A),
797+ ('1.4-0ubuntu1', (), upstream, self.revid_upstream_C),
798+ ]
799+ self._setup_branch(name, vdata, ubuntup, 'u')
800+
801+ # Return the ubuntu and the debian packaging branches.
802+ return (ubuntup, debianp)
803+
804+ def _setup_branch(self, name, vdata, tree=None, log_format=None):
805+ vids = list(string.ascii_uppercase)
806+ days = range(len(string.ascii_uppercase))
807+
808+ if tree is None:
809+ tree = self.make_branch_and_tree(name)
810+
811+ def revid_name(vid):
812+ return 'revid_%s_%s' % (name.replace('-', '_'), vid)
813+
814+ def add_paths(paths):
815+ qpaths = ['%s/%s' % (name, path) for path in paths]
816+ self.build_tree(qpaths)
817+ tree.add(paths)
818+
819+ def changelog(vdata, vid):
820+ result = ''
821+ day = days.pop(0)
822+ if isinstance(vdata, tuple):
823+ uver, dver = vdata[:2]
824+ ucle = _Ubuntu_changelog % (uver, vid, day)
825+ dcle = _Debian_changelog % (dver, vid, day)
826+ result = ucle + dcle
827+ else:
828+ if log_format == 'u':
829+ result = _Ubuntu_changelog % (vdata, vid, day)
830+ elif log_format == 'd':
831+ result = _Debian_changelog % (vdata, vid, day)
832+
833+ return result
834+
835+ def commit(msg, version):
836+ vid = vids.pop(0)
837+ if log_format is not None:
838+ cle = changelog(version, vid)
839+ p = '%s/work/%s/debian/changelog' % (self.test_base_dir, name)
840+ _prepend_log(cle, p)
841+ revid = tree.commit('%s: %s' % (vid, msg), rev_id='%s-%s' % (name, vid))
842+ setattr(self, revid_name(vid), revid)
843+ tree.branch.tags.set_tag(version, revid)
844+
845+ def tree_nick(tree):
846+ return str(tree)[1:-1].split('/')[-1]
847+
848+ for version, paths, utree, urevid in vdata:
849+ msg = ''
850+ if utree is not None:
851+ tree.merge_from_branch(
852+ utree.branch, to_revision=urevid, merge_type=WeaveMerger)
853+ utree.branch.tags.merge_to(tree.branch.tags)
854+ if urevid is not None:
855+ msg += 'Merged tree %s|%s. ' % (tree_nick(utree), urevid)
856+ else:
857+ msg += 'Merged tree %s. ' % utree
858+ if paths is not None:
859+ add_paths(paths)
860+ msg += 'Added paths: %s. ' % str(paths)
861+
862+ commit(msg, version)
863+
864+ return tree
865+
866+
867+if __name__ == '__main__':
868+ # unittest.main()
869+ suite = unittest.TestLoader().loadTestsFromTestCase(MergePackageTests)
870+ unittest.TextTestRunner(verbosity=2).run(suite)
871+
872
873=== modified file 'upstream.py'
874--- upstream.py 2009-07-26 18:21:49 +0000
875+++ upstream.py 2009-08-06 08:41:23 +0000
876@@ -79,7 +79,7 @@
877 db = DistributionBranch(self.branch, None, tree=self.tree)
878 if not db.has_upstream_version_in_packaging_branch(version):
879 raise PackageVersionNotPresent(package, version, self)
880- revid = db._revid_of_upstream_version_from_branch(version)
881+ revid = db.revid_of_upstream_version_from_branch(version)
882 if not db.has_pristine_tar_delta(revid):
883 raise PackageVersionNotPresent(package, version, self)
884 info("Using pristine-tar to reconstruct the needed tarball.")

Subscribers

People subscribed via source and target branches