~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/fetch.py

  • Committer: Martin Pool
  • Date: 2005-09-13 06:24:27 UTC
  • Revision ID: mbp@sourcefrog.net-20050913062426-330489777cdc9099
- more progress on fetch on top of weaves

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import sys
18
18
import os
 
19
from cStringIO import StringIO
19
20
 
20
21
import bzrlib.errors
21
 
from bzrlib.trace import mutter, note
22
 
from bzrlib.branch import Branch
 
22
from bzrlib.trace import mutter, note, warning
 
23
from bzrlib.branch import Branch, INVENTORY_FILEID, ANCESTRY_FILEID
23
24
from bzrlib.progress import ProgressBar
 
25
from bzrlib.xml5 import serializer_v5
 
26
from bzrlib.osutils import sha_string, split_lines
 
27
 
 
28
"""Copying of history from one branch to another.
 
29
 
 
30
The basic plan is that every branch knows the history of everything
 
31
that has merged into it.  As the first step of a merge, pull, or
 
32
branch operation we copy history from the source into the destination
 
33
branch.
 
34
 
 
35
The copying is done in a slightly complicated order.  We don't want to
 
36
add a revision to the store until everything it refers to is also
 
37
stored, so that if a revision is present we can totally recreate it.
 
38
However, we can't know what files are included in a revision until we
 
39
read its inventory.  Therefore, we first pull the XML and hold it in
 
40
memory until we've updated all of the files referenced.
 
41
"""
 
42
 
 
43
# TODO: Avoid repeatedly opening weaves so many times.
24
44
 
25
45
 
26
46
def greedy_fetch(to_branch, from_branch, revision, pb):
29
49
 
30
50
 
31
51
class Fetcher(object):
32
 
    """Pull history from one branch to another."""
 
52
    """Pull history from one branch to another.
 
53
 
 
54
    revision_limit
 
55
        If set, pull only up to this revision_id.
 
56
        """
33
57
    def __init__(self, to_branch, from_branch, revision_limit=None, pb=None):
34
58
        self.to_branch = to_branch
35
59
        self.from_branch = from_branch
36
60
        self.revision_limit = revision_limit
 
61
        self.failed_revisions = []
 
62
        self.count_copied = 0
37
63
        if pb is None:
38
64
            self.pb = bzrlib.ui.ui_factory.progress_bar()
39
65
        else:
40
66
            self.pb = pb
41
 
        self._scan_histories()
42
 
        self.failed_revisions = []
43
 
        self.count_copied = 0
44
 
        self._copy()
45
 
 
46
 
 
47
 
    def _scan_histories(self):
48
 
        self.from_history = from_branch.revision_history()
49
 
        self.required_revisions = set(from_history)
50
 
        self.to_history = to_branch.revision_history()
 
67
        self._load_histories()
 
68
        revs_to_fetch = self._compare_ancestries()
 
69
        self._copy_revisions(revs_to_fetch)
 
70
        # - get a list of revisions that need to be pulled in
 
71
        # - for each one, pull in that revision file
 
72
        #   and get the inventory, and store the inventory with right
 
73
        #   parents.
 
74
        # - and get the ancestry, and store that with right parents too
 
75
        # - and keep a note of all file ids and version seen
 
76
        # - then go through all files; for each one get the weave,
 
77
        #   and add in all file versions
 
78
 
 
79
 
 
80
    def _load_histories(self):
 
81
        """Load histories of both branches, up to the limit."""
 
82
        self.from_history = self.from_branch.revision_history()
 
83
        self.to_history = self.to_branch.revision_history()
51
84
        if self.revision_limit:
52
 
            raise NotImplementedError('sorry, revision_limit not handled yet')
53
 
        self.need_revisions = []
54
 
        for rev_id in self.from_history:
55
 
            if not has_revision(self.to_branch):
56
 
                self.need_revisions.append(rev_id)
 
85
            assert isinstance(revision_limit, basestring)
 
86
            try:
 
87
                rev_index = self.from_history.index(revision_limit)
 
88
            except ValueError:
 
89
                rev_index = None
 
90
            if rev_index is not None:
 
91
                self.from_history = self.from_history[:rev_index + 1]
 
92
            else:
 
93
                self.from_history = [revision]
 
94
            
 
95
 
 
96
    def _compare_ancestries(self):
 
97
        """Get a list of revisions that must be copied.
 
98
 
 
99
        That is, every revision that's in the ancestry of the source
 
100
        branch and not in the destination branch."""
 
101
        if self.from_history:
 
102
            self.from_ancestry = self.from_branch.get_ancestry(self.from_history[-1])
 
103
        else:
 
104
            self.from_ancestry = []
 
105
        if self.to_history:
 
106
            self.to_history = self.to_branch.get_ancestry(self.to_history[-1])
 
107
        else:
 
108
            self.to_history = []
 
109
        ss = set(self.to_history)
 
110
        to_fetch = []
 
111
        for rev_id in self.from_ancestry:
 
112
            if rev_id not in ss:
 
113
                to_fetch.append(rev_id)
57
114
                mutter('need to get revision {%s}', rev_id)
58
 
 
59
 
 
60
 
    def _copy(self):
61
 
        while self.need_revisions:
62
 
            rev_id = self.need_revisions.pop()
63
 
            mutter('try to get revision {%s}', rev_id)
64
 
 
65
 
    
 
115
        mutter('need to get %d revisions in total', len(to_fetch))
 
116
        return to_fetch
 
117
                
 
118
 
 
119
 
 
120
    def _copy_revisions(self, revs_to_fetch):
 
121
        for rev_id in revs_to_fetch:
 
122
            self._copy_one_revision(rev_id)
 
123
 
 
124
 
 
125
    def _copy_one_revision(self, rev_id):
 
126
        """Copy revision and everything referenced by it."""
 
127
        mutter('copying revision {%s}', rev_id)
 
128
        rev_xml = self.from_branch.get_revision_xml(rev_id)
 
129
        inv_xml = self.from_branch.get_inventory_xml(rev_id)
 
130
        rev = serializer_v5.read_revision_from_string(rev_xml)
 
131
        inv = serializer_v5.read_inventory_from_string(inv_xml)
 
132
        assert rev.revision_id == rev_id
 
133
        assert rev.inventory_sha1 == sha_string(inv_xml)
 
134
        mutter('  commiter %s, %d parents',
 
135
               rev.committer,
 
136
               len(rev.parents))
 
137
        self._copy_new_texts(rev_id, inv)
 
138
        self.to_branch.weave_store.add_text(INVENTORY_FILEID, rev_id,
 
139
                                            split_lines(inv_xml), rev.parents)
 
140
        self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
 
141
 
66
142
        
 
143
    def _copy_new_texts(self, rev_id, inv):
 
144
        """Copy any new texts occuring in this revision."""
 
145
        # TODO: Rather than writing out weaves every time, hold them
 
146
        # in memory until everything's done?  But this way is nicer
 
147
        # if it's interrupted.
 
148
        for path, ie in inv.iter_entries():
 
149
            if ie.kind != 'file':
 
150
                continue
 
151
            if ie.text_version != rev_id:
 
152
                continue
 
153
            mutter('%s {%s} is changed in this revision',
 
154
                   path, ie.file_id)
 
155
            self._copy_one_text(rev_id, ie.file_id)
 
156
 
 
157
 
 
158
    def _copy_one_text(self, rev_id, file_id):
 
159
        """Copy one file text."""
 
160
        from_weave = self.from_branch.weave_store.get_weave(file_id)
 
161
        from_idx = from_weave.lookup(rev_id)
 
162
        from_parents = map(from_weave.idx_to_name, from_weave.parents(from_idx))
 
163
        text_lines = from_weave.get(from_idx)
 
164
        to_weave = self.to_branch.weave_store.get_weave_or_empty(file_id)
 
165
        if rev_id in to_weave._name_map:
 
166
            warning('version {%s} already present in weave of file {%s}',
 
167
                    rev_id, file_id)
 
168
            return
 
169
        to_parents = map(to_weave.lookup, from_parents)
 
170
        to_weave.add(rev_id, to_parents, text_lines)
 
171
        self.to_branch.weave_store.put_weave(file_id, to_weave)
67
172
    
68
173
 
69
174
def has_revision(branch, revision_id):
70
175
    try:
71
 
        branch.get_revision_xml(revision_id)
 
176
        branch.get_revision_xml_file(revision_id)
72
177
        return True
73
178
    except bzrlib.errors.NoSuchRevision:
74
179
        return False