~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2006-03-10 20:15:17 UTC
  • mfrom: (147.1.73 bzrtools)
  • Revision ID: abentley@panoramicfeedback.com-20060310201517-958f8d9307a49b2e
Merged the Arch-1 bzrtools import

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Aaron Bentley
 
1
# Copyright (C) 2005, 2006 by Aaron Bentley
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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.bzrdir import BzrDir
 
20
import bzrlib.bzrdir as bzrdir
 
21
from bzrlib.errors import (BzrError,
 
22
                           NotBranchError,
 
23
                           NoWorkingTree,
 
24
                           BzrCommandError, 
 
25
                           NoSuchRevision,
 
26
                           NoRepositoryPresent,
 
27
                          )
16
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
 
35
import bzrlib.ui
 
36
from bzrlib.workingtree import WorkingTree
18
37
from errors import NoPyBaz
19
38
try:
20
39
    import pybaz
21
40
    import pybaz.errors
 
41
    from pybaz import NameParser as NameParser
22
42
    from pybaz.backends.baz import null_cmd
23
43
except ImportError:
24
44
    raise NoPyBaz
 
45
from fai import iter_new_merges, direct_merges
25
46
import tempfile
26
47
import os
27
48
import os.path
28
49
import shutil
29
50
import bzrlib
30
 
from bzrlib.errors import BzrError, NotBranchError, BzrCommandError
31
51
import bzrlib.trace
32
52
import bzrlib.merge
33
53
import bzrlib.inventory
36
56
import email.Utils
37
57
from progress import *
38
58
 
 
59
class ImportCommitReporter(NullCommitReporter):
 
60
    def __init__(self, pb):
 
61
        self.pb = pb
 
62
 
 
63
    def escaped(self, escape_count, message):
 
64
        self.pb.clear()
 
65
        bzrlib.trace.warning("replaced %d control characters in message" %
 
66
                             escape_count)
 
67
 
39
68
def add_id(files, id=None):
40
69
    """Adds an explicit id to a list of files.
41
70
 
50
79
    args.extend(files)
51
80
    return null_cmd(args)
52
81
 
 
82
saved_dir = None
 
83
 
 
84
def make_archive(name, location):
 
85
    pb_location = pybaz.ArchiveLocation(location)
 
86
    pb_location.create_master(pybaz.Archive(name), 
 
87
                              pybaz.ArchiveLocationParams())
 
88
 
53
89
def test_environ():
54
90
    """
55
91
    >>> q = test_environ()
61
97
    >>> os.path.exists(q)
62
98
    False
63
99
    """
 
100
    global saved_dir
 
101
    saved_dir = os.getcwdu()
64
102
    tdir = tempfile.mkdtemp(prefix="testdir-")
65
103
    os.environ["HOME"] = os.path.join(tdir, "home")
66
104
    os.mkdir(os.environ["HOME"])
67
105
    arch_dir = os.path.join(tdir, "archive_dir")
68
 
    pybaz.make_archive("test@example.com", arch_dir)
 
106
    make_archive("test@example.com", arch_dir)
69
107
    work_dir = os.path.join(tdir, "work_dir")
70
108
    os.mkdir(work_dir)
71
109
    os.chdir(work_dir)
104
142
    add_id([path], id)
105
143
 
106
144
def teardown_environ(tdir):
107
 
    os.chdir("/")
 
145
    os.chdir(saved_dir)
108
146
    shutil.rmtree(tdir)
109
147
 
110
148
def timport(tree, summary):
196
234
    """
197
235
    try:
198
236
        revision = version.iter_revisions(reverse=True).next()
 
237
    except StopIteration:
 
238
        return ()
199
239
    except:
 
240
        print version
200
241
        if not version.exists():
201
242
            raise NoSuchVersion(version)
202
243
        else:
212
253
    except NotArchRevision:
213
254
        raise UserError(
214
255
            "Directory \"%s\" already exists, and the last revision is not"
215
 
            " an Arch revision (%s)" % (output_dir, last_patch))
216
 
 
217
 
 
218
 
def get_remaining_revisions(output_dir, version):
 
256
            " an Arch revision (%s)" % (branch.base, last_patch))
 
257
 
 
258
def do_branch(br_from, to_location, revision_id):
 
259
    """Derived from branch in builtins."""
 
260
    br_from.lock_read()
 
261
    try:
 
262
        try:
 
263
            os.mkdir(to_location)
 
264
        except OSError, e:
 
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.' %
 
270
                                      to_location)
 
271
            else:
 
272
                raise
 
273
        try:
 
274
            br_from.bzrdir.clone(to_location, revision_id)
 
275
        except NoSuchRevision:
 
276
            rmtree(to_location)
 
277
            msg = "The branch %s has no revision %s." % (from_location, 
 
278
                                                         revision_id)
 
279
            raise UserError(msg)
 
280
    finally:
 
281
        br_from.unlock()
 
282
 
 
283
def get_remaining_revisions(output_dir, version, reuse_history_from=[]):
219
284
    last_patch = None
220
285
    old_revno = None
221
 
    if os.path.exists(output_dir):
 
286
    output_exists = os.path.exists(output_dir)
 
287
    if output_exists:
222
288
        # We are starting from an existing directory, figure out what
223
289
        # the current version is
224
 
        branch = find_branch(output_dir)
 
290
        branch = Branch.open(output_dir)
225
291
        last_patch = get_last_revision(branch)
226
292
        if last_patch is None:
227
293
            raise NotPreviousImport(branch.base)
232
298
 
233
299
    try:
234
300
        ancestors = version_ancestry(version)
235
 
        if len(ancestors) > 0 and not ancestors[0].archive.is_registered():
236
 
            ancestors = ancestors[1:]
 
301
        if not output_exists and reuse_history_from != []:
 
302
            for ancestor in reversed(ancestors):
 
303
                if last_patch is not None:
 
304
                    # found something to copy
 
305
                    break
 
306
                # try to grab a copy of ancestor
 
307
                # note that is not optimised: we could look for namespace
 
308
                # transitions and only look for the past after the 
 
309
                # transition.
 
310
                for history_root in reuse_history_from:
 
311
                    possible_source = os.path.join(history_root,
 
312
                        map_namespace(ancestor.version))
 
313
                    try:
 
314
                        source = Branch.open(possible_source)
 
315
                        rev_id = revision_id(ancestor)
 
316
                        if rev_id in source.revision_history():
 
317
                            do_branch(source, output_dir, rev_id)
 
318
                            last_patch = ancestor
 
319
                            break
 
320
                    except NotBranchError:
 
321
                        pass
237
322
    except NoSuchVersion, e:
238
 
        raise UserError(e)
 
323
        raise UserError(str(e))
239
324
 
240
325
    if last_patch:
241
326
        for i in range(len(ancestors)):
248
333
        # Strip off all of the ancestors which are already present
249
334
        # And get a directory starting with the latest ancestor
250
335
        latest_ancestor = ancestors[i]
251
 
        old_revno = find_branch(output_dir).revno()
 
336
        old_revno = Branch.open(output_dir).revno()
252
337
        ancestors = ancestors[i+1:]
253
338
    return ancestors, old_revno
254
339
 
255
 
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
256
 
                   dry_run=False, max_count=None, skip_symlinks=False):
 
340
 
 
341
###class Importer(object):
 
342
###    """An importer.
 
343
###    
 
344
###    Currently this is used as a parameter object, though more behaviour is
 
345
###    possible later.
 
346
###    """
 
347
###
 
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
 
353
###        self.
 
354
 
 
355
 
 
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):
257
359
    """
258
360
    >>> q = test_environ()
259
361
    >>> result_path = os.path.join(q, "result")
260
362
    >>> commit_test_revisions()
261
363
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
262
 
    >>> import_version('/', version, fancy=False, dry_run=True)
 
364
    >>> def printer(message): print message
 
365
    >>> import_version('/', version, printer, fancy=False, dry_run=True)
263
366
    Traceback (most recent call last):
264
 
    UserError: / exists, but is not a bzr branch.
265
 
    >>> 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)
266
369
    Traceback (most recent call last):
267
370
    UserError: The version test@example.com/test--test--0.1 does not exist.
268
371
    >>> version = pybaz.Version("test@example.com/test--test--0")
269
 
    >>> import_version(result_path, version, fancy=False, dry_run=True)
 
372
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
270
373
    not fancy
271
374
    ....
272
375
    Dry run, not modifying output_dir
273
376
    Cleaning up
274
 
    >>> import_version(result_path, version, fancy=False)
 
377
    >>> import_version(result_path, version, printer, fancy=False)
275
378
    not fancy
276
379
    ....
277
380
    Cleaning up
278
381
    Import complete.
279
 
    >>> import_version(result_path, version, fancy=False)
 
382
    >>> import_version(result_path, version, printer, fancy=False)
280
383
    Tree is up-to-date with test@example.com/test--test--0--patch-2
281
384
    >>> commit_more_test_revisions()
282
 
    >>> import_version(result_path, version, fancy=False)
 
385
    >>> import_version(result_path, version, printer, fancy=False)
283
386
    not fancy
284
387
    ..
285
388
    Cleaning up
287
390
    >>> teardown_environ(q)
288
391
    """
289
392
    try:
290
 
        ancestors, old_revno = get_remaining_revisions(output_dir, version)
291
 
    except NotInABranch, e:
 
393
        ancestors, old_revno = get_remaining_revisions(output_dir, version,
 
394
                                                       reuse_history_from)
 
395
    except NotBranchError, e:
292
396
        raise NotPreviousImport(e.path)
 
397
    if old_revno is None and len(ancestors) == 0:
 
398
        print 'Version %s has no revisions.' % version
 
399
        return
293
400
    if len(ancestors) == 0:
294
 
        last_revision = get_last_revision(find_branch(output_dir))
 
401
        last_revision = get_last_revision(Branch.open(output_dir))
295
402
        print 'Tree is up-to-date with %s' % last_revision
296
403
        return
297
404
 
298
 
    progress_bar = ProgressBar()
 
405
    progress_bar = bzrlib.ui.ui_factory.progress_bar()
299
406
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
300
407
                               dir=os.path.dirname(output_dir))
301
408
    try:
 
409
        wt = WorkingTree.open(output_dir)
 
410
    except (NotBranchError, NoWorkingTree):
 
411
        wt = None
 
412
    if wt is None:
 
413
        old_basis = EmptyTree()
 
414
    else:
 
415
        old_basis = wt.basis_tree()
 
416
    try:
302
417
        if not fancy:
303
418
            print "not fancy"
304
419
        try:
305
420
            for result in iter_import_version(output_dir, ancestors, tempdir,
306
 
                    fast=fast, verbose=verbose, dry_run=dry_run, 
307
 
                    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):
308
423
                if fancy:
309
424
                    show_progress(progress_bar, result)
310
425
                else:
318
433
        if dry_run:
319
434
            print 'Dry run, not modifying output_dir'
320
435
            return
321
 
        if os.path.exists(output_dir):
322
 
            # Move the bzr control directory back, and update the working tree
323
 
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
324
 
            
325
 
            bzr_dir = os.path.join(output_dir, '.bzr')
326
 
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
327
436
 
328
 
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
329
 
            os.rename(new_bzr_dir, bzr_dir)
330
 
            try:
331
 
                bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno), 
332
 
                                   check_clean=False, this_dir=output_dir, 
333
 
                                   ignore_zero=True)
334
 
            except:
335
 
                # If something failed, move back the original bzr directory
336
 
                os.rename(bzr_dir, new_bzr_dir)
337
 
                os.rename(tmp_bzr_dir, bzr_dir)
338
 
                raise
339
 
        else:
340
 
            revdir = os.path.join(tempdir, "rd")
341
 
            os.rename(revdir, output_dir)
 
437
        # Update the working tree of the branch
 
438
        try:
 
439
            wt = WorkingTree.open(output_dir)
 
440
        except NoWorkingTree:
 
441
            wt = None
 
442
        if wt is not None:
 
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)
 
446
            wt.revert([])
342
447
 
343
448
    finally:
344
 
        print 'Cleaning up'
 
449
        printer('Cleaning up')
345
450
        shutil.rmtree(tempdir)
346
 
    print "Import complete."
 
451
    printer("Import complete.")
347
452
            
348
453
class UserError(BzrCommandError):
349
454
    def __init__(self, message):
368
473
    :param arch_revision: The Arch revision to generate an ID for.
369
474
 
370
475
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
371
 
    'Arch-x:you@example.com%cat--br--0--base-0'
 
476
    'Arch-1:you@example.com%cat--br--0--base-0'
372
477
    """
373
 
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
 
478
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
374
479
 
375
480
class NotArchRevision(Exception):
376
481
    def __init__(self, revision_id):
380
485
 
381
486
def arch_revision(revision_id):
382
487
    """
383
 
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
384
 
    Traceback (most recent call last):
385
 
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
386
 
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
387
 
    Traceback (most recent call last):
388
 
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
389
 
    >>> 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"))
390
495
    'jrandom@example.com/test--test--0--patch-5'
391
496
    """
392
497
    if revision_id is None:
393
498
        return None
394
 
    if revision_id[:7] != 'Arch-x:':
 
499
    if revision_id[:7] != 'Arch-1:':
395
500
        raise NotArchRevision(revision_id)
396
501
    else:
397
502
        try:
398
503
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
399
504
        except pybaz.errors.NamespaceError, e:
400
505
            raise NotArchRevision(revision_id)
401
 
            
402
 
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
 
506
 
 
507
 
 
508
def create_shared_repository(output_dir):
 
509
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
510
    bd.create_repository(shared=True)
 
511
 
 
512
def create_branch(output_dir):
 
513
    os.mkdir(output_dir)
 
514
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
515
    return bd.create_branch()
 
516
 
 
517
 
 
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)
 
522
 
 
523
 
 
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)
 
530
    return wt
 
531
 
 
532
 
 
533
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
403
534
                        verbose=False, dry_run=False, max_count=None,
404
 
                        skip_symlinks=False):
 
535
                        standalone=False):
405
536
    revdir = None
406
537
 
407
538
    # Uncomment this for testing, it basically just has baz2bzr only update
419
550
    #        print '\t%s' % a
420
551
 
421
552
    previous_version=None
 
553
    missing_ancestor = None
 
554
    if dry_run:
 
555
        dry_output_dir = os.path.join(tempdir, 'od')
 
556
        if os.path.exists(output_dir):
 
557
            shutil.copytree(output_dir, dry_output_dir)
 
558
        output_dir = dry_output_dir
 
559
 
 
560
    if os.path.exists(output_dir):
 
561
        target_branch = Branch.open(output_dir)
 
562
    else:
 
563
        if standalone:
 
564
            wt = BzrDir.create_standalone_workingtree(output_dir)
 
565
            target_branch = wt.branch
 
566
        else:
 
567
            target_branch = create_branch(output_dir)
422
568
 
423
569
    for i in range(len(ancestors)):
424
570
        revision = ancestors[i]
 
571
        rev_id = revision_id(revision)
 
572
        direct_merges = []
425
573
        if verbose:
426
574
            version = str(revision.version)
427
575
            if version != previous_version:
431
579
            previous_version = version
432
580
        else:
433
581
            yield Progress("revisions", i, len(ancestors))
 
582
 
 
583
        if target_branch.repository.has_revision(rev_id):
 
584
            target_branch.append_revision(rev_id)
 
585
            continue
434
586
        if revdir is None:
435
587
            revdir = os.path.join(tempdir, "rd")
436
 
            baz_inv, log = get_revision(revdir, revision, 
437
 
                                        skip_symlinks=skip_symlinks)
438
 
            if os.path.exists(output_dir):
439
 
                bzr_dir = os.path.join(output_dir, '.bzr')
440
 
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
441
 
                # This would be much faster with a simple os.rename(), but if
442
 
                # we fail, we have corrupted the original .bzr directory.  Is
443
 
                # that a big problem, as we can just back out the last
444
 
                # revisions in .bzr/revision_history I don't really know
445
 
                shutil.copytree(bzr_dir, new_bzr_dir)
446
 
                # Now revdir should have a tree with the latest .bzr, and the
447
 
                # next revision of the baz tree
448
 
                branch = find_branch(revdir)
449
 
            else:
450
 
                branch = Branch.initialize(revdir)
 
588
            try:
 
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:
 
592
                    raise
 
593
                missing_ancestor = revision
 
594
                revdir = None
 
595
                print ("unable to access ancestor %s, making into a merge."
 
596
                       % missing_ancestor)
 
597
                continue
 
598
            target_tree = create_checkout_metadata(target_branch, revdir)
 
599
            branch = target_tree.branch
451
600
        else:
452
601
            old = os.path.join(revdir, ".bzr")
453
602
            new = os.path.join(tempdir, ".bzr")
454
603
            os.rename(old, new)
455
 
            baz_inv, log = apply_revision(revdir, revision, 
456
 
                                          skip_symlinks=skip_symlinks)
 
604
            baz_inv, log = apply_revision(tree, revision)
457
605
            os.rename(new, old)
458
 
            branch = find_branch(revdir)
459
 
        timestamp = email.Utils.mktime_tz(log.date + (0,))
460
 
        rev_id = revision_id(revision)
 
606
            target_tree = WorkingTree.open(revdir)
 
607
            branch = target_tree.branch
 
608
        # cached so we can delete the log
 
609
        log_date = log.date
 
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)
 
615
 
 
616
        timestamp = email.Utils.mktime_tz(log_date + (0,))
 
617
        if log_summary is None:
 
618
            log_summary = ""
 
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))
 
622
        else:
 
623
            log_message = log_summary
 
624
        target_tree.lock_write()
461
625
        branch.lock_write()
462
626
        try:
463
 
            branch.working_tree().set_inventory(baz_inv)
464
 
            bzrlib.trace.silent = True
465
 
            wt = branch.working_tree()
466
 
            wt.commit(log.summary, verbose=False, committer=log.creator,
467
 
                      timestamp=timestamp, timezone=0, rev_id=rev_id)
 
627
            if missing_ancestor:
 
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,
 
639
                             revprops={})
468
640
        finally:
469
 
            bzrlib.trace.silent = False   
 
641
            target_tree.unlock()
470
642
            branch.unlock()
471
643
    yield Progress("revisions", len(ancestors), len(ancestors))
472
 
    unlink_unversioned(branch, revdir)
473
 
 
474
 
def unlink_unversioned(branch, revdir):
475
 
    for unversioned in branch.working_tree().extras():
476
 
        path = os.path.join(revdir, unversioned)
 
644
 
 
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)
 
658
    return direct
 
659
 
 
660
def unlink_unversioned(wt):
 
661
    for unversioned in wt.extras():
 
662
        path = wt.abspath(unversioned)
477
663
        if os.path.isdir(path):
478
664
            shutil.rmtree(path)
479
665
        else:
480
666
            os.unlink(path)
481
667
 
482
668
def get_log(tree, revision):
483
 
    log = tree.iter_logs(version=revision.version, reverse=True).next()
 
669
    log = pybaz.Patchlog(revision, tree=tree)
484
670
    assert str(log.revision) == str(revision), (log.revision, revision)
485
671
    return log
486
672
 
487
 
def get_revision(revdir, revision, skip_symlinks=False):
488
 
    revision.get(revdir)
489
 
    tree = pybaz.tree_root(revdir)
 
673
def get_revision(revdir, revision):
 
674
    tree = revision.get(revdir)
490
675
    log = get_log(tree, revision)
491
676
    try:
492
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
 
677
        return tree, bzr_inventory_data(tree), log 
493
678
    except BadFileKind, e:
494
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
495
 
 
496
 
 
497
 
def apply_revision(revdir, revision, skip_symlinks=False):
498
 
    tree = pybaz.tree_root(revdir)
 
679
        raise UserError("Cannot convert %s because %s is a %s" % 
 
680
                        (revision,e.path, e.kind))
 
681
 
 
682
 
 
683
def apply_revision(tree, revision):
499
684
    revision.apply(tree)
500
685
    log = get_log(tree, revision)
501
686
    try:
502
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
 
687
        return bzr_inventory_data(tree), log
503
688
    except BadFileKind, e:
504
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
505
 
 
506
 
 
 
689
        raise UserError("Cannot convert %s because %s is a %s" % 
 
690
                        (revision,e.path, e.kind))
507
691
 
508
692
 
509
693
class BadFileKind(Exception):
515
699
        Exception.__init__(self, "File %s is of forbidden type %s" %
516
700
                           (os.path.join(tree_root, path), kind))
517
701
 
518
 
def bzr_inventory_data(tree, skip_symlinks=False):
 
702
 
 
703
def bzr_inventory_data(tree):
519
704
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
520
705
    inv_map = {}
521
706
    for arch_id, path in inv_iter:
522
 
        bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
 
707
        bzr_file_id = map_file_id(arch_id)
523
708
        inv_map[path] = bzr_file_id 
524
709
 
525
710
    bzr_inv = []
526
711
    for path, file_id in inv_map.iteritems():
527
712
        full_path = os.path.join(tree, path)
528
713
        kind = bzrlib.osutils.file_kind(full_path)
529
 
        if skip_symlinks and kind == "symlink":
530
 
            continue
531
 
        if kind not in ("file", "directory"):
 
714
        if kind not in ("file", "directory", "symlink"):
532
715
            raise BadFileKind(tree, path, kind)
533
716
        parent_dir = os.path.dirname(path)
534
717
        if parent_dir != "":
539
722
    bzr_inv.sort()
540
723
    return bzr_inv
541
724
 
542
 
class NotInABranch(Exception):
543
 
    def __init__(self, path):
544
 
        Exception.__init__(self, "%s is not in a branch." % path)
545
 
        self.path = path
546
 
 
547
 
 
548
 
def find_branch(path):
549
 
    """
550
 
    >>> find_branch('/')
551
 
    Traceback (most recent call last):
552
 
    NotInABranch: / is not in a branch.
553
 
    >>> sb = bzrlib.ScratchBranch()
554
 
    >>> isinstance(find_branch(sb.base), Branch)
555
 
    True
556
 
    """
557
 
    try:
558
 
        return Branch.open(path)
559
 
    except NotBranchError, e:
560
 
        raise NotInABranch(path)
561
 
 
562
 
class cmd_baz_import(Command):
 
725
_global_option('max-count', type = int)
 
726
class cmd_baz_import_branch(Command):
563
727
    """Import an Arch or Baz branch into a bzr branch"""
564
 
    takes_args = ['to_location', 'from_branch?']
565
 
    takes_options = ['verbose']
566
 
 
567
 
    def run(self, to_location, from_branch=None, skip_symlinks=False, 
568
 
            fast=False, max_count=None, verbose=False, dry_run=False):
 
728
    takes_args = ['to_location', 'from_branch?', 'reuse_history*']
 
729
    takes_options = ['verbose', 'max-count']
 
730
 
 
731
    def printer(self, name):
 
732
        print name
 
733
 
 
734
    def run(self, to_location, from_branch=None, fast=False, max_count=None,
 
735
            verbose=False, dry_run=False, reuse_history_list=[]):
569
736
        to_location = os.path.realpath(str(to_location))
570
737
        if from_branch is not None:
571
738
            try:
573
740
            except pybaz.errors.NamespaceError:
574
741
                print "%s is not a valid Arch branch." % from_branch
575
742
                return 1
576
 
        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, 
 
746
                       max_count=max_count, 
 
747
                       reuse_history_from=reuse_history_list)
 
748
 
 
749
 
 
750
class NotInABranch(Exception):
 
751
    def __init__(self, path):
 
752
        Exception.__init__(self, "%s is not in a branch." % path)
 
753
        self.path = path
 
754
 
 
755
 
 
756
class cmd_baz_import(Command):
 
757
    """Import an Arch or Baz archive into bzr branches.
 
758
 
 
759
    This command should be used on local archives (or mirrors) only.  It is
 
760
    quite slow on remote archives.
 
761
    
 
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.
 
767
 
 
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
 
773
    """
 
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")]
 
777
 
 
778
    def printer(self, name):
 
779
        print name
 
780
 
 
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):
 
787
            os.mkdir(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)
 
792
 
 
793
 
 
794
def import_archive(to_root, from_archive, verbose, printer,
 
795
                   reuse_history_from=[], standalone=False,
 
796
                   prefixes=None):
 
797
    def selected(version):
 
798
        if prefixes is None:
 
799
            return True
 
800
        else:
 
801
            for prefix in prefixes:
 
802
                if version.nonarch.startswith(prefix):
 
803
                    return True
 
804
            return False
 
805
    real_to = os.path.realpath(to_root)
 
806
    history_locations = [real_to] + reuse_history_from
 
807
    if standalone is False:
 
808
        try:
 
809
            bd = BzrDir.open(to_root)
 
810
            bd.find_repository()
 
811
        except NotBranchError:
 
812
            create_shared_repository(to_root)
 
813
        except NoRepositoryPresent:
 
814
            raise BzrCommandError("Can't create repository at existing branch.")
 
815
    for version in pybaz.Archive(str(from_archive)).iter_versions():
 
816
        if not selected(version):
 
817
            print "Skipping %s" % version
 
818
            continue
 
819
        target = os.path.join(to_root, map_namespace(version))
 
820
        printer("importing %s into %s" % (version, target))
 
821
        if not os.path.exists(os.path.dirname(target)):
 
822
            os.makedirs(os.path.dirname(target))
 
823
        try:
 
824
            import_version(target, version, printer,
 
825
                           reuse_history_from=reuse_history_from, 
 
826
                           standalone=standalone)
 
827
        except pybaz.errors.ExecProblem,e:
 
828
            if str(e).find('The requested revision cannot be built.') != -1:
 
829
                printer("Skipping version %s as it cannot be built due"
 
830
                        " to a missing parent archive." % version)
 
831
            else:
 
832
                raise
 
833
        except UserError, e:
 
834
            if str(e).find('already exists, and the last revision ') != -1:
 
835
                printer("Skipping version %s as it has had commits made"
 
836
                        " since it was converted to bzr." % version)
 
837
            else:
 
838
                raise
 
839
 
 
840
 
 
841
def map_namespace(a_version):
 
842
    a_version = pybaz.Version("%s" % a_version)
 
843
    parser = NameParser(a_version)
 
844
    version = parser.get_version()
 
845
    branch = parser.get_branch()
 
846
    category = parser.get_category()
 
847
    if branch is None or branch == '':
 
848
        branch = "+trunk"
 
849
    if version == '0':
 
850
        return "%s/%s" % (category, branch)
 
851
    return "%s/%s/%s" % (category, version, branch)
 
852
 
 
853
def map_file_id(file_id):
 
854
    """Convert a baz file id to a bzr one."""
 
855
    return file_id.replace('%', '%25').replace('/', '%2f')