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
34
class RenameMap(object):
60
66
:param tree: The tree containing the files.
61
67
: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()
69
desired_files = [(tree.id2path(f), f) for f in file_ids]
70
with ui_factory.nested_progress_bar() as task:
66
71
for num, (file_id, contents) in enumerate(
67
tree.iter_files_bytes(desired_files)):
68
task.update('Calculating hashes', num, len(file_ids))
72
tree.iter_files_bytes(desired_files)):
73
task.update(gettext('Calculating hashes'), num, len(file_ids))
70
75
s.writelines(contents)
72
77
self.add_edge_hashes(s.readlines(), file_id)
76
79
def hitcounts(self, lines):
77
80
"""Count the number of hash hits for each tag, for the given lines.
100
103
:return: A list of tuples of count, path, file_id.
103
task = ui_factory.nested_progress_bar()
106
with ui_factory.nested_progress_bar() as task:
105
107
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())
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))
114
113
def file_match(self, paths):
175
174
missing_files = set()
176
175
missing_parents = {}
177
176
candidate_files = set()
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)
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)
189
#other kinds are not handled
189
# other kinds are not handled
191
if versioned == (False, False):
192
if self.tree.is_ignored(paths[1]):
191
if change.versioned == (False, False):
192
if self.tree.is_ignored(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]):
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]):
198
198
for child in children:
199
199
if child[2] == 'file':
200
200
candidate_files.add(child[0])
203
201
return missing_files, missing_parents, candidate_files
206
def guess_renames(klass, tree, dry_run=False):
204
def guess_renames(klass, from_tree, to_tree, dry_run=False):
207
205
"""Guess which files to rename, and perform the rename.
209
207
We assume that unversioned files and missing files indicate that
210
208
versioned files have been renamed outside of Bazaar.
212
:param tree: A write-locked working tree.
210
:param from_tree: A tree to compare from
211
:param to_tree: A write-locked working tree.
214
213
required_parents = {}
215
task = ui_factory.nested_progress_bar()
214
with ui_factory.nested_progress_bar() as task:
217
215
pp = progress.ProgressPhase('Guessing renames', 4, task)
218
basis = tree.basis_tree()
216
with from_tree.lock_read():
223
219
missing_files, missing_parents, candidate_files = (
224
rn._find_missing_files(basis))
220
rn._find_missing_files(from_tree))
226
rn.add_file_edge_hashes(basis, missing_files)
222
rn.add_file_edge_hashes(from_tree, missing_files)
230
224
matches = rn.file_match(candidate_files)
231
225
parents_matches = matches
239
233
delta = rn._make_inventory_delta(matches)
240
234
for old, new, file_id, entry in delta:
241
trace.note("%s => %s", old, new)
235
trace.note(gettext("{0} => {1}").format(old, new))
243
tree.add(required_parents)
244
tree.apply_inventory_delta(delta)
237
to_tree.add(required_parents)
238
to_tree.apply_inventory_delta(delta)
248
240
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()):
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):
252
251
new_path = file_id_matches[entry.file_id]
253
252
parent_path, new_name = osutils.split(new_path)
254
253
parent_id = matches.get(parent_path)
255
254
if parent_id is None:
256
255
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)
257
263
if entry.name == new_name and entry.parent_id == parent_id:
259
265
new_entry = entry.copy()