~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-11-01 14:50:37 UTC
  • mto: This revision was merged to the branch mainline in revision 269.
  • Revision ID: abentley@panoramicfeedback.com-20051101145037-27ee4174a53eeef3
Improved completion in the middle of lines

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
17
 
import errno
18
 
 
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
28
19
try:
29
20
    import pybaz
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:
34
24
    raise NoPyBaz
35
 
from fai import iter_new_merges, direct_merges
36
25
import tempfile
37
26
import os
38
27
import os.path
39
28
import shutil
40
29
import bzrlib
 
30
from bzrlib.errors import BzrError
41
31
import bzrlib.trace
42
32
import bzrlib.merge
43
33
import bzrlib.inventory
46
36
import email.Utils
47
37
from progress import *
48
38
 
49
 
class ImportCommitReporter(NullCommitReporter):
50
 
    def __init__(self, pb):
51
 
        self.pb = pb
52
 
 
53
 
    def escaped(self, escape_count, message):
54
 
        self.pb.clear()
55
 
        bzrlib.trace.warning("replaced %d control characters in message" %
56
 
                             escape_count)
57
 
 
58
39
def add_id(files, id=None):
59
40
    """Adds an explicit id to a list of files.
60
41
 
69
50
    args.extend(files)
70
51
    return null_cmd(args)
71
52
 
72
 
saved_dir = None
73
 
 
74
53
def test_environ():
75
54
    """
76
55
    >>> q = test_environ()
82
61
    >>> os.path.exists(q)
83
62
    False
84
63
    """
85
 
    global saved_dir
86
 
    saved_dir = os.getcwdu()
87
64
    tdir = tempfile.mkdtemp(prefix="testdir-")
88
65
    os.environ["HOME"] = os.path.join(tdir, "home")
89
66
    os.mkdir(os.environ["HOME"])
127
104
    add_id([path], id)
128
105
 
129
106
def teardown_environ(tdir):
130
 
    os.chdir(saved_dir)
 
107
    os.chdir("/")
131
108
    shutil.rmtree(tdir)
132
109
 
133
110
def timport(tree, summary):
219
196
    """
220
197
    try:
221
198
        revision = version.iter_revisions(reverse=True).next()
222
 
    except StopIteration:
223
 
        return ()
224
199
    except:
225
 
        print version
226
200
        if not version.exists():
227
201
            raise NoSuchVersion(version)
228
202
        else:
238
212
    except NotArchRevision:
239
213
        raise UserError(
240
214
            "Directory \"%s\" already exists, and the last revision is not"
241
 
            " an Arch revision (%s)" % (branch.base, last_patch))
242
 
 
243
 
def do_branch(br_from, to_location, revision_id):
244
 
    """Derived from branch in builtins."""
245
 
    br_from.lock_read()
246
 
    try:
247
 
        try:
248
 
            os.mkdir(to_location)
249
 
        except OSError, e:
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.' %
255
 
                                      to_location)
256
 
            else:
257
 
                raise
258
 
        try:
259
 
            copy_branch(br_from, to_location, revision_id, None)
260
 
        except NoSuchRevision:
261
 
            rmtree(to_location)
262
 
            msg = "The branch %s has no revision %s." % (from_location, revision_id)
263
 
            raise UserError(msg)
264
 
    finally:
265
 
        br_from.unlock()
266
 
 
267
 
def get_remaining_revisions(output_dir, version, reuse_history_from=[]):
 
215
            " an Arch revision (%s)" % (output_dir, last_patch))
 
216
 
 
217
 
 
218
def get_remaining_revisions(output_dir, version):
268
219
    last_patch = None
269
220
    old_revno = None
270
 
    output_exists = os.path.exists(output_dir)
271
 
    if output_exists:
 
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:
282
230
 
283
231
    try:
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
289
 
                    break
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 
293
 
                # transition.
294
 
                for history_root in reuse_history_from:
295
 
                    possible_source = os.path.join(history_root,
296
 
                        map_namespace(ancestor.version))
297
 
                    try:
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
303
 
                            break
304
 
                    except NotBranchError:
305
 
                        pass
306
233
    except NoSuchVersion, e:
307
 
        raise UserError(str(e))
 
234
        raise UserError(e)
308
235
 
309
236
    if last_patch:
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
323
250
 
324
 
 
325
 
###class Importer(object):
326
 
###    """An importer.
327
 
###    
328
 
###    Currently this is used as a parameter object, though more behaviour is
329
 
###    possible later.
330
 
###    """
331
 
###
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
337
 
###        self.
338
 
 
339
 
 
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):
343
253
    """
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)
357
266
    not fancy
358
267
    ....
359
268
    Dry run, not modifying output_dir
360
269
    Cleaning up
361
 
    >>> import_version(result_path, version, printer, fancy=False)
 
270
    >>> import_version(result_path, version, fancy=False)
362
271
    not fancy
363
272
    ....
364
273
    Cleaning up
365
274
    Import complete.
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)
370
279
    not fancy
371
280
    ..
372
281
    Cleaning up
374
283
    >>> teardown_environ(q)
375
284
    """
376
285
    try:
377
 
        ancestors, old_revno = get_remaining_revisions(output_dir, version,
378
 
                                                       reuse_history_from)
379
 
    except NotBranchError, e:
380
 
        raise NotPreviousImport(e.path)
381
 
    if old_revno is None and len(ancestors) == 0:
382
 
        print 'Version %s has no revisions.' % version
383
 
        return
 
286
        ancestors, old_revno = get_remaining_revisions(output_dir, version)
 
287
    except NotInABranch, e:
 
288
        raise UserError("%s exists, but is not a bzr branch." % e.path)
384
289
    if len(ancestors) == 0:
385
 
        last_revision = get_last_revision(Branch.open(output_dir))
 
290
        last_revision = get_last_revision(find_branch(output_dir))
386
291
        print 'Tree is up-to-date with %s' % last_revision
387
292
        return
388
293
 
394
299
            print "not fancy"
395
300
        try:
396
301
            for result in iter_import_version(output_dir, ancestors, tempdir,
397
 
                    progress_bar, fast=fast, verbose=verbose, dry_run=dry_run,
398
 
                    max_count=max_count):
 
302
                    fast=fast, verbose=verbose, dry_run=dry_run, 
 
303
                    max_count=max_count, skip_symlinks=skip_symlinks):
399
304
                if fancy:
400
305
                    show_progress(progress_bar, result)
401
306
                else:
411
316
            return
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')
418
 
                
419
 
                bzr_dir = os.path.join(output_dir, '.bzr')
420
 
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
421
 
    
422
 
                os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
423
 
                os.rename(new_bzr_dir, bzr_dir)
424
 
                try:
425
 
                    bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno), 
426
 
                                       check_clean=False, this_dir=output_dir, 
427
 
                                       ignore_zero=True)
428
 
                except:
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)
432
 
                    raise
433
 
            else:
434
 
                # no imports - perhaps just append_revisions
435
 
                # should not fail:
436
 
                bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno), 
 
319
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
 
320
            
 
321
            bzr_dir = os.path.join(output_dir, '.bzr')
 
322
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
323
 
 
324
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
 
325
            os.rename(new_bzr_dir, bzr_dir)
 
326
            try:
 
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)
 
330
            except:
 
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)
 
334
                raise
439
335
        else:
440
336
            revdir = os.path.join(tempdir, "rd")
441
337
            os.rename(revdir, output_dir)
442
338
 
443
339
    finally:
444
 
        printer('Cleaning up')
 
340
        print 'Cleaning up'
445
341
        shutil.rmtree(tempdir)
446
 
    printer("Import complete.")
 
342
    print "Import complete."
447
343
            
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
453
349
        """
454
 
        BzrCommandError.__init__(self, message)
455
 
 
456
 
class NotPreviousImport(UserError):
457
 
    def __init__(self, path):
458
 
        UserError.__init__(self, "%s is not the location of a previous import."
459
 
                           % path)
460
 
 
 
350
        Exception.__init__(self, message)
461
351
 
462
352
def revision_id(arch_revision):
463
353
    """
468
358
    :param arch_revision: The Arch revision to generate an ID for.
469
359
 
470
360
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
471
 
    'Arch-1:you@example.com%cat--br--0--base-0'
 
361
    'Arch-x:you@example.com%cat--br--0--base-0'
472
362
    """
473
 
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
 
363
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
474
364
 
475
365
class NotArchRevision(Exception):
476
366
    def __init__(self, revision_id):
480
370
 
481
371
def arch_revision(revision_id):
482
372
    """
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'
491
381
    """
492
382
    if revision_id is None:
493
383
        return None
494
 
    if revision_id[:7] != 'Arch-1:':
 
384
    if revision_id[:7] != 'Arch-x:':
495
385
        raise NotArchRevision(revision_id)
496
386
    else:
497
387
        try:
499
389
        except pybaz.errors.NamespaceError, e:
500
390
            raise NotArchRevision(revision_id)
501
391
            
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):
504
395
    revdir = None
505
396
 
506
397
    # Uncomment this for testing, it basically just has baz2bzr only update
518
409
    #        print '\t%s' % a
519
410
 
520
411
    previous_version=None
521
 
    missing_ancestor = None
522
412
 
523
413
    for i in range(len(ancestors)):
524
414
        revision = ancestors[i]
525
 
        rev_id = revision_id(revision)
526
 
        direct_merges = []
527
415
        if verbose:
528
416
            version = str(revision.version)
529
417
            if version != previous_version:
533
421
            previous_version = version
534
422
        else:
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)
541
 
                continue
542
424
        if revdir is None:
543
425
            revdir = os.path.join(tempdir, "rd")
544
 
            try:
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:
548
 
                    raise
549
 
                missing_ancestor = revision
550
 
                revdir = None
551
 
                print ("unable to access ancestor %s, making into a merge."
552
 
                       % missing_ancestor)
553
 
                continue
 
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)
567
439
            else:
568
440
                branch = Branch.initialize(revdir)
569
441
        else:
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
577
 
        log_date = log.date
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)
583
 
 
584
 
        timestamp = email.Utils.mktime_tz(log_date + (0,))
585
 
        if log_summary is None:
586
 
            log_summary = ""
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))
590
 
        else:
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()
595
452
        try:
596
 
            if missing_ancestor:
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)
607
457
        finally:
608
 
            target_tree.unlock()
 
458
            bzrlib.trace.silent = False   
609
459
            branch.unlock()
610
460
    yield Progress("revisions", len(ancestors), len(ancestors))
611
461
    unlink_unversioned(branch, revdir)
612
462
 
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)
626
 
    return direct
627
 
 
628
463
def unlink_unversioned(branch, revdir):
629
464
    for unversioned in branch.working_tree().extras():
630
465
        path = os.path.join(revdir, unversioned)
634
469
            os.unlink(path)
635
470
 
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)
639
474
    return log
640
475
 
641
 
def get_revision(revdir, revision):
642
 
    tree = revision.get(revdir)
 
476
def get_revision(revdir, revision, skip_symlinks=False):
 
477
    revision.get(revdir)
 
478
    tree = pybaz.tree_root(revdir)
643
479
    log = get_log(tree, revision)
644
480
    try:
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) )
648
484
 
649
485
 
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)
653
490
    try:
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) )
657
494
 
658
495
 
 
496
 
 
497
 
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))
667
506
 
668
 
 
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)
671
509
    inv_map = {}
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 
675
513
 
676
514
    bzr_inv = []
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":
 
519
            continue
 
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 != "":
688
528
    bzr_inv.sort()
689
529
    return bzr_inv
690
530
 
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)
 
534
        self.path = path
 
535
 
 
536
 
 
537
def find_branch(path):
 
538
    """
 
539
    >>> find_branch('/')
 
540
    Traceback (most recent call last):
 
541
    NotInABranch: / is not in a branch.
 
542
    >>> sb = bzrlib.ScratchBranch()
 
543
    >>> isinstance(find_branch(sb.base), Branch)
 
544
    True
 
545
    """
 
546
    try:
 
547
        return Branch.open(path)
 
548
    except BzrError, e:
 
549
        if e.args[0].endswith("' is not in a branch"):
 
550
            raise NotInABranch(path)
 
551
 
 
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']
696
 
 
697
 
    def printer(self, name):
698
 
        print name
699
 
 
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']
 
556
 
 
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:
704
561
            try:
706
563
            except pybaz.errors.NamespaceError:
707
564
                print "%s is not a valid Arch branch." % from_branch
708
565
                return 1
709
 
        if reuse_history_list is None:
710
 
            reuse_history_list = []
711
 
        import_version(to_location, from_branch, self.printer, 
712
 
                       max_count=max_count, 
713
 
                       reuse_history_from=reuse_history_list)
714
 
 
715
 
 
716
 
class NotInABranch(Exception):
717
 
    def __init__(self, path):
718
 
        Exception.__init__(self, "%s is not in a branch." % path)
719
 
        self.path = path
720
 
 
721
 
 
722
 
class cmd_baz_import(Command):
723
 
    """Import an Arch or Baz archive into bzr branches.
724
 
    
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.
730
 
    """
731
 
    takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
732
 
    takes_options = ['verbose']
733
 
 
734
 
    def printer(self, name):
735
 
        print name
736
 
 
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):
743
 
            os.mkdir(to_root)
744
 
        import_archive(to_root, from_archive, verbose, self.printer, 
745
 
                       reuse_history_list)
746
 
 
747
 
 
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))
757
 
        try:
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)
764
 
            else:
765
 
                raise
766
 
        except UserError, e:
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)
770
 
            else:
771
 
                raise
772
 
 
773
 
 
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 == '':
781
 
        branch = "+trunk"
782
 
    if version == '0':
783
 
        return "%s/%s" % (category, branch)
784
 
    return "%s/%s/%s" % (category, version, branch)
785
 
 
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)