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