/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
7490.109.1 by Jelmer Vernooij
Add a workspace module.
1
# Copyright (C) 2018-2020 Jelmer Vernooij
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
17
"""Convenience functions for efficiently making changes to a working tree.
18
19
If possible, uses inotify to track changes in the tree - providing
20
high performance in large trees with a small number of changes.
21
"""
22
7490.109.5 by Jelmer Vernooij
Enable absolute import.
23
from __future__ import absolute_import
24
7490.109.1 by Jelmer Vernooij
Add a workspace module.
25
import errno
26
import os
27
import shutil
28
29
30
from .clean_tree import iter_deletables
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
31
from .errors import BzrError, DependencyNotPresent
7490.109.1 by Jelmer Vernooij
Add a workspace module.
32
from .trace import warning
33
from .transform import revert
7490.109.4 by Jelmer Vernooij
add convenience function Workspace.from_path.
34
from .workingtree import WorkingTree
7490.109.1 by Jelmer Vernooij
Add a workspace module.
35
36
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
37
class WorkspaceDirty(BzrError):
38
    _fmt = "The directory %(path)s has pending changes."
7490.109.1 by Jelmer Vernooij
Add a workspace module.
39
7490.122.2 by Jelmer Vernooij
Fix error handling.
40
    def __init__(self, path):
41
        BzrError.__init__(self, path=path)
7490.109.1 by Jelmer Vernooij
Add a workspace module.
42
43
44
# TODO(jelmer): Move to .clean_tree?
45
def reset_tree(local_tree, subpath=''):
46
    """Reset a tree back to its basis tree.
47
48
    This will leave ignored and detritus files alone.
49
50
    Args:
51
      local_tree: tree to work on
52
      subpath: Subpath to operate on
53
    """
54
    revert(local_tree, local_tree.branch.basis_tree(),
55
           [subpath] if subpath not in ('.', '') else None)
56
    deletables = list(iter_deletables(
57
        local_tree, unknown=True, ignored=False, detritus=False))
58
    delete_items(deletables)
59
60
61
# TODO(jelmer): Move to .clean_tree?
62
def check_clean_tree(local_tree):
63
    """Check that a tree is clean and has no pending changes or unknown files.
64
65
    Args:
66
      local_tree: The tree to check
67
    Raises:
68
      PendingChanges: When there are pending changes
69
    """
70
    # Just check there are no changes to begin with
71
    if local_tree.has_changes():
7490.122.2 by Jelmer Vernooij
Fix error handling.
72
        raise WorkspaceDirty(local_tree.abspath('.'))
7490.109.1 by Jelmer Vernooij
Add a workspace module.
73
    if list(local_tree.unknowns()):
7490.122.2 by Jelmer Vernooij
Fix error handling.
74
        raise WorkspaceDirty(local_tree.abspath('.'))
75
76
77
def delete_items(deletables, dry_run=False):
7490.109.1 by Jelmer Vernooij
Add a workspace module.
78
    """Delete files in the deletables iterable"""
79
    def onerror(function, path, excinfo):
80
        """Show warning for errors seen by rmtree.
81
        """
82
        # Handle only permission error while removing files.
83
        # Other errors are re-raised.
84
        if function is not os.remove or excinfo[1].errno != errno.EACCES:
85
            raise
86
        warnings.warn('unable to remove %s' % path)
87
    for path, subp in deletables:
88
        if os.path.isdir(path):
89
            shutil.rmtree(path, onerror=onerror)
90
        else:
91
            try:
92
                os.unlink(path)
93
            except OSError as e:
94
                # We handle only permission error here
95
                if e.errno != errno.EACCES:
96
                    raise e
97
                warning('unable to remove "%s": %s.', path, e.strerror)
98
99
100
def get_dirty_tracker(local_tree, subpath='', use_inotify=None):
101
    """Create a dirty tracker object."""
102
    if use_inotify is True:
103
        from .dirty_tracker import DirtyTracker
104
        return DirtyTracker(local_tree, subpath)
105
    elif use_inotify is False:
106
        return None
107
    else:
108
        try:
109
            from .dirty_tracker import DirtyTracker
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
110
        except DependencyNotPresent:
7490.109.1 by Jelmer Vernooij
Add a workspace module.
111
            return None
112
        else:
113
            return DirtyTracker(local_tree, subpath)
114
115
116
class Workspace(object):
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
117
    """Create a workspace.
118
119
    :param tree: Tree to work in
120
    :param subpath: path under which to consider and commit changes
121
    :param use_inotify: whether to use inotify (default: yes, if available)
122
    """
7490.109.1 by Jelmer Vernooij
Add a workspace module.
123
124
    def __init__(self, tree, subpath='', use_inotify=None):
125
        self.tree = tree
126
        self.subpath = subpath
127
        self.use_inotify = use_inotify
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
128
        self._dirty_tracker = None
7490.109.1 by Jelmer Vernooij
Add a workspace module.
129
7490.109.4 by Jelmer Vernooij
add convenience function Workspace.from_path.
130
    @classmethod
131
    def from_path(cls, path, use_inotify=None):
132
        tree, subpath = WorkingTree.open_containing(path)
133
        return cls(tree, subpath, use_inotify=use_inotify)
134
7490.109.1 by Jelmer Vernooij
Add a workspace module.
135
    def __enter__(self):
136
        check_clean_tree(self.tree)
137
        self._dirty_tracker = get_dirty_tracker(
138
            self.tree, subpath=self.subpath, use_inotify=self.use_inotify)
139
        return self
140
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
141
    def __exit__(self, exc_type, exc_val, exc_tb):
142
        if self._dirty_tracker:
143
            del self._dirty_tracker
144
            self._dirty_tracker = None
145
        return False
146
147
    def tree_path(self, path=''):
148
        """Return a path relative to the tree subpath used by this workspace.
149
        """
150
        return os.path.join(self.subpath, path)
151
152
    def abspath(self, path=''):
153
        """Return an absolute path for the tree."""
154
        return self.tree.abspath(self.tree_path(path))
155
7490.109.1 by Jelmer Vernooij
Add a workspace module.
156
    def reset(self):
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
157
        """Reset - revert local changes, revive deleted files, remove added.
158
        """
7490.109.1 by Jelmer Vernooij
Add a workspace module.
159
        if self._dirty_tracker and not self._dirty_tracker.is_dirty():
160
            return
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
161
        reset_tree(self.tree, self.subpath)
7490.109.1 by Jelmer Vernooij
Add a workspace module.
162
        if self._dirty_tracker is not None:
163
            self._dirty_tracker.mark_clean()
164
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
165
    def _stage(self):
7490.109.1 by Jelmer Vernooij
Add a workspace module.
166
        if self._dirty_tracker:
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
167
            relpaths = self._dirty_tracker.relpaths()
7490.109.1 by Jelmer Vernooij
Add a workspace module.
168
            # Sort paths so that directories get added before the files they
169
            # contain (on VCSes where it matters)
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
170
            self.tree.add(
7490.109.1 by Jelmer Vernooij
Add a workspace module.
171
                [p for p in sorted(relpaths)
172
                 if self.tree.has_filename(p) and not
173
                    self.tree.is_ignored(p)])
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
174
            return [
7490.109.1 by Jelmer Vernooij
Add a workspace module.
175
                p for p in relpaths
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
176
                if self.tree.is_versioned(p)]
7490.109.1 by Jelmer Vernooij
Add a workspace module.
177
        else:
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
178
            self.tree.smart_add([self.tree.abspath(self.subpath)])
179
            return [self.subpath] if self.subpath else None
7490.109.1 by Jelmer Vernooij
Add a workspace module.
180
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
181
    def iter_changes(self):
182
        with self.tree.lock_write():
183
            specific_files = self._stage()
7490.109.1 by Jelmer Vernooij
Add a workspace module.
184
            basis_tree = self.tree.basis_tree()
7490.109.2 by Jelmer Vernooij
Update NEWS, add tests.
185
            # TODO(jelmer): After Python 3.3, use 'yield from'
186
            for change in self.tree.iter_changes(
187
                    basis_tree, specific_files=specific_files,
188
                    want_unversioned=False, require_versioned=True):
189
                if change.kind[1] is None and change.versioned[1]:
190
                    if change.path[0] is None:
191
                        continue
192
                    # "missing" path
193
                    change = change.discard_new()
194
                yield change
195
196
    def commit(self, **kwargs):
197
        """Create a commit.
198
199
        See WorkingTree.commit() for documentation.
200
        """
201
        if 'specific_files' in kwargs:
202
            raise NotImplementedError(self.commit)
203
204
        with self.tree.lock_write():
205
            specific_files = self._stage()
206
207
            if self.tree.supports_setting_file_ids():
208
                from .rename_map import RenameMap
209
                basis_tree = self.tree.basis_tree()
210
                RenameMap.guess_renames(
211
                    basis_tree, self.tree, dry_run=False)
212
213
            kwargs['specific_files'] = specific_files
214
            revid = self.tree.commit(**kwargs)
215
            if self._dirty_tracker:
216
                self._dirty_tracker.mark_clean()
217
            return revid