157
161
class BzrFastExporter(object):
159
163
def __init__(self, source, outf, ref=None, checkpoint=-1,
160
import_marks_file=None, export_marks_file=None, revision=None,
161
verbose=False, plain_format=False, rewrite_tags=False,
162
no_tags=False, baseline=False):
164
import_marks_file=None, export_marks_file=None, revision=None,
165
verbose=False, plain_format=False, rewrite_tags=False,
166
no_tags=False, baseline=False):
163
167
"""Export branch data in fast import format.
165
169
:param plain_format: if True, 'classic' fast-import format is
204
208
marks_info = marks_file.import_marks(self.import_marks_file)
205
209
if marks_info is not None:
206
210
self.revid_to_mark = dict((r, m) for m, r in
208
212
# These are no longer included in the marks file
209
213
#self.branch_names = marks_info[1]
211
215
def interesting_history(self):
212
216
if self.revision:
213
217
rev1, rev2 = builtins._get_revision_range(self.revision,
214
self.branch, "fast-export")
218
self.branch, "fast-export")
215
219
start_rev_id = rev1.rev_id
216
220
end_rev_id = rev2.rev_id
219
223
end_rev_id = None
220
224
self.note("Calculating the revisions to include ...")
221
225
view_revisions = [rev_id for rev_id, _, _, _ in
222
self.branch.iter_merge_sorted_revisions(end_rev_id, start_rev_id)]
226
self.branch.iter_merge_sorted_revisions(end_rev_id, start_rev_id)]
223
227
view_revisions.reverse()
224
228
# If a starting point was given, we need to later check that we don't
225
229
# start emitting revisions from before that point. Collect the
227
231
if start_rev_id is not None:
228
232
self.note("Calculating the revisions to exclude ...")
229
233
self.excluded_revisions = set([rev_id for rev_id, _, _, _ in
230
self.branch.iter_merge_sorted_revisions(start_rev_id)])
234
self.branch.iter_merge_sorted_revisions(start_rev_id)])
231
235
if self.baseline:
232
236
# needed so the first relative commit knows its parent
233
237
self.excluded_revisions.remove(start_rev_id)
287
291
time_required = progress.str_tdelta(time.time() - self._start_time)
288
292
rc = len(self.revid_to_mark)
289
293
self.note("Exported %d %s in %s",
290
rc, helpers.single_plural(rc, "revision", "revisions"),
294
rc, helpers.single_plural(rc, "revision", "revisions"),
293
297
def print_cmd(self, cmd):
327
331
revobj = self.branch.repository.get_revision(revid)
329
333
self.revid_to_mark[revid] = mark
330
file_cmds = self._get_filecommands(breezy.revision.NULL_REVISION, revid)
334
file_cmds = self._get_filecommands(
335
breezy.revision.NULL_REVISION, revid)
331
336
self.print_cmd(self._get_commit_command(ref, mark, revobj, file_cmds))
333
338
def emit_commit(self, revid, ref):
341
346
# This is a ghost revision. Mark it as not found and next!
342
347
self.revid_to_mark[revid] = -1
345
350
# Get the primary parent
346
351
# TODO: Consider the excluded revisions when deciding the parents.
347
352
# Currently, a commit with parents that are excluded ought to be
369
374
# Report progress and checkpoint if it's time for that
370
375
self.report_progress(ncommits)
371
if (self.checkpoint is not None and self.checkpoint > 0 and ncommits
372
and ncommits % self.checkpoint == 0):
376
if (self.checkpoint is not None and self.checkpoint > 0 and ncommits and
377
ncommits % self.checkpoint == 0):
373
378
self.note("Exported %i commits - adding checkpoint to output"
375
380
self._save_marks()
376
381
self.print_cmd(commands.CheckpointCommand())
447
452
# Build and return the result
448
453
return commands.CommitCommand(git_ref, mark, author_info,
449
committer_info, revobj.message.encode("utf-8"), from_, merges, iter(file_cmds),
450
more_authors=more_author_info, properties=properties)
454
committer_info, revobj.message.encode(
455
"utf-8"), from_, merges, iter(file_cmds),
456
more_authors=more_author_info, properties=properties)
452
458
def _get_revision_trees(self, parent, revision_id):
454
460
tree_old = self.branch.repository.revision_tree(parent)
455
461
except bazErrors.UnexpectedInventoryFormat:
456
self.warning("Parent is malformed - diffing against previous parent")
463
"Parent is malformed - diffing against previous parent")
457
464
# We can't find the old parent. Let's diff against his parent
458
465
pp = self.branch.repository.get_revision(parent)
459
466
tree_old = self.branch.repository.revision_tree(pp.parent_ids[0])
475
482
changes = tree_new.changes_from(tree_old)
477
484
# Make "modified" have 3-tuples, as added does
478
my_modified = [ x[0:3] for x in changes.modified ]
485
my_modified = [x[0:3] for x in changes.modified]
480
487
# The potential interaction between renames and deletes is messy.
481
488
# Handle it here ...
488
495
# IGC: I don't understand why a delete is needed here.
489
496
# In fact, it seems harmful? If you uncomment this line,
490
497
# please file a bug explaining why you needed to.
491
#file_cmds.append(commands.FileDeleteCommand(path))
498
# file_cmds.append(commands.FileDeleteCommand(path))
492
499
my_modified.append((path, id_, kind2))
494
501
# Record modifications
495
502
for path, id_, kind in changes.added + my_modified + rd_modifies:
496
503
if kind == 'file':
497
text = tree_new.get_file_text(path, id_)
498
file_cmds.append(commands.FileModifyCommand(path.encode("utf-8"),
499
helpers.kind_to_mode(
500
'file', tree_new.is_executable(path, id_)),
504
text = tree_new.get_file_text(path)
505
file_cmds.append(commands.FileModifyCommand(
506
path.encode("utf-8"),
507
helpers.kind_to_mode('file', tree_new.is_executable(path)),
502
509
elif kind == 'symlink':
503
file_cmds.append(commands.FileModifyCommand(path.encode("utf-8"),
510
file_cmds.append(commands.FileModifyCommand(
511
path.encode("utf-8"),
504
512
helpers.kind_to_mode('symlink', False),
505
None, tree_new.get_symlink_target(path, id_)))
513
None, tree_new.get_symlink_target(path)))
506
514
elif kind == 'directory':
507
515
if not self.plain_format:
508
516
file_cmds.append(
509
commands.FileModifyCommand(path.encode("utf-8"),
510
helpers.kind_to_mode('directory', False), None,
517
commands.FileModifyCommand(
518
path.encode("utf-8"),
519
helpers.kind_to_mode('directory', False), None,
513
522
self.warning("cannot export '%s' of kind %s yet - ignoring" %
517
526
def _process_renames_and_deletes(self, renames, deletes,
518
revision_id, tree_old):
527
revision_id, tree_old):
548
557
emit = kind != 'directory' or not self.plain_format
549
558
if newpath in deleted_paths:
551
file_cmds.append(commands.FileDeleteCommand(newpath.encode("utf-8")))
560
file_cmds.append(commands.FileDeleteCommand(
561
newpath.encode("utf-8")))
552
562
deleted_paths.remove(newpath)
553
563
if (self.is_empty_dir(tree_old, oldpath)):
554
564
self.note("Skipping empty dir %s in rev %s" % (oldpath,
557
#oldpath = self._adjust_path_for_renames(oldpath, renamed,
567
# oldpath = self._adjust_path_for_renames(oldpath, renamed,
559
569
renamed.append([oldpath, newpath])
560
570
old_to_new[oldpath] = newpath
582
592
new_child_path = must_be_renamed[old_child_path]
584
594
self.note("implicitly renaming %s => %s" % (old_child_path,
586
596
file_cmds.append(commands.FileRenameCommand(old_child_path.encode("utf-8"),
587
new_child_path.encode("utf-8")))
597
new_child_path.encode("utf-8")))
589
599
# Record remaining deletes
590
600
for path, id_, kind in deletes:
601
611
for old, new in renamed:
603
613
self.note("Changing path %s given rename to %s in revision %s"
604
% (path, new, revision_id))
614
% (path, new, revision_id))
606
616
elif path.startswith(old + '/'):
616
626
mark = self.revid_to_mark[revid]
618
628
self.warning('not creating tag %r pointing to non-existent '
619
'revision %s' % (tag, revid))
629
'revision %s' % (tag, revid))
621
631
git_ref = b'refs/tags/%s' % tag.encode("utf-8")
622
632
if self.plain_format and not check_ref_format(git_ref):