~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2006-09-25 13:42:47 UTC
  • Revision ID: abentley@panoramicfeedback.com-20060925134247-c9956e8bd0de0958
Remove shove from README

Show diffs side-by-side

added added

removed removed

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