~bzr-pqm/bzr/bzr.dev

0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
1
#!/usr/bin/env python
2
"""\
3
This contains the apply changset function for bzr
4
"""
5
6
import bzrlib
0.5.67 by John Arbash Meinel
Working on apply_changeset
7
import os
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
8
import sys
9
from cStringIO import StringIO
10
import tempfile
11
import shutil
0.5.67 by John Arbash Meinel
Working on apply_changeset
12
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
13
from bzrlib.xml5 import serializer_v5
0.5.80 by John Arbash Meinel
Starting to write tests for changeset, discovering some errors as I go.
14
from bzrlib.trace import mutter, warning
0.5.110 by Aaron Bentley
Switched to using the common ancestor for changeset merge base
15
from bzrlib.revision import common_ancestor
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
16
from bzrlib.merge import merge_inner
17
from bzrlib.errors import BzrCommandError
18
from bzrlib.diff import compare_trees
19
from bzrlib.osutils import sha_string, split_lines
20
0.5.80 by John Arbash Meinel
Starting to write tests for changeset, discovering some errors as I go.
21
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
22
def _install_info(branch, cset_info, cset_tree):
0.5.67 by John Arbash Meinel
Working on apply_changeset
23
    """Make sure that there is a text entry for each 
24
    file in the changeset.
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
25
26
    TODO: This might be supplanted by some sort of Commit() object, though
27
          some of the side-effects should not occur
28
    TODO: The latest code assumes that if you have the Revision information
29
          then you have to have everything else.
30
          So there may be no point in adding older revision information to
31
          the bottom of a changeset (though I would like to add them all
32
          as ghost revisions)
33
    """
34
35
    if not branch.has_revision(cset_info.target):
36
        branch.lock_write()
37
        try:
38
            # install the inventory
39
            # TODO: Figure out how to handle ghosted revisions
40
            present_parents = []
41
            parent_invs = []
42
            rev = cset_info.real_revisions[-1]
43
            for p_id in rev.parent_ids:
44
                if branch.has_revision(p_id):
45
                    present_parents.append(p_id)
46
                    parent_invs.append(branch.get_inventory(revision))
47
48
            inv = cset_tree.inventory
49
            
50
            # Add the texts that are not already present
51
            for path, ie in inv.iter_entries():
52
                w = branch.weave_store.get_weave_or_empty(ie.file_id,
53
                        branch.get_transaction())
54
                if ie.revision not in w._name_map:
55
                    branch.weave_store.add_text(ie.file_id, rev.revision_id,
56
                        cset_tree.get_file(ie.file_id).readlines(),
57
                        present_parents, branch.get_transaction())
58
59
            # Now add the inventory information
60
            txt = serializer_v5.write_inventory_to_string(inv)
61
            sha1 = sha_string(txt)
62
            branch.control_weaves.add_text('inventory', 
63
                rev.revision_id, 
64
                split_lines(txt), 
65
                present_parents,
66
                branch.get_transaction())
67
68
            # And finally, insert the revision
69
            rev_tmp = StringIO()
70
            serializer_v5.write_revision(rev, rev_tmp)
71
            rev_tmp.seek(0)
72
            branch.revision_store.add(rev_tmp, rev.revision_id)
73
        finally:
74
            branch.unlock()
0.5.67 by John Arbash Meinel
Working on apply_changeset
75
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
76
def apply_changeset(branch, from_file, reverse=False, auto_commit=False):
0.5.80 by John Arbash Meinel
Starting to write tests for changeset, discovering some errors as I go.
77
    """Read in a changeset from the given file, and apply it to
78
    the supplied branch.
0.5.86 by John Arbash Meinel
Updated the auto-commit functionality, and adding to pending-merges, more testing.
79
80
    :return: True if the changeset was automatically committed to the
81
             ancestry, False otherwise.
0.5.80 by John Arbash Meinel
Starting to write tests for changeset, discovering some errors as I go.
82
    """
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
83
    import read_changeset
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
84
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
85
    if reverse:
86
        raise Exception('reverse not implemented yet')
0.5.80 by John Arbash Meinel
Starting to write tests for changeset, discovering some errors as I go.
87
88
    cset = read_changeset.read_changeset(from_file, branch)
89
0.5.86 by John Arbash Meinel
Updated the auto-commit functionality, and adding to pending-merges, more testing.
90
    return _apply_cset(branch, cset, reverse=reverse, auto_commit=auto_commit)
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
91
        
0.5.80 by John Arbash Meinel
Starting to write tests for changeset, discovering some errors as I go.
92
def _apply_cset(branch, cset, reverse=False, auto_commit=False):
93
    """Apply an in-memory changeset to a given branch.
94
    """
95
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
96
    cset_info, cset_tree = cset
0.5.67 by John Arbash Meinel
Working on apply_changeset
97
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
98
    _install_info(branch, cset_info, cset_tree)
0.5.67 by John Arbash Meinel
Working on apply_changeset
99
100
    # We could technically optimize more, by using the ChangesetTree
101
    # we already have in memory, but after installing revisions
102
    # this code can work the way merge should work in the
103
    # future.
104
    #
105
    # TODO:
106
    #   This shouldn't use the base of the changeset as the base
107
    #   for the merge, the merge code should pick the best merge
108
    #   based on the ancestry of both trees.
109
    #
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
110
    if branch.last_revision() is None:
111
        base = None
112
    else:
113
        base = common_ancestor(branch.last_revision(), cset_info.target, branch)
114
    merge_inner(branch, branch.revision_tree(cset_info.target),
115
                branch.revision_tree(base))
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
116
0.5.86 by John Arbash Meinel
Updated the auto-commit functionality, and adding to pending-merges, more testing.
117
    auto_committed = False
118
    
119
    # There are 2 cases where I am allowing automatic committing.
120
    # 1) If the final revision has a parent of the current last revision
121
    #    (branch.last_patch() in cset.target.parents)
122
    #    that means that the changeset target has already merged the current
123
    #    tree.
124
    # 2) A cset contains a list of revisions. If the first entry has a parent
125
    #    of branch.last_patch(), then we can start merging there, and add the
126
    #    rest of the revisions. But it gets better. Some of the entries in the
127
    #    list might already be in the revision list, so we keep going until
128
    #    we find the first revision *not* in the list. If it's parent is
129
    #    branch.last_patch(), then we can also append history from there.
130
    #    This second part is a little more controversial, because the cset
131
    #    probably does not include all of the inventories. So you would have
132
    #    entries in branch.revision_history() without an associated inventory.
133
    #    we could just explicitly disable this. But if we had the inventory
134
    #    entries available, it is what 'bzr merge' would do.
135
    #    If we disable this, the target will just show up as a pending_merge
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
136
    if auto_commit:
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
137
        raise NotImplementedError('automatic committing has not been implemented after the changes')
0.5.67 by John Arbash Meinel
Working on apply_changeset
138
        # When merging, if the revision to be merged has a parent
139
        # of the current revision, then it can be installed
140
        # directly.
141
        #
142
        # TODO: 
143
        #   There is actually a slightly stronger statement
144
        #   whereby if the current revision is in the ancestry
145
        #   of the merged revisions, it doesn't need to be the
146
        #   immediate ancestry, but that requires searching
147
        #   a potentially branching history.
148
        #
0.5.86 by John Arbash Meinel
Updated the auto-commit functionality, and adding to pending-merges, more testing.
149
        rh = branch.revision_history()
150
        revs_to_merge = None
151
        found_parent = False
152
        if len(rh) == 0 and len(cset_info.real_revisions[0].parents) == 0:
153
            found_parent = True
154
            revs_to_merge = cset_info.real_revisions
155
        else:
156
            for rev_num, rev in enumerate(cset_info.real_revisions):
157
                if rev.revision_id not in rh:
158
                    for parent in rev.parents:
159
                        if parent.revision_id == rh[-1]:
160
                            found_parent = True
161
                    if found_parent:
162
                        # All revisions up until now already
163
                        # existed in the target history
164
                        # and this last one is a child of the
165
                        # last entry in the history.
166
                        # so we can add the rest
167
                        revs_to_merge = cset_info.real_revisions[rev_num:]
168
                    # Even if we don't find anything, we need to
169
                    # stop here
170
                    break
171
172
        if found_parent:
173
            rev_ids = [r.revision_id for r in revs_to_merge]
174
            branch.append_revision(*rev_ids)
175
            auto_committed = True
176
        else:
177
            # We can also merge if the *last* revision has an
178
            # appropriate parent.
179
            target_has_parent = False
180
            target_rev = branch.get_revision(cset_info.target)
181
            lastrev_id = branch.last_patch()
182
            for parent in target_rev.parents:
183
                if parent.revision_id == lastrev_id:
184
                    target_has_parent = True
185
186
            if target_has_parent:
187
                branch.append_revision(target_rev.revision_id)
188
            else:
189
                print '** Could not auto-commit.'
190
191
    if not auto_committed:
192
        branch.add_pending_merge(cset_info.target)
193
194
    return auto_committed
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
195