/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/fetch.py

  • Committer: Martin Pool
  • Date: 2005-09-13 23:08:19 UTC
  • Revision ID: mbp@sourcefrog.net-20050913230819-6ceae96050d32faa
ignore .bzr-shelf

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
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
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import sys
 
18
import os
 
19
from cStringIO import StringIO
 
20
 
 
21
import bzrlib.errors
 
22
from bzrlib.trace import mutter, note, warning
 
23
from bzrlib.branch import Branch, INVENTORY_FILEID, ANCESTRY_FILEID
 
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.
 
44
 
 
45
# XXX: This doesn't handle ghost (not present in branch) revisions at
 
46
# all yet.
 
47
 
 
48
# - get a list of revisions that need to be pulled in
 
49
# - for each one, pull in that revision file
 
50
#   and get the inventory, and store the inventory with right
 
51
#   parents.
 
52
# - and get the ancestry, and store that with right parents too
 
53
# - and keep a note of all file ids and version seen
 
54
# - then go through all files; for each one get the weave,
 
55
#   and add in all file versions
 
56
 
 
57
 
 
58
 
 
59
def greedy_fetch(to_branch, from_branch, revision, pb):
 
60
    f = Fetcher(to_branch, from_branch, revision, pb)
 
61
    return f.count_copied, f.failed_revisions
 
62
 
 
63
 
 
64
class Fetcher(object):
 
65
    """Pull history from one branch to another.
 
66
 
 
67
    revision_limit
 
68
        If set, pull only up to this revision_id.
 
69
        """
 
70
    def __init__(self, to_branch, from_branch, revision_limit=None, pb=None):
 
71
        self.to_branch = to_branch
 
72
        self.from_branch = from_branch
 
73
        self.revision_limit = revision_limit
 
74
        self.failed_revisions = []
 
75
        self.count_copied = 0
 
76
        if pb is None:
 
77
            self.pb = bzrlib.ui.ui_factory.progress_bar()
 
78
        else:
 
79
            self.pb = pb
 
80
        self._load_histories()
 
81
        revs_to_fetch = self._compare_ancestries()
 
82
        self._copy_revisions(revs_to_fetch)
 
83
 
 
84
    def _load_histories(self):
 
85
        """Load histories of both branches, up to the limit."""
 
86
        self.from_history = self.from_branch.revision_history()
 
87
        self.to_history = self.to_branch.revision_history()
 
88
        if self.revision_limit:
 
89
            assert isinstance(revision_limit, basestring)
 
90
            try:
 
91
                rev_index = self.from_history.index(revision_limit)
 
92
            except ValueError:
 
93
                rev_index = None
 
94
            if rev_index is not None:
 
95
                self.from_history = self.from_history[:rev_index + 1]
 
96
            else:
 
97
                self.from_history = [revision]
 
98
            
 
99
 
 
100
    def _compare_ancestries(self):
 
101
        """Get a list of revisions that must be copied.
 
102
 
 
103
        That is, every revision that's in the ancestry of the source
 
104
        branch and not in the destination branch."""
 
105
        if self.from_history:
 
106
            self.from_ancestry = self.from_branch.get_ancestry(self.from_history[-1])
 
107
        else:
 
108
            self.from_ancestry = []
 
109
        if self.to_history:
 
110
            self.to_history = self.to_branch.get_ancestry(self.to_history[-1])
 
111
        else:
 
112
            self.to_history = []
 
113
        ss = set(self.to_history)
 
114
        to_fetch = []
 
115
        for rev_id in self.from_ancestry:
 
116
            if rev_id not in ss:
 
117
                to_fetch.append(rev_id)
 
118
                mutter('need to get revision {%s}', rev_id)
 
119
        mutter('need to get %d revisions in total', len(to_fetch))
 
120
        return to_fetch
 
121
                
 
122
 
 
123
 
 
124
    def _copy_revisions(self, revs_to_fetch):
 
125
        for rev_id in revs_to_fetch:
 
126
            self._copy_one_revision(rev_id)
 
127
 
 
128
 
 
129
    def _copy_one_revision(self, rev_id):
 
130
        """Copy revision and everything referenced by it."""
 
131
        mutter('copying revision {%s}', rev_id)
 
132
        rev_xml = self.from_branch.get_revision_xml(rev_id)
 
133
        inv_xml = self.from_branch.get_inventory_xml(rev_id)
 
134
        rev = serializer_v5.read_revision_from_string(rev_xml)
 
135
        inv = serializer_v5.read_inventory_from_string(inv_xml)
 
136
        assert rev.revision_id == rev_id
 
137
        assert rev.inventory_sha1 == sha_string(inv_xml)
 
138
        mutter('  commiter %s, %d parents',
 
139
               rev.committer,
 
140
               len(rev.parents))
 
141
        self._copy_new_texts(rev_id, inv)
 
142
        self.to_branch.weave_store.add_text(INVENTORY_FILEID, rev_id,
 
143
                                            split_lines(inv_xml), rev.parents)
 
144
        self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
 
145
 
 
146
        
 
147
    def _copy_new_texts(self, rev_id, inv):
 
148
        """Copy any new texts occuring in this revision."""
 
149
        # TODO: Rather than writing out weaves every time, hold them
 
150
        # in memory until everything's done?  But this way is nicer
 
151
        # if it's interrupted.
 
152
        for path, ie in inv.iter_entries():
 
153
            if ie.kind != 'file':
 
154
                continue
 
155
            if ie.text_version != rev_id:
 
156
                continue
 
157
            mutter('%s {%s} is changed in this revision',
 
158
                   path, ie.file_id)
 
159
            self._copy_one_text(rev_id, ie.file_id)
 
160
 
 
161
 
 
162
    def _copy_one_text(self, rev_id, file_id):
 
163
        """Copy one file text."""
 
164
        from_weave = self.from_branch.weave_store.get_weave(file_id)
 
165
        from_idx = from_weave.lookup(rev_id)
 
166
        from_parents = map(from_weave.idx_to_name, from_weave.parents(from_idx))
 
167
        text_lines = from_weave.get(from_idx)
 
168
        to_weave = self.to_branch.weave_store.get_weave_or_empty(file_id)
 
169
        to_parents = map(to_weave.lookup, from_parents)
 
170
        # it's ok to add even if the text is already there
 
171
        to_weave.add(rev_id, to_parents, text_lines)
 
172
        self.to_branch.weave_store.put_weave(file_id, to_weave)