Fix compatibility with bzr 2.5.
[bzr-fastimport.git] / branch_updater.py
blobfcb13d5f5a5b74e61ded490cc9bcd1dee312cac8
1 # Copyright (C) 2009 Canonical Ltd
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 """An object that updates a bunch of branches based on data imported."""
18 from operator import itemgetter
20 from bzrlib import bzrdir, errors, osutils, transport
21 from bzrlib.trace import show_error, note
23 from bzrlib.plugins.fastimport.helpers import (
24 best_format_for_objects_in_a_repository,
28 class BranchUpdater(object):
30 def __init__(self, repo, branch, cache_mgr, heads_by_ref, last_ref, tags):
31 """Create an object responsible for updating branches.
33 :param heads_by_ref: a dictionary where
34 names are git-style references like refs/heads/master;
35 values are one item lists of commits marks.
36 """
37 self.repo = repo
38 self.branch = branch
39 self.cache_mgr = cache_mgr
40 self.heads_by_ref = heads_by_ref
41 self.last_ref = last_ref
42 self.tags = tags
43 self._branch_format = \
44 best_format_for_objects_in_a_repository(repo)
46 def update(self):
47 """Update the Bazaar branches and tips matching the heads.
49 If the repository is shared, this routine creates branches
50 as required. If it isn't, warnings are produced about the
51 lost of information.
53 :return: updated, lost_heads where
54 updated = the list of branches updated ('trunk' is first)
55 lost_heads = a list of (bazaar-name,revision) for branches that
56 would have been created had the repository been shared
57 """
58 updated = []
59 branch_tips, lost_heads = self._get_matching_branches()
60 for br, tip in branch_tips:
61 if self._update_branch(br, tip):
62 updated.append(br)
63 return updated, lost_heads
65 def _get_matching_branches(self):
66 """Get the Bazaar branches.
68 :return: branch_tips, lost_heads where
69 branch_tips = a list of (branch,tip) tuples for branches. The
70 first tip is the 'trunk'.
71 lost_heads = a list of (bazaar-name,revision) for branches that
72 would have been created had the repository been shared and
73 everything succeeded
74 """
75 branch_tips = []
76 lost_heads = []
77 ref_names = self.heads_by_ref.keys()
78 if self.branch is not None:
79 trunk = self.select_trunk(ref_names)
80 default_tip = self.heads_by_ref[trunk][0]
81 branch_tips.append((self.branch, default_tip))
82 ref_names.remove(trunk)
84 # Convert the reference names into Bazaar speak. If we haven't
85 # already put the 'trunk' first, do it now.
86 git_to_bzr_map = {}
87 for ref_name in ref_names:
88 git_to_bzr_map[ref_name] = self.cache_mgr.branch_mapper.git_to_bzr(ref_name)
89 if ref_names and self.branch is None:
90 trunk = self.select_trunk(ref_names)
91 git_bzr_items = [(trunk, git_to_bzr_map[trunk])]
92 del git_to_bzr_map[trunk]
93 else:
94 git_bzr_items = []
95 git_bzr_items.extend(sorted(git_to_bzr_map.items(), key=itemgetter(1)))
97 # Policy for locating branches
98 def dir_under_current(name, ref_name):
99 # Using the Bazaar name, get a directory under the current one
100 repo_base = self.repo.bzrdir.transport.base
101 return osutils.pathjoin(repo_base, "..", name)
102 def dir_sister_branch(name, ref_name):
103 # Using the Bazaar name, get a sister directory to the branch
104 return osutils.pathjoin(self.branch.base, "..", name)
105 if self.branch is not None:
106 dir_policy = dir_sister_branch
107 else:
108 dir_policy = dir_under_current
110 # Create/track missing branches
111 shared_repo = self.repo.is_shared()
112 for ref_name, name in git_bzr_items:
113 tip = self.heads_by_ref[ref_name][0]
114 if shared_repo:
115 location = dir_policy(name, ref_name)
116 try:
117 br = self.make_branch(location)
118 branch_tips.append((br,tip))
119 continue
120 except errors.BzrError, ex:
121 show_error("ERROR: failed to create branch %s: %s",
122 location, ex)
123 lost_head = self.cache_mgr.lookup_committish(tip)
124 lost_info = (name, lost_head)
125 lost_heads.append(lost_info)
126 return branch_tips, lost_heads
128 def select_trunk(self, ref_names):
129 """Given a set of ref names, choose one as the trunk."""
130 for candidate in ['refs/heads/master']:
131 if candidate in ref_names:
132 return candidate
133 # Use the last reference in the import stream
134 return self.last_ref
136 def make_branch(self, location):
137 """Make a branch in the repository if not already there."""
138 to_transport = transport.get_transport(location)
139 to_transport.create_prefix()
140 try:
141 return bzrdir.BzrDir.open(location).open_branch()
142 except errors.NotBranchError, ex:
143 return bzrdir.BzrDir.create_branch_convenience(location,
144 format=self._branch_format,
145 possible_transports=[to_transport])
147 def _update_branch(self, br, last_mark):
148 """Update a branch with last revision and tag information.
150 :return: whether the branch was changed or not
152 from fastimport.helpers import single_plural
153 last_rev_id = self.cache_mgr.lookup_committish(last_mark)
154 self.repo.lock_read()
155 try:
156 graph = self.repo.get_graph()
157 revno = graph.find_distance_to_null(last_rev_id, [])
158 finally:
159 self.repo.unlock()
160 existing_revno, existing_last_rev_id = br.last_revision_info()
161 changed = False
162 if revno != existing_revno or last_rev_id != existing_last_rev_id:
163 br.set_last_revision_info(revno, last_rev_id)
164 changed = True
165 # apply tags known in this branch
166 my_tags = {}
167 if self.tags:
168 ancestry = self.repo.get_ancestry(last_rev_id)
169 for tag,rev in self.tags.items():
170 if rev in ancestry:
171 my_tags[tag] = rev
172 if my_tags:
173 br.tags._set_tag_dict(my_tags)
174 changed = True
175 if changed:
176 tagno = len(my_tags)
177 note("\t branch %s now has %d %s and %d %s", br.nick,
178 revno, single_plural(revno, "revision", "revisions"),
179 tagno, single_plural(tagno, "tag", "tags"))
180 return changed