~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2008-03-01 04:27:02 UTC
  • Revision ID: aaron@aaronbentley.com-20080301042702-h3foyp8n9k68y3pv
Add link-tree command

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
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
16
 
17
 
from bzrlib.errors import NotBranchError
18
 
from bzrlib.branch import Branch, find_branch
 
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
                          )
 
28
from bzrlib.branch import Branch
 
29
from bzrlib.commit import Commit, NullCommitReporter
19
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, is_null
 
34
import bzrlib.ui
 
35
import bzrlib.ui.text
 
36
from bzrlib.workingtree import WorkingTree
20
37
from errors import NoPyBaz
21
38
try:
22
39
    import pybaz
23
40
    import pybaz.errors
 
41
    from pybaz import NameParser as NameParser
24
42
    from pybaz.backends.baz import null_cmd
25
43
except ImportError:
26
44
    raise NoPyBaz
 
45
from baz_helper import iter_new_merges, direct_merges
27
46
import tempfile
28
47
import os
29
48
import os.path
30
49
import shutil
31
50
import bzrlib
32
 
from bzrlib.errors import BzrError
33
51
import bzrlib.trace
34
52
import bzrlib.merge
35
53
import bzrlib.inventory
38
56
import email.Utils
39
57
from progress import *
40
58
 
 
59
 
 
60
BAZ_IMPORT_ROOT = 'TREE_ROOT'
 
61
 
 
62
 
 
63
class ImportCommitReporter(NullCommitReporter):
 
64
 
 
65
    def escaped(self, escape_count, message):
 
66
        bzrlib.trace.warning("replaced %d control characters in message" %
 
67
                             escape_count)
 
68
 
41
69
def add_id(files, id=None):
42
70
    """Adds an explicit id to a list of files.
43
71
 
54
82
 
55
83
saved_dir = None
56
84
 
 
85
def make_archive(name, location):
 
86
    pb_location = pybaz.ArchiveLocation(location)
 
87
    pb_location.create_master(pybaz.Archive(name),
 
88
                              pybaz.ArchiveLocationParams())
 
89
 
57
90
def test_environ():
58
91
    """
59
92
    >>> q = test_environ()
71
104
    os.environ["HOME"] = os.path.join(tdir, "home")
72
105
    os.mkdir(os.environ["HOME"])
73
106
    arch_dir = os.path.join(tdir, "archive_dir")
74
 
    pybaz.make_archive("test@example.com", arch_dir)
 
107
    make_archive("test@example.com", arch_dir)
75
108
    work_dir = os.path.join(tdir, "work_dir")
76
109
    os.mkdir(work_dir)
77
110
    os.chdir(work_dir)
202
235
    """
203
236
    try:
204
237
        revision = version.iter_revisions(reverse=True).next()
 
238
    except StopIteration:
 
239
        return ()
205
240
    except:
206
241
        print version
207
242
        if not version.exists():
213
248
    return ancestors
214
249
 
215
250
def get_last_revision(branch):
216
 
    last_patch = branch.last_patch()
 
251
    last_patch = branch.last_revision()
217
252
    try:
218
253
        return arch_revision(last_patch)
219
254
    except NotArchRevision:
220
255
        raise UserError(
221
256
            "Directory \"%s\" already exists, and the last revision is not"
222
 
            " an Arch revision (%s)" % (output_dir, last_patch))
223
 
 
224
 
 
225
 
def get_remaining_revisions(output_dir, version):
 
257
            " an Arch revision (%s)" % (branch.base, last_patch))
 
258
 
 
259
def do_branch(br_from, to_location, revision_id):
 
260
    """Derived from branch in builtins."""
 
261
    br_from.lock_read()
 
262
    try:
 
263
        try:
 
264
            os.mkdir(to_location)
 
265
        except OSError, e:
 
266
            if e.errno == errno.EEXIST:
 
267
                raise UserError('Target directory "%s" already'
 
268
                                      ' exists.' % to_location)
 
269
            if e.errno == errno.ENOENT:
 
270
                raise UserError('Parent of "%s" does not exist.' %
 
271
                                      to_location)
 
272
            else:
 
273
                raise
 
274
        try:
 
275
            br_from.bzrdir.clone(to_location, revision_id)
 
276
        except NoSuchRevision:
 
277
            rmtree(to_location)
 
278
            msg = "The branch %s has no revision %s." % (from_location,
 
279
                                                         revision_id)
 
280
            raise UserError(msg)
 
281
    finally:
 
282
        br_from.unlock()
 
283
 
 
284
def get_remaining_revisions(output_dir, version, encoding,
 
285
                            reuse_history_from=[]):
226
286
    last_patch = None
227
287
    old_revno = None
228
 
    if os.path.exists(output_dir):
 
288
    output_exists = os.path.exists(output_dir)
 
289
    if output_exists:
229
290
        # We are starting from an existing directory, figure out what
230
291
        # the current version is
231
 
        branch = find_branch(output_dir, find_root=False)
232
 
        last_patch = get_last_revision(branch)
233
 
        if version is None:
 
292
        branch = Branch.open(output_dir)
 
293
        last_patch, last_encoding = get_last_revision(branch)
 
294
        assert encoding == last_encoding
 
295
        if last_patch is None:
 
296
            if not is_null(branch.last_revision()):
 
297
                raise NotPreviousImport(branch.base)
 
298
        elif version is None:
234
299
            version = last_patch.version
235
300
    elif version is None:
236
301
        raise UserError("No version specified, and directory does not exist.")
237
302
 
238
303
    try:
239
304
        ancestors = version_ancestry(version)
 
305
        if not output_exists and reuse_history_from != []:
 
306
            for ancestor in reversed(ancestors):
 
307
                if last_patch is not None:
 
308
                    # found something to copy
 
309
                    break
 
310
                # try to grab a copy of ancestor
 
311
                # note that is not optimised: we could look for namespace
 
312
                # transitions and only look for the past after the
 
313
                # transition.
 
314
                for history_root in reuse_history_from:
 
315
                    possible_source = os.path.join(history_root,
 
316
                        map_namespace(ancestor.version))
 
317
                    try:
 
318
                        source = Branch.open(possible_source)
 
319
                        rev_id = revision_id(ancestor, encoding)
 
320
                        if rev_id in source.revision_history():
 
321
                            do_branch(source, output_dir, rev_id)
 
322
                            last_patch = ancestor
 
323
                            break
 
324
                    except NotBranchError:
 
325
                        pass
240
326
    except NoSuchVersion, e:
241
 
        raise UserError(e)
 
327
        raise UserError(str(e))
242
328
 
243
329
    if last_patch:
244
330
        for i in range(len(ancestors)):
246
332
                break
247
333
        else:
248
334
            raise UserError("Directory \"%s\" already exists, and the last "
249
 
                "revision (%s) is not in the ancestry of %s" % 
 
335
                "revision (%s) is not in the ancestry of %s" %
250
336
                (output_dir, last_patch, version))
251
337
        # Strip off all of the ancestors which are already present
252
338
        # And get a directory starting with the latest ancestor
253
339
        latest_ancestor = ancestors[i]
254
 
        old_revno = find_branch(output_dir, find_root=False).revno()
 
340
        old_revno = Branch.open(output_dir).revno()
255
341
        ancestors = ancestors[i+1:]
256
342
    return ancestors, old_revno
257
343
 
258
 
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
259
 
                   dry_run=False, max_count=None):
 
344
 
 
345
###class Importer(object):
 
346
###    """An importer.
 
347
###
 
348
###    Currently this is used as a parameter object, though more behaviour is
 
349
###    possible later.
 
350
###    """
 
351
###
 
352
###    def __init__(self, output_dir, version, fast=False,
 
353
###                 verbose=False, dry_run=False, max_count=None,
 
354
###                   reuse_history_from=[]):
 
355
###        self.output_dir = output_dir
 
356
###        self.version = version
 
357
###        self.
 
358
 
 
359
 
 
360
def import_version(output_dir, version, encoding, fast=False,
 
361
                   verbose=False, dry_run=False, max_count=None,
 
362
                   reuse_history_from=[], standalone=True):
260
363
    """
261
364
    >>> q = test_environ()
 
365
 
 
366
    Progress bars output to stderr, but doctest does not capture that.
 
367
 
 
368
    >>> old_stderr = sys.stderr
 
369
    >>> sys.stderr = sys.stdout
 
370
 
262
371
    >>> result_path = os.path.join(q, "result")
263
372
    >>> commit_test_revisions()
264
373
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
265
 
    >>> import_version('/', version, fancy=False, dry_run=True)
 
374
    >>> old_ui = bzrlib.ui.ui_factory
 
375
    >>> bzrlib.ui.ui_factory = bzrlib.ui.text.TextUIFactory(
 
376
    ...     bar_type=bzrlib.progress.DotsProgressBar)
 
377
 
 
378
    >>> import_version('/', version, None, dry_run=True)
266
379
    Traceback (most recent call last):
267
 
    UserError: / exists, but is not a bzr branch.
268
 
    >>> import_version(result_path, version, fancy=False, dry_run=True)
 
380
    NotPreviousImport: / is not the location of a previous import.
 
381
    >>> import_version(result_path, version, None, dry_run=True)
269
382
    Traceback (most recent call last):
270
383
    UserError: The version test@example.com/test--test--0.1 does not exist.
271
384
    >>> version = pybaz.Version("test@example.com/test--test--0")
272
 
    >>> import_version(result_path, version, fancy=False, dry_run=True)
273
 
    not fancy
274
 
    ....
 
385
    >>> import_version(result_path, version, None, dry_run=True) #doctest: +ELLIPSIS
 
386
    importing test@example.com/test--test--0 into ...
 
387
    ...
 
388
    revisions: ..........................................
275
389
    Dry run, not modifying output_dir
276
390
    Cleaning up
277
 
    >>> import_version(result_path, version, fancy=False)
278
 
    not fancy
279
 
    ....
 
391
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
 
392
    importing test@example.com/test--test--0 into ...
 
393
    ...
 
394
    revisions: .....................................................................
280
395
    Cleaning up
281
396
    Import complete.
282
 
    >>> import_version(result_path, version, fancy=False)
 
397
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
283
398
    Tree is up-to-date with test@example.com/test--test--0--patch-2
284
399
    >>> commit_more_test_revisions()
285
 
    >>> import_version(result_path, version, fancy=False)
286
 
    not fancy
287
 
    ..
 
400
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
 
401
    importing test@example.com/test--test--0 into ...
 
402
    revisions: ....................................................
288
403
    Cleaning up
289
404
    Import complete.
 
405
    >>> bzrlib.ui.ui_factory = old_ui
 
406
    >>> sys.stderr = old_stderr
290
407
    >>> teardown_environ(q)
291
408
    """
292
 
    try:
293
 
        ancestors, old_revno = get_remaining_revisions(output_dir, version)
294
 
    except NotBranchError, e:
295
 
        raise UserError("%s exists, but is not a bzr branch." % output_dir)
296
 
    if len(ancestors) == 0:
297
 
        last_revision = get_last_revision(find_branch(output_dir, find_root=False))
298
 
        print 'Tree is up-to-date with %s' % last_revision
299
 
        return
300
 
 
301
 
    progress_bar = ProgressBar()
302
 
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
303
 
                               dir=os.path.dirname(output_dir))
304
 
    try:
305
 
        if not fancy:
306
 
            print "not fancy"
 
409
    progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
 
410
    try:
 
411
        try:
 
412
            ancestors, old_revno = get_remaining_revisions(output_dir, version,
 
413
                                                           encoding,
 
414
                                                           reuse_history_from)
 
415
        except NotBranchError, e:
 
416
            raise NotPreviousImport(e.path)
 
417
        if old_revno is None and len(ancestors) == 0:
 
418
            progress_bar.note('Version %s has no revisions.' % version)
 
419
            return
 
420
        if len(ancestors) == 0:
 
421
            last_revision, last_encoding = \
 
422
                get_last_revision(Branch.open(output_dir))
 
423
            progress_bar.note('Tree is up-to-date with %s' % last_revision)
 
424
            return
 
425
 
 
426
        progress_bar.note("importing %s into %s" % (version, output_dir))
 
427
 
 
428
        tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
 
429
                                   dir=os.path.dirname(output_dir))
 
430
        try:
 
431
            wt = WorkingTree.open(output_dir)
 
432
        except (NotBranchError, NoWorkingTree):
 
433
            wt = None
307
434
        try:
308
435
            for result in iter_import_version(output_dir, ancestors, tempdir,
309
 
                    fast=fast, verbose=verbose, dry_run=dry_run, 
310
 
                    max_count=max_count):
311
 
                if fancy:
312
 
                    show_progress(progress_bar, result)
313
 
                else:
314
 
                    sys.stdout.write('.')
 
436
                    pb=progress_bar, encoding=encoding, fast=fast,
 
437
                    verbose=verbose, dry_run=dry_run, max_count=max_count,
 
438
                    standalone=standalone):
 
439
                show_progress(progress_bar, result)
 
440
            if dry_run:
 
441
                progress_bar.note('Dry run, not modifying output_dir')
 
442
                return
 
443
 
 
444
            # Update the working tree of the branch
 
445
            try:
 
446
                wt = WorkingTree.open(output_dir)
 
447
            except NoWorkingTree:
 
448
                wt = None
 
449
            if wt is not None:
 
450
                wt.set_last_revision(wt.branch.last_revision())
 
451
                wt.set_root_id(BAZ_IMPORT_ROOT)
 
452
                wt.revert()
 
453
 
315
454
        finally:
316
 
            if fancy:
317
 
                progress_bar.clear()
318
 
            else:
319
 
                sys.stdout.write('\n')
320
 
 
321
 
        if dry_run:
322
 
            print 'Dry run, not modifying output_dir'
323
 
            return
324
 
        if os.path.exists(output_dir):
325
 
            # Move the bzr control directory back, and update the working tree
326
 
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
327
 
            
328
 
            bzr_dir = os.path.join(output_dir, '.bzr')
329
 
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
330
 
 
331
 
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
332
 
            os.rename(new_bzr_dir, bzr_dir)
333
 
            try:
334
 
                bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno), 
335
 
                                   check_clean=False, this_dir=output_dir, 
336
 
                                   ignore_zero=True)
337
 
            except:
338
 
                # If something failed, move back the original bzr directory
339
 
                os.rename(bzr_dir, new_bzr_dir)
340
 
                os.rename(tmp_bzr_dir, bzr_dir)
341
 
                raise
342
 
        else:
343
 
            revdir = os.path.join(tempdir, "rd")
344
 
            os.rename(revdir, output_dir)
345
 
 
 
455
 
 
456
            progress_bar.note('Cleaning up')
 
457
            shutil.rmtree(tempdir)
 
458
        progress_bar.note("Import complete.")
346
459
    finally:
347
 
        print 'Cleaning up'
348
 
        shutil.rmtree(tempdir)
349
 
    print "Import complete."
350
 
            
351
 
class UserError(Exception):
 
460
        progress_bar.finished()
 
461
 
 
462
class UserError(BzrCommandError):
352
463
    def __init__(self, message):
353
464
        """Exception to throw when a user makes an impossible request
354
465
        :param message: The message to emit when printing this exception
355
466
        :type message: string
356
467
        """
357
 
        Exception.__init__(self, message)
358
 
 
359
 
def revision_id(arch_revision):
 
468
        BzrCommandError.__init__(self, message)
 
469
 
 
470
class NotPreviousImport(UserError):
 
471
    def __init__(self, path):
 
472
        UserError.__init__(self, "%s is not the location of a previous import."
 
473
                           % path)
 
474
 
 
475
 
 
476
def revision_id(arch_revision, encoding):
360
477
    """
361
478
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
362
479
    designates a revision imported with an experimental algorithm.  A number
364
481
 
365
482
    :param arch_revision: The Arch revision to generate an ID for.
366
483
 
367
 
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
 
484
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"), None)
368
485
    'Arch-1:you@example.com%cat--br--0--base-0'
 
486
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"), 'utf-8')
 
487
    'Arch-1-utf-8:you@example.com%cat--br--0--base-0'
369
488
    """
370
 
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
 
489
    if encoding is None:
 
490
        encoding = ''
 
491
    else:
 
492
        encoding = '-' + encoding
 
493
    return "Arch-1%s:%s" % (encoding, str(arch_revision).replace('/', '%'))
371
494
 
372
495
class NotArchRevision(Exception):
373
496
    def __init__(self, revision_id):
383
506
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
384
507
    Traceback (most recent call last):
385
508
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
386
 
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
387
 
    'jrandom@example.com/test--test--0--patch-5'
 
509
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5")[0])
 
510
    'jrandom@example.com/test--test--0--patch-5'
 
511
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5")[0])
 
512
    'jrandom@example.com/test--test--0--patch-5'
 
513
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5")[1])
 
514
    'None'
 
515
    >>> str(arch_revision("Arch-1-utf-8:jrandom@example.com%test--test--0--patch-5")[1])
 
516
    'utf-8'
 
517
    >>> str(arch_revision('null:'))
 
518
    '(None, None)'
388
519
    """
389
 
    if revision_id is None:
390
 
        return None
391
 
    if revision_id[:7] != 'Arch-1:':
 
520
    if is_null(revision_id):
 
521
        return None, None
 
522
    if revision_id[:7] not in ('Arch-1:', 'Arch-1-'):
392
523
        raise NotArchRevision(revision_id)
393
524
    else:
394
525
        try:
395
 
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
 
526
            encoding, arch_name = revision_id[6:].split(':', 1)
 
527
            arch_name = arch_name.replace('%', '/')
 
528
            if encoding == '':
 
529
                encoding = None
 
530
            else:
 
531
                encoding = encoding[1:]
 
532
            return pybaz.Revision(arch_name), encoding
396
533
        except pybaz.errors.NamespaceError, e:
397
534
            raise NotArchRevision(revision_id)
398
 
            
399
 
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
400
 
                        verbose=False, dry_run=False, max_count=None):
 
535
 
 
536
 
 
537
def create_shared_repository(output_dir):
 
538
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
539
    bd.create_repository(shared=True)
 
540
 
 
541
def create_branch(output_dir):
 
542
    os.mkdir(output_dir)
 
543
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
544
    return bd.create_branch()
 
545
 
 
546
 
 
547
def create_checkout(source, to_location, revision_id=None):
 
548
    checkout = bzrdir.BzrDirMetaFormat1().initialize(to_location)
 
549
    bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
 
550
    return checkout.create_workingtree(revision_id)
 
551
 
 
552
 
 
553
def create_checkout_metadata(source, to_location, revision_id=None):
 
554
    if revision_id is None:
 
555
        revision_id = source.last_revision()
 
556
    wt = create_checkout(source, to_location, NULL_REVISION)
 
557
    wt.lock_write()
 
558
    try:
 
559
        wt.set_last_revision(revision_id)
 
560
        wt.flush()
 
561
        if revision_id not in (NULL_REVISION, None):
 
562
            basis = wt.basis_tree()
 
563
            basis.lock_read()
 
564
            try:
 
565
                wt._write_inventory(basis.inventory)
 
566
            finally:
 
567
                basis.unlock()
 
568
    finally:
 
569
        wt.unlock()
 
570
    return wt
 
571
 
 
572
 
 
573
def iter_import_version(output_dir, ancestors, tempdir, pb, encoding,
 
574
                        fast=False, verbose=False, dry_run=False,
 
575
                        max_count=None, standalone=False):
401
576
    revdir = None
 
577
    log_encoding = 'ascii'
 
578
    if encoding is not None:
 
579
        log_encoding = encoding
402
580
 
403
581
    # Uncomment this for testing, it basically just has baz2bzr only update
404
582
    # 5 patches at a time
415
593
    #        print '\t%s' % a
416
594
 
417
595
    previous_version=None
418
 
 
419
 
    for i in range(len(ancestors)):
420
 
        revision = ancestors[i]
 
596
    missing_ancestor = None
 
597
    if dry_run:
 
598
        dry_output_dir = os.path.join(tempdir, 'od')
 
599
        if os.path.exists(output_dir):
 
600
            shutil.copytree(output_dir, dry_output_dir)
 
601
        output_dir = dry_output_dir
 
602
 
 
603
    if os.path.exists(output_dir):
 
604
        target_branch = Branch.open(output_dir)
 
605
    else:
 
606
        if standalone:
 
607
            wt = BzrDir.create_standalone_workingtree(output_dir)
 
608
            target_branch = wt.branch
 
609
        else:
 
610
            target_branch = create_branch(output_dir)
 
611
 
 
612
    for i, revision in enumerate(ancestors):
 
613
        rev_id = revision_id(revision, encoding)
421
614
        direct_merges = []
422
615
        if verbose:
423
616
            version = str(revision.version)
424
617
            if version != previous_version:
425
 
                clear_progress_bar()
426
 
                print '\rOn version: %s' % version
 
618
                pb.note('On version: %s' % version)
427
619
            yield Progress(str(revision.patchlevel), i, len(ancestors))
428
620
            previous_version = version
429
621
        else:
430
622
            yield Progress("revisions", i, len(ancestors))
 
623
 
 
624
        if target_branch.repository.has_revision(rev_id):
 
625
            target_branch.set_last_revision_info(i+1, rev_id)
 
626
            continue
431
627
        if revdir is None:
432
628
            revdir = os.path.join(tempdir, "rd")
433
 
            baz_inv, log = get_revision(revdir, revision)
434
 
            # cached so we can delete the log
435
 
            log_date = log.date
436
 
            log_summary = log.summary
437
 
            log_creator = log.creator
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, find_root=False)
449
 
            else:
450
 
                branch = Branch(revdir, init=True)
 
629
            try:
 
630
                tree, baz_inv, log = get_revision(revdir, revision)
 
631
            except pybaz.errors.ExecProblem, e:
 
632
                if ("%s" % e.args).find('could not connect') == -1:
 
633
                    raise
 
634
                missing_ancestor = revision
 
635
                revdir = None
 
636
                pb.note("unable to access ancestor %s, making into a merge."
 
637
                       % missing_ancestor)
 
638
                continue
 
639
            target_tree = create_checkout_metadata(target_branch, revdir)
 
640
            branch = target_tree.branch
451
641
        else:
452
642
            old = os.path.join(revdir, ".bzr")
453
643
            new = os.path.join(tempdir, ".bzr")
454
644
            os.rename(old, new)
455
 
            baz_inv, log = apply_revision(revdir, revision)
456
 
            log_date = log.date
457
 
            log_summary = log.summary
458
 
            log_creator = log.creator
459
 
            direct_merges = get_direct_merges(revdir, revision)
 
645
            baz_inv, log = apply_revision(tree, revision)
460
646
            os.rename(new, old)
461
 
            branch = find_branch(revdir, find_root=False)
 
647
            target_tree = WorkingTree.open(revdir)
 
648
            branch = target_tree.branch
 
649
        # cached so we can delete the log
 
650
        log_date = log.date
 
651
        log_summary = log.summary
 
652
        log_description = log.description
 
653
        is_continuation = log.continuation_of is not None
 
654
        log_creator = log.creator
 
655
        direct_merges = get_direct_merges(revdir, revision, log)
 
656
 
462
657
        timestamp = email.Utils.mktime_tz(log_date + (0,))
463
 
        rev_id = revision_id(revision)
 
658
        if log_summary is None:
 
659
            log_summary = ""
 
660
        # log_descriptions of None and "" are ignored.
 
661
        if not is_continuation and log_description:
 
662
            log_message = "\n".join((log_summary, log_description))
 
663
        else:
 
664
            log_message = log_summary
 
665
        target_tree.lock_write()
464
666
        branch.lock_write()
465
667
        try:
466
 
            for merge in direct_merges:
467
 
                branch.add_pending_merge(revision_id(merge.revision))
468
 
            branch.set_inventory(baz_inv)
469
 
            bzrlib.trace.silent = True
470
 
            branch.commit(log_summary, verbose=False, committer=log_creator,
471
 
                          timestamp=timestamp, timezone=0, rev_id=rev_id)
 
668
            if missing_ancestor:
 
669
                # if we want it to be in revision-history, do that here.
 
670
                target_tree.set_parent_ids(
 
671
                    [revision_id(missing_ancestor, encoding)],
 
672
                    allow_leftmost_as_ghost=True)
 
673
                missing_ancestor = None
 
674
            for merged_rev in direct_merges:
 
675
                target_tree.add_pending_merge(revision_id(merged_rev,
 
676
                                                          encoding))
 
677
            target_tree.set_root_id(BAZ_IMPORT_ROOT)
 
678
            target_tree.flush()
 
679
            target_tree.set_inventory(baz_inv)
 
680
            commitobj = Commit(reporter=ImportCommitReporter())
 
681
            commitobj.commit(working_tree=target_tree,
 
682
                message=log_message.decode(log_encoding, 'replace'),
 
683
                verbose=False, committer=log_creator, timestamp=timestamp,
 
684
                timezone=0, rev_id=rev_id, revprops={})
472
685
        finally:
473
 
            bzrlib.trace.silent = False   
 
686
            target_tree.unlock()
474
687
            branch.unlock()
475
688
    yield Progress("revisions", len(ancestors), len(ancestors))
476
 
    unlink_unversioned(branch, revdir)
477
689
 
478
 
def get_direct_merges(revdir, revision):
479
 
    from fai.cmdutil import pylon, direct_merges
480
 
    if pybaz.WorkingTree(revdir).tree_version != revision.version:
481
 
        pybaz.WorkingTree(revdir).set_tree_version(revision.version)
482
 
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir, 
483
 
        revision.category.nonarch, revision.branch.nonarch, 
 
690
def get_direct_merges(revdir, revision, log):
 
691
    continuation = log.continuation_of
 
692
    previous_version = revision.version
 
693
    if pybaz.WorkingTree(revdir).tree_version != previous_version:
 
694
        pybaz.WorkingTree(revdir).set_tree_version(previous_version)
 
695
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir,
 
696
        revision.category.nonarch, revision.branch.nonarch,
484
697
        revision.version.nonarch, revision.archive, revision.patchlevel)
485
 
    temp_path = "templog"
 
698
    temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
486
699
    os.rename(log_path, temp_path)
487
 
    merges = list(pylon.iter_new_merges(revdir, revision.version))
488
 
    direct = direct_merges (merges)
 
700
    merges = list(iter_new_merges(revdir, revision.version))
 
701
    direct = direct_merges(merges, [continuation])
489
702
    os.rename(temp_path, log_path)
490
703
    return direct
491
704
 
492
 
def unlink_unversioned(branch, revdir):
493
 
    for unversioned in branch.working_tree().extras():
494
 
        path = os.path.join(revdir, unversioned)
 
705
def unlink_unversioned(wt):
 
706
    for unversioned in wt.extras():
 
707
        path = wt.abspath(unversioned)
495
708
        if os.path.isdir(path):
496
709
            shutil.rmtree(path)
497
710
        else:
498
711
            os.unlink(path)
499
712
 
500
713
def get_log(tree, revision):
501
 
    log = tree.iter_logs(version=revision.version, reverse=True).next()
 
714
    log = pybaz.Patchlog(revision, tree=tree)
502
715
    assert str(log.revision) == str(revision), (log.revision, revision)
503
716
    return log
504
717
 
505
718
def get_revision(revdir, revision):
506
 
    revision.get(revdir)
507
 
    tree = pybaz.tree_root(revdir)
 
719
    tree = revision.get(revdir)
508
720
    log = get_log(tree, revision)
509
721
    try:
510
 
        return bzr_inventory_data(tree), log 
 
722
        return tree, bzr_inventory_data(tree), log
511
723
    except BadFileKind, e:
512
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
513
 
 
514
 
 
515
 
def apply_revision(revdir, revision):
516
 
    tree = pybaz.tree_root(revdir)
 
724
        raise UserError("Cannot convert %s because %s is a %s" %
 
725
                        (revision,e.path, e.kind))
 
726
 
 
727
 
 
728
def apply_revision(tree, revision):
517
729
    revision.apply(tree)
518
730
    log = get_log(tree, revision)
519
731
    try:
520
732
        return bzr_inventory_data(tree), log
521
733
    except BadFileKind, e:
522
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
523
 
 
524
 
 
 
734
        raise UserError("Cannot convert %s because %s is a %s" %
 
735
                        (revision,e.path, e.kind))
525
736
 
526
737
 
527
738
class BadFileKind(Exception):
533
744
        Exception.__init__(self, "File %s is of forbidden type %s" %
534
745
                           (os.path.join(tree_root, path), kind))
535
746
 
 
747
 
536
748
def bzr_inventory_data(tree):
537
749
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
538
750
    inv_map = {}
539
751
    for arch_id, path in inv_iter:
540
 
        bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
541
 
        inv_map[path] = bzr_file_id 
 
752
        bzr_file_id = map_file_id(arch_id)
 
753
        inv_map[path] = bzr_file_id
542
754
 
543
755
    bzr_inv = []
544
756
    for path, file_id in inv_map.iteritems():
556
768
    return bzr_inv
557
769
 
558
770
 
559
 
class cmd_baz_import(Command):
560
 
    """Import an Arch or Baz branch into a bzr branch"""
561
 
    takes_args = ['to_location', 'from_branch?']
562
 
    takes_options = ['verbose']
563
 
 
564
 
    def run(self, to_location, from_branch=None, fast=False, max_count=None,
565
 
            verbose=False, dry_run=False):
566
 
        to_location = os.path.realpath(str(to_location))
567
 
        if from_branch is not None:
 
771
def baz_import_branch(to_location, from_branch, fast, max_count, verbose,
 
772
                      encoding, dry_run, reuse_history_list):
 
773
    to_location = os.path.realpath(str(to_location))
 
774
    if from_branch is not None:
 
775
        try:
 
776
            from_branch = pybaz.Version(from_branch)
 
777
        except pybaz.errors.NamespaceError:
 
778
            print "%s is not a valid Arch branch." % from_branch
 
779
            return 1
 
780
    if reuse_history_list is None:
 
781
        reuse_history_list = []
 
782
    import_version(to_location, from_branch, encoding, max_count=max_count,
 
783
                   reuse_history_from=reuse_history_list)
 
784
 
 
785
 
 
786
class NotInABranch(Exception):
 
787
    def __init__(self, path):
 
788
        Exception.__init__(self, "%s is not in a branch." % path)
 
789
        self.path = path
 
790
 
 
791
 
 
792
 
 
793
def baz_import(to_root_dir, from_archive, encoding, verbose=False,
 
794
               reuse_history_list=[], prefixes=None):
 
795
    if reuse_history_list is None:
 
796
        reuse_history_list = []
 
797
    to_root = str(os.path.realpath(to_root_dir))
 
798
    if not os.path.exists(to_root):
 
799
        os.mkdir(to_root)
 
800
    if prefixes is not None:
 
801
        prefixes = prefixes.split(':')
 
802
    import_archive(to_root, from_archive, verbose, encoding,
 
803
                   reuse_history_list, prefixes=prefixes)
 
804
 
 
805
 
 
806
def import_archive(to_root, from_archive, verbose,
 
807
                   encoding, reuse_history_from=[], standalone=False,
 
808
                   prefixes=None):
 
809
    def selected(version):
 
810
        if prefixes is None:
 
811
            return True
 
812
        else:
 
813
            for prefix in prefixes:
 
814
                if version.nonarch.startswith(prefix):
 
815
                    return True
 
816
            return False
 
817
    real_to = os.path.realpath(to_root)
 
818
    history_locations = [real_to] + reuse_history_from
 
819
    if standalone is False:
 
820
        try:
 
821
            bd = BzrDir.open(to_root)
 
822
            bd.find_repository()
 
823
        except NotBranchError:
 
824
            create_shared_repository(to_root)
 
825
        except NoRepositoryPresent:
 
826
            raise BzrCommandError("Can't create repository at existing branch.")
 
827
    versions = list(pybaz.Archive(str(from_archive)).iter_versions())
 
828
    progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
 
829
    try:
 
830
        for num, version in enumerate(versions):
 
831
            progress_bar.update("Branch", num, len(versions))
 
832
            if not selected(version):
 
833
                print "Skipping %s" % version
 
834
                continue
 
835
            target = os.path.join(to_root, map_namespace(version))
 
836
            if not os.path.exists(os.path.dirname(target)):
 
837
                os.makedirs(os.path.dirname(target))
568
838
            try:
569
 
                from_branch = pybaz.Version(from_branch)
570
 
            except pybaz.errors.NamespaceError:
571
 
                print "%s is not a valid Arch branch." % from_branch
572
 
                return 1
573
 
        import_version(to_location, from_branch)
 
839
                import_version(target, version, encoding,
 
840
                               reuse_history_from=reuse_history_from,
 
841
                               standalone=standalone)
 
842
            except pybaz.errors.ExecProblem,e:
 
843
                if str(e).find('The requested revision cannot be built.') != -1:
 
844
                    progress_bar.note(
 
845
                        "Skipping version %s as it cannot be built due"
 
846
                        " to a missing parent archive." % version)
 
847
                else:
 
848
                    raise
 
849
            except UserError, e:
 
850
                if str(e).find('already exists, and the last revision ') != -1:
 
851
                    progress_bar.note(
 
852
                        "Skipping version %s as it has had commits made"
 
853
                        " since it was converted to bzr." % version)
 
854
                else:
 
855
                    raise
 
856
    finally:
 
857
        progress_bar.finished()
 
858
 
 
859
 
 
860
def map_namespace(a_version):
 
861
    a_version = pybaz.Version("%s" % a_version)
 
862
    parser = NameParser(a_version)
 
863
    version = parser.get_version()
 
864
    branch = parser.get_branch()
 
865
    category = parser.get_category()
 
866
    if branch is None or branch == '':
 
867
        branch = "+trunk"
 
868
    if version == '0':
 
869
        return "%s/%s" % (category, branch)
 
870
    return "%s/%s/%s" % (category, version, branch)
 
871
 
 
872
 
 
873
def map_file_id(file_id):
 
874
    """Convert a baz file id to a bzr one."""
 
875
    return file_id.replace('%', '%25').replace('/', '%2f')