~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/fetch.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-02-22 07:59:56 UTC
  • mfrom: (1553.5.33 bzr.mbp.locks)
  • Revision ID: pqm@pqm.ubuntu.com-20060222075956-fb281c427e571da6
add LockDir and related fixes

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
 
import os
18
 
from cStringIO import StringIO
19
 
 
20
 
import bzrlib
21
 
import bzrlib.errors as errors
22
 
from bzrlib.errors import InstallFailed, NoSuchRevision, WeaveError
23
 
from bzrlib.trace import mutter, note, warning
24
 
from bzrlib.branch import Branch
25
 
from bzrlib.progress import ProgressBar
26
 
from bzrlib.xml5 import serializer_v5
27
 
from bzrlib.osutils import sha_string, split_lines
28
17
 
29
18
"""Copying of history from one branch to another.
30
19
 
41
30
memory until we've updated all of the files referenced.
42
31
"""
43
32
 
 
33
import bzrlib
 
34
import bzrlib.errors as errors
 
35
from bzrlib.errors import (InstallFailed, NoSuchRevision, WeaveError,
 
36
                           MissingText)
 
37
from bzrlib.trace import mutter
 
38
from bzrlib.progress import ProgressBar
 
39
from bzrlib.revision import NULL_REVISION
 
40
from bzrlib.symbol_versioning import *
 
41
 
 
42
 
44
43
# TODO: Avoid repeatedly opening weaves so many times.
45
44
 
46
45
# XXX: This doesn't handle ghost (not present in branch) revisions at
59
58
#   and add in all file versions
60
59
 
61
60
 
62
 
 
 
61
@deprecated_function(zero_eight)
63
62
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
 
63
    """Legacy API, please see branch.fetch(from_branch, last_revision, pb)."""
64
64
    f = Fetcher(to_branch, from_branch, revision, pb)
65
65
    return f.count_copied, f.failed_revisions
66
66
 
67
 
 
68
 
 
69
 
class Fetcher(object):
70
 
    """Pull revisions and texts from one branch to another.
71
 
 
72
 
    This doesn't update the destination's history; that can be done
73
 
    separately if desired.  
74
 
 
75
 
    revision_limit
76
 
        If set, pull only up to this revision_id.
77
 
 
78
 
    After running:
79
 
 
80
 
    last_revision -- if last_revision
81
 
        is given it will be that, otherwise the last revision of
82
 
        from_branch
83
 
 
 
67
fetch = greedy_fetch
 
68
 
 
69
 
 
70
class RepoFetcher(object):
 
71
    """Pull revisions and texts from one repository to another.
 
72
 
 
73
    last_revision
 
74
        if set, try to limit to the data this revision references.
 
75
 
 
76
    after running:
84
77
    count_copied -- number of revisions copied
85
78
 
86
 
    count_weaves -- number of file weaves copied
 
79
    This should not be used directory, its essential a object to encapsulate
 
80
    the logic in InterRepository.fetch().
87
81
    """
88
 
    def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
89
 
        if to_branch == from_branch:
90
 
            raise Exception("can't fetch from a branch to itself")
91
 
        self.to_branch = to_branch
92
 
        self.to_weaves = to_branch.weave_store
93
 
        self.to_control = to_branch.control_weaves
94
 
        self.from_branch = from_branch
95
 
        self.from_weaves = from_branch.weave_store
96
 
        self.from_control = from_branch.control_weaves
 
82
    def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
 
83
        # result variables.
97
84
        self.failed_revisions = []
98
85
        self.count_copied = 0
99
 
        self.count_total = 0
100
 
        self.count_weaves = 0
101
 
        self.copied_file_ids = set()
 
86
        if to_repository.control_files._transport.base == from_repository.control_files._transport.base:
 
87
            # check that last_revision is in 'from' and then return a no-operation.
 
88
            if last_revision not in (None, NULL_REVISION):
 
89
                from_repository.get_revision(last_revision)
 
90
            return
 
91
        self.to_repository = to_repository
 
92
        self.from_repository = from_repository
 
93
        # must not mutate self._last_revision as its potentially a shared instance
 
94
        self._last_revision = last_revision
102
95
        if pb is None:
103
96
            self.pb = bzrlib.ui.ui_factory.progress_bar()
104
97
        else:
105
98
            self.pb = pb
106
 
        self.from_branch.lock_read()
107
 
        try:
108
 
            self._fetch_revisions(last_revision)
109
 
        finally:
110
 
            self.from_branch.unlock()
 
99
        self.from_repository.lock_read()
 
100
        try:
 
101
            self.to_repository.lock_write()
 
102
            try:
 
103
                self.__fetch()
 
104
            finally:
 
105
                self.to_repository.unlock()
 
106
        finally:
 
107
            self.from_repository.unlock()
 
108
 
 
109
    def __fetch(self):
 
110
        """Primary worker function.
 
111
 
 
112
        This initialises all the needed variables, and then fetches the 
 
113
        requested revisions, finally clearing the progress bar.
 
114
        """
 
115
        self.to_weaves = self.to_repository.weave_store
 
116
        self.to_control = self.to_repository.control_weaves
 
117
        self.from_weaves = self.from_repository.weave_store
 
118
        self.from_control = self.from_repository.control_weaves
 
119
        self.count_total = 0
 
120
        self.file_ids_names = {}
 
121
        try:
 
122
            revs = self._revids_to_fetch()
 
123
            # nothing to do
 
124
            if revs: 
 
125
                self._fetch_weave_texts(revs)
 
126
                self._fetch_inventory_weave(revs)
 
127
                self._fetch_revision_texts(revs)
 
128
                self.count_copied += len(revs)
 
129
        finally:
111
130
            self.pb.clear()
112
131
 
113
 
    def _fetch_revisions(self, last_revision):
114
 
        try:
115
 
            self.last_revision = self._find_last_revision(last_revision)
116
 
        except NoSuchRevision, e:
117
 
            mutter('failed getting last revision: %s', e)
118
 
            raise InstallFailed([last_revision])
119
 
        mutter('fetch up to rev {%s}', self.last_revision)
120
 
        if (self.last_revision is not None and 
121
 
            self.to_branch.has_revision(self.last_revision)):
122
 
            return
123
 
        try:
124
 
            revs_to_fetch = self._compare_ancestries()
125
 
        except WeaveError:
126
 
            raise InstallFailed([self.last_revision])
127
 
        self._copy_revisions(revs_to_fetch)
128
 
        self.new_ancestry = revs_to_fetch
129
 
 
130
 
    def _find_last_revision(self, last_revision):
131
 
        """Find the limiting source revision.
132
 
 
133
 
        Every ancestor of that revision will be merged across.
134
 
 
135
 
        Returns the revision_id, or returns None if there's no history
136
 
        in the source branch."""
137
 
        self.pb.update('get source history')
138
 
        from_history = self.from_branch.revision_history()
 
132
    def _revids_to_fetch(self):
139
133
        self.pb.update('get destination history')
140
 
        if last_revision:
141
 
            self.from_branch.get_revision(last_revision)
142
 
            return last_revision
143
 
        elif from_history:
144
 
            return from_history[-1]
145
 
        else:
146
 
            return None                 # no history in the source branch
 
134
        mutter('fetch up to rev {%s}', self._last_revision)
 
135
        if self._last_revision is NULL_REVISION:
 
136
            # explicit limit of no revisions needed
 
137
            return None
 
138
        if (self._last_revision != None and
 
139
            self.to_repository.has_revision(self._last_revision)):
 
140
            return None
147
141
            
148
 
 
149
 
    def _compare_ancestries(self):
150
 
        """Get a list of revisions that must be copied.
151
 
 
152
 
        That is, every revision that's in the ancestry of the source
153
 
        branch and not in the destination branch."""
154
 
        self.pb.update('get source ancestry')
155
 
        self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
156
 
 
157
 
        dest_last_rev = self.to_branch.last_revision()
158
 
        self.pb.update('get destination ancestry')
159
 
        if dest_last_rev:
160
 
            dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
 
142
        try:
 
143
            return self.to_repository.missing_revision_ids(self.from_repository,
 
144
                                                           self._last_revision)
 
145
        except errors.NoSuchRevision:
 
146
            raise InstallFailed([self._last_revision])
 
147
 
 
148
    def _fetch_revision_texts(self, revs):
 
149
        self.to_repository.revision_store.copy_multi(
 
150
            self.from_repository.revision_store,
 
151
            revs,
 
152
            pb=self.pb)
 
153
 
 
154
    def _fetch_weave_texts(self, revs):
 
155
        file_ids = self.from_repository.fileid_involved_by_set(revs)
 
156
        count = 0
 
157
        num_file_ids = len(file_ids)
 
158
        for file_id in file_ids:
 
159
            self.pb.update("merge weaves", count, num_file_ids)
 
160
            count +=1
 
161
            to_weave = self.to_weaves.get_weave_or_empty(file_id,
 
162
                self.to_repository.get_transaction())
 
163
            from_weave = self.from_weaves.get_weave(file_id,
 
164
                self.from_repository.get_transaction())
 
165
 
 
166
            if to_weave.numversions() > 0:
 
167
                # destination has contents, must merge
 
168
                try:
 
169
                    to_weave.join(from_weave)
 
170
                except errors.WeaveParentMismatch:
 
171
                    to_weave.reweave(from_weave)
 
172
            else:
 
173
                # destination is empty, just replace it
 
174
                to_weave = from_weave.copy()
 
175
 
 
176
            self.to_weaves.put_weave(file_id, to_weave,
 
177
                self.to_repository.get_transaction())
 
178
        self.pb.clear()
 
179
 
 
180
    def _fetch_inventory_weave(self, revs):
 
181
        self.pb.update("inventory fetch", 0, 2)
 
182
        from_weave = self.from_repository.get_inventory_weave()
 
183
        to_weave = self.to_repository.get_inventory_weave()
 
184
        self.pb.update("inventory fetch", 1, 2)
 
185
        to_weave = self.to_control.get_weave('inventory',
 
186
                self.to_repository.get_transaction())
 
187
        self.pb.update("inventory fetch", 2, 2)
 
188
 
 
189
        if to_weave.numversions() > 0:
 
190
            # destination has contents, must merge
 
191
            try:
 
192
                to_weave.join(from_weave, pb=self.pb, msg='merge inventory')
 
193
            except errors.WeaveParentMismatch:
 
194
                to_weave.reweave(from_weave, pb=self.pb, msg='reweave inventory')
161
195
        else:
162
 
            dest_ancestry = []
163
 
        ss = set(dest_ancestry)
164
 
        to_fetch = []
165
 
        for rev_id in self.from_ancestry:
166
 
            if rev_id not in ss:
167
 
                to_fetch.append(rev_id)
168
 
                mutter('need to get revision {%s}', rev_id)
169
 
        mutter('need to get %d revisions in total', len(to_fetch))
170
 
        self.count_total = len(to_fetch)
171
 
        return to_fetch
172
 
 
173
 
    def _copy_revisions(self, revs_to_fetch):
174
 
        i = 0
175
 
        for rev_id in revs_to_fetch:
176
 
            i += 1
177
 
            if rev_id is None:
178
 
                continue
179
 
            if self.to_branch.has_revision(rev_id):
180
 
                continue
181
 
            self.pb.update('fetch revision', i, self.count_total)
182
 
            self._copy_one_revision(rev_id)
183
 
            self.count_copied += 1
184
 
 
185
 
 
186
 
    def _copy_one_revision(self, rev_id):
187
 
        """Copy revision and everything referenced by it."""
188
 
        mutter('copying revision {%s}', rev_id)
189
 
        rev_xml = self.from_branch.get_revision_xml(rev_id)
190
 
        inv_xml = self.from_branch.get_inventory_xml(rev_id)
191
 
        rev = serializer_v5.read_revision_from_string(rev_xml)
192
 
        inv = serializer_v5.read_inventory_from_string(inv_xml)
193
 
        assert rev.revision_id == rev_id
194
 
        assert rev.inventory_sha1 == sha_string(inv_xml)
195
 
        mutter('  commiter %s, %d parents',
196
 
               rev.committer,
197
 
               len(rev.parent_ids))
198
 
        self._copy_new_texts(rev_id, inv)
199
 
        parents = rev.parent_ids
200
 
        for parent in parents:
201
 
            if not self.to_branch.has_revision(parent):
202
 
                parents.pop(parents.index(parent))
203
 
        self._copy_inventory(rev_id, inv_xml, parents)
204
 
        self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
205
 
        mutter('copied revision %s', rev_id)
206
 
 
207
 
 
208
 
    def _copy_inventory(self, rev_id, inv_xml, parent_ids):
209
 
        self.to_control.add_text('inventory', rev_id,
210
 
                                split_lines(inv_xml), parent_ids,
211
 
                                self.to_branch.get_transaction())
212
 
 
213
 
    def _copy_new_texts(self, rev_id, inv):
214
 
        """Copy any new texts occuring in this revision."""
215
 
        # TODO: Rather than writing out weaves every time, hold them
216
 
        # in memory until everything's done?  But this way is nicer
217
 
        # if it's interrupted.
218
 
        for path, ie in inv.iter_entries():
219
 
            if ie.revision != rev_id:
220
 
                continue
221
 
            mutter('%s {%s} is changed in this revision',
222
 
                   path, ie.file_id)
223
 
            self._copy_one_weave(rev_id, ie.file_id)
224
 
 
225
 
 
226
 
    def _copy_one_weave(self, rev_id, file_id):
227
 
        """Copy one file weave."""
228
 
        mutter('copy file {%s} modified in {%s}', file_id, rev_id)
229
 
        if file_id in self.copied_file_ids:
230
 
            mutter('file {%s} already copied', file_id)
231
 
            return
232
 
        from_weave = self.from_weaves.get_weave(file_id,
233
 
            self.from_branch.get_transaction())
234
 
        to_weave = self.to_weaves.get_weave_or_empty(file_id,
235
 
            self.to_branch.get_transaction())
236
 
        try:
237
 
            to_weave.join(from_weave)
238
 
        except errors.WeaveParentMismatch:
239
 
            to_weave.reweave(from_weave)
240
 
        self.to_weaves.put_weave(file_id, to_weave,
241
 
            self.to_branch.get_transaction())
242
 
        self.count_weaves += 1
243
 
        self.copied_file_ids.add(file_id)
244
 
        mutter('copied file {%s}', file_id)
245
 
 
246
 
 
247
 
fetch = Fetcher
 
196
            # destination is empty, just replace it
 
197
            to_weave = from_weave.copy()
 
198
 
 
199
        self.to_control.put_weave('inventory', to_weave,
 
200
            self.to_repository.get_transaction())
 
201
 
 
202
        self.pb.clear()
 
203
 
 
204
 
 
205
class Fetcher(object):
 
206
    """Backwards compatability glue for branch.fetch()."""
 
207
 
 
208
    @deprecated_method(zero_eight)
 
209
    def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
 
210
        """Please see branch.fetch()."""
 
211
        to_branch.fetch(from_branch, last_revision, pb)