~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-06-14 16:29:36 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050614162936-3da2641a806b43e1
Added annotate plugin

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
 
from bzrlib.branch import Branch
22
 
from bzrlib.clone import copy_branch
23
 
from bzrlib.commit import Commit, NullCommitReporter
 
16
from bzrlib import Branch
24
17
from bzrlib.commands import Command
25
 
from bzrlib.option import _global_option
26
 
from bzrlib.workingtree import WorkingTree
27
 
from errors import NoPyBaz
28
18
try:
29
19
    import pybaz
30
20
    import pybaz.errors
31
 
    from pybaz import NameParser as NameParser
32
 
    from pybaz.backends.baz import null_cmd
33
21
except ImportError:
34
 
    raise NoPyBaz
35
 
from fai import iter_new_merges, direct_merges
 
22
    print "This command requires PyBaz.  Please ensure that it is installed."
 
23
    import sys
 
24
    sys.exit(1)
 
25
from pybaz.backends.baz import null_cmd
36
26
import tempfile
37
27
import os
38
28
import os.path
39
29
import shutil
40
30
import bzrlib
 
31
from bzrlib.errors import BzrError
41
32
import bzrlib.trace
42
33
import bzrlib.merge
43
 
import bzrlib.inventory
44
 
import bzrlib.osutils
45
34
import sys
46
35
import email.Utils
47
36
from progress import *
48
37
 
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
38
def add_id(files, id=None):
59
39
    """Adds an explicit id to a list of files.
60
40
 
69
49
    args.extend(files)
70
50
    return null_cmd(args)
71
51
 
72
 
saved_dir = None
73
 
 
74
52
def test_environ():
75
53
    """
76
54
    >>> q = test_environ()
82
60
    >>> os.path.exists(q)
83
61
    False
84
62
    """
85
 
    global saved_dir
86
 
    saved_dir = os.getcwdu()
87
63
    tdir = tempfile.mkdtemp(prefix="testdir-")
88
64
    os.environ["HOME"] = os.path.join(tdir, "home")
89
65
    os.mkdir(os.environ["HOME"])
127
103
    add_id([path], id)
128
104
 
129
105
def teardown_environ(tdir):
130
 
    os.chdir(saved_dir)
 
106
    os.chdir("/")
131
107
    shutil.rmtree(tdir)
132
108
 
133
109
def timport(tree, summary):
219
195
    """
220
196
    try:
221
197
        revision = version.iter_revisions(reverse=True).next()
222
 
    except StopIteration:
223
 
        return ()
224
198
    except:
225
 
        print version
226
199
        if not version.exists():
227
200
            raise NoSuchVersion(version)
228
201
        else:
232
205
    return ancestors
233
206
 
234
207
def get_last_revision(branch):
235
 
    last_patch = branch.last_revision()
 
208
    last_patch = branch.last_patch()
236
209
    try:
237
210
        return arch_revision(last_patch)
238
211
    except NotArchRevision:
239
212
        raise UserError(
240
213
            "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=[]):
 
214
            " an Arch revision (%s)" % (output_dir, last_patch))
 
215
 
 
216
 
 
217
def get_remaining_revisions(output_dir, version):
268
218
    last_patch = None
269
219
    old_revno = None
270
 
    output_exists = os.path.exists(output_dir)
271
 
    if output_exists:
 
220
    if os.path.exists(output_dir):
272
221
        # We are starting from an existing directory, figure out what
273
222
        # the current version is
274
 
        branch = Branch.open(output_dir)
 
223
        branch = find_branch(output_dir)
275
224
        last_patch = get_last_revision(branch)
276
 
        if last_patch is None:
277
 
            raise NotPreviousImport(branch.base)
278
225
        if version is None:
279
226
            version = last_patch.version
280
227
    elif version is None:
282
229
 
283
230
    try:
284
231
        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
232
    except NoSuchVersion, e:
307
 
        raise UserError(str(e))
 
233
        raise UserError(e)
308
234
 
309
235
    if last_patch:
310
236
        for i in range(len(ancestors)):
317
243
        # Strip off all of the ancestors which are already present
318
244
        # And get a directory starting with the latest ancestor
319
245
        latest_ancestor = ancestors[i]
320
 
        old_revno = Branch.open(output_dir).revno()
 
246
        old_revno = find_branch(output_dir).revno()
321
247
        ancestors = ancestors[i+1:]
322
248
    return ancestors, old_revno
323
249
 
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=[]):
 
250
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
 
251
                   dry_run=False, max_count=None, skip_symlinks=False):
343
252
    """
344
253
    >>> q = test_environ()
345
254
    >>> result_path = os.path.join(q, "result")
346
255
    >>> commit_test_revisions()
347
256
    >>> 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)
 
257
    >>> import_version('/', version, fancy=False, dry_run=True)
350
258
    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)
 
259
    UserError: / exists, but is not a bzr branch.
 
260
    >>> import_version(result_path, version, fancy=False, dry_run=True)
353
261
    Traceback (most recent call last):
354
262
    UserError: The version test@example.com/test--test--0.1 does not exist.
355
263
    >>> version = pybaz.Version("test@example.com/test--test--0")
356
 
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
 
264
    >>> import_version(result_path, version, fancy=False, dry_run=True)
357
265
    not fancy
358
266
    ....
359
267
    Dry run, not modifying output_dir
360
268
    Cleaning up
361
 
    >>> import_version(result_path, version, printer, fancy=False)
 
269
    >>> import_version(result_path, version, fancy=False)
362
270
    not fancy
363
271
    ....
364
272
    Cleaning up
365
273
    Import complete.
366
 
    >>> import_version(result_path, version, printer, fancy=False)
 
274
    >>> import_version(result_path, version, fancy=False)
367
275
    Tree is up-to-date with test@example.com/test--test--0--patch-2
368
276
    >>> commit_more_test_revisions()
369
 
    >>> import_version(result_path, version, printer, fancy=False)
 
277
    >>> import_version(result_path, version, fancy=False)
370
278
    not fancy
371
279
    ..
372
280
    Cleaning up
374
282
    >>> teardown_environ(q)
375
283
    """
376
284
    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
 
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)
384
288
    if len(ancestors) == 0:
385
 
        last_revision = get_last_revision(Branch.open(output_dir))
 
289
        last_revision = get_last_revision(find_branch(output_dir))
386
290
        print 'Tree is up-to-date with %s' % last_revision
387
291
        return
388
292
 
394
298
            print "not fancy"
395
299
        try:
396
300
            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):
 
301
                    fast=fast, verbose=verbose, dry_run=dry_run, 
 
302
                    max_count=max_count, skip_symlinks=skip_symlinks):
399
303
                if fancy:
400
 
                    show_progress(progress_bar, result)
 
304
                    progress_bar(result)
401
305
                else:
402
306
                    sys.stdout.write('.')
403
307
        finally:
404
308
            if fancy:
405
 
                progress_bar.clear()
 
309
                clear_progress_bar()
406
310
            else:
407
311
                sys.stdout.write('\n')
408
312
 
411
315
            return
412
316
        if os.path.exists(output_dir):
413
317
            # 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), 
 
318
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
 
319
            
 
320
            bzr_dir = os.path.join(output_dir, '.bzr')
 
321
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
322
 
 
323
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
 
324
            os.rename(new_bzr_dir, bzr_dir)
 
325
            try:
 
326
                bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno), 
437
327
                                   check_clean=False, this_dir=output_dir, 
438
328
                                   ignore_zero=True)
 
329
            except:
 
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)
 
333
                raise
439
334
        else:
440
335
            revdir = os.path.join(tempdir, "rd")
441
336
            os.rename(revdir, output_dir)
442
337
 
443
338
    finally:
444
 
        printer('Cleaning up')
 
339
        print 'Cleaning up'
445
340
        shutil.rmtree(tempdir)
446
 
    printer("Import complete.")
 
341
    print "Import complete."
447
342
            
448
 
class UserError(BzrCommandError):
 
343
class UserError(Exception):
449
344
    def __init__(self, message):
450
345
        """Exception to throw when a user makes an impossible request
451
346
        :param message: The message to emit when printing this exception
452
347
        :type message: string
453
348
        """
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
 
 
 
349
        Exception.__init__(self, message)
461
350
 
462
351
def revision_id(arch_revision):
463
352
    """
468
357
    :param arch_revision: The Arch revision to generate an ID for.
469
358
 
470
359
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
471
 
    'Arch-1:you@example.com%cat--br--0--base-0'
 
360
    'Arch-x:you@example.com%cat--br--0--base-0'
472
361
    """
473
 
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
 
362
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
474
363
 
475
364
class NotArchRevision(Exception):
476
365
    def __init__(self, revision_id):
480
369
 
481
370
def arch_revision(revision_id):
482
371
    """
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"))
 
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"))
490
379
    'jrandom@example.com/test--test--0--patch-5'
491
380
    """
492
381
    if revision_id is None:
493
382
        return None
494
 
    if revision_id[:7] != 'Arch-1:':
 
383
    if revision_id[:7] != 'Arch-x:':
495
384
        raise NotArchRevision(revision_id)
496
385
    else:
497
386
        try:
499
388
        except pybaz.errors.NamespaceError, e:
500
389
            raise NotArchRevision(revision_id)
501
390
            
502
 
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
503
 
                        verbose=False, dry_run=False, max_count=None):
 
391
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
 
392
                        verbose=False, dry_run=False, max_count=None,
 
393
                        skip_symlinks=False):
504
394
    revdir = None
505
395
 
506
396
    # Uncomment this for testing, it basically just has baz2bzr only update
518
408
    #        print '\t%s' % a
519
409
 
520
410
    previous_version=None
521
 
    missing_ancestor = None
522
411
 
523
412
    for i in range(len(ancestors)):
524
413
        revision = ancestors[i]
525
 
        rev_id = revision_id(revision)
526
 
        direct_merges = []
527
414
        if verbose:
528
415
            version = str(revision.version)
529
416
            if version != previous_version:
533
420
            previous_version = version
534
421
        else:
535
422
            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
423
        if revdir is None:
543
424
            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
 
425
            baz_inv, log = get_revision(revdir, revision, 
 
426
                                        skip_symlinks=skip_symlinks)
554
427
            if os.path.exists(output_dir):
555
428
                bzr_dir = os.path.join(output_dir, '.bzr')
556
429
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
558
431
                # we fail, we have corrupted the original .bzr directory.  Is
559
432
                # that a big problem, as we can just back out the last
560
433
                # 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
434
                shutil.copytree(bzr_dir, new_bzr_dir)
564
435
                # Now revdir should have a tree with the latest .bzr, and the
565
436
                # next revision of the baz tree
566
 
                branch = Branch.open(revdir)
 
437
                branch = find_branch(revdir)
567
438
            else:
568
 
                branch = Branch.initialize(revdir)
 
439
                branch = bzrlib.Branch(revdir, init=True)
569
440
        else:
570
441
            old = os.path.join(revdir, ".bzr")
571
442
            new = os.path.join(tempdir, ".bzr")
572
443
            os.rename(old, new)
573
 
            baz_inv, log = apply_revision(tree, revision)
 
444
            baz_inv, log = apply_revision(revdir, revision, 
 
445
                                          skip_symlinks=skip_symlinks)
574
446
            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
 
447
            branch = find_branch(revdir)
 
448
        timestamp = email.Utils.mktime_tz(log.date + (0,))
 
449
        rev_id = revision_id(revision)
592
450
        branch.lock_write()
593
 
        target_tree = WorkingTree(revdir ,branch=branch)
594
 
        target_tree.lock_write()
595
451
        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)
 
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)
607
456
        finally:
608
 
            target_tree.unlock()
 
457
            bzrlib.trace.silent = False   
609
458
            branch.unlock()
610
459
    yield Progress("revisions", len(ancestors), len(ancestors))
611
460
    unlink_unversioned(branch, revdir)
612
461
 
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
462
def unlink_unversioned(branch, revdir):
629
463
    for unversioned in branch.working_tree().extras():
630
464
        path = os.path.join(revdir, unversioned)
634
468
            os.unlink(path)
635
469
 
636
470
def get_log(tree, revision):
637
 
    log = pybaz.Patchlog(revision, tree=tree)
638
 
    assert str(log.revision) == str(revision), (log.revision, revision)
 
471
    log = tree.iter_logs(version=revision.version, reverse=True).next()
 
472
    assert log.revision == revision
639
473
    return log
640
474
 
641
 
def get_revision(revdir, revision):
642
 
    tree = revision.get(revdir)
 
475
def get_revision(revdir, revision, skip_symlinks=False):
 
476
    revision.get(revdir)
 
477
    tree = pybaz.tree_root(revdir)
643
478
    log = get_log(tree, revision)
644
479
    try:
645
 
        return tree, bzr_inventory_data(tree), log 
 
480
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
646
481
    except BadFileKind, e:
647
482
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
648
483
 
649
484
 
650
 
def apply_revision(tree, revision):
 
485
def apply_revision(revdir, revision, skip_symlinks=False):
 
486
    tree = pybaz.tree_root(revdir)
651
487
    revision.apply(tree)
652
488
    log = get_log(tree, revision)
653
489
    try:
654
 
        return bzr_inventory_data(tree), log
 
490
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
655
491
    except BadFileKind, e:
656
492
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
657
493
 
658
494
 
 
495
 
 
496
 
659
497
class BadFileKind(Exception):
660
498
    """The file kind is not permitted in bzr inventories"""
661
499
    def __init__(self, tree_root, path, kind):
665
503
        Exception.__init__(self, "File %s is of forbidden type %s" %
666
504
                           (os.path.join(tree_root, path), kind))
667
505
 
668
 
 
669
 
def bzr_inventory_data(tree):
 
506
def bzr_inventory_data(tree, skip_symlinks=False):
670
507
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
671
508
    inv_map = {}
672
 
    for arch_id, path in inv_iter:
673
 
        bzr_file_id = map_file_id(arch_id)
674
 
        inv_map[path] = bzr_file_id 
 
509
    for file_id, path in inv_iter:
 
510
        inv_map[path] = file_id 
675
511
 
676
512
    bzr_inv = []
677
513
    for path, file_id in inv_map.iteritems():
678
514
        full_path = os.path.join(tree, path)
679
515
        kind = bzrlib.osutils.file_kind(full_path)
680
 
        if kind not in ("file", "directory", "symlink"):
 
516
        if skip_symlinks and kind == "symlink":
 
517
            continue
 
518
        if kind not in ("file", "directory"):
681
519
            raise BadFileKind(tree, path, kind)
682
520
        parent_dir = os.path.dirname(path)
683
521
        if parent_dir != "":
688
526
    bzr_inv.sort()
689
527
    return bzr_inv
690
528
 
691
 
_global_option('max-count', type = int)
692
 
class cmd_baz_import_branch(Command):
 
529
class NotInABranch(Exception):
 
530
    def __init__(self, path):
 
531
        Exception.__init__(self, "%s is not in a branch." % path)
 
532
        self.path = path
 
533
 
 
534
 
 
535
def find_branch(path):
 
536
    """
 
537
    >>> find_branch('/')
 
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)
 
542
    True
 
543
    """
 
544
    try:
 
545
        return bzrlib.Branch(path)
 
546
    except BzrError, e:
 
547
        if e.args[0].endswith("' is not in a branch"):
 
548
            raise NotInABranch(path)
 
549
 
 
550
class cmd_baz_import(Command):
693
551
    """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=[]):
 
552
    takes_args = ['to_location', 'from_branch?']
 
553
    takes_options = ['verbose']
 
554
 
 
555
    def run(self, to_location, from_branch=None, skip_symlinks=False, 
 
556
            fast=False, max_count=None, verbose=False, dry_run=False):
 
557
        fancy = rewriting_supported()
702
558
        to_location = os.path.realpath(str(to_location))
703
559
        if from_branch is not None:
704
560
            try:
705
 
                from_branch = pybaz.Version(from_branch)
 
561
                from_branch = pybaz.Version(version)
706
562
            except pybaz.errors.NamespaceError:
707
563
                print "%s is not a valid Arch branch." % from_branch
708
564
                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')
 
565
        import_version(to_location, from_branch, fancy=fancy)