~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 05:22:41 UTC
  • Revision ID: mbp@sourcefrog.net-20050913052241-52dbd8e8ced620f6
- better BZR_DEBUG trace output

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 copy import copy
 
17
import sys
18
18
import os
19
 
from cStringIO import StringIO
20
19
 
21
 
import bzrlib
22
 
import bzrlib.errors as errors
23
 
from bzrlib.errors import (InstallFailed, NoSuchRevision, WeaveError,
24
 
                           MissingText)
25
 
from bzrlib.trace import mutter, note, warning
 
20
import bzrlib.errors
 
21
from bzrlib.trace import mutter, note
26
22
from bzrlib.branch import Branch
27
23
from bzrlib.progress import ProgressBar
28
 
from bzrlib.xml5 import serializer_v5
29
 
from bzrlib.osutils import sha_string, split_lines
30
 
 
31
 
"""Copying of history from one branch to another.
32
 
 
33
 
The basic plan is that every branch knows the history of everything
34
 
that has merged into it.  As the first step of a merge, pull, or
35
 
branch operation we copy history from the source into the destination
36
 
branch.
37
 
 
38
 
The copying is done in a slightly complicated order.  We don't want to
39
 
add a revision to the store until everything it refers to is also
40
 
stored, so that if a revision is present we can totally recreate it.
41
 
However, we can't know what files are included in a revision until we
42
 
read its inventory.  Therefore, we first pull the XML and hold it in
43
 
memory until we've updated all of the files referenced.
44
 
"""
45
 
 
46
 
# TODO: Avoid repeatedly opening weaves so many times.
47
 
 
48
 
# XXX: This doesn't handle ghost (not present in branch) revisions at
49
 
# all yet.  I'm not sure they really should be supported.
50
 
 
51
 
# NOTE: This doesn't copy revisions which may be present but not
52
 
# merged into the last revision.  I'm not sure we want to do that.
53
 
 
54
 
# - get a list of revisions that need to be pulled in
55
 
# - for each one, pull in that revision file
56
 
#   and get the inventory, and store the inventory with right
57
 
#   parents.
58
 
# - and get the ancestry, and store that with right parents too
59
 
# - and keep a note of all file ids and version seen
60
 
# - then go through all files; for each one get the weave,
61
 
#   and add in all file versions
62
 
 
63
 
 
64
 
 
65
 
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
 
24
 
 
25
 
 
26
def greedy_fetch(to_branch, from_branch, revision, pb):
66
27
    f = Fetcher(to_branch, from_branch, revision, pb)
67
28
    return f.count_copied, f.failed_revisions
68
29
 
69
30
 
70
 
 
71
31
class Fetcher(object):
72
 
    """Pull revisions and texts from one branch to another.
73
 
 
74
 
    This doesn't update the destination's history; that can be done
75
 
    separately if desired.  
76
 
 
77
 
    revision_limit
78
 
        If set, pull only up to this revision_id.
79
 
 
80
 
    After running:
81
 
 
82
 
    last_revision -- if last_revision
83
 
        is given it will be that, otherwise the last revision of
84
 
        from_branch
85
 
 
86
 
    count_copied -- number of revisions copied
87
 
 
88
 
    count_weaves -- number of file weaves copied
89
 
    """
90
 
    def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
91
 
        if to_branch == from_branch:
92
 
            raise Exception("can't fetch from a branch to itself")
 
32
    """Pull history from one branch to another."""
 
33
    def __init__(self, to_branch, from_branch, revision_limit=None, pb=None):
93
34
        self.to_branch = to_branch
94
 
        self.to_weaves = to_branch.weave_store
95
 
        self.to_control = to_branch.control_weaves
96
35
        self.from_branch = from_branch
97
 
        self.from_weaves = from_branch.weave_store
98
 
        self.from_control = from_branch.control_weaves
99
 
        self.failed_revisions = []
100
 
        self.count_copied = 0
101
 
        self.count_total = 0
102
 
        self.count_weaves = 0
103
 
        self.copied_file_ids = set()
104
 
        self.file_ids_names = {}
 
36
        self.revision_limit = revision_limit
105
37
        if pb is None:
106
38
            self.pb = bzrlib.ui.ui_factory.progress_bar()
107
39
        else:
108
40
            self.pb = pb
109
 
        self.from_branch.lock_read()
110
 
        try:
111
 
            self._fetch_revisions(last_revision)
112
 
        finally:
113
 
            self.from_branch.unlock()
114
 
            self.pb.clear()
115
 
 
116
 
    def _fetch_revisions(self, last_revision):
117
 
        self.last_revision = self._find_last_revision(last_revision)
118
 
        mutter('fetch up to rev {%s}', self.last_revision)
119
 
        if (self.last_revision is not None and 
120
 
            self.to_branch.has_revision(self.last_revision)):
121
 
            return
122
 
        try:
123
 
            revs_to_fetch = self._compare_ancestries()
124
 
        except WeaveError:
125
 
            raise InstallFailed([self.last_revision])
126
 
        self._copy_revisions(revs_to_fetch)
127
 
        self.new_ancestry = revs_to_fetch
128
 
 
129
 
    def _find_last_revision(self, last_revision):
130
 
        """Find the limiting source revision.
131
 
 
132
 
        Every ancestor of that revision will be merged across.
133
 
 
134
 
        Returns the revision_id, or returns None if there's no history
135
 
        in the source branch."""
136
 
        if last_revision:
137
 
            return last_revision
138
 
        self.pb.update('get source history')
139
 
        from_history = self.from_branch.revision_history()
140
 
        self.pb.update('get destination history')
141
 
        if from_history:
142
 
            return from_history[-1]
143
 
        else:
144
 
            return None                 # no history in the source branch
145
 
            
146
 
 
147
 
    def _compare_ancestries(self):
148
 
        """Get a list of revisions that must be copied.
149
 
 
150
 
        That is, every revision that's in the ancestry of the source
151
 
        branch and not in the destination branch."""
152
 
        self.pb.update('get source ancestry')
153
 
        self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
154
 
 
155
 
        dest_last_rev = self.to_branch.last_revision()
156
 
        self.pb.update('get destination ancestry')
157
 
        if dest_last_rev:
158
 
            dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
159
 
        else:
160
 
            dest_ancestry = []
161
 
        ss = set(dest_ancestry)
162
 
        to_fetch = []
163
 
        for rev_id in self.from_ancestry:
164
 
            if rev_id not in ss:
165
 
                to_fetch.append(rev_id)
 
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()
 
51
        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)
166
57
                mutter('need to get revision {%s}', rev_id)
167
 
        mutter('need to get %d revisions in total', len(to_fetch))
168
 
        self.count_total = len(to_fetch)
169
 
        return to_fetch
170
 
 
171
 
    def _copy_revisions(self, revs_to_fetch):
172
 
        i = 0
173
 
        for rev_id in revs_to_fetch:
174
 
            i += 1
175
 
            if rev_id is None:
176
 
                continue
177
 
            if self.to_branch.has_revision(rev_id):
178
 
                continue
179
 
            self.pb.update('copy revision', i, self.count_total)
180
 
            self._copy_one_revision(rev_id)
181
 
            self.count_copied += 1
182
 
 
183
 
 
184
 
    def _copy_one_revision(self, rev_id):
185
 
        """Copy revision and everything referenced by it."""
186
 
        mutter('copying revision {%s}', rev_id)
187
 
        rev_xml = self.from_branch.get_revision_xml(rev_id)
188
 
        inv_xml = self.from_branch.get_inventory_xml(rev_id)
189
 
        rev = serializer_v5.read_revision_from_string(rev_xml)
190
 
        inv = serializer_v5.read_inventory_from_string(inv_xml)
191
 
        assert rev.revision_id == rev_id
192
 
        assert rev.inventory_sha1 == sha_string(inv_xml)
193
 
        mutter('  commiter %s, %d parents',
194
 
               rev.committer,
195
 
               len(rev.parent_ids))
196
 
        self._copy_new_texts(rev_id, inv)
197
 
        parents = rev.parent_ids
198
 
        new_parents = copy(parents)
199
 
        for parent in parents:
200
 
            if not self.to_branch.has_revision(parent):
201
 
                new_parents.pop(new_parents.index(parent))
202
 
        self._copy_inventory(rev_id, inv_xml, new_parents)
203
 
        self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
204
 
        mutter('copied revision %s', rev_id)
205
 
 
206
 
    def _copy_inventory(self, rev_id, inv_xml, parent_ids):
207
 
        self.to_control.add_text('inventory', rev_id,
208
 
                                split_lines(inv_xml), parent_ids,
209
 
                                self.to_branch.get_transaction())
210
 
 
211
 
    def _copy_new_texts(self, rev_id, inv):
212
 
        """Copy any new texts occuring in this revision."""
213
 
        # TODO: Rather than writing out weaves every time, hold them
214
 
        # in memory until everything's done?  But this way is nicer
215
 
        # if it's interrupted.
216
 
        for path, ie in inv.iter_entries():
217
 
            self._copy_one_weave(rev_id, ie.file_id, ie.revision)
218
 
 
219
 
    def _copy_one_weave(self, rev_id, file_id, text_revision):
220
 
        """Copy one file weave, esuring the result contains text_revision."""
221
 
        # check if the revision is already there
222
 
        if file_id in self.file_ids_names.keys( ) and \
223
 
            text_revision in self.file_ids_names[file_id]:
224
 
                return        
225
 
        to_weave = self.to_weaves.get_weave_or_empty(file_id,
226
 
            self.to_branch.get_transaction())
227
 
        if not file_id in self.file_ids_names.keys( ):
228
 
            self.file_ids_names[file_id] = to_weave.names( )
229
 
        if text_revision in to_weave:
230
 
            return
231
 
        from_weave = self.from_weaves.get_weave(file_id,
232
 
            self.from_branch.get_transaction())
233
 
        if text_revision not in from_weave:
234
 
            raise MissingText(self.from_branch, text_revision, file_id)
235
 
        mutter('copy file {%s} modified in {%s}', file_id, rev_id)
236
 
 
237
 
        if to_weave.numversions() > 0:
238
 
            # destination has contents, must merge
 
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
    
 
66
        
 
67
    
 
68
 
 
69
def has_revision(branch, revision_id):
 
70
    try:
 
71
        branch.get_revision_xml(revision_id)
 
72
        return True
 
73
    except bzrlib.errors.NoSuchRevision:
 
74
        return False
 
75
 
 
76
 
 
77
def old_greedy_fetch(to_branch, from_branch, revision=None, pb=None):
 
78
    """Copy all history from one branch to another.
 
79
 
 
80
    revision
 
81
        If set, copy only up to this point in the source branch.
 
82
 
 
83
    @returns: number copied, missing ids       
 
84
    """
 
85
    from_history = from_branch.revision_history()
 
86
    required_revisions = set(from_history)
 
87
    all_failed = set()
 
88
    if revision is not None:
 
89
        required_revisions.add(revision)
 
90
        try:
 
91
            rev_index = from_history.index(revision)
 
92
        except ValueError:
 
93
            rev_index = None
 
94
        if rev_index is not None:
 
95
            from_history = from_history[:rev_index + 1]
 
96
        else:
 
97
            from_history = [revision]
 
98
    to_history = to_branch.revision_history()
 
99
    missing = []
 
100
    for rev_id in from_history:
 
101
        if not has_revision(to_branch, rev_id):
 
102
            missing.append(rev_id)
 
103
 
 
104
    # recurse down through the revision graph, looking for things that
 
105
    # can't be found.
 
106
    count = 0
 
107
    while len(missing) > 0:
 
108
        installed, failed = to_branch.install_revisions(from_branch, 
 
109
                                                        revision_ids=missing,
 
110
                                                        pb=pb)
 
111
        count += installed
 
112
        required_failed = failed.intersection(required_revisions)
 
113
        if len(required_failed) > 0:
 
114
            raise bzrlib.errors.InstallFailed(required_failed)
 
115
        for rev_id in failed:
 
116
            note("Failed to install %s" % rev_id)
 
117
        all_failed.update(failed)
 
118
        new_missing = set() 
 
119
        for rev_id in missing:
239
120
            try:
240
 
                to_weave.join(from_weave)
241
 
            except errors.WeaveParentMismatch:
242
 
                to_weave.reweave(from_weave)
243
 
        else:
244
 
            # destination is empty, just replace it
245
 
            to_weave = from_weave.copy( )
246
 
        self.to_weaves.put_weave(file_id, to_weave,
247
 
            self.to_branch.get_transaction())
248
 
        self.count_weaves += 1
249
 
        self.copied_file_ids.add(file_id)
250
 
        self.file_ids_names[file_id] = to_weave.names()
251
 
        mutter('copied file {%s}', file_id)
252
 
 
253
 
 
254
 
fetch = Fetcher
 
121
                revision = from_branch.get_revision(rev_id)
 
122
            except bzrlib.errors.NoSuchRevision:
 
123
                if revision in from_history:
 
124
                    raise
 
125
                else:
 
126
                    continue
 
127
            for parent in [p.revision_id for p in revision.parents]:
 
128
                if not has_revision(to_branch, parent):
 
129
                    new_missing.add(parent)
 
130
        missing = new_missing
 
131
    return count, all_failed
 
132
 
 
133
 
 
134
def old_install_revisions(branch, other, revision_ids, pb):
 
135
    """Copy revisions from other branch into branch.
 
136
 
 
137
    This is a lower-level function used by a pull or a merge.  It
 
138
    incorporates some history from one branch into another, but
 
139
    does not update the revision history or operate on the working
 
140
    copy.
 
141
 
 
142
    revision_ids
 
143
        Sequence of revisions to copy.
 
144
 
 
145
    pb
 
146
        Progress bar for copying.
 
147
    """
 
148
    if False:
 
149
        if hasattr(other.revision_store, "prefetch"):
 
150
            other.revision_store.prefetch(revision_ids)
 
151
        if hasattr(other.inventory_store, "prefetch"):
 
152
            other.inventory_store.prefetch(revision_ids)
 
153
 
 
154
    if pb is None:
 
155
        pb = bzrlib.ui.ui_factory.progress_bar()
 
156
 
 
157
    revisions = []
 
158
    needed_texts = set()
 
159
    i = 0
 
160
 
 
161
    failures = set()
 
162
    for i, rev_id in enumerate(revision_ids):
 
163
        pb.update('fetching revision', i+1, len(revision_ids))
 
164
        try:
 
165
            rev = other.get_revision(rev_id)
 
166
        except bzrlib.errors.NoSuchRevision:
 
167
            failures.add(rev_id)
 
168
            continue
 
169
 
 
170
        revisions.append(rev)
 
171
        inv = other.get_inventory(rev_id)
 
172
        for key, entry in inv.iter_entries():
 
173
            if entry.text_id is None:
 
174
                continue
 
175
            if entry.text_id not in branch.text_store:
 
176
                needed_texts.add(entry.text_id)
 
177
 
 
178
    pb.clear()
 
179
 
 
180
    count, cp_fail = branch.text_store.copy_multi(other.text_store, 
 
181
                                                needed_texts)
 
182
    count, cp_fail = branch.inventory_store.copy_multi(other.inventory_store, 
 
183
                                                     revision_ids)
 
184
    count, cp_fail = branch.revision_store.copy_multi(other.revision_store, 
 
185
                                                    revision_ids,
 
186
                                                    permit_failure=True)
 
187
    assert len(cp_fail) == 0 
 
188
    return count, failures
 
189
 
 
190