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