~bzr/ubuntu/maverick/bzr-svn/bzr-ppa

« back to all changes in this revision

Viewing changes to fetch.py

  • Committer: Jelmer Vernooij
  • Date: 2008-05-11 19:29:26 UTC
  • mfrom: (220.36.144 0.4)
  • Revision ID: jelmer@samba.org-20080511192926-7mh02j45r25qmzkz
Merge 0.4 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
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
 
5
# the Free Software Foundation; either version 3 of the License, or
6
6
# (at your option) any later version.
7
7
 
8
8
# This program is distributed in the hope that it will be useful,
29
29
import svn.core
30
30
 
31
31
from bzrlib.plugins.svn.errors import InvalidFileName
32
 
from mapping import (SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_MERGE, 
 
32
from logwalker import lazy_dict
 
33
from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_MERGE, 
33
34
                     SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO, 
34
 
                     SVN_PROP_BZR_BRANCHING_SCHEME, SVN_PROP_BZR_REVISION_ID,
 
35
                     SVN_PROP_BZR_REVISION_ID,
35
36
                     SVN_PROP_BZR_FILEIDS, SVN_REVPROP_BZR_SIGNATURE,
36
37
                     parse_merge_property,
37
38
                     parse_revision_metadata)
38
 
from repository import (SvnRepository, SvnRepositoryFormat)
 
39
from repository import SvnRepository, SvnRepositoryFormat
39
40
from svk import SVN_PROP_SVK_MERGE
40
41
from tree import (apply_txdelta_handler, parse_externals_description, 
41
42
                  inventory_add_external)
93
94
        self.source = source
94
95
        self.transact = target.get_transaction()
95
96
 
96
 
    def start_revision(self, revid, prev_inventory):
 
97
    def start_revision(self, revid, prev_inventory, revmeta):
97
98
        self.revid = revid
98
99
        (self.branch_path, self.revnum, self.mapping) = self.source.lookup_revision_id(revid)
99
 
        self.svn_revprops = self.source._log._get_transport().revprop_list(self.revnum)
100
 
        changes = self.source._log.get_revision_paths(self.revnum, self.branch_path)
101
 
        renames = self.source.revision_fileid_renames(self.branch_path, self.revnum, self.mapping, 
102
 
                                                      revprops=self.svn_revprops)
103
 
        self.id_map = self.source.transform_fileid_map(self.source.uuid, 
104
 
                              self.revnum, self.branch_path, changes, renames, 
105
 
                              self.mapping)
 
100
        self.revmeta = revmeta
 
101
        self._id_map = None
106
102
        self.dir_baserev = {}
107
103
        self._revinfo = None
108
 
        self._bzr_merges = ()
109
 
        self._svk_merges = []
110
104
        self._premature_deletes = set()
111
105
        self.pool = Pool()
112
106
        self.old_inventory = prev_inventory
113
107
        self.inventory = prev_inventory.copy()
114
 
        self._branch_fileprops = {}
115
108
        self._start_revision()
116
109
 
117
 
    def _get_parent_ids(self):
118
 
        return self.source.revision_parents(self.revid, self._branch_fileprops)
 
110
    def _get_id_map(self):
 
111
        if self._id_map is not None:
 
112
            return self._id_map
 
113
 
 
114
        renames = self.mapping.import_fileid_map(self.revmeta.revprops, self.revmeta.fileprops)
 
115
        self._id_map = self.source.transform_fileid_map(self.source.uuid, 
 
116
                              self.revnum, self.branch_path, self.revmeta.paths, renames, 
 
117
                              self.mapping)
 
118
 
 
119
        return self._id_map
119
120
 
120
121
    def _get_revision(self, revid):
121
122
        """Creates the revision object.
124
125
        """
125
126
 
126
127
        # Commit SVN revision properties to a Revision object
127
 
        rev = Revision(revision_id=revid, parent_ids=self._get_parent_ids())
128
 
 
129
 
        self.mapping.import_revision(self.svn_revprops, self._branch_fileprops, rev)
130
 
 
131
 
        signature = self.svn_revprops.get(SVN_REVPROP_BZR_SIGNATURE)
 
128
        rev = Revision(revision_id=revid, parent_ids=self.revmeta.get_parent_ids(self.mapping))
 
129
 
 
130
        self.mapping.import_revision(self.revmeta.revprops, self.revmeta.fileprops, rev)
 
131
 
 
132
        signature = self.revmeta.revprops.get(SVN_REVPROP_BZR_SIGNATURE)
132
133
 
133
134
        return (rev, signature)
134
135
 
141
142
        else:
142
143
            assert self.old_inventory.root.revision is not None
143
144
            old_file_id = self.old_inventory.root.file_id
144
 
            if self.id_map.has_key(""):
145
 
                file_id = self.id_map[""]
146
 
            else:
147
 
                file_id = old_file_id
 
145
            file_id = self._get_id_map().get("", old_file_id)
148
146
            self.dir_baserev[file_id] = [self.old_inventory.root.revision]
149
147
 
150
148
        if self.inventory.root is not None and \
159
157
        assert isinstance(path, unicode)
160
158
        assert isinstance(old_parent_id, str)
161
159
        assert isinstance(new_parent_id, str)
162
 
        if self.id_map.has_key(path):
163
 
            return self.id_map[path]
 
160
        ret = self._get_id_map().get(path)
 
161
        if ret is not None:
 
162
            return ret
164
163
        return self.old_inventory[old_parent_id].children[urlutils.basename(path)].file_id
165
164
 
166
165
    def _get_old_id(self, parent_id, old_path):
171
170
    def _get_new_id(self, parent_id, new_path):
172
171
        assert isinstance(new_path, unicode)
173
172
        assert isinstance(parent_id, str)
174
 
        if self.id_map.has_key(new_path):
175
 
            return self.id_map[new_path]
 
173
        ret = self._get_id_map().get(new_path)
 
174
        if ret is not None:
 
175
            return ret
176
176
        return self.mapping.generate_file_id(self.source.uuid, self.revnum, self.branch_path, new_path)
177
177
 
178
178
    def _rename(self, file_id, parent_id, path):
256
256
 
257
257
    def change_dir_prop(self, (old_id, new_id), name, value, pool):
258
258
        if new_id == self.inventory.root.file_id:
259
 
            self._branch_fileprops[name] = value
 
259
            # Replay lazy_dict, since it may be more expensive
 
260
            if type(self.revmeta.fileprops) != dict:
 
261
                self.revmeta.fileprops = {}
 
262
            self.revmeta.fileprops[name] = value
260
263
 
261
 
        if name == SVN_PROP_BZR_BRANCHING_SCHEME:
262
 
            if new_id != self.inventory.root.file_id:
263
 
                mutter('rogue %r on non-root directory' % name)
264
 
                return
265
 
        elif name == SVN_PROP_BZR_ANCESTRY+str(self.mapping.scheme):
266
 
            if new_id != self.inventory.root.file_id:
267
 
                mutter('rogue %r on non-root directory' % name)
268
 
                return
269
 
            
270
 
            self._bzr_merges = parse_merge_property(value.splitlines()[-1])
271
 
        elif name == SVN_PROP_SVK_MERGE:
272
 
            self._svk_merges = None # Force Repository.revision_parents() to look it up
273
 
        elif name == SVN_PROP_BZR_REVISION_INFO:
274
 
            if new_id != self.inventory.root.file_id:
275
 
                mutter('rogue %r on non-root directory' % SVN_PROP_BZR_REVISION_INFO)
276
 
                return
277
 
 
278
 
        elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
 
264
        if name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
279
265
                      svn.core.SVN_PROP_ENTRY_COMMITTED_REV,
280
266
                      svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
281
267
                      svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
282
268
                      svn.core.SVN_PROP_ENTRY_UUID,
283
 
                      svn.core.SVN_PROP_EXECUTABLE,
284
 
                      SVN_PROP_BZR_MERGE, SVN_PROP_BZR_FILEIDS):
285
 
            pass
286
 
        elif (name.startswith(SVN_PROP_BZR_ANCESTRY) or 
287
 
              name.startswith(SVN_PROP_BZR_REVISION_ID) or 
288
 
              name.startswith(svn.core.SVN_PROP_WC_PREFIX)):
289
 
            pass
290
 
        elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
291
 
              name.startswith(SVN_PROP_BZR_PREFIX)):
 
269
                      svn.core.SVN_PROP_EXECUTABLE):
 
270
            pass
 
271
        elif (name.startswith(svn.core.SVN_PROP_WC_PREFIX)):
 
272
            pass
 
273
        elif name.startswith(svn.core.SVN_PROP_PREFIX):
292
274
            mutter('unsupported dir property %r' % name)
293
275
 
294
276
    def change_file_prop(self, id, name, value, pool):
458
440
        self.inventory.revision_id = self.revid
459
441
        # Escaping the commit message is really the task of the serialiser
460
442
        rev.message = _escape_commit_message(rev.message)
461
 
        rev.inventory_sha1 = osutils.sha_string(
462
 
                self.target.serialise_inventory(self.inventory))
 
443
        rev.inventory_sha1 = None
463
444
        self.target.add_revision(self.revid, rev, self.inventory)
464
445
        if signature is not None:
465
446
            self.target.add_signature_text(self.revid, signature)
520
501
    def _get_repo_format_to_test():
521
502
        return None
522
503
 
523
 
    def _find_all(self):
 
504
    def _find_all(self, mapping, pb=None):
524
505
        """Find all revisions from the source repository that are not 
525
506
        yet in the target repository.
526
507
        """
527
508
        parents = {}
528
 
        needed = filter(lambda x: not self.target.has_revision(x), 
529
 
                        self.source.all_revision_ids())
530
 
        for revid in needed:
531
 
            (branch, revnum, mapping) = self.source.lookup_revision_id(revid)
532
 
            parents[revid] = self.source.lhs_revision_parent(branch, revnum, mapping)
533
 
        needed.reverse()
534
 
        return (needed, parents)
 
509
        meta_map = {}
 
510
        graph = self.source.get_graph()
 
511
        available_revs = set()
 
512
        for revmeta in self.source.iter_all_changes(pb=pb):
 
513
            revid = revmeta.get_revision_id(mapping)
 
514
            available_revs.add(revid)
 
515
            meta_map[revid] = revmeta
 
516
        missing = available_revs.difference(self.target.has_revisions(available_revs))
 
517
        needed = list(graph.iter_topo_order(missing))
 
518
        parents = graph.get_parent_map(needed)
 
519
        return [(revid, parents[revid][0], meta_map[revid]) for revid in needed]
535
520
 
536
 
    def _find_branches(self, branches, find_ghosts=False):
 
521
    def _find_branches(self, branches, find_ghosts=False, fetch_rhs_ancestry=False, pb=None):
537
522
        set_needed = set()
538
523
        ret_needed = list()
539
 
        ret_parents = dict()
540
524
        for revid in branches:
541
 
            (needed, parents) = self._find_until(revid, find_ghosts=find_ghosts)
542
 
            for rev in needed:
543
 
                if not rev in set_needed:
544
 
                    ret_needed.append(rev)
545
 
                    set_needed.add(rev)
546
 
            ret_parents.update(parents)
547
 
        return ret_needed, ret_parents
 
525
            if pb:
 
526
                pb.update("determining revisions to fetch", branches.index(revid), len(branches))
 
527
            try:
 
528
                nestedpb = ui.ui_factory.nested_progress_bar()
 
529
                for rev in self._find_until(revid, find_ghosts=find_ghosts, fetch_rhs_ancestry=False,
 
530
                                            pb=nestedpb):
 
531
                    if rev[0] not in set_needed:
 
532
                        ret_needed.append(rev)
 
533
                        set_needed.add(rev[0])
 
534
            finally:
 
535
                nestedpb.finished()
 
536
        return ret_needed
548
537
 
549
 
    def _find_until(self, revision_id, find_ghosts=False):
 
538
    def _find_until(self, revision_id, find_ghosts=False, fetch_rhs_ancestry=False, pb=None):
550
539
        """Find all missing revisions until revision_id
551
540
 
552
541
        :param revision_id: Stop revision
553
542
        :param find_ghosts: Find ghosts
 
543
        :param fetch_rhs_ancestry: Fetch right hand side ancestors
554
544
        :return: Tuple with revisions missing and a dictionary with 
555
545
            parents for those revision.
556
546
        """
 
547
        extra = set()
557
548
        needed = []
558
 
        parents = {}
559
 
 
560
 
        prev_revid = None
561
 
        pb = ui.ui_factory.nested_progress_bar()
562
 
        try:
563
 
            for revid in self.source.iter_lhs_ancestry(revision_id, pb):
564
 
 
565
 
                if prev_revid is not None:
566
 
                    parents[prev_revid] = revid
567
 
 
568
 
                prev_revid = revid
569
 
 
 
549
        revs = []
 
550
        meta_map = {}
 
551
        lhs_parent = {}
 
552
        def check_revid(revision_id):
 
553
            prev = None
 
554
            (branch_path, revnum, mapping) = self.source.lookup_revision_id(revision_id)
 
555
            for revmeta in self.source.iter_reverse_branch_changes(branch_path, revnum, mapping):
 
556
                if pb:
 
557
                    pb.update("determining revisions to fetch", revnum-revmeta.revnum, revnum)
 
558
                revid = revmeta.get_revision_id(mapping)
 
559
                lhs_parent[prev] = revid
 
560
                meta_map[revid] = revmeta
 
561
                if fetch_rhs_ancestry:
 
562
                    extra.update(revmeta.get_rhs_parents(mapping))
570
563
                if not self.target.has_revision(revid):
571
 
                    needed.append(revid)
 
564
                    revs.append(revid)
572
565
                elif not find_ghosts:
 
566
                    prev = None
573
567
                    break
574
 
        finally:
575
 
            pb.finished()
576
 
 
577
 
        parents[prev_revid] = None
578
 
        needed.reverse()
579
 
        return (needed, parents)
 
568
                prev = revid
 
569
            lhs_parent[prev] = NULL_REVISION
 
570
 
 
571
        check_revid(revision_id)
 
572
 
 
573
        for revid in extra:
 
574
            if revid not in revs:
 
575
                check_revid(revid)
 
576
 
 
577
        needed = [(revid, lhs_parent[revid], meta_map[revid]) for revid in reversed(revs)]
 
578
 
 
579
        return needed
580
580
 
581
581
    def copy_content(self, revision_id=None, pb=None):
582
582
        """See InterRepository.copy_content."""
590
590
        """
591
591
        raise NotImplementedError(self._copy_revisions_replay)
592
592
 
593
 
    def _fetch_switch(self, revids, pb=None, lhs_parent=None):
 
593
    def _fetch_switch(self, conn, revids, pb=None):
594
594
        """Copy a set of related revisions using svn.ra.switch.
595
595
 
596
596
        :param revids: List of revision ids of revisions to copy, 
597
597
                       newest first.
598
598
        :param pb: Optional progress bar.
599
599
        """
600
 
        repos_root = self.source.transport.get_svn_repos_root()
 
600
        repos_root = conn.get_repos_root()
601
601
 
602
602
        prev_revid = None
603
 
        transport = self.source.transport
604
603
        if pb is None:
605
604
            pb = ui.ui_factory.nested_progress_bar()
606
605
            nested_pb = pb
614
613
        editor = revbuildklass(self.source, self.target)
615
614
 
616
615
        try:
617
 
            for revid in revids:
 
616
            for (revid, parent_revid, revmeta) in revids:
618
617
                pb.update('copying revision', num, len(revids))
619
618
 
620
 
                parent_revid = lhs_parent[revid]
 
619
                assert parent_revid is not None
621
620
 
622
 
                if parent_revid is None:
 
621
                if parent_revid == NULL_REVISION:
623
622
                    parent_inv = Inventory(root_id=None)
624
623
                elif prev_revid != parent_revid:
625
624
                    parent_inv = self.target.get_inventory(parent_revid)
626
625
                else:
627
626
                    parent_inv = prev_inv
628
627
 
629
 
                editor.start_revision(revid, parent_inv)
 
628
                editor.start_revision(revid, parent_inv, revmeta)
630
629
 
631
630
                try:
632
631
                    pool = Pool()
633
632
 
634
 
                    if parent_revid is None:
 
633
                    if parent_revid == NULL_REVISION:
635
634
                        branch_url = urlutils.join(repos_root, 
636
635
                                                   editor.branch_path)
637
 
                        transport.reparent(branch_url)
638
 
                        assert transport.svn_url == branch_url.rstrip("/"), \
639
 
                            "Expected %r, got %r" % (transport.svn_url, branch_url)
640
 
                        reporter = transport.do_update(editor.revnum, True, 
 
636
                        conn.reparent(branch_url)
 
637
                        assert conn.url == branch_url, \
 
638
                            "Expected %r, got %r" % (conn.url, branch_url)
 
639
                        reporter = conn.do_update(editor.revnum, True, 
641
640
                                                       editor, pool)
642
641
 
643
642
                        # Report status of existing paths
645
644
                    else:
646
645
                        (parent_branch, parent_revnum, mapping) = \
647
646
                                self.source.lookup_revision_id(parent_revid)
648
 
                        transport.reparent(urlutils.join(repos_root, parent_branch))
 
647
                        conn.reparent(urlutils.join(repos_root, parent_branch))
649
648
 
650
649
                        if parent_branch != editor.branch_path:
651
 
                            reporter = transport.do_switch(editor.revnum, True, 
 
650
                            reporter = conn.do_switch(editor.revnum, True, 
652
651
                                urlutils.join(repos_root, editor.branch_path), 
653
652
                                editor, pool)
654
653
                        else:
655
 
                            reporter = transport.do_update(editor.revnum, True, editor)
 
654
                            reporter = conn.do_update(editor.revnum, True, editor)
656
655
 
657
656
                        # Report status of existing paths
658
657
                        reporter.set_path("", parent_revnum, False, None, pool)
659
658
 
660
 
                    lock = transport.lock_read(".")
661
659
                    reporter.finish_report(pool)
662
 
                    lock.unlock()
663
660
                except:
664
661
                    editor.abort_edit()
665
662
                    raise
672
669
            self.target.unlock()
673
670
            if nested_pb is not None:
674
671
                nested_pb.finished()
675
 
        self.source.transport.reparent_root()
676
672
 
677
673
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
678
 
              branches=None):
 
674
              branches=None, fetch_rhs_ancestry=False):
679
675
        """Fetch revisions. """
680
676
        if revision_id == NULL_REVISION:
681
677
            return
682
678
        # Dictionary with paths as keys, revnums as values
683
679
 
 
680
        if pb:
 
681
            pb.update("determining revisions to fetch", 0, 2)
 
682
 
684
683
        # Loop over all the revnums until revision_id
685
684
        # (or youngest_revnum) and call self.target.add_revision() 
686
685
        # or self.target.add_inventory() each time
687
686
        self.target.lock_read()
688
687
        try:
689
688
            if branches is not None:
690
 
                (needed, lhs_parent) = self._find_branches(branches, 
691
 
                                                           find_ghosts)
 
689
                needed = self._find_branches(branches, find_ghosts, fetch_rhs_ancestry, pb=pb)
692
690
            elif revision_id is None:
693
 
                (needed, lhs_parent) = self._find_all()
 
691
                needed = self._find_all(self.source.get_mapping(), pb=pb)
694
692
            else:
695
 
                (needed, lhs_parent) = self._find_until(revision_id, 
696
 
                                                        find_ghosts)
 
693
                needed = self._find_until(revision_id, find_ghosts, fetch_rhs_ancestry, pb=pb)
697
694
        finally:
698
695
            self.target.unlock()
699
696
 
701
698
            # Nothing to fetch
702
699
            return
703
700
 
704
 
        self._fetch_switch(needed, pb, lhs_parent)
 
701
        conn = self.source.transport.get_connection()
 
702
        try:
 
703
            self._fetch_switch(conn, needed, pb)
 
704
        finally:
 
705
            self.source.transport.add_connection(conn)
705
706
 
706
707
    @staticmethod
707
708
    def is_compatible(source, target):