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
19
from bzrlib.errors import BzrError
20
from bzrlib.errors import NotBranchError, BzrCommandError, NoSuchRevision
21
16
from bzrlib.branch import Branch
22
from bzrlib.clone import copy_branch
23
from bzrlib.commit import Commit, NullCommitReporter
24
17
from bzrlib.commands import Command
25
from bzrlib.option import _global_option
26
from bzrlib.workingtree import WorkingTree
27
18
from errors import NoPyBaz
30
21
import pybaz.errors
31
from pybaz import NameParser as NameParser
32
22
from pybaz.backends.baz import null_cmd
33
23
except ImportError:
35
from fai import iter_new_merges, direct_merges
30
from bzrlib.errors import BzrError
41
31
import bzrlib.trace
42
32
import bzrlib.merge
43
33
import bzrlib.inventory
238
212
except NotArchRevision:
240
214
"Directory \"%s\" already exists, and the last revision is not"
241
" an Arch revision (%s)" % (branch.base, last_patch))
243
def do_branch(br_from, to_location, revision_id):
244
"""Derived from branch in builtins."""
248
os.mkdir(to_location)
250
if e.errno == errno.EEXIST:
251
raise UserError('Target directory "%s" already'
252
' exists.' % to_location)
253
if e.errno == errno.ENOENT:
254
raise UserError('Parent of "%s" does not exist.' %
259
copy_branch(br_from, to_location, revision_id, None)
260
except NoSuchRevision:
262
msg = "The branch %s has no revision %s." % (from_location, revision_id)
267
def get_remaining_revisions(output_dir, version, reuse_history_from=[]):
215
" an Arch revision (%s)" % (output_dir, last_patch))
218
def get_remaining_revisions(output_dir, version):
268
219
last_patch = None
270
output_exists = os.path.exists(output_dir)
221
if os.path.exists(output_dir):
272
222
# We are starting from an existing directory, figure out what
273
223
# the current version is
274
branch = Branch.open(output_dir)
224
branch = find_branch(output_dir)
275
225
last_patch = get_last_revision(branch)
276
if last_patch is None:
277
raise NotPreviousImport(branch.base)
278
226
if version is None:
279
227
version = last_patch.version
280
228
elif version is None:
284
232
ancestors = version_ancestry(version)
285
if not output_exists and reuse_history_from != []:
286
for ancestor in reversed(ancestors):
287
if last_patch is not None:
288
# found something to copy
290
# try to grab a copy of ancestor
291
# note that is not optimised: we could look for namespace
292
# transitions and only look for the past after the
294
for history_root in reuse_history_from:
295
possible_source = os.path.join(history_root,
296
map_namespace(ancestor.version))
298
source = Branch.open(possible_source)
299
rev_id = revision_id(ancestor)
300
if rev_id in source.revision_history():
301
do_branch(source, output_dir, rev_id)
302
last_patch = ancestor
304
except NotBranchError:
306
233
except NoSuchVersion, e:
307
raise UserError(str(e))
310
237
for i in range(len(ancestors)):
317
244
# Strip off all of the ancestors which are already present
318
245
# And get a directory starting with the latest ancestor
319
246
latest_ancestor = ancestors[i]
320
old_revno = Branch.open(output_dir).revno()
247
old_revno = find_branch(output_dir).revno()
321
248
ancestors = ancestors[i+1:]
322
249
return ancestors, old_revno
325
###class Importer(object):
328
### Currently this is used as a parameter object, though more behaviour is
332
### def __init__(self, output_dir, version, printer, fancy=True, fast=False,
333
### verbose=False, dry_run=False, max_count=None,
334
### reuse_history_from=[]):
335
### self.output_dir = output_dir
336
### self.version = version
340
def import_version(output_dir, version, printer, fancy=True, fast=False,
341
verbose=False, dry_run=False, max_count=None,
342
reuse_history_from=[]):
251
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
252
dry_run=False, max_count=None, skip_symlinks=False):
344
254
>>> q = test_environ()
345
255
>>> result_path = os.path.join(q, "result")
346
256
>>> commit_test_revisions()
347
257
>>> version = pybaz.Version("test@example.com/test--test--0.1")
348
>>> def printer(message): print message
349
>>> import_version('/', version, printer, fancy=False, dry_run=True)
258
>>> import_version('/', version, fancy=False, dry_run=True)
350
259
Traceback (most recent call last):
351
NotPreviousImport: / is not the location of a previous import.
352
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
260
UserError: / exists, but is not a bzr branch.
261
>>> import_version(result_path, version, fancy=False, dry_run=True)
353
262
Traceback (most recent call last):
354
263
UserError: The version test@example.com/test--test--0.1 does not exist.
355
264
>>> version = pybaz.Version("test@example.com/test--test--0")
356
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
265
>>> import_version(result_path, version, fancy=False, dry_run=True)
359
268
Dry run, not modifying output_dir
361
>>> import_version(result_path, version, printer, fancy=False)
270
>>> import_version(result_path, version, fancy=False)
366
>>> import_version(result_path, version, printer, fancy=False)
275
>>> import_version(result_path, version, fancy=False)
367
276
Tree is up-to-date with test@example.com/test--test--0--patch-2
368
277
>>> commit_more_test_revisions()
369
>>> import_version(result_path, version, printer, fancy=False)
278
>>> import_version(result_path, version, fancy=False)
412
317
if os.path.exists(output_dir):
413
318
# Move the bzr control directory back, and update the working tree
414
revdir = os.path.join(tempdir, "rd")
415
if os.path.exists(revdir):
416
# actual imports were done
417
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
419
bzr_dir = os.path.join(output_dir, '.bzr')
420
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
422
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
423
os.rename(new_bzr_dir, bzr_dir)
425
bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno),
426
check_clean=False, this_dir=output_dir,
429
# If something failed, move back the original bzr directory
430
os.rename(bzr_dir, new_bzr_dir)
431
os.rename(tmp_bzr_dir, bzr_dir)
434
# no imports - perhaps just append_revisions
436
bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno),
319
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
321
bzr_dir = os.path.join(output_dir, '.bzr')
322
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
324
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
325
os.rename(new_bzr_dir, bzr_dir)
327
bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno),
437
328
check_clean=False, this_dir=output_dir,
438
329
ignore_zero=True)
331
# If something failed, move back the original bzr directory
332
os.rename(bzr_dir, new_bzr_dir)
333
os.rename(tmp_bzr_dir, bzr_dir)
440
336
revdir = os.path.join(tempdir, "rd")
441
337
os.rename(revdir, output_dir)
444
printer('Cleaning up')
445
341
shutil.rmtree(tempdir)
446
printer("Import complete.")
342
print "Import complete."
448
class UserError(BzrCommandError):
344
class UserError(Exception):
449
345
def __init__(self, message):
450
346
"""Exception to throw when a user makes an impossible request
451
347
:param message: The message to emit when printing this exception
452
348
:type message: string
454
BzrCommandError.__init__(self, message)
456
class NotPreviousImport(UserError):
457
def __init__(self, path):
458
UserError.__init__(self, "%s is not the location of a previous import."
350
Exception.__init__(self, message)
462
352
def revision_id(arch_revision):
481
371
def arch_revision(revision_id):
483
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
484
Traceback (most recent call last):
485
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
486
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
487
Traceback (most recent call last):
488
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
489
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
373
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
374
Traceback (most recent call last):
375
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
376
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
377
Traceback (most recent call last):
378
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
379
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
490
380
'jrandom@example.com/test--test--0--patch-5'
492
382
if revision_id is None:
494
if revision_id[:7] != 'Arch-1:':
384
if revision_id[:7] != 'Arch-x:':
495
385
raise NotArchRevision(revision_id)
499
389
except pybaz.errors.NamespaceError, e:
500
390
raise NotArchRevision(revision_id)
502
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
503
verbose=False, dry_run=False, max_count=None):
392
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
393
verbose=False, dry_run=False, max_count=None,
394
skip_symlinks=False):
506
397
# Uncomment this for testing, it basically just has baz2bzr only update
533
421
previous_version = version
535
423
yield Progress("revisions", i, len(ancestors))
536
if revdir is None and os.path.exists(output_dir):
537
# check for imported revisions and if present just append immediately
538
branch = Branch.open(output_dir)
539
if branch.has_revision(rev_id):
540
branch.append_revision(rev_id)
542
424
if revdir is None:
543
425
revdir = os.path.join(tempdir, "rd")
545
tree, baz_inv, log = get_revision(revdir, revision)
546
except pybaz.errors.ExecProblem, e:
547
if ("%s" % e.args).find('could not connect') == -1:
549
missing_ancestor = revision
551
print ("unable to access ancestor %s, making into a merge."
426
baz_inv, log = get_revision(revdir, revision,
427
skip_symlinks=skip_symlinks)
554
428
if os.path.exists(output_dir):
555
429
bzr_dir = os.path.join(output_dir, '.bzr')
556
430
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
558
432
# we fail, we have corrupted the original .bzr directory. Is
559
433
# that a big problem, as we can just back out the last
560
434
# revisions in .bzr/revision_history I don't really know
561
# RBC20051024 - yes, it would be a problem as we could not then
562
# apply the corrupted revision.
563
435
shutil.copytree(bzr_dir, new_bzr_dir)
564
436
# Now revdir should have a tree with the latest .bzr, and the
565
437
# next revision of the baz tree
566
branch = Branch.open(revdir)
438
branch = find_branch(revdir)
568
440
branch = Branch.initialize(revdir)
570
442
old = os.path.join(revdir, ".bzr")
571
443
new = os.path.join(tempdir, ".bzr")
572
444
os.rename(old, new)
573
baz_inv, log = apply_revision(tree, revision)
445
baz_inv, log = apply_revision(revdir, revision,
446
skip_symlinks=skip_symlinks)
574
447
os.rename(new, old)
575
branch = Branch.open(revdir)
576
# cached so we can delete the log
578
log_summary = log.summary
579
log_description = log.description
580
is_continuation = log.continuation_of is not None
581
log_creator = log.creator
582
direct_merges = get_direct_merges(revdir, revision, log)
584
timestamp = email.Utils.mktime_tz(log_date + (0,))
585
if log_summary is None:
587
# log_descriptions of None and "" are ignored.
588
if not is_continuation and log_description:
589
log_message = "\n".join((log_summary, log_description))
591
log_message = log_summary
448
branch = find_branch(revdir)
449
timestamp = email.Utils.mktime_tz(log.date + (0,))
450
rev_id = revision_id(revision)
592
451
branch.lock_write()
593
target_tree = WorkingTree(revdir ,branch=branch)
594
target_tree.lock_write()
597
# if we want it to be in revision-history, do that here.
598
target_tree.add_pending_merge(revision_id(missing_ancestor))
599
missing_ancestor = None
600
for merged_rev in direct_merges:
601
target_tree.add_pending_merge(revision_id(merged_rev))
602
target_tree.set_inventory(baz_inv)
603
commitobj = Commit(reporter=ImportCommitReporter(pb))
604
commitobj.commit(branch, log_message.decode('ascii', 'replace'),
605
verbose=False, committer=log_creator,
606
timestamp=timestamp, timezone=0, rev_id=rev_id)
453
branch.set_inventory(baz_inv)
454
bzrlib.trace.silent = True
455
branch.commit(log.summary, verbose=False, committer=log.creator,
456
timestamp=timestamp, timezone=0, rev_id=rev_id)
458
bzrlib.trace.silent = False
610
460
yield Progress("revisions", len(ancestors), len(ancestors))
611
461
unlink_unversioned(branch, revdir)
613
def get_direct_merges(revdir, revision, log):
614
continuation = log.continuation_of
615
previous_version = revision.version
616
if pybaz.WorkingTree(revdir).tree_version != previous_version:
617
pybaz.WorkingTree(revdir).set_tree_version(previous_version)
618
log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir,
619
revision.category.nonarch, revision.branch.nonarch,
620
revision.version.nonarch, revision.archive, revision.patchlevel)
621
temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
622
os.rename(log_path, temp_path)
623
merges = list(iter_new_merges(revdir, revision.version))
624
direct = direct_merges(merges, [continuation])
625
os.rename(temp_path, log_path)
628
463
def unlink_unversioned(branch, revdir):
629
464
for unversioned in branch.working_tree().extras():
630
465
path = os.path.join(revdir, unversioned)
636
471
def get_log(tree, revision):
637
log = pybaz.Patchlog(revision, tree=tree)
472
log = tree.iter_logs(version=revision.version, reverse=True).next()
638
473
assert str(log.revision) == str(revision), (log.revision, revision)
641
def get_revision(revdir, revision):
642
tree = revision.get(revdir)
476
def get_revision(revdir, revision, skip_symlinks=False):
478
tree = pybaz.tree_root(revdir)
643
479
log = get_log(tree, revision)
645
return tree, bzr_inventory_data(tree), log
481
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
646
482
except BadFileKind, e:
647
483
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
650
def apply_revision(tree, revision):
486
def apply_revision(revdir, revision, skip_symlinks=False):
487
tree = pybaz.tree_root(revdir)
651
488
revision.apply(tree)
652
489
log = get_log(tree, revision)
654
return bzr_inventory_data(tree), log
491
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
655
492
except BadFileKind, e:
656
493
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
659
498
class BadFileKind(Exception):
660
499
"""The file kind is not permitted in bzr inventories"""
661
500
def __init__(self, tree_root, path, kind):
665
504
Exception.__init__(self, "File %s is of forbidden type %s" %
666
505
(os.path.join(tree_root, path), kind))
669
def bzr_inventory_data(tree):
507
def bzr_inventory_data(tree, skip_symlinks=False):
670
508
inv_iter = tree.iter_inventory_ids(source=True, both=True)
672
510
for arch_id, path in inv_iter:
673
bzr_file_id = map_file_id(arch_id)
511
bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
674
512
inv_map[path] = bzr_file_id
677
515
for path, file_id in inv_map.iteritems():
678
516
full_path = os.path.join(tree, path)
679
517
kind = bzrlib.osutils.file_kind(full_path)
680
if kind not in ("file", "directory", "symlink"):
518
if skip_symlinks and kind == "symlink":
520
if kind not in ("file", "directory"):
681
521
raise BadFileKind(tree, path, kind)
682
522
parent_dir = os.path.dirname(path)
683
523
if parent_dir != "":
691
_global_option('max-count', type = int)
692
class cmd_baz_import_branch(Command):
531
class NotInABranch(Exception):
532
def __init__(self, path):
533
Exception.__init__(self, "%s is not in a branch." % path)
537
def find_branch(path):
540
Traceback (most recent call last):
541
NotInABranch: / is not in a branch.
542
>>> sb = bzrlib.ScratchBranch()
543
>>> isinstance(find_branch(sb.base), Branch)
547
return Branch.open(path)
549
if e.args[0].endswith("' is not in a branch"):
550
raise NotInABranch(path)
552
class cmd_baz_import(Command):
693
553
"""Import an Arch or Baz branch into a bzr branch"""
694
takes_args = ['to_location', 'from_branch?', 'reuse_history*']
695
takes_options = ['verbose', 'max-count']
697
def printer(self, name):
700
def run(self, to_location, from_branch=None, fast=False, max_count=None,
701
verbose=False, dry_run=False, reuse_history_list=[]):
554
takes_args = ['to_location', 'from_branch?']
555
takes_options = ['verbose']
557
def run(self, to_location, from_branch=None, skip_symlinks=False,
558
fast=False, max_count=None, verbose=False, dry_run=False):
702
559
to_location = os.path.realpath(str(to_location))
703
560
if from_branch is not None:
706
563
except pybaz.errors.NamespaceError:
707
564
print "%s is not a valid Arch branch." % from_branch
709
if reuse_history_list is None:
710
reuse_history_list = []
711
import_version(to_location, from_branch, self.printer,
713
reuse_history_from=reuse_history_list)
716
class NotInABranch(Exception):
717
def __init__(self, path):
718
Exception.__init__(self, "%s is not in a branch." % path)
722
class cmd_baz_import(Command):
723
"""Import an Arch or Baz archive into bzr branches.
725
reuse_history allows you to specify any previous imports you
726
have done of different archives, which this archive has branches
727
tagged from. This will dramatically reduce the time to convert
728
the archive as it will not have to convert the history already
729
converted in that other branch.
731
takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
732
takes_options = ['verbose']
734
def printer(self, name):
737
def run(self, to_root_dir, from_archive, verbose=False,
738
reuse_history_list=[]):
739
if reuse_history_list is None:
740
reuse_history_list = []
741
to_root = str(os.path.realpath(to_root_dir))
742
if not os.path.exists(to_root):
744
import_archive(to_root, from_archive, verbose, self.printer,
748
def import_archive(to_root, from_archive, verbose, printer,
749
reuse_history_from=[]):
750
real_to = os.path.realpath(to_root)
751
history_locations = [real_to] + reuse_history_from
752
for version in pybaz.Archive(str(from_archive)).iter_versions():
753
target = os.path.join(to_root, map_namespace(version))
754
printer("importing %s into %s" % (version, target))
755
if not os.path.exists(os.path.dirname(target)):
756
os.makedirs(os.path.dirname(target))
758
import_version(target, version, printer,
759
reuse_history_from=reuse_history_from)
760
except pybaz.errors.ExecProblem,e:
761
if str(e).find('The requested revision cannot be built.') != -1:
762
printer("Skipping version %s as it cannot be built due"
763
" to a missing parent archive." % version)
767
if str(e).find('already exists, and the last revision ') != -1:
768
printer("Skipping version %s as it has had commits made"
769
" since it was converted to bzr." % version)
774
def map_namespace(a_version):
775
a_version = pybaz.Version("%s" % a_version)
776
parser = NameParser(a_version)
777
version = parser.get_version()
778
branch = parser.get_branch()
779
category = parser.get_category()
780
if branch is None or branch == '':
783
return "%s/%s" % (category, branch)
784
return "%s/%s/%s" % (category, version, branch)
786
def map_file_id(file_id):
787
"""Convert a baz file id to a bzr one."""
788
return file_id.replace('%', '%25').replace('/', '%2f')
566
import_version(to_location, from_branch)