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 |