/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
1
# Copyright (C) 2008 Canonical Ltd
2
#
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.
7
#
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.
12
#
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
16
17
"""Import processor that supports all Bazaar repository formats."""
18
19
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
20
import re
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
21
import time
0.64.5 by Ian Clatworthy
first cut at generic processing method
22
from bzrlib import (
0.64.37 by Ian Clatworthy
create branches as required
23
    builtins,
24
    bzrdir,
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
25
    delta,
0.64.5 by Ian Clatworthy
first cut at generic processing method
26
    errors,
27
    generate_ids,
28
    inventory,
29
    lru_cache,
30
    osutils,
0.64.26 by Ian Clatworthy
more progress reporting tweaks
31
    progress,
0.64.5 by Ian Clatworthy
first cut at generic processing method
32
    revision,
33
    revisiontree,
0.64.37 by Ian Clatworthy
create branches as required
34
    transport,
0.64.5 by Ian Clatworthy
first cut at generic processing method
35
    )
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
36
from bzrlib.trace import (
37
    note,
38
    warning,
0.64.37 by Ian Clatworthy
create branches as required
39
    error,
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
40
    )
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
41
import bzrlib.util.configobj.configobj as configobj
0.64.5 by Ian Clatworthy
first cut at generic processing method
42
from bzrlib.plugins.fastimport import (
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
43
    helpers,
0.64.5 by Ian Clatworthy
first cut at generic processing method
44
    processor,
45
    revisionloader,
46
    )
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
47
48
0.64.41 by Ian Clatworthy
update multiple working trees if requested
49
# How many commits before automatically reporting progress
50
_DEFAULT_AUTO_PROGRESS = 1000
51
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
52
# How many commits before automatically checkpointing
53
_DEFAULT_AUTO_CHECKPOINT = 10000
54
0.64.44 by Ian Clatworthy
smart caching of serialised inventories
55
# How many inventories to cache
56
_DEFAULT_INV_CACHE_SIZE = 10
57
0.64.41 by Ian Clatworthy
update multiple working trees if requested
58
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
59
class GenericProcessor(processor.ImportProcessor):
60
    """An import processor that handles basic imports.
61
62
    Current features supported:
63
0.64.16 by Ian Clatworthy
safe processing tweaks
64
    * blobs are cached in memory
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
65
    * files and symlinks commits are supported
66
    * checkpoints automatically happen at a configurable frequency
67
      over and above the stream requested checkpoints
68
    * timestamped progress reporting, both automatic and stream requested
0.64.41 by Ian Clatworthy
update multiple working trees if requested
69
    * LATER: reset support, tags for each branch
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
70
    * some basic statistics are dumped on completion.
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
71
72
    Here are the supported parameters:
73
0.64.38 by Ian Clatworthy
clean-up doc ready for initial release
74
    * info - name of a hints file holding the analysis generated
75
      by running the fast-import-info processor in verbose mode. When
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
76
      importing large repositories, this parameter is needed so
77
      that the importer knows what blobs to intelligently cache.
78
0.64.41 by Ian Clatworthy
update multiple working trees if requested
79
    * trees - update the working trees before completing.
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
80
      By default, the importer updates the repository
81
      and branches and the user needs to run 'bzr update' for the
0.64.41 by Ian Clatworthy
update multiple working trees if requested
82
      branches of interest afterwards.
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
83
84
    * checkpoint - automatically checkpoint every n commits over and
85
      above any checkpoints contained in the import stream.
86
      The default is 10000.
87
0.64.44 by Ian Clatworthy
smart caching of serialised inventories
88
    * count - only import this many commits then exit. If not set
89
      or negative, all commits are imported.
90
    
91
    * inv-cache - number of inventories to cache.
92
      If not set, the default is 10.
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
93
94
    * experimental - enable experimental mode, i.e. use features
95
      not yet fully tested.
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
96
    """
97
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
98
    known_params = [
99
        'info',
100
        'trees',
101
        'checkpoint',
102
        'count',
103
        'inv-cache',
104
        'experimental',
105
        ]
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
106
107
    def note(self, msg, *args):
108
        """Output a note but timestamp it."""
109
        msg = "%s %s" % (self._time_of_day(), msg)
110
        note(msg, *args)
111
112
    def warning(self, msg, *args):
113
        """Output a warning but timestamp it."""
0.64.34 by Ian Clatworthy
report lost branches
114
        msg = "%s WARNING: %s" % (self._time_of_day(), msg)
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
115
        warning(msg, *args)
116
117
    def _time_of_day(self):
118
        """Time of day as a string."""
119
        # Note: this is a separate method so tests can patch in a fixed value
120
        return time.strftime("%H:%M:%S")
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
121
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
122
    def pre_process(self):
0.64.26 by Ian Clatworthy
more progress reporting tweaks
123
        self._start_time = time.time()
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
124
        self._load_info_and_params()
0.64.44 by Ian Clatworthy
smart caching of serialised inventories
125
        self.cache_mgr = GenericCacheManager(self.info, self.verbose,
126
            self.inventory_cache_size)
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
127
        self.init_stats()
128
129
        # mapping of tag name to revision_id
130
        self.tags = {}
131
132
        # Create a write group. This is committed at the end of the import.
133
        # Checkpointing closes the current one and starts a new one.
134
        self.repo.start_write_group()
135
0.64.44 by Ian Clatworthy
smart caching of serialised inventories
136
        # Turn on caching for the inventory versioned file
137
        inv_vf = self.repo.get_inventory_weave()
138
        inv_vf.enable_cache()
139
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
140
    def _load_info_and_params(self):
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
141
        self._experimental = self.params.get('experimental', False)
142
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
143
        # Load the info file, if any
144
        info_path = self.params.get('info')
145
        if info_path is not None:
146
            self.info = configobj.ConfigObj(info_path)
147
        else:
148
            self.info = None
149
0.64.41 by Ian Clatworthy
update multiple working trees if requested
150
        # Decide how often to automatically report progress
151
        # (not a parameter yet)
152
        self.progress_every = _DEFAULT_AUTO_PROGRESS
153
        if self.verbose:
154
            self.progress_every = self.progress_every / 10
155
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
156
        # Decide how often to automatically checkpoint
157
        self.checkpoint_every = int(self.params.get('checkpoint',
158
            _DEFAULT_AUTO_CHECKPOINT))
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
159
0.64.44 by Ian Clatworthy
smart caching of serialised inventories
160
        # Decide how big to make the inventory cache
161
        self.inventory_cache_size = int(self.params.get('inv-cache',
162
            _DEFAULT_INV_CACHE_SIZE))
163
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
164
        # Find the maximum number of commits to import (None means all)
165
        # and prepare progress reporting. Just in case the info file
166
        # has an outdated count of commits, we store the max counts
167
        # at which we need to terminate separately to the total used
168
        # for progress tracking.
169
        try:
170
            self.max_commits = int(self.params['count'])
0.64.38 by Ian Clatworthy
clean-up doc ready for initial release
171
            if self.max_commits < 0:
172
                self.max_commits = None
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
173
        except KeyError:
174
            self.max_commits = None
0.64.25 by Ian Clatworthy
slightly better progress reporting
175
        if self.info is not None:
176
            self.total_commits = int(self.info['Command counts']['commit'])
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
177
            if (self.max_commits is not None and
178
                self.total_commits > self.max_commits):
179
                self.total_commits = self.max_commits
0.64.25 by Ian Clatworthy
slightly better progress reporting
180
        else:
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
181
            self.total_commits = self.max_commits
0.64.25 by Ian Clatworthy
slightly better progress reporting
182
0.64.27 by Ian Clatworthy
1st cut at performance tuning
183
184
    def _process(self, command_iter):
185
        # if anything goes wrong, abort the write group if any
186
        try:
187
            processor.ImportProcessor._process(self, command_iter)
188
        except:
189
            if self.repo is not None and self.repo.is_in_write_group():
190
                self.repo.abort_write_group()
191
            raise
192
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
193
    def post_process(self):
0.64.27 by Ian Clatworthy
1st cut at performance tuning
194
        # Commit the current write group.
195
        self.repo.commit_write_group()
196
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
197
        # Update the branches
198
        self.note("Updating branch information ...")
0.64.37 by Ian Clatworthy
create branches as required
199
        updater = GenericBranchUpdater(self.repo, self.branch, self.cache_mgr,
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
200
            helpers.invert_dict(self.cache_mgr.heads),
201
            self.cache_mgr.last_ref)
0.64.34 by Ian Clatworthy
report lost branches
202
        branches_updated, branches_lost = updater.update()
203
        self._branch_count = len(branches_updated)
204
205
        # Tell the user about branches that were not created
206
        if branches_lost:
0.64.37 by Ian Clatworthy
create branches as required
207
            if not self.repo.is_shared():
208
                self.warning("Cannot import multiple branches into "
209
                    "an unshared repository")
210
            self.warning("Not creating branches for these head revisions:")
0.64.34 by Ian Clatworthy
report lost branches
211
            for lost_info in branches_lost:
212
                head_revision = lost_info[1]
213
                branch_name = lost_info[0]
214
                note("\t %s = %s", head_revision, branch_name)
215
216
        # Update the working trees as requested and dump stats
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
217
        self._tree_count = 0
0.64.34 by Ian Clatworthy
report lost branches
218
        remind_about_update = True
0.64.38 by Ian Clatworthy
clean-up doc ready for initial release
219
        if self.params.get('trees', False):
0.64.41 by Ian Clatworthy
update multiple working trees if requested
220
            trees = self._get_working_trees(branches_updated)
221
            if trees:
222
                self.note("Updating the working trees ...")
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
223
                if self.verbose:
224
                    report = delta._ChangeReporter()
225
                else:
226
                    reporter = None
0.64.41 by Ian Clatworthy
update multiple working trees if requested
227
                for wt in trees:
228
                    wt.update(reporter)
229
                    self._tree_count += 1
0.64.34 by Ian Clatworthy
report lost branches
230
                remind_about_update = False
0.64.41 by Ian Clatworthy
update multiple working trees if requested
231
            else:
232
                self.warning("No working trees available to update")
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
233
        self.dump_stats()
0.64.34 by Ian Clatworthy
report lost branches
234
        if remind_about_update:
0.64.41 by Ian Clatworthy
update multiple working trees if requested
235
            self.note("To refresh the working tree for a branch, "
236
                "use 'bzr update'")
237
238
    def _get_working_trees(self, branches):
239
        """Get the working trees for branches in the repository."""
240
        result = []
241
        wt_expected = self.repo.make_working_trees()
242
        for br in branches:
243
            if br == self.branch and br is not None:
244
                wt = self.working_tree
245
            elif wt_expected:
246
                try:
247
                    wt = br.bzrdir.open_workingtree()
248
                except errors.NoWorkingTree:
249
                    self.warning("No working tree for branch %s", br)
250
                    continue
251
            else:
252
                continue
253
            result.append(wt)
254
        return result
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
255
256
    def init_stats(self):
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
257
        self._revision_count = 0
0.64.5 by Ian Clatworthy
first cut at generic processing method
258
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
259
    def dump_stats(self):
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
260
        time_required = progress.str_tdelta(time.time() - self._start_time)
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
261
        rc = self._revision_count
262
        bc = self._branch_count
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
263
        wtc = self._tree_count
264
        self.note("Imported %d %s, updating %d %s and %d %s in %s",
0.64.32 by Ian Clatworthy
move single_plural into helpers
265
            rc, helpers.single_plural(rc, "revision", "revisions"),
266
            bc, helpers.single_plural(bc, "branch", "branches"),
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
267
            wtc, helpers.single_plural(wtc, "tree", "trees"),
268
            time_required)
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
269
0.64.5 by Ian Clatworthy
first cut at generic processing method
270
    def blob_handler(self, cmd):
271
        """Process a BlobCommand."""
272
        if cmd.mark is not None:
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
273
            dataref = cmd.id
0.64.5 by Ian Clatworthy
first cut at generic processing method
274
        else:
275
            dataref = osutils.sha_strings(cmd.data)
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
276
        self.cache_mgr.store_blob(dataref, cmd.data)
0.64.5 by Ian Clatworthy
first cut at generic processing method
277
278
    def checkpoint_handler(self, cmd):
279
        """Process a CheckpointCommand."""
0.64.27 by Ian Clatworthy
1st cut at performance tuning
280
        # Commit the current write group and start a new one
281
        self.repo.commit_write_group()
282
        self.repo.start_write_group()
0.64.5 by Ian Clatworthy
first cut at generic processing method
283
284
    def commit_handler(self, cmd):
285
        """Process a CommitCommand."""
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
286
        # 'Commit' the revision
0.64.7 by Ian Clatworthy
start of multiple commit handling
287
        handler = GenericCommitHandler(cmd, self.repo, self.cache_mgr,
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
288
            self.verbose, self._experimental)
0.64.27 by Ian Clatworthy
1st cut at performance tuning
289
        handler.process()
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
290
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
291
        # Update caches
292
        self.cache_mgr.revision_ids[cmd.id] = handler.revision_id
293
        self.cache_mgr.last_ids[cmd.ref] = cmd.id
294
        self.cache_mgr.last_ref = cmd.ref
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
295
296
        # Report progress
0.64.27 by Ian Clatworthy
1st cut at performance tuning
297
        self._revision_count += 1
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
298
        self.report_progress("(%s)" % cmd.id)
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
299
300
        # Check if we should finish up or automatically checkpoint
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
301
        if (self.max_commits is not None and
302
            self._revision_count >= self.max_commits):
303
            self.note("stopping after reaching requested count of commits")
304
            self.finished = True
305
        elif self._revision_count % self.checkpoint_every == 0:
306
            self.note("%d commits - automatic checkpoint triggered",
307
                self._revision_count)
308
            self.checkpoint_handler(None)
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
309
0.64.25 by Ian Clatworthy
slightly better progress reporting
310
    def report_progress(self, details=''):
311
        # TODO: use a progress bar with ETA enabled
0.64.41 by Ian Clatworthy
update multiple working trees if requested
312
        if self._revision_count % self.progress_every == 0:
0.64.26 by Ian Clatworthy
more progress reporting tweaks
313
            if self.total_commits is not None:
314
                counts = "%d/%d" % (self._revision_count, self.total_commits)
315
                eta = progress.get_eta(self._start_time, self._revision_count,
316
                    self.total_commits)
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
317
                eta_str = progress.str_tdelta(eta)
318
                if eta_str.endswith('--'):
319
                    eta_str = ''
320
                else:
321
                    eta_str = '[%s] ' % eta_str
0.64.26 by Ian Clatworthy
more progress reporting tweaks
322
            else:
323
                counts = "%d" % (self._revision_count,)
324
                eta_str = ''
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
325
            self.note("%s commits processed %s%s" % (counts, eta_str, details))
0.64.25 by Ian Clatworthy
slightly better progress reporting
326
0.64.1 by Ian Clatworthy
1st cut: gfi parser + --info processing method
327
    def progress_handler(self, cmd):
328
        """Process a ProgressCommand."""
0.64.34 by Ian Clatworthy
report lost branches
329
        # We could use a progress bar here instead
0.64.28 by Ian Clatworthy
checkpoint and count params to generic processor
330
        self.note("progress %s" % (cmd.message,))
0.64.5 by Ian Clatworthy
first cut at generic processing method
331
332
    def reset_handler(self, cmd):
333
        """Process a ResetCommand."""
0.64.12 by Ian Clatworthy
lightweight tags, filter processor and param validation
334
        if cmd.ref.startswith('refs/tags/'):
335
            self._set_tag(cmd.ref[len('refs/tags/'):], cmd.from_)
336
        else:
0.64.41 by Ian Clatworthy
update multiple working trees if requested
337
            self.warning("resets are not supported yet"
0.64.16 by Ian Clatworthy
safe processing tweaks
338
                " - ignoring reset of '%s'", cmd.ref)
0.64.5 by Ian Clatworthy
first cut at generic processing method
339
340
    def tag_handler(self, cmd):
341
        """Process a TagCommand."""
0.64.12 by Ian Clatworthy
lightweight tags, filter processor and param validation
342
        self._set_tag(cmd.id, cmd.from_)
343
344
    def _set_tag(self, name, from_):
345
        """Define a tag given a name an import 'from' reference."""
346
        bzr_tag_name = name.decode('utf-8', 'replace')
347
        bzr_rev_id = self.cache_mgr.revision_ids[from_]
0.64.11 by Ian Clatworthy
tag support
348
        self.tags[bzr_tag_name] = bzr_rev_id
0.64.5 by Ian Clatworthy
first cut at generic processing method
349
350
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
351
class GenericCacheManager(object):
352
    """A manager of caches for the GenericProcessor."""
353
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
354
    def __init__(self, info, verbose=False, inventory_cache_size=10):
355
        """Create a manager of caches.
356
357
        :param info: a ConfigObj holding the output from
358
            the --info processor, or None if no hints are available
359
        """
360
        self.verbose = verbose
361
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
362
        # dataref -> data. datref is either :mark or the sha-1.
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
363
        # Sticky blobs aren't removed after being referenced.
364
        self._blobs = {}
365
        self._sticky_blobs = {}
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
366
367
        # revision-id -> Inventory cache
368
        # these are large and we probably don't need too many as
369
        # most parents are recent in history
370
        self.inventories = lru_cache.LRUCache(inventory_cache_size)
371
372
        # import-ref -> revision-id lookup table
373
        # we need to keep all of these but they are small
374
        self.revision_ids = {}
375
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
376
        # path -> file-ids - as generated
0.64.14 by Ian Clatworthy
commit of modified files working
377
        self.file_ids = {}
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
378
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
379
        # Head tracking: last ref, last id per ref & map of commit mark to ref
380
        self.last_ref = None
381
        self.last_ids = {}
382
        self.heads = {}
383
0.64.44 by Ian Clatworthy
smart caching of serialised inventories
384
        # Cache of recent serialised inventories
385
        self.inv_parent_texts = lru_cache.LRUCache(inventory_cache_size)
386
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
387
        # Work out the blobs to make sticky - None means all
0.64.25 by Ian Clatworthy
slightly better progress reporting
388
        self._blobs_to_keep = None
389
        if info is not None:
390
            try:
391
                self._blobs_to_keep = info['Blob usage tracking']['multi']
392
            except KeyError:
393
                # info not in file - possible when no blobs used
394
                pass
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
395
396
    def store_blob(self, id, data):
397
        """Store a blob of data."""
398
        if (self._blobs_to_keep is None or data == '' or
399
            id in self._blobs_to_keep):
400
            self._sticky_blobs[id] = data
401
        else:
402
            self._blobs[id] = data
403
404
    def fetch_blob(self, id):
405
        """Fetch a blob of data."""
406
        try:
407
            return self._sticky_blobs[id]
408
        except KeyError:
409
            return self._blobs.pop(id)
410
0.64.16 by Ian Clatworthy
safe processing tweaks
411
    def _delete_path(self, path):
412
        """Remove a path from caches."""
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
413
        # we actually want to remember what file-id we gave a path,
414
        # even when that file is deleted, so doing nothing is correct
415
        pass
0.64.16 by Ian Clatworthy
safe processing tweaks
416
417
    def _rename_path(self, old_path, new_path):
418
        """Rename a path in the caches."""
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
419
        # we actually want to remember what file-id we gave a path,
420
        # even when that file is renamed, so both paths should have
421
        # the same value and we don't delete any information
0.64.16 by Ian Clatworthy
safe processing tweaks
422
        self.file_ids[new_path] = self.file_ids[old_path]
423
424
0.64.5 by Ian Clatworthy
first cut at generic processing method
425
class GenericCommitHandler(processor.CommitHandler):
426
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
427
    def __init__(self, command, repo, cache_mgr, verbose=False,
428
        _experimental=False):
0.64.5 by Ian Clatworthy
first cut at generic processing method
429
        processor.CommitHandler.__init__(self, command)
430
        self.repo = repo
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
431
        self.cache_mgr = cache_mgr
0.64.14 by Ian Clatworthy
commit of modified files working
432
        self.verbose = verbose
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
433
        self._experimental = _experimental
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
434
        # smart loader that uses these caches
0.64.44 by Ian Clatworthy
smart caching of serialised inventories
435
        self.loader = revisionloader.ImportRevisionLoader(repo,
436
            lambda revision_ids: self._get_inventories(revision_ids),
437
            cache_mgr.inv_parent_texts)
438
        #self.loader = revisionloader.RevisionLoader(repo,
439
        #    lambda revision_ids: self._get_inventories(revision_ids))
0.64.5 by Ian Clatworthy
first cut at generic processing method
440
0.64.43 by Ian Clatworthy
verbose mode cleanup
441
    def note(self, msg, *args):
442
        """Output a note but add context."""
443
        msg = "%s (%s)" % (msg, self.command.id)
444
        note(msg, *args)
445
446
    def warning(self, msg, *args):
447
        """Output a warning but add context."""
448
        msg = "WARNING: %s (%s)" % (msg, self.command.id)
449
        warning(msg, *args)
450
0.64.5 by Ian Clatworthy
first cut at generic processing method
451
    def pre_process_files(self):
452
        """Prepare for committing."""
453
        self.revision_id = self.gen_revision_id()
454
        self.inv_delta = []
455
        # cache of texts for this commit, indexed by file-id
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
456
        self.lines_for_commit = {}
0.64.5 by Ian Clatworthy
first cut at generic processing method
457
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
458
        # Work out the true set of parents
459
        cmd = self.command
460
        if cmd.mark is None:
461
            last_id = self.cache_mgr.last_ids.get(cmd.ref)
462
            if last_id is not None:
463
                parents = [last_id]
464
            else:
465
                parents = []
466
        else:
467
            parents = cmd.parents
468
469
        # Track the heads
470
        for parent in parents:
471
            try:
472
                del self.cache_mgr.heads[parent]
473
            except KeyError:
0.64.42 by Ian Clatworthy
removed parent not found warnings as not a problem
474
                # it's ok if the parent isn't there - another
475
                # commit may have already removed it
476
                pass
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
477
        self.cache_mgr.heads[cmd.id] = cmd.ref
478
0.64.14 by Ian Clatworthy
commit of modified files working
479
        # Get the parent inventories
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
480
        if parents:
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
481
            self.parents = [self.cache_mgr.revision_ids[p]
0.64.36 by Ian Clatworthy
fix head tracking when unmarked commits used
482
                for p in parents]
0.64.7 by Ian Clatworthy
start of multiple commit handling
483
        else:
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
484
            self.parents = []
0.64.7 by Ian Clatworthy
start of multiple commit handling
485
0.64.14 by Ian Clatworthy
commit of modified files working
486
        # Seed the inventory from the previous one
487
        if len(self.parents) == 0:
488
            self.inventory = self.gen_initial_inventory()
0.64.5 by Ian Clatworthy
first cut at generic processing method
489
        else:
490
            # use the bzr_revision_id to lookup the inv cache
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
491
            inv = self.get_inventory(self.parents[0])
492
            # TODO: Shallow copy - deep inventory copying is expensive
493
            self.inventory = inv.copy()
0.64.13 by Ian Clatworthy
commit of new files working
494
        if not self.repo.supports_rich_root():
495
            # In this repository, root entries have no knit or weave. When
496
            # serializing out to disk and back in, root.revision is always
497
            # the new revision_id.
0.64.14 by Ian Clatworthy
commit of modified files working
498
            self.inventory.root.revision = self.revision_id
0.64.5 by Ian Clatworthy
first cut at generic processing method
499
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
500
        # directory-path -> inventory-entry for current inventory
501
        self.directory_entries = dict(self.inventory.directories())
502
0.64.14 by Ian Clatworthy
commit of modified files working
503
    def post_process_files(self):
504
        """Save the revision."""
505
        self.inventory.apply_delta(self.inv_delta)
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
506
        self.cache_mgr.inventories[self.revision_id] = self.inventory
0.64.5 by Ian Clatworthy
first cut at generic processing method
507
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
508
        # Load the revision into the repository
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
509
        rev_props = {}
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
510
        committer = self.command.committer
511
        who = "%s <%s>" % (committer[0],committer[1])
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
512
        author = self.command.author
513
        if author is not None:
514
            author_id = "%s <%s>" % (author[0],author[1])
515
            if author_id != who:
516
                rev_props['author'] = author_id
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
517
        rev = revision.Revision(
518
           timestamp=committer[2],
519
           timezone=committer[3],
520
           committer=who,
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
521
           message=self._escape_commit_message(self.command.message),
522
           revision_id=self.revision_id,
523
           properties=rev_props,
524
           parent_ids=self.parents)
0.64.14 by Ian Clatworthy
commit of modified files working
525
        self.loader.load(rev, self.inventory, None,
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
526
            lambda file_id: self._get_lines(file_id))
527
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
528
    def _escape_commit_message(self, message):
529
        """Replace xml-incompatible control characters."""
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
530
        # It's crap that we need to do this at this level (but we do)
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
531
        # Code copied from bzrlib.commit.
532
        
533
        # Python strings can include characters that can't be
534
        # represented in well-formed XML; escape characters that
535
        # aren't listed in the XML specification
536
        # (http://www.w3.org/TR/REC-xml/#NT-Char).
537
        message, _ = re.subn(
538
            u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
539
            lambda match: match.group(0).encode('unicode_escape'),
540
            message)
541
        return message
0.64.5 by Ian Clatworthy
first cut at generic processing method
542
543
    def modify_handler(self, filecmd):
544
        if filecmd.dataref is not None:
0.64.24 by Ian Clatworthy
smart blob caching using analysis done by --info
545
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
0.64.5 by Ian Clatworthy
first cut at generic processing method
546
        else:
547
            data = filecmd.data
548
        self._modify_inventory(filecmd.path, filecmd.kind,
549
            filecmd.is_executable, data)
550
551
    def delete_handler(self, filecmd):
552
        path = filecmd.path
0.64.21 by Ian Clatworthy
fix one inventory lookup bug
553
        try:
554
            del self.inventory[self.bzr_file_id(path)]
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
555
        except KeyError:
556
            self.warning("ignoring delete of %s as not in inventory", path)
0.64.21 by Ian Clatworthy
fix one inventory lookup bug
557
        except errors.NoSuchId:
0.64.43 by Ian Clatworthy
verbose mode cleanup
558
            self.warning("ignoring delete of %s as not in inventory", path)
0.64.45 by Ian Clatworthy
fix compatibility with Python 2.4
559
        try:
560
            self.cache_mgr._delete_path(path)
561
        except KeyError:
562
            pass
0.64.5 by Ian Clatworthy
first cut at generic processing method
563
564
    def copy_handler(self, filecmd):
565
        raise NotImplementedError(self.copy_handler)
566
567
    def rename_handler(self, filecmd):
0.64.16 by Ian Clatworthy
safe processing tweaks
568
        old_path = filecmd.old_path
569
        new_path = filecmd.new_path
570
        file_id = self.bzr_file_id(old_path)
571
        ie = self.inventory[file_id]
572
        self.inv_delta.append((old_path, new_path, file_id, ie))
573
        self.cache_mgr._rename_path(old_path, new_path)
0.64.5 by Ian Clatworthy
first cut at generic processing method
574
575
    def deleteall_handler(self, filecmd):
576
        raise NotImplementedError(self.deleteall_handler)
577
0.64.16 by Ian Clatworthy
safe processing tweaks
578
    def bzr_file_id_and_new(self, path):
579
        """Get a Bazaar file identifier and new flag for a path.
580
        
0.64.17 by Ian Clatworthy
escape commit messages, diff author to committer and cache fixes
581
        :return: file_id, is_new where
582
          is_new = True if the file_id is newly created
0.64.16 by Ian Clatworthy
safe processing tweaks
583
        """
584
        try:
585
            return self.cache_mgr.file_ids[path], False
586
        except KeyError:
587
            id = generate_ids.gen_file_id(path)
588
            self.cache_mgr.file_ids[path] = id
589
            return id, True
590
0.64.5 by Ian Clatworthy
first cut at generic processing method
591
    def bzr_file_id(self, path):
0.64.14 by Ian Clatworthy
commit of modified files working
592
        """Get a Bazaar file identifier for a path."""
0.64.16 by Ian Clatworthy
safe processing tweaks
593
        return self.bzr_file_id_and_new(path)[0]
0.64.5 by Ian Clatworthy
first cut at generic processing method
594
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
595
    def gen_initial_inventory(self):
596
        """Generate an inventory for a parentless revision."""
597
        inv = inventory.Inventory(revision_id=self.revision_id)
598
        return inv
599
0.64.5 by Ian Clatworthy
first cut at generic processing method
600
    def gen_revision_id(self):
601
        """Generate a revision id.
602
603
        Subclasses may override this to produce deterministic ids say.
604
        """
605
        committer = self.command.committer
0.64.16 by Ian Clatworthy
safe processing tweaks
606
        # Perhaps 'who' being the person running the import is ok? If so,
607
        # it might be a bit quicker and give slightly better compression?
0.64.5 by Ian Clatworthy
first cut at generic processing method
608
        who = "%s <%s>" % (committer[0],committer[1])
609
        timestamp = committer[2]
610
        return generate_ids.gen_revision_id(who, timestamp)
611
0.64.7 by Ian Clatworthy
start of multiple commit handling
612
    def get_inventory(self, revision_id):
613
        """Get the inventory for a revision id."""
614
        try:
615
            inv = self.cache_mgr.inventories[revision_id]
616
        except KeyError:
0.64.43 by Ian Clatworthy
verbose mode cleanup
617
            if self.verbose:
618
                self.note("get_inventory cache miss for %s", revision_id)
0.64.7 by Ian Clatworthy
start of multiple commit handling
619
            # Not cached so reconstruct from repository
620
            inv = self.repo.revision_tree(revision_id).inventory
621
            self.cache_mgr.inventories[revision_id] = inv
622
        return inv
623
0.64.5 by Ian Clatworthy
first cut at generic processing method
624
    def _get_inventories(self, revision_ids):
625
        """Get the inventories for revision-ids.
626
        
627
        This is a callback used by the RepositoryLoader to
628
        speed up inventory reconstruction."""
629
        present = []
630
        inventories = []
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
631
        # If an inventory is in the cache, we assume it was
0.64.5 by Ian Clatworthy
first cut at generic processing method
632
        # successfully loaded into the repsoitory
633
        for revision_id in revision_ids:
634
            try:
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
635
                inv = self.cache_mgr.inventories[revision_id]
0.64.5 by Ian Clatworthy
first cut at generic processing method
636
                present.append(revision_id)
637
            except KeyError:
0.64.43 by Ian Clatworthy
verbose mode cleanup
638
                if self.verbose:
639
                    self.note("get_inventories cache miss for %s", revision_id)
0.64.5 by Ian Clatworthy
first cut at generic processing method
640
                # Not cached so reconstruct from repository
641
                if self.repo.has_revision(revision_id):
642
                    rev_tree = self.repo.revision_tree(revision_id)
643
                    present.append(revision_id)
644
                else:
645
                    rev_tree = self.repo.revision_tree(None)
646
                inv = rev_tree.inventory
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
647
                self.cache_mgr.inventories[revision_id] = inv
648
            inventories.append(inv)
0.64.5 by Ian Clatworthy
first cut at generic processing method
649
        return present, inventories
650
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
651
    def _get_lines(self, file_id):
652
        """Get the lines for a file-id."""
653
        return self.lines_for_commit[file_id]
0.64.5 by Ian Clatworthy
first cut at generic processing method
654
655
    def _modify_inventory(self, path, kind, is_executable, data):
656
        """Add to or change an item in the inventory."""
657
        # Create the new InventoryEntry
658
        basename, parent_ie = self._ensure_directory(path)
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
659
        file_id = self.bzr_file_id(path)
0.64.16 by Ian Clatworthy
safe processing tweaks
660
        ie = inventory.make_entry(kind, basename, parent_ie.file_id, file_id)
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
661
        ie.revision = self.revision_id
0.64.5 by Ian Clatworthy
first cut at generic processing method
662
        if isinstance(ie, inventory.InventoryFile):
663
            ie.executable = is_executable
0.64.13 by Ian Clatworthy
commit of new files working
664
            lines = osutils.split_lines(data)
665
            ie.text_sha1 = osutils.sha_strings(lines)
666
            ie.text_size = sum(map(len, lines))
0.64.6 by Ian Clatworthy
generic processing method working for one revision in one branch
667
            self.lines_for_commit[file_id] = lines
0.64.5 by Ian Clatworthy
first cut at generic processing method
668
        elif isinstance(ie, inventory.InventoryLnk):
669
            ie.symlink_target = data
670
        else:
671
            raise errors.BzrError("Cannot import items of kind '%s' yet" %
672
                (kind,))
673
0.64.16 by Ian Clatworthy
safe processing tweaks
674
        # Record this new inventory entry
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
675
        if file_id in self.inventory:
0.64.21 by Ian Clatworthy
fix one inventory lookup bug
676
            # HACK: no API for this (del+add does more than it needs to)
677
            self.inventory._byid[file_id] = ie
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
678
        else:
679
            self.inventory.add(ie)
0.64.5 by Ian Clatworthy
first cut at generic processing method
680
681
    def _ensure_directory(self, path):
682
        """Ensure that the containing directory exists for 'path'"""
683
        dirname, basename = osutils.split(path)
684
        if dirname == '':
685
            # the root node doesn't get updated
0.64.16 by Ian Clatworthy
safe processing tweaks
686
            return basename, self.inventory.root
0.64.5 by Ian Clatworthy
first cut at generic processing method
687
        try:
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
688
            ie = self.directory_entries[dirname]
0.64.5 by Ian Clatworthy
first cut at generic processing method
689
        except KeyError:
690
            # We will create this entry, since it doesn't exist
691
            pass
692
        else:
693
            return basename, ie
694
695
        # No directory existed, we will just create one, first, make sure
696
        # the parent exists
697
        dir_basename, parent_ie = self._ensure_directory(dirname)
698
        dir_file_id = self.bzr_file_id(dirname)
699
        ie = inventory.entry_factory['directory'](dir_file_id,
700
                                                  dir_basename,
701
                                                  parent_ie.file_id)
702
        ie.revision = self.revision_id
0.64.22 by Ian Clatworthy
fix more inventory lookup bugs
703
        self.directory_entries[dirname] = ie
0.64.16 by Ian Clatworthy
safe processing tweaks
704
        # There are no lines stored for a directory so
705
        # make sure the cache used by get_lines knows that
706
        self.lines_for_commit[dir_file_id] = []
0.64.47 by Ian Clatworthy
add option for enabling experimental stuff
707
        #print "adding dir for %s" % path
0.64.16 by Ian Clatworthy
safe processing tweaks
708
        self.inventory.add(ie)
0.64.5 by Ian Clatworthy
first cut at generic processing method
709
        return basename, ie
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
710
711
0.64.34 by Ian Clatworthy
report lost branches
712
class GenericBranchUpdater(object):
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
713
0.64.37 by Ian Clatworthy
create branches as required
714
    def __init__(self, repo, branch, cache_mgr, heads_by_ref, last_ref):
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
715
        """Create an object responsible for updating branches.
716
717
        :param heads_by_ref: a dictionary where
718
          names are git-style references like refs/heads/master;
719
          values are one item lists of commits marks.
720
        """
0.64.37 by Ian Clatworthy
create branches as required
721
        self.repo = repo
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
722
        self.branch = branch
723
        self.cache_mgr = cache_mgr
724
        self.heads_by_ref = heads_by_ref
725
        self.last_ref = last_ref
726
727
    def update(self):
728
        """Update the Bazaar branches and tips matching the heads.
729
730
        If the repository is shared, this routine creates branches
731
        as required. If it isn't, warnings are produced about the
732
        lost of information.
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
733
0.64.34 by Ian Clatworthy
report lost branches
734
        :return: updated, lost_heads where
735
          updated = the list of branches updated
736
          lost_heads = a list of (bazaar-name,revision) for branches that
737
            would have been created had the repository been shared
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
738
        """
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
739
        updated = []
0.64.37 by Ian Clatworthy
create branches as required
740
        branch_tips, lost_heads = self._get_matching_branches()
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
741
        for br, tip in branch_tips:
742
            self._update_branch(br, tip)
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
743
            updated.append(br)
0.64.34 by Ian Clatworthy
report lost branches
744
        return updated, lost_heads
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
745
746
    def _get_matching_branches(self):
747
        """Get the Bazaar branches.
748
0.64.34 by Ian Clatworthy
report lost branches
749
        :return: default_tip, branch_tips, lost_tips where
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
750
          default_tip = the last commit mark for the default branch
751
          branch_tips = a list of (branch,tip) tuples for other branches.
0.64.34 by Ian Clatworthy
report lost branches
752
          lost_heads = a list of (bazaar-name,revision) for branches that
0.64.37 by Ian Clatworthy
create branches as required
753
            would have been created had the repository been shared and
754
            everything succeeded
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
755
        """
0.64.37 by Ian Clatworthy
create branches as required
756
        branch_tips = []
757
        lost_heads = []
758
        ref_names = self.heads_by_ref.keys()
759
        if self.branch is not None:
0.64.40 by Ian Clatworthy
always use heads/master as the trunk if it is present
760
            trunk = self.select_trunk(ref_names)
761
            default_tip = self.heads_by_ref[trunk][0]
0.64.37 by Ian Clatworthy
create branches as required
762
            branch_tips.append((self.branch, default_tip))
0.64.40 by Ian Clatworthy
always use heads/master as the trunk if it is present
763
            ref_names.remove(trunk)
0.64.34 by Ian Clatworthy
report lost branches
764
765
        # Convert the reference names into Bazaar speak
766
        bzr_names = self._get_bzr_names_from_ref_names(ref_names)
767
0.64.37 by Ian Clatworthy
create branches as required
768
        # Policy for locating branches
769
        def dir_under_current(name, ref_name):
770
            # Using the Bazaar name, get a directory under the current one
771
            return name
772
        def dir_sister_branch(name, ref_name):
773
            # Using the Bazaar name, get a sister directory to the branch
774
            return osutils.pathjoin(self.branch.base, "..", name)
775
        if self.branch is not None:
776
            dir_policy = dir_sister_branch
777
        else:
778
            dir_policy = dir_under_current
779
0.64.34 by Ian Clatworthy
report lost branches
780
        # Create/track missing branches
781
        shared_repo = self.repo.is_shared()
782
        for name in sorted(bzr_names.keys()):
783
            ref_name = bzr_names[name]
784
            tip = self.heads_by_ref[ref_name][0]
785
            if shared_repo:
0.64.37 by Ian Clatworthy
create branches as required
786
                location = dir_policy(name, ref_name)
787
                try:
788
                    br = self.make_branch(location)
789
                    branch_tips.append((br,tip))
790
                    continue
791
                except errors.BzrError, ex:
792
                    error("ERROR: failed to create branch %s: %s",
793
                        location, ex)
794
            lost_head = self.cache_mgr.revision_ids[tip]
795
            lost_info = (name, lost_head)
796
            lost_heads.append(lost_info)
797
        return branch_tips, lost_heads
798
0.64.40 by Ian Clatworthy
always use heads/master as the trunk if it is present
799
    def select_trunk(self, ref_names):
800
        """Given a set of ref names, choose one as the trunk."""
801
        for candidate in ['refs/heads/master']:
802
            if candidate in ref_names:
803
                return candidate
804
        # Use the last reference in the import stream
805
        return self.last_ref
806
0.64.37 by Ian Clatworthy
create branches as required
807
    def make_branch(self, location):
808
        """Create a branch in the repository."""
809
        return bzrdir.BzrDir.create_branch_convenience(location)
0.64.34 by Ian Clatworthy
report lost branches
810
811
    def _get_bzr_names_from_ref_names(self, ref_names):
0.64.37 by Ian Clatworthy
create branches as required
812
        """Generate Bazaar branch names from import ref names.
813
        
814
        :return: a dictionary with Bazaar names as keys and
815
          the original reference names as values.
816
        """
0.64.34 by Ian Clatworthy
report lost branches
817
        bazaar_names = {}
818
        for ref_name in sorted(ref_names):
819
            parts = ref_name.split('/')
820
            if parts[0] == 'refs':
821
                parts.pop(0)
822
            full_name = "--".join(parts)
823
            bazaar_name = parts[-1]
824
            if bazaar_name in bazaar_names:
825
                bazaar_name = full_name
826
            bazaar_names[bazaar_name] = ref_name
827
        return bazaar_names
0.64.31 by Ian Clatworthy
fix branch updating for the single branch case
828
829
    def _update_branch(self, br, last_mark):
830
        """Update a branch with last revision and tag information."""
831
        last_rev_id = self.cache_mgr.revision_ids[last_mark]
832
        revno = len(list(self.repo.iter_reverse_revision_history(last_rev_id)))
833
        br.set_last_revision_info(revno, last_rev_id)
834
        # TODO: apply tags known in this branch
835
        #if self.tags:
836
        #    br.tags._set_tag_dict(self.tags)
0.64.33 by Ian Clatworthy
make tree updating optional and minor UI improvements
837
        note("\t branch %s has %d revisions", br.nick, revno)