1
# Copyright (C) 2005 by Canonical Ltd
 
 
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.
 
 
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.
 
 
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
 
 
19
from cStringIO import StringIO
 
 
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
 
 
28
"""Copying of history from one branch to another.
 
 
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
 
 
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.
 
 
43
# TODO: Avoid repeatedly opening weaves so many times.
 
 
45
# XXX: This doesn't handle ghost (not present in branch) revisions at
 
 
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
 
 
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
 
 
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
 
 
64
class Fetcher(object):
 
 
65
    """Pull history from one branch to another.
 
 
68
        If set, pull only up to this revision_id.
 
 
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 = []
 
 
77
            self.pb = bzrlib.ui.ui_factory.progress_bar()
 
 
80
        self._load_histories()
 
 
81
        revs_to_fetch = self._compare_ancestries()
 
 
82
        self._copy_revisions(revs_to_fetch)
 
 
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)
 
 
91
                rev_index = self.from_history.index(revision_limit)
 
 
94
            if rev_index is not None:
 
 
95
                self.from_history = self.from_history[:rev_index + 1]
 
 
97
                self.from_history = [revision]
 
 
100
    def _compare_ancestries(self):
 
 
101
        """Get a list of revisions that must be copied.
 
 
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])
 
 
108
            self.from_ancestry = []
 
 
110
            self.to_history = self.to_branch.get_ancestry(self.to_history[-1])
 
 
113
        ss = set(self.to_history)
 
 
115
        for rev_id in self.from_ancestry:
 
 
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))
 
 
124
    def _copy_revisions(self, revs_to_fetch):
 
 
125
        for rev_id in revs_to_fetch:
 
 
126
            self._copy_one_revision(rev_id)
 
 
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',
 
 
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)
 
 
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':
 
 
155
            if ie.text_version != rev_id:
 
 
157
            mutter('%s {%s} is changed in this revision',
 
 
159
            self._copy_one_text(rev_id, ie.file_id)
 
 
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)