14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from io import BytesIO
17
from __future__ import absolute_import
19
from cStringIO import StringIO
24
from .i18n import gettext
25
from .ui import ui_factory
26
from brzlib.ui import ui_factory
27
from brzlib.i18n import gettext
28
29
class RenameMap(object):
29
30
"""Determine a mapping of renames."""
60
61
:param tree: The tree containing the files.
61
62
:param file_ids: A list of file_ids to perform the updates for.
63
desired_files = [(tree.id2path(f), f) for f in file_ids]
64
with ui_factory.nested_progress_bar() as task:
64
desired_files = [(f, f) for f in file_ids]
65
task = ui_factory.nested_progress_bar()
65
67
for num, (file_id, contents) in enumerate(
66
tree.iter_files_bytes(desired_files)):
68
tree.iter_files_bytes(desired_files)):
67
69
task.update(gettext('Calculating hashes'), num, len(file_ids))
69
71
s.writelines(contents)
71
73
self.add_edge_hashes(s.readlines(), file_id)
73
77
def hitcounts(self, lines):
74
78
"""Count the number of hash hits for each tag, for the given lines.
97
101
:return: A list of tuples of count, path, file_id.
100
with ui_factory.nested_progress_bar() as task:
104
task = ui_factory.nested_progress_bar()
101
106
for num, path in enumerate(paths):
102
107
task.update(gettext('Determining hash hits'), num, len(paths))
103
hits = self.hitcounts(self.tree.get_file_lines(path))
108
hits = self.hitcounts(self.tree.get_file_lines(None,
104
110
all_hits.extend((v, path, k) for k, v in hits.items())
107
115
def file_match(self, paths):
168
176
missing_files = set()
169
177
missing_parents = {}
170
178
candidate_files = set()
171
with ui_factory.nested_progress_bar() as task:
172
iterator = self.tree.iter_changes(basis, want_unversioned=True,
174
for change in iterator:
175
if change.kind[1] is None and change.versioned[1]:
176
if not self.tree.has_filename(
177
self.tree.id2path(change.parent_id[0])):
178
missing_parents.setdefault(
179
change.parent_id[0], set()).add(change.file_id)
180
if change.kind[0] == 'file':
181
missing_files.add(change.file_id)
179
task = ui_factory.nested_progress_bar()
180
iterator = self.tree.iter_changes(basis, want_unversioned=True,
183
for (file_id, paths, changed_content, versioned, parent, name,
184
kind, executable) in iterator:
185
if kind[1] is None and versioned[1]:
186
missing_parents.setdefault(parent[0], set()).add(file_id)
187
if kind[0] == 'file':
188
missing_files.add(file_id)
183
# other kinds are not handled
190
#other kinds are not handled
185
if change.versioned == (False, False):
186
if self.tree.is_ignored(change.path[1]):
192
if versioned == (False, False):
193
if self.tree.is_ignored(paths[1]):
188
if change.kind[1] == 'file':
189
candidate_files.add(change.path[1])
190
if change.kind[1] == 'directory':
191
for _dir, children in self.tree.walkdirs(change.path[1]):
195
if kind[1] == 'file':
196
candidate_files.add(paths[1])
197
if kind[1] == 'directory':
198
for _dir, children in self.tree.walkdirs(paths[1]):
192
199
for child in children:
193
200
if child[2] == 'file':
194
201
candidate_files.add(child[0])
195
204
return missing_files, missing_parents, candidate_files
198
def guess_renames(klass, from_tree, to_tree, dry_run=False):
207
def guess_renames(klass, tree, dry_run=False):
199
208
"""Guess which files to rename, and perform the rename.
201
210
We assume that unversioned files and missing files indicate that
202
211
versioned files have been renamed outside of Bazaar.
204
:param from_tree: A tree to compare from
205
:param to_tree: A write-locked working tree.
213
:param tree: A write-locked working tree.
207
215
required_parents = {}
208
with ui_factory.nested_progress_bar() as task:
216
task = ui_factory.nested_progress_bar()
209
218
pp = progress.ProgressPhase('Guessing renames', 4, task)
210
with from_tree.lock_read():
219
basis = tree.basis_tree()
213
224
missing_files, missing_parents, candidate_files = (
214
rn._find_missing_files(from_tree))
225
rn._find_missing_files(basis))
216
rn.add_file_edge_hashes(from_tree, missing_files)
227
rn.add_file_edge_hashes(basis, missing_files)
218
231
matches = rn.file_match(candidate_files)
219
232
parents_matches = matches
227
240
delta = rn._make_inventory_delta(matches)
228
241
for old, new, file_id, entry in delta:
229
trace.note(gettext("{0} => {1}").format(old, new))
242
trace.note( gettext("{0} => {1}").format(old, new) )
231
to_tree.add(required_parents)
232
to_tree.apply_inventory_delta(delta)
244
tree.add(required_parents)
245
tree.apply_inventory_delta(delta)
234
249
def _make_inventory_delta(self, matches):
236
251
file_id_matches = dict((f, p) for p, f in matches.items())
238
for f in matches.values():
240
file_id_query.append(self.tree.id2path(f))
241
except errors.NoSuchId:
243
for old_path, entry in self.tree.iter_entries_by_dir(
244
specific_files=file_id_query):
252
for old_path, entry in self.tree.iter_entries_by_dir(matches.values()):
245
253
new_path = file_id_matches[entry.file_id]
246
254
parent_path, new_name = osutils.split(new_path)
247
255
parent_id = matches.get(parent_path)
248
256
if parent_id is None:
249
257
parent_id = self.tree.path2id(parent_path)
250
if parent_id is None:
251
added, ignored = self.tree.smart_add(
252
[parent_path], recurse=False)
253
if len(ignored) > 0 and ignored[0] == parent_path:
256
parent_id = self.tree.path2id(parent_path)
257
258
if entry.name == new_name and entry.parent_id == parent_id:
259
260
new_entry = entry.copy()