~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Jamie Wilkinson
  • Date: 2006-07-18 23:59:52 UTC
  • mfrom: (1868 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1874.
  • Revision ID: jaq@spacepants.org-20060718235952-1e362401a7858958
merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
from binascii import hexlify
17
18
from copy import deepcopy
18
19
from cStringIO import StringIO
 
20
import re
 
21
import time
19
22
from unittest import TestSuite
20
23
 
21
 
import bzrlib.bzrdir as bzrdir
 
24
from bzrlib import bzrdir, check, delta, gpg, errors, xml5, ui, transactions, osutils
22
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
23
 
import bzrlib.errors as errors
24
26
from bzrlib.errors import InvalidRevisionId
25
 
import bzrlib.gpg as gpg
26
27
from bzrlib.graph import Graph
27
28
from bzrlib.inter import InterObject
 
29
from bzrlib.inventory import Inventory
28
30
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
29
31
from bzrlib.lockable_files import LockableFiles, TransportLock
30
32
from bzrlib.lockdir import LockDir
31
 
from bzrlib.osutils import safe_unicode
32
 
from bzrlib.revision import NULL_REVISION
 
33
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date, 
 
34
                            local_time_offset)
 
35
from bzrlib.revision import NULL_REVISION, Revision
33
36
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
34
37
from bzrlib.store.text import TextStore
35
 
from bzrlib.symbol_versioning import *
 
38
from bzrlib.symbol_versioning import (deprecated_method,
 
39
        zero_nine, 
 
40
        )
36
41
from bzrlib.trace import mutter, note
37
 
from bzrlib.tree import RevisionTree
 
42
from bzrlib.tree import RevisionTree, EmptyTree
38
43
from bzrlib.tsort import topo_sort
39
44
from bzrlib.testament import Testament
40
45
from bzrlib.tree import EmptyTree
41
 
import bzrlib.ui
42
46
from bzrlib.weave import WeaveFile
43
 
import bzrlib.xml5
44
47
 
45
48
 
46
49
class Repository(object):
64
67
 
65
68
        returns the sha1 of the serialized inventory.
66
69
        """
67
 
        inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
68
 
        inv_sha1 = bzrlib.osutils.sha_string(inv_text)
 
70
        assert inv.revision_id is None or inv.revision_id == revid, \
 
71
            "Mismatch between inventory revision" \
 
72
            " id and insertion revid (%r, %r)" % (inv.revision_id, revid)
 
73
        inv_text = xml5.serializer_v5.write_inventory_to_string(inv)
 
74
        inv_sha1 = osutils.sha_string(inv_text)
69
75
        inv_vf = self.control_weaves.get_weave('inventory',
70
76
                                               self.get_transaction())
71
 
        inv_vf.add_lines(revid, parents, bzrlib.osutils.split_lines(inv_text))
 
77
        self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
72
78
        return inv_sha1
73
79
 
 
80
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
81
        final_parents = []
 
82
        for parent in parents:
 
83
            if parent in inv_vf:
 
84
                final_parents.append(parent)
 
85
 
 
86
        inv_vf.add_lines(revid, final_parents, lines)
 
87
 
74
88
    @needs_write_lock
75
89
    def add_revision(self, rev_id, rev, inv=None, config=None):
76
90
        """Add rev to the revision store as rev_id.
103
117
        """Return all the possible revisions that we could find."""
104
118
        return self.get_inventory_weave().versions()
105
119
 
 
120
    def all_revision_ids(self):
 
121
        """Returns a list of all the revision ids in the repository. 
 
122
 
 
123
        This is deprecated because code should generally work on the graph
 
124
        reachable from a particular revision, and ignore any other revisions
 
125
        that might be present.  There is no direct replacement method.
 
126
        """
 
127
        return self._all_revision_ids()
 
128
 
106
129
    @needs_read_lock
107
 
    def all_revision_ids(self):
 
130
    def _all_revision_ids(self):
108
131
        """Returns a list of all the revision ids in the repository. 
109
132
 
110
133
        These are in as much topological order as the underlying store can 
158
181
        self.control_files = control_files
159
182
        self._revision_store = _revision_store
160
183
        self.text_store = text_store
161
 
        # backwards compatability
 
184
        # backwards compatibility
162
185
        self.weave_store = text_store
163
186
        # not right yet - should be more semantically clear ? 
164
187
        # 
200
223
        For instance, if the repository is at URL/.bzr/repository,
201
224
        Repository.open(URL) -> a Repository instance.
202
225
        """
203
 
        control = bzrlib.bzrdir.BzrDir.open(base)
 
226
        control = bzrdir.BzrDir.open(base)
204
227
        return control.open_repository()
205
228
 
206
229
    def copy_content_into(self, destination, revision_id=None, basis=None):
219
242
        return InterRepository.get(source, self).fetch(revision_id=revision_id,
220
243
                                                       pb=pb)
221
244
 
 
245
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
 
246
                           timezone=None, committer=None, revprops=None, 
 
247
                           revision_id=None):
 
248
        """Obtain a CommitBuilder for this repository.
 
249
        
 
250
        :param branch: Branch to commit to.
 
251
        :param parents: Revision ids of the parents of the new revision.
 
252
        :param config: Configuration to use.
 
253
        :param timestamp: Optional timestamp recorded for commit.
 
254
        :param timezone: Optional timezone for timestamp.
 
255
        :param committer: Optional committer to set for commit.
 
256
        :param revprops: Optional dictionary of revision properties.
 
257
        :param revision_id: Optional revision id.
 
258
        """
 
259
        return CommitBuilder(self, parents, config, timestamp, timezone,
 
260
                             committer, revprops, revision_id)
 
261
 
222
262
    def unlock(self):
223
263
        self.control_files.unlock()
224
264
 
234
274
            result = a_bzrdir.create_repository()
235
275
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
236
276
        elif isinstance(a_bzrdir._format,
237
 
                      (bzrlib.bzrdir.BzrDirFormat4,
238
 
                       bzrlib.bzrdir.BzrDirFormat5,
239
 
                       bzrlib.bzrdir.BzrDirFormat6)):
 
277
                      (bzrdir.BzrDirFormat4,
 
278
                       bzrdir.BzrDirFormat5,
 
279
                       bzrdir.BzrDirFormat6)):
240
280
            result = a_bzrdir.open_repository()
241
281
        else:
242
282
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
260
300
        """
261
301
        if not revision_id or not isinstance(revision_id, basestring):
262
302
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
263
 
        return self._revision_store.get_revision(revision_id,
264
 
                                                 self.get_transaction())
 
303
        return self._revision_store.get_revisions([revision_id],
 
304
                                                  self.get_transaction())[0]
 
305
    @needs_read_lock
 
306
    def get_revisions(self, revision_ids):
 
307
        return self._revision_store.get_revisions(revision_ids,
 
308
                                                  self.get_transaction())
265
309
 
266
310
    @needs_read_lock
267
311
    def get_revision_xml(self, revision_id):
287
331
        self._check_revision_parents(r, inv)
288
332
        return r
289
333
 
 
334
    @needs_read_lock
 
335
    def get_deltas_for_revisions(self, revisions):
 
336
        """Produce a generator of revision deltas.
 
337
        
 
338
        Note that the input is a sequence of REVISIONS, not revision_ids.
 
339
        Trees will be held in memory until the generator exits.
 
340
        Each delta is relative to the revision's lefthand predecessor.
 
341
        """
 
342
        required_trees = set()
 
343
        for revision in revisions:
 
344
            required_trees.add(revision.revision_id)
 
345
            required_trees.update(revision.parent_ids[:1])
 
346
        trees = dict((t.get_revision_id(), t) for 
 
347
                     t in self.revision_trees(required_trees))
 
348
        for revision in revisions:
 
349
            if not revision.parent_ids:
 
350
                old_tree = EmptyTree()
 
351
            else:
 
352
                old_tree = trees[revision.parent_ids[0]]
 
353
            yield delta.compare_trees(old_tree, trees[revision.revision_id])
 
354
 
 
355
    @needs_read_lock
 
356
    def get_revision_delta(self, revision_id):
 
357
        """Return the delta for one revision.
 
358
 
 
359
        The delta is relative to the left-hand predecessor of the
 
360
        revision.
 
361
        """
 
362
        r = self.get_revision(revision_id)
 
363
        return list(self.get_deltas_for_revisions([r]))[0]
 
364
 
290
365
    def _check_revision_parents(self, revision, inventory):
291
366
        """Private to Repository and Fetch.
292
367
        
322
397
                                         RepositoryFormat6,
323
398
                                         RepositoryFormat7,
324
399
                                         RepositoryFormatKnit1)), \
325
 
            "fileid_involved only supported for branches which store inventory as unnested xml"
 
400
            ("fileids_altered_by_revision_ids only supported for branches " 
 
401
             "which store inventory as unnested xml, not on %r" % self)
326
402
        selected_revision_ids = set(revision_ids)
327
403
        w = self.get_inventory_weave()
328
404
        result = {}
329
405
 
330
406
        # this code needs to read every new line in every inventory for the
331
407
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
332
 
        # not pesent in one of those inventories is unnecessary but not 
 
408
        # not present in one of those inventories is unnecessary but not 
333
409
        # harmful because we are filtering by the revision id marker in the
334
410
        # inventory lines : we only select file ids altered in one of those  
335
 
        # revisions. We dont need to see all lines in the inventory because
 
411
        # revisions. We don't need to see all lines in the inventory because
336
412
        # only those added in an inventory in rev X can contain a revision=X
337
413
        # line.
338
414
        for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
359
435
    @needs_read_lock
360
436
    def get_inventory(self, revision_id):
361
437
        """Get Inventory object by hash."""
362
 
        xml = self.get_inventory_xml(revision_id)
363
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
438
        return self.deserialise_inventory(
 
439
            revision_id, self.get_inventory_xml(revision_id))
 
440
 
 
441
    def deserialise_inventory(self, revision_id, xml):
 
442
        """Transform the xml into an inventory object. 
 
443
 
 
444
        :param revision_id: The expected revision id of the inventory.
 
445
        :param xml: A serialised inventory.
 
446
        """
 
447
        return xml5.serializer_v5.read_inventory_from_string(xml)
364
448
 
365
449
    @needs_read_lock
366
450
    def get_inventory_xml(self, revision_id):
370
454
            iw = self.get_inventory_weave()
371
455
            return iw.get_text(revision_id)
372
456
        except IndexError:
373
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
457
            raise errors.HistoryMissing(self, 'inventory', revision_id)
374
458
 
375
459
    @needs_read_lock
376
460
    def get_inventory_sha1(self, revision_id):
382
466
    def get_revision_graph(self, revision_id=None):
383
467
        """Return a dictionary containing the revision graph.
384
468
        
 
469
        :param revision_id: The revision_id to get a graph from. If None, then
 
470
        the entire revision graph is returned. This is a deprecated mode of
 
471
        operation and will be removed in the future.
385
472
        :return: a dictionary of revision_id->revision_parents_list.
386
473
        """
 
474
        # special case NULL_REVISION
 
475
        if revision_id == NULL_REVISION:
 
476
            return {}
387
477
        weave = self.get_inventory_weave()
388
478
        all_revisions = self._eliminate_revisions_not_present(weave.versions())
389
479
        entire_graph = dict([(node, weave.get_parents(node)) for 
417
507
            required = set([])
418
508
        else:
419
509
            pending = set(revision_ids)
420
 
            required = set(revision_ids)
 
510
            # special case NULL_REVISION
 
511
            if NULL_REVISION in pending:
 
512
                pending.remove(NULL_REVISION)
 
513
            required = set(pending)
421
514
        done = set([])
422
515
        while len(pending):
423
516
            revision_id = pending.pop()
483
576
            return RevisionTree(self, inv, revision_id)
484
577
 
485
578
    @needs_read_lock
 
579
    def revision_trees(self, revision_ids):
 
580
        """Return Tree for a revision on this branch.
 
581
 
 
582
        `revision_id` may not be None or 'null:'"""
 
583
        assert None not in revision_ids
 
584
        assert NULL_REVISION not in revision_ids
 
585
        texts = self.get_inventory_weave().get_texts(revision_ids)
 
586
        for text, revision_id in zip(texts, revision_ids):
 
587
            inv = self.deserialise_inventory(revision_id, text)
 
588
            yield RevisionTree(self, inv, revision_id)
 
589
 
 
590
    @needs_read_lock
486
591
    def get_ancestry(self, revision_id):
487
592
        """Return a list of revision-ids integrated by a revision.
 
593
 
 
594
        The first element of the list is always None, indicating the origin 
 
595
        revision.  This might change when we have history horizons, or 
 
596
        perhaps we should have a new API.
488
597
        
489
598
        This is topologically sorted.
490
599
        """
508
617
        # use inventory as it was in that revision
509
618
        file_id = tree.inventory.path2id(file)
510
619
        if not file_id:
511
 
            raise BzrError("%r is not present in revision %s" % (file, revno))
512
 
            try:
513
 
                revno = self.revision_id_to_revno(revision_id)
514
 
            except errors.NoSuchRevision:
515
 
                # TODO: This should not be BzrError,
516
 
                # but NoSuchFile doesn't fit either
517
 
                raise BzrError('%r is not present in revision %s' 
518
 
                                % (file, revision_id))
519
 
            else:
520
 
                raise BzrError('%r is not present in revision %s'
521
 
                                % (file, revno))
 
620
            # TODO: jam 20060427 Write a test for this code path
 
621
            #       it had a bug in it, and was raising the wrong
 
622
            #       exception.
 
623
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
522
624
        tree.print_file(file_id)
523
625
 
524
626
    def get_transaction(self):
560
662
        return self._revision_store.get_signature_text(revision_id,
561
663
                                                       self.get_transaction())
562
664
 
 
665
    @needs_read_lock
 
666
    def check(self, revision_ids):
 
667
        """Check consistency of all history of given revision_ids.
 
668
 
 
669
        Different repository implementations should override _check().
 
670
 
 
671
        :param revision_ids: A non-empty list of revision_ids whose ancestry
 
672
             will be checked.  Typically the last revision_id of a branch.
 
673
        """
 
674
        if not revision_ids:
 
675
            raise ValueError("revision_ids must be non-empty in %s.check" 
 
676
                    % (self,))
 
677
        return self._check(revision_ids)
 
678
 
 
679
    def _check(self, revision_ids):
 
680
        result = check.Check(self)
 
681
        result.check()
 
682
        return result
 
683
 
563
684
 
564
685
class AllInOneRepository(Repository):
565
686
    """Legacy support - the repository behaviour for all-in-one branches."""
616
737
        return True
617
738
 
618
739
 
 
740
def install_revision(repository, rev, revision_tree):
 
741
    """Install all revision data into a repository."""
 
742
    present_parents = []
 
743
    parent_trees = {}
 
744
    for p_id in rev.parent_ids:
 
745
        if repository.has_revision(p_id):
 
746
            present_parents.append(p_id)
 
747
            parent_trees[p_id] = repository.revision_tree(p_id)
 
748
        else:
 
749
            parent_trees[p_id] = EmptyTree()
 
750
 
 
751
    inv = revision_tree.inventory
 
752
    
 
753
    # Add the texts that are not already present
 
754
    for path, ie in inv.iter_entries():
 
755
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
 
756
                repository.get_transaction())
 
757
        if ie.revision not in w:
 
758
            text_parents = []
 
759
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
 
760
            # with InventoryEntry.find_previous_heads(). if it is, then there
 
761
            # is a latent bug here where the parents may have ancestors of each
 
762
            # other. RBC, AB
 
763
            for revision, tree in parent_trees.iteritems():
 
764
                if ie.file_id not in tree:
 
765
                    continue
 
766
                parent_id = tree.inventory[ie.file_id].revision
 
767
                if parent_id in text_parents:
 
768
                    continue
 
769
                text_parents.append(parent_id)
 
770
                    
 
771
            vfile = repository.weave_store.get_weave_or_empty(ie.file_id, 
 
772
                repository.get_transaction())
 
773
            lines = revision_tree.get_file(ie.file_id).readlines()
 
774
            vfile.add_lines(rev.revision_id, text_parents, lines)
 
775
    try:
 
776
        # install the inventory
 
777
        repository.add_inventory(rev.revision_id, inv, present_parents)
 
778
    except errors.RevisionAlreadyPresent:
 
779
        pass
 
780
    repository.add_revision(rev.revision_id, rev, inv)
 
781
 
 
782
 
619
783
class MetaDirRepository(Repository):
620
784
    """Repositories in the new meta-dir layout."""
621
785
 
661
825
class KnitRepository(MetaDirRepository):
662
826
    """Knit format repository."""
663
827
 
 
828
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
829
        inv_vf.add_lines_with_ghosts(revid, parents, lines)
 
830
 
664
831
    @needs_read_lock
665
 
    def all_revision_ids(self):
 
832
    def _all_revision_ids(self):
666
833
        """See Repository.all_revision_ids()."""
 
834
        # Knits get the revision graph from the index of the revision knit, so
 
835
        # it's always possible even if they're on an unlistable transport.
667
836
        return self._revision_store.all_revision_ids(self.get_transaction())
668
837
 
669
838
    def fileid_involved_between_revs(self, from_revid, to_revid):
713
882
    @needs_read_lock
714
883
    def get_revision_graph(self, revision_id=None):
715
884
        """Return a dictionary containing the revision graph.
716
 
        
 
885
 
 
886
        :param revision_id: The revision_id to get a graph from. If None, then
 
887
        the entire revision graph is returned. This is a deprecated mode of
 
888
        operation and will be removed in the future.
717
889
        :return: a dictionary of revision_id->revision_parents_list.
718
890
        """
 
891
        # special case NULL_REVISION
 
892
        if revision_id == NULL_REVISION:
 
893
            return {}
719
894
        weave = self._get_revision_vf()
720
895
        entire_graph = weave.get_graph()
721
896
        if revision_id is None:
749
924
            required = set([])
750
925
        else:
751
926
            pending = set(revision_ids)
752
 
            required = set(revision_ids)
 
927
            # special case NULL_REVISION
 
928
            if NULL_REVISION in pending:
 
929
                pending.remove(NULL_REVISION)
 
930
            required = set(pending)
753
931
        done = set([])
754
932
        while len(pending):
755
933
            revision_id = pending.pop()
758
936
                    raise errors.NoSuchRevision(self, revision_id)
759
937
                # a ghost
760
938
                result.add_ghost(revision_id)
761
 
                # mark it as done so we dont try for it again.
 
939
                # mark it as done so we don't try for it again.
762
940
                done.add(revision_id)
763
941
                continue
764
942
            parent_ids = vf.get_parents_with_ghosts(revision_id)
785
963
        reconciler.reconcile()
786
964
        return reconciler
787
965
    
788
 
    def revision_parents(self, revid):
789
 
        return self._get_revision_vf().get_parents(rev_id)
 
966
    def revision_parents(self, revision_id):
 
967
        return self._get_revision_vf().get_parents(revision_id)
 
968
 
790
969
 
791
970
class RepositoryFormat(object):
792
971
    """A repository format.
828
1007
        except errors.NoSuchFile:
829
1008
            raise errors.NoRepositoryPresent(a_bzrdir)
830
1009
        except KeyError:
831
 
            raise errors.UnknownFormatError(format_string)
 
1010
            raise errors.UnknownFormatError(format=format_string)
832
1011
 
833
1012
    def _get_control_store(self, repo_transport, control_files):
834
1013
        """Return the control store for this repository."""
848
1027
        raise NotImplementedError(self.get_format_string)
849
1028
 
850
1029
    def get_format_description(self):
851
 
        """Return the short desciption for this format."""
 
1030
        """Return the short description for this format."""
852
1031
        raise NotImplementedError(self.get_format_description)
853
1032
 
854
1033
    def _get_revision_store(self, repo_transport, control_files):
955
1134
        
956
1135
        # Create an empty weave
957
1136
        sio = StringIO()
958
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
1137
        write_weave_v5(Weave(), sio)
959
1138
        empty_weave = sio.getvalue()
960
1139
 
961
1140
        mutter('creating repository in %s.', a_bzrdir.transport.base)
963
1142
        files = [('inventory.weave', StringIO(empty_weave)),
964
1143
                 ]
965
1144
        
966
 
        # FIXME: RBC 20060125 dont peek under the covers
 
1145
        # FIXME: RBC 20060125 don't peek under the covers
967
1146
        # NB: no need to escape relative paths that are url safe.
968
1147
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
969
1148
                                      TransportLock)
1015
1194
     - TextStores for texts, inventories,revisions.
1016
1195
 
1017
1196
    This format is deprecated: it indexes texts using a text id which is
1018
 
    removed in format 5; initializationa and write support for this format
 
1197
    removed in format 5; initialization and write support for this format
1019
1198
    has been removed.
1020
1199
    """
1021
1200
 
1022
1201
    def __init__(self):
1023
1202
        super(RepositoryFormat4, self).__init__()
1024
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
 
1203
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
1025
1204
 
1026
1205
    def get_format_description(self):
1027
1206
        """See RepositoryFormat.get_format_description()."""
1070
1249
 
1071
1250
    def __init__(self):
1072
1251
        super(RepositoryFormat5, self).__init__()
1073
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
 
1252
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
1074
1253
 
1075
1254
    def get_format_description(self):
1076
1255
        """See RepositoryFormat.get_format_description()."""
1100
1279
 
1101
1280
    def __init__(self):
1102
1281
        super(RepositoryFormat6, self).__init__()
1103
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
 
1282
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
1104
1283
 
1105
1284
    def get_format_description(self):
1106
1285
        """See RepositoryFormat.get_format_description()."""
1120
1299
 
1121
1300
 
1122
1301
class MetaDirRepositoryFormat(RepositoryFormat):
1123
 
    """Common base class for the new repositories using the metadir layour."""
 
1302
    """Common base class for the new repositories using the metadir layout."""
1124
1303
 
1125
1304
    def __init__(self):
1126
1305
        super(MetaDirRepositoryFormat, self).__init__()
1127
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
 
1306
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1128
1307
 
1129
1308
    def _create_control_files(self, a_bzrdir):
1130
1309
        """Create the required files and the initial control_files object."""
1131
 
        # FIXME: RBC 20060125 dont peek under the covers
 
1310
        # FIXME: RBC 20060125 don't peek under the covers
1132
1311
        # NB: no need to escape relative paths that are url safe.
1133
1312
        repository_transport = a_bzrdir.get_repository_transport(self)
1134
1313
        control_files = LockableFiles(repository_transport, 'lock', LockDir)
1205
1384
 
1206
1385
        # Create an empty weave
1207
1386
        sio = StringIO()
1208
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
1387
        write_weave_v5(Weave(), sio)
1209
1388
        empty_weave = sio.getvalue()
1210
1389
 
1211
1390
        mutter('creating repository in %s.', a_bzrdir.transport.base)
1316
1495
        repo_transport = a_bzrdir.get_repository_transport(None)
1317
1496
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
1318
1497
        control_store = self._get_control_store(repo_transport, control_files)
1319
 
        transaction = bzrlib.transactions.WriteTransaction()
 
1498
        transaction = transactions.WriteTransaction()
1320
1499
        # trigger a write of the inventory store.
1321
1500
        control_store.get_weave_or_empty('inventory', transaction)
1322
1501
        _revision_store = self._get_revision_store(repo_transport, control_files)
1394
1573
        # grab the basis available data
1395
1574
        if basis is not None:
1396
1575
            self.target.fetch(basis, revision_id=revision_id)
1397
 
        # but dont bother fetching if we have the needed data now.
 
1576
        # but don't bother fetching if we have the needed data now.
1398
1577
        if (revision_id not in (None, NULL_REVISION) and 
1399
1578
            self.target.has_revision(revision_id)):
1400
1579
            return
1491
1670
    def is_compatible(source, target):
1492
1671
        """Be compatible with known Weave formats.
1493
1672
        
1494
 
        We dont test for the stores being of specific types becase that
 
1673
        We don't test for the stores being of specific types because that
1495
1674
        could lead to confusing results, and there is no need to be 
1496
1675
        overly general.
1497
1676
        """
1512
1691
        if basis is not None:
1513
1692
            # copy the basis in, then fetch remaining data.
1514
1693
            basis.copy_content_into(self.target, revision_id)
1515
 
            # the basis copy_content_into could misset this.
 
1694
            # the basis copy_content_into could miss-set this.
1516
1695
            try:
1517
1696
                self.target.set_make_working_trees(self.source.make_working_trees())
1518
1697
            except NotImplementedError:
1525
1704
                pass
1526
1705
            # FIXME do not peek!
1527
1706
            if self.source.control_files._transport.listable():
1528
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1707
                pb = ui.ui_factory.nested_progress_bar()
1529
1708
                try:
1530
1709
                    self.target.weave_store.copy_all_ids(
1531
1710
                        self.source.weave_store,
1561
1740
    def missing_revision_ids(self, revision_id=None):
1562
1741
        """See InterRepository.missing_revision_ids()."""
1563
1742
        # we want all revisions to satisfy revision_id in source.
1564
 
        # but we dont want to stat every file here and there.
 
1743
        # but we don't want to stat every file here and there.
1565
1744
        # we want then, all revisions other needs to satisfy revision_id 
1566
1745
        # checked, but not those that we have locally.
1567
1746
        # so the first thing is to get a subset of the revisions to 
1580
1759
        source_ids_set = set(source_ids)
1581
1760
        # source_ids is the worst possible case we may need to pull.
1582
1761
        # now we want to filter source_ids against what we actually
1583
 
        # have in target, but dont try to check for existence where we know
 
1762
        # have in target, but don't try to check for existence where we know
1584
1763
        # we do not have a revision as that would be pointless.
1585
1764
        target_ids = set(self.target._all_possible_ids())
1586
1765
        possibly_present_revisions = target_ids.intersection(source_ids_set)
1609
1788
    def is_compatible(source, target):
1610
1789
        """Be compatible with known Knit formats.
1611
1790
        
1612
 
        We dont test for the stores being of specific types becase that
 
1791
        We don't test for the stores being of specific types because that
1613
1792
        could lead to confusing results, and there is no need to be 
1614
1793
        overly general.
1615
1794
        """
1643
1822
        source_ids_set = set(source_ids)
1644
1823
        # source_ids is the worst possible case we may need to pull.
1645
1824
        # now we want to filter source_ids against what we actually
1646
 
        # have in target, but dont try to check for existence where we know
 
1825
        # have in target, but don't try to check for existence where we know
1647
1826
        # we do not have a revision as that would be pointless.
1648
1827
        target_ids = set(self.target._all_possible_ids())
1649
1828
        possibly_present_revisions = target_ids.intersection(source_ids_set)
1797
1976
        self.pb.update(message, self.count, self.total)
1798
1977
 
1799
1978
 
1800
 
# Copied from xml.sax.saxutils
 
1979
class CommitBuilder(object):
 
1980
    """Provides an interface to build up a commit.
 
1981
 
 
1982
    This allows describing a tree to be committed without needing to 
 
1983
    know the internals of the format of the repository.
 
1984
    """
 
1985
    def __init__(self, repository, parents, config, timestamp=None, 
 
1986
                 timezone=None, committer=None, revprops=None, 
 
1987
                 revision_id=None):
 
1988
        """Initiate a CommitBuilder.
 
1989
 
 
1990
        :param repository: Repository to commit to.
 
1991
        :param parents: Revision ids of the parents of the new revision.
 
1992
        :param config: Configuration to use.
 
1993
        :param timestamp: Optional timestamp recorded for commit.
 
1994
        :param timezone: Optional timezone for timestamp.
 
1995
        :param committer: Optional committer to set for commit.
 
1996
        :param revprops: Optional dictionary of revision properties.
 
1997
        :param revision_id: Optional revision id.
 
1998
        """
 
1999
        self._config = config
 
2000
 
 
2001
        if committer is None:
 
2002
            self._committer = self._config.username()
 
2003
        else:
 
2004
            assert isinstance(committer, basestring), type(committer)
 
2005
            self._committer = committer
 
2006
 
 
2007
        self.new_inventory = Inventory()
 
2008
        self._new_revision_id = revision_id
 
2009
        self.parents = parents
 
2010
        self.repository = repository
 
2011
 
 
2012
        self._revprops = {}
 
2013
        if revprops is not None:
 
2014
            self._revprops.update(revprops)
 
2015
 
 
2016
        if timestamp is None:
 
2017
            timestamp = time.time()
 
2018
        # Restrict resolution to 1ms
 
2019
        self._timestamp = round(timestamp, 3)
 
2020
 
 
2021
        if timezone is None:
 
2022
            self._timezone = local_time_offset()
 
2023
        else:
 
2024
            self._timezone = int(timezone)
 
2025
 
 
2026
        self._generate_revision_if_needed()
 
2027
 
 
2028
    def commit(self, message):
 
2029
        """Make the actual commit.
 
2030
 
 
2031
        :return: The revision id of the recorded revision.
 
2032
        """
 
2033
        rev = Revision(timestamp=self._timestamp,
 
2034
                       timezone=self._timezone,
 
2035
                       committer=self._committer,
 
2036
                       message=message,
 
2037
                       inventory_sha1=self.inv_sha1,
 
2038
                       revision_id=self._new_revision_id,
 
2039
                       properties=self._revprops)
 
2040
        rev.parent_ids = self.parents
 
2041
        self.repository.add_revision(self._new_revision_id, rev, 
 
2042
            self.new_inventory, self._config)
 
2043
        return self._new_revision_id
 
2044
 
 
2045
    def finish_inventory(self):
 
2046
        """Tell the builder that the inventory is finished."""
 
2047
        self.new_inventory.revision_id = self._new_revision_id
 
2048
        self.inv_sha1 = self.repository.add_inventory(
 
2049
            self._new_revision_id,
 
2050
            self.new_inventory,
 
2051
            self.parents
 
2052
            )
 
2053
 
 
2054
    def _gen_revision_id(self):
 
2055
        """Return new revision-id."""
 
2056
        s = '%s-%s-' % (self._config.user_email(), 
 
2057
                        compact_date(self._timestamp))
 
2058
        s += hexlify(rand_bytes(8))
 
2059
        return s
 
2060
 
 
2061
    def _generate_revision_if_needed(self):
 
2062
        """Create a revision id if None was supplied.
 
2063
        
 
2064
        If the repository can not support user-specified revision ids
 
2065
        they should override this function and raise UnsupportedOperation
 
2066
        if _new_revision_id is not None.
 
2067
 
 
2068
        :raises: UnsupportedOperation
 
2069
        """
 
2070
        if self._new_revision_id is None:
 
2071
            self._new_revision_id = self._gen_revision_id()
 
2072
 
 
2073
    def record_entry_contents(self, ie, parent_invs, path, tree):
 
2074
        """Record the content of ie from tree into the commit if needed.
 
2075
 
 
2076
        :param ie: An inventory entry present in the commit.
 
2077
        :param parent_invs: The inventories of the parent revisions of the
 
2078
            commit.
 
2079
        :param path: The path the entry is at in the tree.
 
2080
        :param tree: The tree which contains this entry and should be used to 
 
2081
        obtain content.
 
2082
        """
 
2083
        self.new_inventory.add(ie)
 
2084
 
 
2085
        # ie.revision is always None if the InventoryEntry is considered
 
2086
        # for committing. ie.snapshot will record the correct revision 
 
2087
        # which may be the sole parent if it is untouched.
 
2088
        if ie.revision is not None:
 
2089
            return
 
2090
        previous_entries = ie.find_previous_heads(
 
2091
            parent_invs,
 
2092
            self.repository.weave_store,
 
2093
            self.repository.get_transaction())
 
2094
        # we are creating a new revision for ie in the history store
 
2095
        # and inventory.
 
2096
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
 
2097
 
 
2098
    def modified_directory(self, file_id, file_parents):
 
2099
        """Record the presence of a symbolic link.
 
2100
 
 
2101
        :param file_id: The file_id of the link to record.
 
2102
        :param file_parents: The per-file parent revision ids.
 
2103
        """
 
2104
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
2105
    
 
2106
    def modified_file_text(self, file_id, file_parents,
 
2107
                           get_content_byte_lines, text_sha1=None,
 
2108
                           text_size=None):
 
2109
        """Record the text of file file_id
 
2110
 
 
2111
        :param file_id: The file_id of the file to record the text of.
 
2112
        :param file_parents: The per-file parent revision ids.
 
2113
        :param get_content_byte_lines: A callable which will return the byte
 
2114
            lines for the file.
 
2115
        :param text_sha1: Optional SHA1 of the file contents.
 
2116
        :param text_size: Optional size of the file contents.
 
2117
        """
 
2118
        mutter('storing text of file {%s} in revision {%s} into %r',
 
2119
               file_id, self._new_revision_id, self.repository.weave_store)
 
2120
        # special case to avoid diffing on renames or 
 
2121
        # reparenting
 
2122
        if (len(file_parents) == 1
 
2123
            and text_sha1 == file_parents.values()[0].text_sha1
 
2124
            and text_size == file_parents.values()[0].text_size):
 
2125
            previous_ie = file_parents.values()[0]
 
2126
            versionedfile = self.repository.weave_store.get_weave(file_id, 
 
2127
                self.repository.get_transaction())
 
2128
            versionedfile.clone_text(self._new_revision_id, 
 
2129
                previous_ie.revision, file_parents.keys())
 
2130
            return text_sha1, text_size
 
2131
        else:
 
2132
            new_lines = get_content_byte_lines()
 
2133
            # TODO: Rather than invoking sha_strings here, _add_text_to_weave
 
2134
            # should return the SHA1 and size
 
2135
            self._add_text_to_weave(file_id, new_lines, file_parents.keys())
 
2136
            return osutils.sha_strings(new_lines), \
 
2137
                sum(map(len, new_lines))
 
2138
 
 
2139
    def modified_link(self, file_id, file_parents, link_target):
 
2140
        """Record the presence of a symbolic link.
 
2141
 
 
2142
        :param file_id: The file_id of the link to record.
 
2143
        :param file_parents: The per-file parent revision ids.
 
2144
        :param link_target: Target location of this link.
 
2145
        """
 
2146
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
2147
 
 
2148
    def _add_text_to_weave(self, file_id, new_lines, parents):
 
2149
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
2150
            file_id, self.repository.get_transaction())
 
2151
        versionedfile.add_lines(self._new_revision_id, parents, new_lines)
 
2152
        versionedfile.clear_cache()
 
2153
 
 
2154
 
 
2155
_unescape_map = {
 
2156
    'apos':"'",
 
2157
    'quot':'"',
 
2158
    'amp':'&',
 
2159
    'lt':'<',
 
2160
    'gt':'>'
 
2161
}
 
2162
 
 
2163
 
 
2164
def _unescaper(match, _map=_unescape_map):
 
2165
    return _map[match.group(1)]
 
2166
 
 
2167
 
 
2168
_unescape_re = None
 
2169
 
 
2170
 
1801
2171
def _unescape_xml(data):
1802
 
    """Unescape &amp;, &lt;, and &gt; in a string of data.
1803
 
    """
1804
 
    data = data.replace("&lt;", "<")
1805
 
    data = data.replace("&gt;", ">")
1806
 
    # must do ampersand last
1807
 
    return data.replace("&amp;", "&")
 
2172
    """Unescape predefined XML entities in a string of data."""
 
2173
    global _unescape_re
 
2174
    if _unescape_re is None:
 
2175
        _unescape_re = re.compile('\&([^;]*);')
 
2176
    return _unescape_re.sub(_unescaper, data)