/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 breezy/git/transform.py

Split out git and bzr-specific transforms.

Merged from https://code.launchpad.net/~jelmer/brz/transform-file-id/+merge/386859

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
2
# Copyright (C) 2020 Breezy Developers
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
 
 
18
from __future__ import absolute_import
 
19
 
 
20
import errno
 
21
import os
 
22
 
 
23
from .. import errors, ui
 
24
from ..i18n import gettext
 
25
from ..mutabletree import MutableTree
 
26
from ..sixish import viewitems
 
27
from ..transform import (
 
28
    TreeTransform,
 
29
    _TransformResults,
 
30
    _FileMover,
 
31
    FinalPaths,
 
32
    unique_add,
 
33
    TransformRenameFailed,
 
34
    )
 
35
 
 
36
from ..bzr import inventory
 
37
from ..bzr.transform import TransformPreview as GitTransformPreview
 
38
 
 
39
 
 
40
class GitTreeTransform(TreeTransform):
 
41
    """Tree transform for Bazaar trees."""
 
42
 
 
43
    def version_file(self, trans_id, file_id=None):
 
44
        """Schedule a file to become versioned."""
 
45
        if file_id is None:
 
46
            raise ValueError()
 
47
        unique_add(self._new_id, trans_id, file_id)
 
48
        unique_add(self._r_new_id, file_id, trans_id)
 
49
 
 
50
    def cancel_versioning(self, trans_id):
 
51
        """Undo a previous versioning of a file"""
 
52
        file_id = self._new_id[trans_id]
 
53
        del self._new_id[trans_id]
 
54
        del self._r_new_id[file_id]
 
55
 
 
56
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
 
57
        """Apply all changes to the inventory and filesystem.
 
58
 
 
59
        If filesystem or inventory conflicts are present, MalformedTransform
 
60
        will be thrown.
 
61
 
 
62
        If apply succeeds, finalize is not necessary.
 
63
 
 
64
        :param no_conflicts: if True, the caller guarantees there are no
 
65
            conflicts, so no check is made.
 
66
        :param precomputed_delta: An inventory delta to use instead of
 
67
            calculating one.
 
68
        :param _mover: Supply an alternate FileMover, for testing
 
69
        """
 
70
        for hook in MutableTree.hooks['pre_transform']:
 
71
            hook(self._tree, self)
 
72
        if not no_conflicts:
 
73
            self._check_malformed()
 
74
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
75
            if precomputed_delta is None:
 
76
                child_pb.update(gettext('Apply phase'), 0, 2)
 
77
                changes = self._generate_transform_changes()
 
78
                offset = 1
 
79
            else:
 
80
                changes = [
 
81
                    (op, np, ie) for (op, np, fid, ie) in precomputed_delta]
 
82
                offset = 0
 
83
            if _mover is None:
 
84
                mover = _FileMover()
 
85
            else:
 
86
                mover = _mover
 
87
            try:
 
88
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
 
89
                self._apply_removals(mover)
 
90
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
 
91
                modified_paths = self._apply_insertions(mover)
 
92
            except BaseException:
 
93
                mover.rollback()
 
94
                raise
 
95
            else:
 
96
                mover.apply_deletions()
 
97
        if self.final_file_id(self.root) is None:
 
98
            changes = [e for e in changes if e[0] != '']
 
99
        self._tree._apply_transform_delta(changes)
 
100
        self._done = True
 
101
        self.finalize()
 
102
        return _TransformResults(modified_paths, self.rename_count)
 
103
 
 
104
    def _apply_removals(self, mover):
 
105
        """Perform tree operations that remove directory/inventory names.
 
106
 
 
107
        That is, delete files that are to be deleted, and put any files that
 
108
        need renaming into limbo.  This must be done in strict child-to-parent
 
109
        order.
 
110
 
 
111
        If inventory_delta is None, no inventory delta generation is performed.
 
112
        """
 
113
        tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
 
114
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
115
            for num, (path, trans_id) in enumerate(tree_paths):
 
116
                # do not attempt to move root into a subdirectory of itself.
 
117
                if path == '':
 
118
                    continue
 
119
                child_pb.update(gettext('removing file'), num, len(tree_paths))
 
120
                full_path = self._tree.abspath(path)
 
121
                if trans_id in self._removed_contents:
 
122
                    delete_path = os.path.join(self._deletiondir, trans_id)
 
123
                    mover.pre_delete(full_path, delete_path)
 
124
                elif (trans_id in self._new_name or
 
125
                      trans_id in self._new_parent):
 
126
                    try:
 
127
                        mover.rename(full_path, self._limbo_name(trans_id))
 
128
                    except TransformRenameFailed as e:
 
129
                        if e.errno != errno.ENOENT:
 
130
                            raise
 
131
                    else:
 
132
                        self.rename_count += 1
 
133
 
 
134
    def _apply_insertions(self, mover):
 
135
        """Perform tree operations that insert directory/inventory names.
 
136
 
 
137
        That is, create any files that need to be created, and restore from
 
138
        limbo any files that needed renaming.  This must be done in strict
 
139
        parent-to-child order.
 
140
 
 
141
        If inventory_delta is None, no inventory delta is calculated, and
 
142
        no list of modified paths is returned.
 
143
        """
 
144
        new_paths = self.new_paths(filesystem_only=True)
 
145
        modified_paths = []
 
146
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
147
            for num, (path, trans_id) in enumerate(new_paths):
 
148
                if (num % 10) == 0:
 
149
                    child_pb.update(gettext('adding file'),
 
150
                                    num, len(new_paths))
 
151
                full_path = self._tree.abspath(path)
 
152
                if trans_id in self._needs_rename:
 
153
                    try:
 
154
                        mover.rename(self._limbo_name(trans_id), full_path)
 
155
                    except TransformRenameFailed as e:
 
156
                        # We may be renaming a dangling inventory id
 
157
                        if e.errno != errno.ENOENT:
 
158
                            raise
 
159
                    else:
 
160
                        self.rename_count += 1
 
161
                    # TODO: if trans_id in self._observed_sha1s, we should
 
162
                    #       re-stat the final target, since ctime will be
 
163
                    #       updated by the change.
 
164
                if (trans_id in self._new_contents
 
165
                        or self.path_changed(trans_id)):
 
166
                    if trans_id in self._new_contents:
 
167
                        modified_paths.append(full_path)
 
168
                if trans_id in self._new_executability:
 
169
                    self._set_executability(path, trans_id)
 
170
                if trans_id in self._observed_sha1s:
 
171
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
 
172
                    st = osutils.lstat(full_path)
 
173
                    self._observed_sha1s[trans_id] = (o_sha1, st)
 
174
        for path, trans_id in new_paths:
 
175
            # new_paths includes stuff like workingtree conflicts. Only the
 
176
            # stuff in new_contents actually comes from limbo.
 
177
            if trans_id in self._limbo_files:
 
178
                del self._limbo_files[trans_id]
 
179
        self._new_contents.clear()
 
180
        return modified_paths
 
181
 
 
182
    def _inventory_altered(self):
 
183
        """Determine which trans_ids need new Inventory entries.
 
184
 
 
185
        An new entry is needed when anything that would be reflected by an
 
186
        inventory entry changes, including file name, file_id, parent file_id,
 
187
        file kind, and the execute bit.
 
188
 
 
189
        Some care is taken to return entries with real changes, not cases
 
190
        where the value is deleted and then restored to its original value,
 
191
        but some actually unchanged values may be returned.
 
192
 
 
193
        :returns: A list of (path, trans_id) for all items requiring an
 
194
            inventory change. Ordered by path.
 
195
        """
 
196
        changed_ids = set()
 
197
        # Find entries whose file_ids are new (or changed).
 
198
        new_file_id = set(t for t in self._new_id
 
199
                          if self._new_id[t] != self.tree_file_id(t))
 
200
        for id_set in [self._new_name, self._new_parent, new_file_id,
 
201
                       self._new_executability]:
 
202
            changed_ids.update(id_set)
 
203
        # removing implies a kind change
 
204
        changed_kind = set(self._removed_contents)
 
205
        # so does adding
 
206
        changed_kind.intersection_update(self._new_contents)
 
207
        # Ignore entries that are already known to have changed.
 
208
        changed_kind.difference_update(changed_ids)
 
209
        #  to keep only the truly changed ones
 
210
        changed_kind = (t for t in changed_kind
 
211
                        if self.tree_kind(t) != self.final_kind(t))
 
212
        # all kind changes will alter the inventory
 
213
        changed_ids.update(changed_kind)
 
214
        # To find entries with changed parent_ids, find parents which existed,
 
215
        # but changed file_id.
 
216
        # Now add all their children to the set.
 
217
        for parent_trans_id in new_file_id:
 
218
            changed_ids.update(self.iter_tree_children(parent_trans_id))
 
219
        return sorted(FinalPaths(self).get_paths(changed_ids))
 
220
 
 
221
    def _generate_transform_changes(self):
 
222
        """Generate an inventory delta for the current transform."""
 
223
        changes = []
 
224
        new_paths = self._inventory_altered()
 
225
        total_entries = len(new_paths) + len(self._removed_id)
 
226
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
227
            for num, trans_id in enumerate(self._removed_id):
 
228
                if (num % 10) == 0:
 
229
                    child_pb.update(gettext('removing file'),
 
230
                                    num, total_entries)
 
231
                if trans_id == self._new_root:
 
232
                    file_id = self._tree.path2id('')
 
233
                else:
 
234
                    file_id = self.tree_file_id(trans_id)
 
235
                # File-id isn't really being deleted, just moved
 
236
                if file_id in self._r_new_id:
 
237
                    continue
 
238
                path = self._tree_id_paths[trans_id]
 
239
                changes.append((path, None, None))
 
240
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
241
                                     new_paths)
 
242
            for num, (path, trans_id) in enumerate(new_paths):
 
243
                if (num % 10) == 0:
 
244
                    child_pb.update(gettext('adding file'),
 
245
                                    num + len(self._removed_id), total_entries)
 
246
                file_id = new_path_file_ids[trans_id]
 
247
                if file_id is None:
 
248
                    continue
 
249
                kind = self.final_kind(trans_id)
 
250
                if kind is None:
 
251
                    kind = self._tree.stored_kind(self._tree.id2path(file_id))
 
252
                parent_trans_id = self.final_parent(trans_id)
 
253
                parent_file_id = new_path_file_ids.get(parent_trans_id)
 
254
                if parent_file_id is None:
 
255
                    parent_file_id = self.final_file_id(parent_trans_id)
 
256
                if trans_id in self._new_reference_revision:
 
257
                    new_entry = inventory.TreeReference(
 
258
                        file_id,
 
259
                        self._new_name[trans_id],
 
260
                        self.final_file_id(self._new_parent[trans_id]),
 
261
                        None, self._new_reference_revision[trans_id])
 
262
                else:
 
263
                    new_entry = inventory.make_entry(kind,
 
264
                                                     self.final_name(trans_id),
 
265
                                                     parent_file_id, file_id)
 
266
                try:
 
267
                    old_path = self._tree.id2path(new_entry.file_id)
 
268
                except errors.NoSuchId:
 
269
                    old_path = None
 
270
                new_executability = self._new_executability.get(trans_id)
 
271
                if new_executability is not None:
 
272
                    new_entry.executable = new_executability
 
273
                changes.append(
 
274
                    (old_path, path, new_entry))
 
275
        return changes