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
33
28
class RenameMap(object):
34
29
"""Determine a mapping of renames."""
65
60
:param tree: The tree containing the files.
66
61
:param file_ids: A list of file_ids to perform the updates for.
68
desired_files = [(tree.id2path(f), f) for f in file_ids]
69
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()
70
66
for num, (file_id, contents) in enumerate(
71
67
tree.iter_files_bytes(desired_files)):
72
task.update(gettext('Calculating hashes'), num, len(file_ids))
68
task.update('Calculating hashes', num, len(file_ids))
74
70
s.writelines(contents)
76
72
self.add_edge_hashes(s.readlines(), file_id)
78
76
def hitcounts(self, lines):
79
77
"""Count the number of hash hits for each tag, for the given lines.
102
100
:return: A list of tuples of count, path, file_id.
105
with ui_factory.nested_progress_bar() as task:
103
task = ui_factory.nested_progress_bar()
106
105
for num, path in enumerate(paths):
107
task.update(gettext('Determining hash hits'), num, len(paths))
108
hits = self.hitcounts(self.tree.get_file_lines(path))
109
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())
112
114
def file_match(self, paths):
143
145
path = osutils.dirname(path)
144
if self.tree.is_versioned(path):
146
if self.tree.path2id(path) is not None:
146
148
required_parents.setdefault(path, []).append(child)
148
for parent, children in viewitems(required_parents):
150
for parent, children in required_parents.iteritems():
149
151
child_file_ids = set()
150
152
for child in children:
151
153
file_id = matches.get(child)
162
164
parent directories.
165
for file_id, file_id_children in viewitems(missing_parents):
166
for path, path_children in viewitems(required_parents):
167
for file_id, file_id_children in missing_parents.iteritems():
168
for path, path_children in required_parents.iteritems():
167
169
hits = len(path_children.intersection(file_id_children))
169
171
all_hits.append((hits, path, file_id))
173
175
missing_files = set()
174
176
missing_parents = {}
175
177
candidate_files = set()
176
with ui_factory.nested_progress_bar() as task:
177
iterator = self.tree.iter_changes(basis, want_unversioned=True,
178
task = ui_factory.nested_progress_bar()
179
iterator = self.tree.iter_changes(basis, want_unversioned=True,
179
182
for (file_id, paths, changed_content, versioned, parent, name,
180
183
kind, executable) in iterator:
181
184
if kind[1] is None and versioned[1]:
182
if not self.tree.has_filename(self.tree.id2path(parent[0])):
183
missing_parents.setdefault(parent[0], set()).add(file_id)
185
missing_parents.setdefault(parent[0], set()).add(file_id)
184
186
if kind[0] == 'file':
185
187
missing_files.add(file_id)
196
198
for child in children:
197
199
if child[2] == 'file':
198
200
candidate_files.add(child[0])
199
203
return missing_files, missing_parents, candidate_files
202
def guess_renames(klass, from_tree, to_tree, dry_run=False):
206
def guess_renames(klass, tree, dry_run=False):
203
207
"""Guess which files to rename, and perform the rename.
205
209
We assume that unversioned files and missing files indicate that
206
210
versioned files have been renamed outside of Bazaar.
208
:param from_tree: A tree to compare from
209
:param to_tree: A write-locked working tree.
212
:param tree: A write-locked working tree.
211
214
required_parents = {}
212
with ui_factory.nested_progress_bar() as task:
215
task = ui_factory.nested_progress_bar()
213
217
pp = progress.ProgressPhase('Guessing renames', 4, task)
214
with from_tree.lock_read():
218
basis = tree.basis_tree()
217
223
missing_files, missing_parents, candidate_files = (
218
rn._find_missing_files(from_tree))
224
rn._find_missing_files(basis))
220
rn.add_file_edge_hashes(from_tree, missing_files)
226
rn.add_file_edge_hashes(basis, missing_files)
222
230
matches = rn.file_match(candidate_files)
223
231
parents_matches = matches
231
239
delta = rn._make_inventory_delta(matches)
232
240
for old, new, file_id, entry in delta:
233
trace.note( gettext("{0} => {1}").format(old, new) )
241
trace.note("%s => %s", old, new)
235
to_tree.add(required_parents)
236
to_tree.apply_inventory_delta(delta)
243
tree.add(required_parents)
244
tree.apply_inventory_delta(delta)
238
248
def _make_inventory_delta(self, matches):
240
file_id_matches = dict((f, p) for p, f in viewitems(matches))
242
for f in viewvalues(matches):
244
file_id_query.append(self.tree.id2path(f))
245
except errors.NoSuchId:
247
for old_path, entry in self.tree.iter_entries_by_dir(
248
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()):
249
252
new_path = file_id_matches[entry.file_id]
250
253
parent_path, new_name = osutils.split(new_path)
251
254
parent_id = matches.get(parent_path)
252
255
if parent_id is None:
253
256
parent_id = self.tree.path2id(parent_path)
254
if parent_id is None:
255
added, ignored = self.tree.smart_add([parent_path], recurse=False)
256
if len(ignored) > 0 and ignored[0] == parent_path:
259
parent_id = self.tree.path2id(parent_path)
260
257
if entry.name == new_name and entry.parent_id == parent_id:
262
259
new_entry = entry.copy()