1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
# merge_package.py -- The plugin for bzr
# Copyright (C) 2009 Canonical Ltd.
#
# :Author: Muharem Hrnjadovic <muharem@ubuntu.com>
#
# This file is part of bzr-builddeb.
#
# bzr-builddeb is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# bzr-builddeb is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with bzr-builddeb; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
import os
import shutil
import tempfile
try:
from debian.changelog import Version
except ImportError:
# Prior to 0.1.15 the debian module was called debian_bundle
from debian_bundle.changelog import Version
from bzrlib.plugins.builddeb.errors import (
SharedUpstreamConflictsWithTargetPackaging)
from bzrlib.plugins.builddeb.import_dsc import DistributionBranch
from bzrlib.plugins.builddeb.util import find_changelog
def _latest_version(branch):
"""Version of the most recent source package upload in the given `branch`.
:param branch: A Branch object containing the source upload of interest.
"""
changelog, _ignore = find_changelog(branch.basis_tree(), False)
return changelog.version
def _upstream_version_data(source, target):
"""Most recent upstream versions/revision IDs of the merge source/target.
Please note: both packaging branches must have been read-locked
beforehand.
:param source: The merge source branch.
:param target: The merge target branch.
"""
results = list()
for branch in (source, target):
db = DistributionBranch(branch, branch)
uver = _latest_version(branch).upstream_version
results.append((Version(uver),
db.revid_of_upstream_version_from_branch(uver)))
return results
def fix_ancestry_as_needed(tree, source):
"""Manipulate the merge target's ancestry to avoid upstream conflicts.
Merging J->I given the following ancestry tree is likely to result in
upstream merge conflicts:
debian-upstream ,------------------H
A-----------B \
ubuntu-upstream \ \`-------G \
\ \ \ \
debian-packaging \ ,---------D--------\-----------J
C \ \
ubuntu-packaging `----E------F--------I
Here there was a new upstream release (G) that Ubuntu packaged (I), and
then another one that Debian packaged, skipping G, at H and J.
Now, the way to solve this is to introduce the missing link.
debian-upstream ,------------------H------.
A-----------B \ \
ubuntu-upstream \ \`-------G-----------\------K
\ \ \ \
debian-packaging \ ,---------D--------\-----------J
C \ \
ubuntu-packaging `----E------F--------I
at K, which isn't a real merge, as we just use the tree from H, but add
G as a parent and then we merge that in to Ubuntu.
debian-upstream ,------------------H------.
A-----------B \ \
ubuntu-upstream \ \`-------G-----------\------K
\ \ \ \ \
debian-packaging \ ,---------D--------\-----------J \
C \ \ \
ubuntu-packaging `----E------F--------I------------------L
At this point we can merge J->L to merge the Debian and Ubuntu changes.
:param tree: The `WorkingTree` of the merge target branch.
:param source: The merge source (packaging) branch.
"""
upstreams_diverged = False
t_upstream_reverted = False
target = tree.branch
source.lock_read()
try:
tree.lock_write()
try:
# "Unpack" the upstream versions and revision ids for the merge
# source and target branch respectively.
[(us_ver, us_revid), (ut_ver, ut_revid)] = _upstream_version_data(source, target)
# Did the upstream branches of the merge source/target diverge?
graph = source.repository.get_graph(target.repository)
upstreams_diverged = (len(graph.heads([us_revid, ut_revid])) > 1)
# No, we're done!
if not upstreams_diverged:
return (upstreams_diverged, t_upstream_reverted)
# Instantiate a `DistributionBranch` object for the merge target
# (packaging) branch.
db = DistributionBranch(tree.branch, tree.branch)
tempdir = tempfile.mkdtemp(dir=os.path.join(tree.basedir, '..'))
try:
# Extract the merge target's upstream tree into a temporary
# directory.
db.extract_upstream_tree(ut_revid, tempdir)
tmp_target_utree = db.upstream_tree
# Merge upstream branch tips to obtain a shared upstream parent.
# This will add revision K (see graph above) to a temporary merge
# target upstream tree.
tmp_target_utree.lock_write()
try:
if us_ver > ut_ver:
# The source upstream tree is more recent and the
# temporary target tree needs to be reshaped to match it.
tmp_target_utree.revert(
None, source.repository.revision_tree(us_revid))
t_upstream_reverted = True
tmp_target_utree.set_parent_ids((ut_revid, us_revid))
new_revid = tmp_target_utree.commit(
'Prepared upstream tree for merging into target branch.')
# Repository updates during a held lock are not visible,
# hence the call to refresh the data in the /target/ repo.
tree.branch.repository.refresh_data()
tree.branch.fetch(source, last_revision=us_revid)
tree.branch.fetch(tmp_target_utree.branch,
last_revision=new_revid)
# Merge shared upstream parent into the target merge branch. This
# creates revison L in the digram above.
conflicts = tree.merge_from_branch(tmp_target_utree.branch)
if conflicts > 0:
raise SharedUpstreamConflictsWithTargetPackaging()
else:
tree.commit('Merging shared upstream rev into target branch.')
finally:
tmp_target_utree.unlock()
finally:
shutil.rmtree(tempdir)
finally:
tree.unlock()
finally:
source.unlock()
return (upstreams_diverged, t_upstream_reverted)
|