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