Fix incremental imports from incremental input streams.
[bzr-fastimport.git] / branch_updater.py
blob0f9b7be8ec39bbd01a9dee9e3230ff1a130c0f0f
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, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 """An object that updates a bunch of branches based on data imported."""
19 from operator import itemgetter
21 from bzrlib import bzrdir, errors, osutils, transport
22 from bzrlib.trace import error, note
24 from bzrlib.plugins.fastimport.helpers import (
25 best_format_for_objects_in_a_repository,
29 class BranchUpdater(object):
31 def __init__(self, repo, branch, cache_mgr, heads_by_ref, last_ref, tags):
32 """Create an object responsible for updating branches.
34 :param heads_by_ref: a dictionary where
35 names are git-style references like refs/heads/master;
36 values are one item lists of commits marks.
37 """
38 self.repo = repo
39 self.branch = branch
40 self.cache_mgr = cache_mgr
41 self.heads_by_ref = heads_by_ref
42 self.last_ref = last_ref
43 self.tags = tags
44 self._branch_format = \
45 best_format_for_objects_in_a_repository(repo)
47 def update(self):
48 """Update the Bazaar branches and tips matching the heads.
50 If the repository is shared, this routine creates branches
51 as required. If it isn't, warnings are produced about the
52 lost of information.
54 :return: updated, lost_heads where
55 updated = the list of branches updated ('trunk' is first)
56 lost_heads = a list of (bazaar-name,revision) for branches that
57 would have been created had the repository been shared
58 """
59 updated = []
60 branch_tips, lost_heads = self._get_matching_branches()
61 for br, tip in branch_tips:
62 if self._update_branch(br, tip):
63 updated.append(br)
64 return updated, lost_heads
66 def _get_matching_branches(self):
67 """Get the Bazaar branches.
69 :return: branch_tips, lost_heads where
70 branch_tips = a list of (branch,tip) tuples for branches. The
71 first tip is the 'trunk'.
72 lost_heads = a list of (bazaar-name,revision) for branches that
73 would have been created had the repository been shared and
74 everything succeeded
75 """
76 branch_tips = []
77 lost_heads = []
78 ref_names = self.heads_by_ref.keys()
79 if self.branch is not None:
80 trunk = self.select_trunk(ref_names)
81 default_tip = self.heads_by_ref[trunk][0]
82 branch_tips.append((self.branch, default_tip))
83 ref_names.remove(trunk)
85 # Convert the reference names into Bazaar speak. If we haven't
86 # already put the 'trunk' first, do it now.
87 git_to_bzr_map = {}
88 for ref_name in ref_names:
89 git_to_bzr_map[ref_name] = self.cache_mgr.branch_mapper.git_to_bzr(ref_name)
90 if ref_names and self.branch is None:
91 trunk = self.select_trunk(ref_names)
92 git_bzr_items = [(trunk, git_to_bzr_map[trunk])]
93 del git_to_bzr_map[trunk]
94 else:
95 git_bzr_items = []
96 git_bzr_items.extend(sorted(git_to_bzr_map.items(), key=itemgetter(1)))
98 # Policy for locating branches
99 def dir_under_current(name, ref_name):
100 # Using the Bazaar name, get a directory under the current one
101 repo_base = self.repo.bzrdir.transport.base
102 return osutils.pathjoin(repo_base, "..", name)
103 def dir_sister_branch(name, ref_name):
104 # Using the Bazaar name, get a sister directory to the branch
105 return osutils.pathjoin(self.branch.base, "..", name)
106 if self.branch is not None:
107 dir_policy = dir_sister_branch
108 else:
109 dir_policy = dir_under_current
111 # Create/track missing branches
112 shared_repo = self.repo.is_shared()
113 for ref_name, name in git_bzr_items:
114 tip = self.heads_by_ref[ref_name][0]
115 if shared_repo:
116 location = dir_policy(name, ref_name)
117 try:
118 br = self.make_branch(location)
119 branch_tips.append((br,tip))
120 continue
121 except errors.BzrError, ex:
122 error("ERROR: failed to create branch %s: %s",
123 location, ex)
124 lost_head = self.cache_mgr.lookup_committish(tip)
125 lost_info = (name, lost_head)
126 lost_heads.append(lost_info)
127 return branch_tips, lost_heads
129 def select_trunk(self, ref_names):
130 """Given a set of ref names, choose one as the trunk."""
131 for candidate in ['refs/heads/master']:
132 if candidate in ref_names:
133 return candidate
134 # Use the last reference in the import stream
135 return self.last_ref
137 def make_branch(self, location):
138 """Make a branch in the repository if not already there."""
139 to_transport = transport.get_transport(location)
140 to_transport.create_prefix()
141 try:
142 return bzrdir.BzrDir.open(location).open_branch()
143 except errors.NotBranchError, ex:
144 return bzrdir.BzrDir.create_branch_convenience(location,
145 format=self._branch_format,
146 possible_transports=[to_transport])
148 def _update_branch(self, br, last_mark):
149 """Update a branch with last revision and tag information.
151 :return: whether the branch was changed or not
153 from fastimport.helpers import single_plural
154 last_rev_id = self.cache_mgr.lookup_committish(last_mark)
155 revs = list(self.repo.iter_reverse_revision_history(last_rev_id))
156 revno = len(revs)
157 existing_revno, existing_last_rev_id = br.last_revision_info()
158 changed = False
159 if revno != existing_revno or last_rev_id != existing_last_rev_id:
160 br.set_last_revision_info(revno, last_rev_id)
161 changed = True
162 # apply tags known in this branch
163 my_tags = {}
164 if self.tags:
165 ancestry = self.repo.get_ancestry(last_rev_id)
166 for tag,rev in self.tags.items():
167 if rev in ancestry:
168 my_tags[tag] = rev
169 if my_tags:
170 br.tags._set_tag_dict(my_tags)
171 changed = True
172 if changed:
173 tagno = len(my_tags)
174 note("\t branch %s now has %d %s and %d %s", br.nick,
175 revno, single_plural(revno, "revision", "revisions"),
176 tagno, single_plural(tagno, "tag", "tags"))
177 return changed