13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
from bzrlib import Branch
19
from bzrlib.bzrdir import BzrDir
20
import bzrlib.bzrdir as bzrdir
21
from bzrlib.errors import (BzrError,
28
from bzrlib.branch import Branch
29
from bzrlib.commit import Commit, NullCommitReporter
17
30
from bzrlib.commands import Command
31
from bzrlib.option import _global_option, Option
32
from bzrlib.merge import merge_inner
33
from bzrlib.revision import NULL_REVISION
34
from bzrlib.tree import EmptyTree
36
from bzrlib.workingtree import WorkingTree
37
from errors import NoPyBaz
20
40
import pybaz.errors
41
from pybaz import NameParser as NameParser
42
from pybaz.backends.baz import null_cmd
21
43
except ImportError:
22
print "This command requires PyBaz. Please ensure that it is installed."
25
from pybaz.backends.baz import null_cmd
45
from fai import iter_new_merges, direct_merges
31
from bzrlib.errors import BzrError
32
51
import bzrlib.trace
33
52
import bzrlib.merge
53
import bzrlib.inventory
36
57
from progress import *
59
class ImportCommitReporter(NullCommitReporter):
60
def __init__(self, pb):
63
def escaped(self, escape_count, message):
65
bzrlib.trace.warning("replaced %d control characters in message" %
38
68
def add_id(files, id=None):
39
69
"""Adds an explicit id to a list of files.
207
249
def get_last_revision(branch):
208
last_patch = branch.last_patch()
250
last_patch = branch.last_revision()
210
252
return arch_revision(last_patch)
211
253
except NotArchRevision:
213
255
"Directory \"%s\" already exists, and the last revision is not"
214
" an Arch revision (%s)" % (output_dir, last_patch))
217
def get_remaining_revisions(output_dir, version):
256
" an Arch revision (%s)" % (branch.base, last_patch))
258
def do_branch(br_from, to_location, revision_id):
259
"""Derived from branch in builtins."""
263
os.mkdir(to_location)
265
if e.errno == errno.EEXIST:
266
raise UserError('Target directory "%s" already'
267
' exists.' % to_location)
268
if e.errno == errno.ENOENT:
269
raise UserError('Parent of "%s" does not exist.' %
274
br_from.bzrdir.clone(to_location, revision_id)
275
except NoSuchRevision:
277
msg = "The branch %s has no revision %s." % (from_location,
283
def get_remaining_revisions(output_dir, version, reuse_history_from=[]):
218
284
last_patch = None
220
if os.path.exists(output_dir):
286
output_exists = os.path.exists(output_dir)
221
288
# We are starting from an existing directory, figure out what
222
289
# the current version is
223
branch = find_branch(output_dir)
290
branch = Branch.open(output_dir)
224
291
last_patch = get_last_revision(branch)
292
if last_patch is None:
293
raise NotPreviousImport(branch.base)
225
294
if version is None:
226
295
version = last_patch.version
227
296
elif version is None:
243
333
# Strip off all of the ancestors which are already present
244
334
# And get a directory starting with the latest ancestor
245
335
latest_ancestor = ancestors[i]
246
old_revno = find_branch(output_dir).revno()
336
old_revno = Branch.open(output_dir).revno()
247
337
ancestors = ancestors[i+1:]
248
338
return ancestors, old_revno
250
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
251
dry_run=False, max_count=None, skip_symlinks=False):
341
###class Importer(object):
344
### Currently this is used as a parameter object, though more behaviour is
348
### def __init__(self, output_dir, version, printer, fancy=True, fast=False,
349
### verbose=False, dry_run=False, max_count=None,
350
### reuse_history_from=[]):
351
### self.output_dir = output_dir
352
### self.version = version
356
def import_version(output_dir, version, printer, fancy=True, fast=False,
357
verbose=False, dry_run=False, max_count=None,
358
reuse_history_from=[], standalone=True):
253
360
>>> q = test_environ()
254
361
>>> result_path = os.path.join(q, "result")
255
362
>>> commit_test_revisions()
256
363
>>> version = pybaz.Version("test@example.com/test--test--0.1")
257
>>> import_version('/', version, fancy=False, dry_run=True)
364
>>> def printer(message): print message
365
>>> import_version('/', version, printer, fancy=False, dry_run=True)
258
366
Traceback (most recent call last):
259
UserError: / exists, but is not a bzr branch.
260
>>> import_version(result_path, version, fancy=False, dry_run=True)
367
NotPreviousImport: / is not the location of a previous import.
368
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
261
369
Traceback (most recent call last):
262
370
UserError: The version test@example.com/test--test--0.1 does not exist.
263
371
>>> version = pybaz.Version("test@example.com/test--test--0")
264
>>> import_version(result_path, version, fancy=False, dry_run=True)
372
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
267
375
Dry run, not modifying output_dir
269
>>> import_version(result_path, version, fancy=False)
377
>>> import_version(result_path, version, printer, fancy=False)
274
>>> import_version(result_path, version, fancy=False)
382
>>> import_version(result_path, version, printer, fancy=False)
275
383
Tree is up-to-date with test@example.com/test--test--0--patch-2
276
384
>>> commit_more_test_revisions()
277
>>> import_version(result_path, version, fancy=False)
385
>>> import_version(result_path, version, printer, fancy=False)
282
390
>>> teardown_environ(q)
285
ancestors, old_revno = get_remaining_revisions(output_dir, version)
286
except NotInABranch, e:
287
raise UserError("%s exists, but is not a bzr branch." % e.path)
393
ancestors, old_revno = get_remaining_revisions(output_dir, version,
395
except NotBranchError, e:
396
raise NotPreviousImport(e.path)
397
if old_revno is None and len(ancestors) == 0:
398
print 'Version %s has no revisions.' % version
288
400
if len(ancestors) == 0:
289
last_revision = get_last_revision(find_branch(output_dir))
401
last_revision = get_last_revision(Branch.open(output_dir))
290
402
print 'Tree is up-to-date with %s' % last_revision
293
progress_bar = ProgressBar()
405
progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
294
406
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
295
407
dir=os.path.dirname(output_dir))
409
wt = WorkingTree.open(output_dir)
410
except (NotBranchError, NoWorkingTree):
413
old_basis = EmptyTree()
415
old_basis = wt.basis_tree()
298
418
print "not fancy"
300
420
for result in iter_import_version(output_dir, ancestors, tempdir,
301
fast=fast, verbose=verbose, dry_run=dry_run,
302
max_count=max_count, skip_symlinks=skip_symlinks):
421
progress_bar, fast=fast, verbose=verbose, dry_run=dry_run,
422
max_count=max_count, standalone=standalone):
424
show_progress(progress_bar, result)
306
426
sys.stdout.write('.')
429
progress_bar.finished()
311
431
sys.stdout.write('\n')
314
434
print 'Dry run, not modifying output_dir'
316
if os.path.exists(output_dir):
317
# Move the bzr control directory back, and update the working tree
318
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
320
bzr_dir = os.path.join(output_dir, '.bzr')
321
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
323
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
324
os.rename(new_bzr_dir, bzr_dir)
326
bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno),
327
check_clean=False, this_dir=output_dir,
330
# If something failed, move back the original bzr directory
331
os.rename(bzr_dir, new_bzr_dir)
332
os.rename(tmp_bzr_dir, bzr_dir)
335
revdir = os.path.join(tempdir, "rd")
336
os.rename(revdir, output_dir)
437
# Update the working tree of the branch
439
wt = WorkingTree.open(output_dir)
440
except NoWorkingTree:
443
wt.set_last_revision(wt.branch.last_revision())
444
merge_inner(wt.branch, wt.basis_tree(), old_basis,
445
ignore_zero=True, this_tree=wt)
449
printer('Cleaning up')
340
450
shutil.rmtree(tempdir)
341
print "Import complete."
451
printer("Import complete.")
343
class UserError(Exception):
453
class UserError(BzrCommandError):
344
454
def __init__(self, message):
345
455
"""Exception to throw when a user makes an impossible request
346
456
:param message: The message to emit when printing this exception
347
457
:type message: string
349
Exception.__init__(self, message)
459
BzrCommandError.__init__(self, message)
461
class NotPreviousImport(UserError):
462
def __init__(self, path):
463
UserError.__init__(self, "%s is not the location of a previous import."
351
467
def revision_id(arch_revision):
370
486
def arch_revision(revision_id):
372
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
373
Traceback (most recent call last):
374
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
375
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
376
Traceback (most recent call last):
377
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
378
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
488
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
489
Traceback (most recent call last):
490
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
491
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
492
Traceback (most recent call last):
493
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
494
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
379
495
'jrandom@example.com/test--test--0--patch-5'
381
497
if revision_id is None:
383
if revision_id[:7] != 'Arch-x:':
499
if revision_id[:7] != 'Arch-1:':
384
500
raise NotArchRevision(revision_id)
387
503
return pybaz.Revision(revision_id[7:].replace('%', '/'))
388
504
except pybaz.errors.NamespaceError, e:
389
505
raise NotArchRevision(revision_id)
391
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
508
def create_shared_repository(output_dir):
509
bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
510
bd.create_repository(shared=True)
512
def create_branch(output_dir):
514
bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
515
return bd.create_branch()
518
def create_checkout(source, to_location, revision_id=None):
519
checkout = bzrdir.BzrDirMetaFormat1().initialize(to_location)
520
bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
521
return checkout.create_workingtree(revision_id)
524
def create_checkout_metadata(source, to_location, revision_id=None):
525
if revision_id is None:
526
revision_id = source.last_revision()
527
wt = create_checkout(source, to_location, NULL_REVISION)
528
wt.set_last_revision(revision_id)
529
wt._write_inventory(wt.basis_tree().inventory)
533
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
392
534
verbose=False, dry_run=False, max_count=None,
393
skip_symlinks=False):
396
538
# Uncomment this for testing, it basically just has baz2bzr only update
420
579
previous_version = version
422
581
yield Progress("revisions", i, len(ancestors))
583
if target_branch.repository.has_revision(rev_id):
584
target_branch.append_revision(rev_id)
423
586
if revdir is None:
424
587
revdir = os.path.join(tempdir, "rd")
425
baz_inv, log = get_revision(revdir, revision,
426
skip_symlinks=skip_symlinks)
427
if os.path.exists(output_dir):
428
bzr_dir = os.path.join(output_dir, '.bzr')
429
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
430
# This would be much faster with a simple os.rename(), but if
431
# we fail, we have corrupted the original .bzr directory. Is
432
# that a big problem, as we can just back out the last
433
# revisions in .bzr/revision_history I don't really know
434
shutil.copytree(bzr_dir, new_bzr_dir)
435
# Now revdir should have a tree with the latest .bzr, and the
436
# next revision of the baz tree
437
branch = find_branch(revdir)
439
branch = bzrlib.Branch(revdir, init=True)
589
tree, baz_inv, log = get_revision(revdir, revision)
590
except pybaz.errors.ExecProblem, e:
591
if ("%s" % e.args).find('could not connect') == -1:
593
missing_ancestor = revision
595
print ("unable to access ancestor %s, making into a merge."
598
target_tree = create_checkout_metadata(target_branch, revdir)
599
branch = target_tree.branch
441
601
old = os.path.join(revdir, ".bzr")
442
602
new = os.path.join(tempdir, ".bzr")
443
603
os.rename(old, new)
444
baz_inv, log = apply_revision(revdir, revision,
445
skip_symlinks=skip_symlinks)
604
baz_inv, log = apply_revision(tree, revision)
446
605
os.rename(new, old)
447
branch = find_branch(revdir)
448
timestamp = email.Utils.mktime_tz(log.date + (0,))
449
rev_id = revision_id(revision)
606
target_tree = WorkingTree.open(revdir)
607
branch = target_tree.branch
608
# cached so we can delete the log
610
log_summary = log.summary
611
log_description = log.description
612
is_continuation = log.continuation_of is not None
613
log_creator = log.creator
614
direct_merges = get_direct_merges(revdir, revision, log)
616
timestamp = email.Utils.mktime_tz(log_date + (0,))
617
if log_summary is None:
619
# log_descriptions of None and "" are ignored.
620
if not is_continuation and log_description:
621
log_message = "\n".join((log_summary, log_description))
623
log_message = log_summary
624
target_tree.lock_write()
450
625
branch.lock_write()
452
branch.set_inventory(baz_inv)
453
bzrlib.trace.silent = True
454
branch.commit(log.summary, verbose=False, committer=log.creator,
455
timestamp=timestamp, timezone=0, rev_id=rev_id)
628
# if we want it to be in revision-history, do that here.
629
target_tree.add_pending_merge(revision_id(missing_ancestor))
630
missing_ancestor = None
631
for merged_rev in direct_merges:
632
target_tree.add_pending_merge(revision_id(merged_rev))
633
target_tree.set_inventory(baz_inv)
634
commitobj = Commit(reporter=ImportCommitReporter(pb))
635
commitobj.commit(working_tree=target_tree,
636
message=log_message.decode('ascii', 'replace'),
637
verbose=False, committer=log_creator,
638
timestamp=timestamp, timezone=0, rev_id=rev_id,
457
bzrlib.trace.silent = False
459
643
yield Progress("revisions", len(ancestors), len(ancestors))
460
unlink_unversioned(branch, revdir)
462
def unlink_unversioned(branch, revdir):
463
for unversioned in branch.working_tree().extras():
464
path = os.path.join(revdir, unversioned)
645
def get_direct_merges(revdir, revision, log):
646
continuation = log.continuation_of
647
previous_version = revision.version
648
if pybaz.WorkingTree(revdir).tree_version != previous_version:
649
pybaz.WorkingTree(revdir).set_tree_version(previous_version)
650
log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir,
651
revision.category.nonarch, revision.branch.nonarch,
652
revision.version.nonarch, revision.archive, revision.patchlevel)
653
temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
654
os.rename(log_path, temp_path)
655
merges = list(iter_new_merges(revdir, revision.version))
656
direct = direct_merges(merges, [continuation])
657
os.rename(temp_path, log_path)
660
def unlink_unversioned(wt):
661
for unversioned in wt.extras():
662
path = wt.abspath(unversioned)
465
663
if os.path.isdir(path):
466
664
shutil.rmtree(path)
470
668
def get_log(tree, revision):
471
log = tree.iter_logs(version=revision.version, reverse=True).next()
472
assert log.revision == revision
669
log = pybaz.Patchlog(revision, tree=tree)
670
assert str(log.revision) == str(revision), (log.revision, revision)
475
def get_revision(revdir, revision, skip_symlinks=False):
477
tree = pybaz.tree_root(revdir)
673
def get_revision(revdir, revision):
674
tree = revision.get(revdir)
478
675
log = get_log(tree, revision)
480
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
677
return tree, bzr_inventory_data(tree), log
481
678
except BadFileKind, e:
482
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
485
def apply_revision(revdir, revision, skip_symlinks=False):
486
tree = pybaz.tree_root(revdir)
679
raise UserError("Cannot convert %s because %s is a %s" %
680
(revision,e.path, e.kind))
683
def apply_revision(tree, revision):
487
684
revision.apply(tree)
488
685
log = get_log(tree, revision)
490
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
687
return bzr_inventory_data(tree), log
491
688
except BadFileKind, e:
492
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
689
raise UserError("Cannot convert %s because %s is a %s" %
690
(revision,e.path, e.kind))
497
693
class BadFileKind(Exception):
529
class NotInABranch(Exception):
530
def __init__(self, path):
531
Exception.__init__(self, "%s is not in a branch." % path)
535
def find_branch(path):
538
Traceback (most recent call last):
539
NotInABranch: / is not in a branch.
540
>>> sb = bzrlib.ScratchBranch()
541
>>> isinstance(find_branch(sb.base), bzrlib.Branch)
545
return bzrlib.Branch(path)
547
if e.args[0].endswith("' is not in a branch"):
548
raise NotInABranch(path)
550
class cmd_baz_import(Command):
551
"""Import an Arch or Baz branch into a bzr branch"""
552
takes_args = ['to_location', 'from_branch?']
553
takes_options = ['verbose']
555
def run(self, to_location, from_branch=None, skip_symlinks=False,
556
fast=False, max_count=None, verbose=False, dry_run=False):
725
_global_option('max-count', type = int)
726
class cmd_baz_import_branch(Command):
727
"""Import an Arch or Baz branch into a bzr branch. <BZRTOOLS>"""
728
takes_args = ['to_location', 'from_branch?', 'reuse_history*']
729
takes_options = ['verbose', 'max-count']
731
def printer(self, name):
734
def run(self, to_location, from_branch=None, fast=False, max_count=None,
735
verbose=False, dry_run=False, reuse_history_list=[]):
557
736
to_location = os.path.realpath(str(to_location))
558
737
if from_branch is not None:
560
from_branch = pybaz.Version(version)
739
from_branch = pybaz.Version(from_branch)
561
740
except pybaz.errors.NamespaceError:
562
741
print "%s is not a valid Arch branch." % from_branch
564
import_version(to_location, from_branch)
743
if reuse_history_list is None:
744
reuse_history_list = []
745
import_version(to_location, from_branch, self.printer,
747
reuse_history_from=reuse_history_list)
750
class NotInABranch(Exception):
751
def __init__(self, path):
752
Exception.__init__(self, "%s is not in a branch." % path)
756
class cmd_baz_import(Command):
757
"""Import an Arch or Baz archive into bzr branches. <BZRTOOLS>
759
This command should be used on local archives (or mirrors) only. It is
760
quite slow on remote archives.
762
reuse_history allows you to specify any previous imports you
763
have done of different archives, which this archive has branches
764
tagged from. This will dramatically reduce the time to convert
765
the archive as it will not have to convert the history already
766
converted in that other branch.
768
If you specify prefixes, only branches whose names start with that prefix
769
will be imported. Skipped branches will be listed, so you can import any
770
branches you missed by accident. Here's an example of doing a partial
771
import from thelove@canonical.com:
772
bzr baz-import thelove thelove@canonical.com --prefixes dists:talloc-except
774
takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
775
takes_options = ['verbose', Option('prefixes', type=str,
776
help="Prefixes of branches to import, colon-separated")]
778
def printer(self, name):
781
def run(self, to_root_dir, from_archive, verbose=False,
782
reuse_history_list=[], prefixes=None):
783
if reuse_history_list is None:
784
reuse_history_list = []
785
to_root = str(os.path.realpath(to_root_dir))
786
if not os.path.exists(to_root):
788
if prefixes is not None:
789
prefixes = prefixes.split(':')
790
import_archive(to_root, from_archive, verbose, self.printer,
791
reuse_history_list, prefixes=prefixes)
794
def import_archive(to_root, from_archive, verbose, printer,
795
reuse_history_from=[], standalone=False,
797
def selected(version):
801
for prefix in prefixes:
802
if version.nonarch.startswith(prefix):
805
real_to = os.path.realpath(to_root)
806
history_locations = [real_to] + reuse_history_from
807
if standalone is False:
809
bd = BzrDir.open(to_root)
811
except NotBranchError:
812
create_shared_repository(to_root)
813
except NoRepositoryPresent:
814
raise BzrCommandError("Can't create repository at existing branch.")
815
versions = list(pybaz.Archive(str(from_archive)).iter_versions())
816
# progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
818
for num, version in enumerate(versions):
819
# progress_bar.update("Branch", num, len(versions))
820
if not selected(version):
821
print "Skipping %s" % version
823
target = os.path.join(to_root, map_namespace(version))
824
printer("importing %s into %s" % (version, target))
825
if not os.path.exists(os.path.dirname(target)):
826
os.makedirs(os.path.dirname(target))
828
import_version(target, version, printer,
829
reuse_history_from=reuse_history_from,
830
standalone=standalone)
831
except pybaz.errors.ExecProblem,e:
832
if str(e).find('The requested revision cannot be built.') != -1:
833
printer("Skipping version %s as it cannot be built due"
834
" to a missing parent archive." % version)
838
if str(e).find('already exists, and the last revision ') != -1:
839
printer("Skipping version %s as it has had commits made"
840
" since it was converted to bzr." % version)
845
# progress_bar.finished()
847
def map_namespace(a_version):
848
a_version = pybaz.Version("%s" % a_version)
849
parser = NameParser(a_version)
850
version = parser.get_version()
851
branch = parser.get_branch()
852
category = parser.get_category()
853
if branch is None or branch == '':
856
return "%s/%s" % (category, branch)
857
return "%s/%s/%s" % (category, version, branch)
859
def map_file_id(file_id):
860
"""Convert a baz file id to a bzr one."""
861
return file_id.replace('%', '%25').replace('/', '%2f')