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 __future__ import absolute_import
18
from cStringIO import StringIO
25
from .i18n import gettext
31
from .ui import ui_factory
25
from bzrlib.ui import ui_factory
34
28
class RenameMap(object):
66
60
:param tree: The tree containing the files.
67
61
:param file_ids: A list of file_ids to perform the updates for.
69
desired_files = [(tree.id2path(f), f) for f in file_ids]
70
with ui_factory.nested_progress_bar() as task:
63
desired_files = [(f, f) for f in file_ids]
64
task = ui_factory.nested_progress_bar()
71
66
for num, (file_id, contents) in enumerate(
72
tree.iter_files_bytes(desired_files)):
73
task.update(gettext('Calculating hashes'), num, len(file_ids))
67
tree.iter_files_bytes(desired_files)):
68
task.update('Calculating hashes', num, len(file_ids))
75
70
s.writelines(contents)
77
72
self.add_edge_hashes(s.readlines(), file_id)
79
76
def hitcounts(self, lines):
80
77
"""Count the number of hash hits for each tag, for the given lines.
103
100
:return: A list of tuples of count, path, file_id.
106
with ui_factory.nested_progress_bar() as task:
103
task = ui_factory.nested_progress_bar()
107
105
for num, path in enumerate(paths):
108
task.update(gettext('Determining hash hits'), num, len(paths))
109
hits = self.hitcounts(self.tree.get_file_lines(path))
110
all_hits.extend((v, path, k) for k, v in viewitems(hits))
106
task.update('Determining hash hits', num, len(paths))
107
hits = self.hitcounts(self.tree.get_file_lines(None,
109
all_hits.extend((v, path, k) for k, v in hits.items())
113
114
def file_match(self, paths):
174
175
missing_files = set()
175
176
missing_parents = {}
176
177
candidate_files = set()
177
with ui_factory.nested_progress_bar() as task:
178
iterator = self.tree.iter_changes(basis, want_unversioned=True,
180
for change in iterator:
181
if change.kind[1] is None and change.versioned[1]:
182
if not self.tree.has_filename(
183
self.tree.id2path(change.parent_id[0])):
184
missing_parents.setdefault(
185
change.parent_id[0], set()).add(change.file_id)
186
if change.kind[0] == 'file':
187
missing_files.add(change.file_id)
178
task = ui_factory.nested_progress_bar()
179
iterator = self.tree.iter_changes(basis, want_unversioned=True,
182
for (file_id, paths, changed_content, versioned, parent, name,
183
kind, executable) in iterator:
184
if kind[1] is None and versioned[1]:
185
missing_parents.setdefault(parent[0], set()).add(file_id)
186
if kind[0] == 'file':
187
missing_files.add(file_id)
189
# other kinds are not handled
189
#other kinds are not handled
191
if change.versioned == (False, False):
192
if self.tree.is_ignored(change.path[1]):
191
if versioned == (False, False):
192
if self.tree.is_ignored(paths[1]):
194
if change.kind[1] == 'file':
195
candidate_files.add(change.path[1])
196
if change.kind[1] == 'directory':
197
for _dir, children in self.tree.walkdirs(change.path[1]):
194
if kind[1] == 'file':
195
candidate_files.add(paths[1])
196
if kind[1] == 'directory':
197
for _dir, children in self.tree.walkdirs(paths[1]):
198
198
for child in children:
199
199
if child[2] == 'file':
200
200
candidate_files.add(child[0])
201
203
return missing_files, missing_parents, candidate_files
204
def guess_renames(klass, from_tree, to_tree, dry_run=False):
206
def guess_renames(klass, tree, dry_run=False):
205
207
"""Guess which files to rename, and perform the rename.
207
209
We assume that unversioned files and missing files indicate that
208
210
versioned files have been renamed outside of Bazaar.
210
:param from_tree: A tree to compare from
211
:param to_tree: A write-locked working tree.
212
:param tree: A write-locked working tree.
213
214
required_parents = {}
214
with ui_factory.nested_progress_bar() as task:
215
task = ui_factory.nested_progress_bar()
215
217
pp = progress.ProgressPhase('Guessing renames', 4, task)
216
with from_tree.lock_read():
218
basis = tree.basis_tree()
219
223
missing_files, missing_parents, candidate_files = (
220
rn._find_missing_files(from_tree))
224
rn._find_missing_files(basis))
222
rn.add_file_edge_hashes(from_tree, missing_files)
226
rn.add_file_edge_hashes(basis, missing_files)
224
230
matches = rn.file_match(candidate_files)
225
231
parents_matches = matches
233
239
delta = rn._make_inventory_delta(matches)
234
240
for old, new, file_id, entry in delta:
235
trace.note(gettext("{0} => {1}").format(old, new))
241
trace.note("%s => %s", old, new)
237
to_tree.add(required_parents)
238
to_tree.apply_inventory_delta(delta)
243
tree.add(required_parents)
244
tree.apply_inventory_delta(delta)
240
248
def _make_inventory_delta(self, matches):
242
file_id_matches = dict((f, p) for p, f in viewitems(matches))
244
for f in viewvalues(matches):
246
file_id_query.append(self.tree.id2path(f))
247
except errors.NoSuchId:
249
for old_path, entry in self.tree.iter_entries_by_dir(
250
specific_files=file_id_query):
250
file_id_matches = dict((f, p) for p, f in matches.items())
251
for old_path, entry in self.tree.iter_entries_by_dir(matches.values()):
251
252
new_path = file_id_matches[entry.file_id]
252
253
parent_path, new_name = osutils.split(new_path)
253
254
parent_id = matches.get(parent_path)
254
255
if parent_id is None:
255
256
parent_id = self.tree.path2id(parent_path)
256
if parent_id is None:
257
added, ignored = self.tree.smart_add(
258
[parent_path], recurse=False)
259
if len(ignored) > 0 and ignored[0] == parent_path:
262
parent_id = self.tree.path2id(parent_path)
263
257
if entry.name == new_name and entry.parent_id == parent_id:
265
259
new_entry = entry.copy()