~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-06-15 15:25:13 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050615152512-e2afe3f794604a12
Added Michael Ellerman's shelf/unshelf

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 by Aaron Bentley
 
1
# Copyright (C) 2005 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
 
                          )
28
 
from bzrlib.branch import Branch
29
 
from bzrlib.commit import Commit, NullCommitReporter
 
16
from bzrlib import Branch
30
17
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
37
 
from errors import NoPyBaz
38
18
try:
39
19
    import pybaz
40
20
    import pybaz.errors
41
 
    from pybaz import NameParser as NameParser
42
 
    from pybaz.backends.baz import null_cmd
43
21
except ImportError:
44
 
    raise NoPyBaz
45
 
from baz_helper 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
46
26
import tempfile
47
27
import os
48
28
import os.path
49
29
import shutil
50
30
import bzrlib
 
31
from bzrlib.errors import BzrError
51
32
import bzrlib.trace
52
33
import bzrlib.merge
53
 
import bzrlib.inventory
54
 
import bzrlib.osutils
55
34
import sys
56
35
import email.Utils
57
36
from progress import *
58
37
 
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
 
 
69
38
def add_id(files, id=None):
70
39
    """Adds an explicit id to a list of files.
71
40
 
80
49
    args.extend(files)
81
50
    return null_cmd(args)
82
51
 
83
 
saved_dir = None
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
 
 
90
52
def test_environ():
91
53
    """
92
54
    >>> q = test_environ()
98
60
    >>> os.path.exists(q)
99
61
    False
100
62
    """
101
 
    global saved_dir
102
 
    saved_dir = os.getcwdu()
103
63
    tdir = tempfile.mkdtemp(prefix="testdir-")
104
64
    os.environ["HOME"] = os.path.join(tdir, "home")
105
65
    os.mkdir(os.environ["HOME"])
106
66
    arch_dir = os.path.join(tdir, "archive_dir")
107
 
    make_archive("test@example.com", arch_dir)
 
67
    pybaz.make_archive("test@example.com", arch_dir)
108
68
    work_dir = os.path.join(tdir, "work_dir")
109
69
    os.mkdir(work_dir)
110
70
    os.chdir(work_dir)
143
103
    add_id([path], id)
144
104
 
145
105
def teardown_environ(tdir):
146
 
    os.chdir(saved_dir)
 
106
    os.chdir("/")
147
107
    shutil.rmtree(tdir)
148
108
 
149
109
def timport(tree, summary):
235
195
    """
236
196
    try:
237
197
        revision = version.iter_revisions(reverse=True).next()
238
 
    except StopIteration:
239
 
        return ()
240
198
    except:
241
 
        print version
242
199
        if not version.exists():
243
200
            raise NoSuchVersion(version)
244
201
        else:
248
205
    return ancestors
249
206
 
250
207
def get_last_revision(branch):
251
 
    last_patch = branch.last_revision()
 
208
    last_patch = branch.last_patch()
252
209
    try:
253
210
        return arch_revision(last_patch)
254
211
    except NotArchRevision:
255
212
        raise UserError(
256
213
            "Directory \"%s\" already exists, and the last revision is not"
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=[]):
 
214
            " an Arch revision (%s)" % (output_dir, last_patch))
 
215
 
 
216
 
 
217
def get_remaining_revisions(output_dir, version):
286
218
    last_patch = None
287
219
    old_revno = None
288
 
    output_exists = os.path.exists(output_dir)
289
 
    if output_exists:
 
220
    if os.path.exists(output_dir):
290
221
        # We are starting from an existing directory, figure out what
291
222
        # the current version is
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:
 
223
        branch = find_branch(output_dir)
 
224
        last_patch = get_last_revision(branch)
 
225
        if version is None:
299
226
            version = last_patch.version
300
227
    elif version is None:
301
228
        raise UserError("No version specified, and directory does not exist.")
302
229
 
303
230
    try:
304
231
        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
326
232
    except NoSuchVersion, e:
327
 
        raise UserError(str(e))
 
233
        raise UserError(e)
328
234
 
329
235
    if last_patch:
330
236
        for i in range(len(ancestors)):
332
238
                break
333
239
        else:
334
240
            raise UserError("Directory \"%s\" already exists, and the last "
335
 
                "revision (%s) is not in the ancestry of %s" %
 
241
                "revision (%s) is not in the ancestry of %s" % 
336
242
                (output_dir, last_patch, version))
337
243
        # Strip off all of the ancestors which are already present
338
244
        # And get a directory starting with the latest ancestor
339
245
        latest_ancestor = ancestors[i]
340
 
        old_revno = Branch.open(output_dir).revno()
 
246
        old_revno = find_branch(output_dir).revno()
341
247
        ancestors = ancestors[i+1:]
342
248
    return ancestors, old_revno
343
249
 
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):
 
250
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
 
251
                   dry_run=False, max_count=None, skip_symlinks=False):
363
252
    """
364
253
    >>> 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
 
 
371
254
    >>> result_path = os.path.join(q, "result")
372
255
    >>> commit_test_revisions()
373
256
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
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)
 
257
    >>> import_version('/', version, fancy=False, dry_run=True)
379
258
    Traceback (most recent call last):
380
 
    NotPreviousImport: / is not the location of a previous import.
381
 
    >>> import_version(result_path, version, None, dry_run=True)
 
259
    UserError: / exists, but is not a bzr branch.
 
260
    >>> import_version(result_path, version, fancy=False, dry_run=True)
382
261
    Traceback (most recent call last):
383
262
    UserError: The version test@example.com/test--test--0.1 does not exist.
384
263
    >>> version = pybaz.Version("test@example.com/test--test--0")
385
 
    >>> import_version(result_path, version, None, dry_run=True) #doctest: +ELLIPSIS
386
 
    importing test@example.com/test--test--0 into ...
387
 
    ...
388
 
    revisions: ..........................................
 
264
    >>> import_version(result_path, version, fancy=False, dry_run=True)
 
265
    not fancy
 
266
    ....
389
267
    Dry run, not modifying output_dir
390
268
    Cleaning up
391
 
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
392
 
    importing test@example.com/test--test--0 into ...
393
 
    ...
394
 
    revisions: .....................................................................
 
269
    >>> import_version(result_path, version, fancy=False)
 
270
    not fancy
 
271
    ....
395
272
    Cleaning up
396
273
    Import complete.
397
 
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
 
274
    >>> import_version(result_path, version, fancy=False)
398
275
    Tree is up-to-date with test@example.com/test--test--0--patch-2
399
276
    >>> commit_more_test_revisions()
400
 
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
401
 
    importing test@example.com/test--test--0 into ...
402
 
    revisions: ....................................................
 
277
    >>> import_version(result_path, version, fancy=False)
 
278
    not fancy
 
279
    ..
403
280
    Cleaning up
404
281
    Import complete.
405
 
    >>> bzrlib.ui.ui_factory = old_ui
406
 
    >>> sys.stderr = old_stderr
407
282
    >>> teardown_environ(q)
408
283
    """
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
 
284
    try:
 
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)
 
288
    if len(ancestors) == 0:
 
289
        last_revision = get_last_revision(find_branch(output_dir))
 
290
        print 'Tree is up-to-date with %s' % last_revision
 
291
        return
 
292
 
 
293
    progress_bar = ProgressBar()
 
294
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
 
295
                               dir=os.path.dirname(output_dir))
 
296
    try:
 
297
        if not fancy:
 
298
            print "not fancy"
434
299
        try:
435
300
            for result in iter_import_version(output_dir, ancestors, tempdir,
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
 
301
                    fast=fast, verbose=verbose, dry_run=dry_run, 
 
302
                    max_count=max_count, skip_symlinks=skip_symlinks):
 
303
                if fancy:
 
304
                    progress_bar(result)
 
305
                else:
 
306
                    sys.stdout.write('.')
 
307
        finally:
 
308
            if fancy:
 
309
                clear_progress_bar()
 
310
            else:
 
311
                sys.stdout.write('\n')
 
312
 
 
313
        if dry_run:
 
314
            print 'Dry run, not modifying output_dir'
 
315
            return
 
316
        if os.path.exists(output_dir):
 
317
            # Move the bzr control directory back, and update the working tree
 
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)
445
325
            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
 
 
454
 
        finally:
455
 
 
456
 
            progress_bar.note('Cleaning up')
457
 
            shutil.rmtree(tempdir)
458
 
        progress_bar.note("Import complete.")
 
326
                bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno), 
 
327
                                   check_clean=False, this_dir=output_dir, 
 
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
 
334
        else:
 
335
            revdir = os.path.join(tempdir, "rd")
 
336
            os.rename(revdir, output_dir)
 
337
 
459
338
    finally:
460
 
        progress_bar.finished()
461
 
 
462
 
class UserError(BzrCommandError):
 
339
        print 'Cleaning up'
 
340
        shutil.rmtree(tempdir)
 
341
    print "Import complete."
 
342
            
 
343
class UserError(Exception):
463
344
    def __init__(self, message):
464
345
        """Exception to throw when a user makes an impossible request
465
346
        :param message: The message to emit when printing this exception
466
347
        :type message: string
467
348
        """
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):
 
349
        Exception.__init__(self, message)
 
350
 
 
351
def revision_id(arch_revision):
477
352
    """
478
353
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
479
354
    designates a revision imported with an experimental algorithm.  A number
481
356
 
482
357
    :param arch_revision: The Arch revision to generate an ID for.
483
358
 
484
 
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"), None)
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'
 
359
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
 
360
    'Arch-x:you@example.com%cat--br--0--base-0'
488
361
    """
489
 
    if encoding is None:
490
 
        encoding = ''
491
 
    else:
492
 
        encoding = '-' + encoding
493
 
    return "Arch-1%s:%s" % (encoding, str(arch_revision).replace('/', '%'))
 
362
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
494
363
 
495
364
class NotArchRevision(Exception):
496
365
    def __init__(self, revision_id):
500
369
 
501
370
def arch_revision(revision_id):
502
371
    """
503
 
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
504
 
    Traceback (most recent call last):
505
 
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
506
 
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
507
 
    Traceback (most recent call last):
508
 
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
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)'
 
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"))
 
379
    'jrandom@example.com/test--test--0--patch-5'
519
380
    """
520
 
    if is_null(revision_id):
521
 
        return None, None
522
 
    if revision_id[:7] not in ('Arch-1:', 'Arch-1-'):
 
381
    if revision_id is None:
 
382
        return None
 
383
    if revision_id[:7] != 'Arch-x:':
523
384
        raise NotArchRevision(revision_id)
524
385
    else:
525
386
        try:
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
 
387
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
533
388
        except pybaz.errors.NamespaceError, e:
534
389
            raise NotArchRevision(revision_id)
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):
 
390
            
 
391
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
 
392
                        verbose=False, dry_run=False, max_count=None,
 
393
                        skip_symlinks=False):
576
394
    revdir = None
577
 
    log_encoding = 'ascii'
578
 
    if encoding is not None:
579
 
        log_encoding = encoding
580
395
 
581
396
    # Uncomment this for testing, it basically just has baz2bzr only update
582
397
    # 5 patches at a time
593
408
    #        print '\t%s' % a
594
409
 
595
410
    previous_version=None
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)
614
 
        direct_merges = []
 
411
 
 
412
    for i in range(len(ancestors)):
 
413
        revision = ancestors[i]
615
414
        if verbose:
616
415
            version = str(revision.version)
617
416
            if version != previous_version:
618
 
                pb.note('On version: %s' % version)
 
417
                clear_progress_bar()
 
418
                print '\rOn version: %s' % version
619
419
            yield Progress(str(revision.patchlevel), i, len(ancestors))
620
420
            previous_version = version
621
421
        else:
622
422
            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
627
423
        if revdir is None:
628
424
            revdir = os.path.join(tempdir, "rd")
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
 
425
            baz_inv, log = get_revision(revdir, revision, 
 
426
                                        skip_symlinks=skip_symlinks)
 
427
            if os.path.exists(output_dir):
 
428
                bzr_dir = os.path.join(output_dir, '.bzr')
 
429
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
430
                # This would be much faster with a simple os.rename(), but if
 
431
                # we fail, we have corrupted the original .bzr directory.  Is
 
432
                # that a big problem, as we can just back out the last
 
433
                # revisions in .bzr/revision_history I don't really know
 
434
                shutil.copytree(bzr_dir, new_bzr_dir)
 
435
                # Now revdir should have a tree with the latest .bzr, and the
 
436
                # next revision of the baz tree
 
437
                branch = find_branch(revdir)
 
438
            else:
 
439
                branch = bzrlib.Branch(revdir, init=True)
641
440
        else:
642
441
            old = os.path.join(revdir, ".bzr")
643
442
            new = os.path.join(tempdir, ".bzr")
644
443
            os.rename(old, new)
645
 
            baz_inv, log = apply_revision(tree, revision)
 
444
            baz_inv, log = apply_revision(revdir, revision, 
 
445
                                          skip_symlinks=skip_symlinks)
646
446
            os.rename(new, old)
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
 
 
657
 
        timestamp = email.Utils.mktime_tz(log_date + (0,))
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()
 
447
            branch = find_branch(revdir)
 
448
        timestamp = email.Utils.mktime_tz(log.date + (0,))
 
449
        rev_id = revision_id(revision)
666
450
        branch.lock_write()
667
451
        try:
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={})
 
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)
685
456
        finally:
686
 
            target_tree.unlock()
 
457
            bzrlib.trace.silent = False   
687
458
            branch.unlock()
688
459
    yield Progress("revisions", len(ancestors), len(ancestors))
689
 
 
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,
697
 
        revision.version.nonarch, revision.archive, revision.patchlevel)
698
 
    temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
699
 
    os.rename(log_path, temp_path)
700
 
    merges = list(iter_new_merges(revdir, revision.version))
701
 
    direct = direct_merges(merges, [continuation])
702
 
    os.rename(temp_path, log_path)
703
 
    return direct
704
 
 
705
 
def unlink_unversioned(wt):
706
 
    for unversioned in wt.extras():
707
 
        path = wt.abspath(unversioned)
 
460
    unlink_unversioned(branch, revdir)
 
461
 
 
462
def unlink_unversioned(branch, revdir):
 
463
    for unversioned in branch.working_tree().extras():
 
464
        path = os.path.join(revdir, unversioned)
708
465
        if os.path.isdir(path):
709
466
            shutil.rmtree(path)
710
467
        else:
711
468
            os.unlink(path)
712
469
 
713
470
def get_log(tree, revision):
714
 
    log = pybaz.Patchlog(revision, tree=tree)
715
 
    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
716
473
    return log
717
474
 
718
 
def get_revision(revdir, revision):
719
 
    tree = revision.get(revdir)
 
475
def get_revision(revdir, revision, skip_symlinks=False):
 
476
    revision.get(revdir)
 
477
    tree = pybaz.tree_root(revdir)
720
478
    log = get_log(tree, revision)
721
479
    try:
722
 
        return tree, bzr_inventory_data(tree), log
 
480
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
723
481
    except BadFileKind, e:
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):
 
482
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
483
 
 
484
 
 
485
def apply_revision(revdir, revision, skip_symlinks=False):
 
486
    tree = pybaz.tree_root(revdir)
729
487
    revision.apply(tree)
730
488
    log = get_log(tree, revision)
731
489
    try:
732
 
        return bzr_inventory_data(tree), log
 
490
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
733
491
    except BadFileKind, e:
734
 
        raise UserError("Cannot convert %s because %s is a %s" %
735
 
                        (revision,e.path, e.kind))
 
492
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
493
 
 
494
 
736
495
 
737
496
 
738
497
class BadFileKind(Exception):
744
503
        Exception.__init__(self, "File %s is of forbidden type %s" %
745
504
                           (os.path.join(tree_root, path), kind))
746
505
 
747
 
 
748
 
def bzr_inventory_data(tree):
 
506
def bzr_inventory_data(tree, skip_symlinks=False):
749
507
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
750
508
    inv_map = {}
751
 
    for arch_id, path in inv_iter:
752
 
        bzr_file_id = map_file_id(arch_id)
753
 
        inv_map[path] = bzr_file_id
 
509
    for file_id, path in inv_iter:
 
510
        inv_map[path] = file_id 
754
511
 
755
512
    bzr_inv = []
756
513
    for path, file_id in inv_map.iteritems():
757
514
        full_path = os.path.join(tree, path)
758
515
        kind = bzrlib.osutils.file_kind(full_path)
759
 
        if kind not in ("file", "directory", "symlink"):
 
516
        if skip_symlinks and kind == "symlink":
 
517
            continue
 
518
        if kind not in ("file", "directory"):
760
519
            raise BadFileKind(tree, path, kind)
761
520
        parent_dir = os.path.dirname(path)
762
521
        if parent_dir != "":
767
526
    bzr_inv.sort()
768
527
    return bzr_inv
769
528
 
770
 
 
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
529
class NotInABranch(Exception):
787
530
    def __init__(self, path):
788
531
        Exception.__init__(self, "%s is not in a branch." % path)
789
532
        self.path = path
790
533
 
791
534
 
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()
 
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
    """
829
544
    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))
 
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):
 
551
    """Import an Arch or Baz branch into a bzr branch"""
 
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()
 
558
        to_location = os.path.realpath(str(to_location))
 
559
        if from_branch is not None:
838
560
            try:
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')
 
561
                from_branch = pybaz.Version(version)
 
562
            except pybaz.errors.NamespaceError:
 
563
                print "%s is not a valid Arch branch." % from_branch
 
564
                return 1
 
565
        import_version(to_location, from_branch, fancy=fancy)