~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-07-25 12:59:22 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050725125922-42977d0e7e81075f
Somewhat less-confusing import errors

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