~bzr-pqm/bzr/bzr.dev

974.1.27 by aaron.bentley at utoronto
Initial greedy fetch work
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
1218 by Martin Pool
- fix up import
16
17
import sys
18
import os
1231 by Martin Pool
- more progress on fetch on top of weaves
19
from cStringIO import StringIO
1218 by Martin Pool
- fix up import
20
974.1.27 by aaron.bentley at utoronto
Initial greedy fetch work
21
import bzrlib.errors
1231 by Martin Pool
- more progress on fetch on top of weaves
22
from bzrlib.trace import mutter, note, warning
23
from bzrlib.branch import Branch, INVENTORY_FILEID, ANCESTRY_FILEID
974.1.28 by aaron.bentley at utoronto
factored install_revisions out of update_revisions, updated test cases for greedy_fetch
24
from bzrlib.progress import ProgressBar
1231 by Martin Pool
- more progress on fetch on top of weaves
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.
974.1.27 by aaron.bentley at utoronto
Initial greedy fetch work
44
1238 by Martin Pool
- remove a lot of dead code from fetch
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
1219 by Martin Pool
- BROKEN: start refactoring fetch code to work well with weaves
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):
1231 by Martin Pool
- more progress on fetch on top of weaves
65
    """Pull history from one branch to another.
66
67
    revision_limit
68
        If set, pull only up to this revision_id.
69
        """
1219 by Martin Pool
- BROKEN: start refactoring fetch code to work well with weaves
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
1231 by Martin Pool
- more progress on fetch on top of weaves
74
        self.failed_revisions = []
75
        self.count_copied = 0
1219 by Martin Pool
- BROKEN: start refactoring fetch code to work well with weaves
76
        if pb is None:
77
            self.pb = bzrlib.ui.ui_factory.progress_bar()
78
        else:
79
            self.pb = pb
1231 by Martin Pool
- more progress on fetch on top of weaves
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()
1219 by Martin Pool
- BROKEN: start refactoring fetch code to work well with weaves
88
        if self.revision_limit:
1231 by Martin Pool
- more progress on fetch on top of weaves
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)
1219 by Martin Pool
- BROKEN: start refactoring fetch code to work well with weaves
118
                mutter('need to get revision {%s}', rev_id)
1231 by Martin Pool
- more progress on fetch on top of weaves
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
1219 by Martin Pool
- BROKEN: start refactoring fetch code to work well with weaves
146
        
1231 by Martin Pool
- more progress on fetch on top of weaves
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)
1238 by Martin Pool
- remove a lot of dead code from fetch
170
        # it's ok to add even if the text is already there
1231 by Martin Pool
- more progress on fetch on top of weaves
171
        to_weave.add(rev_id, to_parents, text_lines)
172
        self.to_branch.weave_store.put_weave(file_id, to_weave)