~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2007-01-15 15:19:20 UTC
  • Revision ID: abentley@panoramicfeedback.com-20070115151920-pb4eto22dzk34co2
Add --silent option to patch

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
 
 
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
 
39
69
def add_id(files, id=None):
40
70
    """Adds an explicit id to a list of files.
41
71
 
50
80
    args.extend(files)
51
81
    return null_cmd(args)
52
82
 
 
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
 
53
90
def test_environ():
54
91
    """
55
92
    >>> q = test_environ()
61
98
    >>> os.path.exists(q)
62
99
    False
63
100
    """
 
101
    global saved_dir
 
102
    saved_dir = os.getcwdu()
64
103
    tdir = tempfile.mkdtemp(prefix="testdir-")
65
104
    os.environ["HOME"] = os.path.join(tdir, "home")
66
105
    os.mkdir(os.environ["HOME"])
67
106
    arch_dir = os.path.join(tdir, "archive_dir")
68
 
    pybaz.make_archive("test@example.com", arch_dir)
 
107
    make_archive("test@example.com", arch_dir)
69
108
    work_dir = os.path.join(tdir, "work_dir")
70
109
    os.mkdir(work_dir)
71
110
    os.chdir(work_dir)
104
143
    add_id([path], id)
105
144
 
106
145
def teardown_environ(tdir):
107
 
    os.chdir("/")
 
146
    os.chdir(saved_dir)
108
147
    shutil.rmtree(tdir)
109
148
 
110
149
def timport(tree, summary):
196
235
    """
197
236
    try:
198
237
        revision = version.iter_revisions(reverse=True).next()
 
238
    except StopIteration:
 
239
        return ()
199
240
    except:
 
241
        print version
200
242
        if not version.exists():
201
243
            raise NoSuchVersion(version)
202
244
        else:
212
254
    except NotArchRevision:
213
255
        raise UserError(
214
256
            "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):
 
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=[]):
219
286
    last_patch = None
220
287
    old_revno = None
221
 
    if os.path.exists(output_dir):
 
288
    output_exists = os.path.exists(output_dir)
 
289
    if output_exists:
222
290
        # We are starting from an existing directory, figure out what
223
291
        # the current version is
224
 
        branch = find_branch(output_dir)
225
 
        last_patch = get_last_revision(branch)
226
 
        if version is None:
 
292
        branch = Branch.open(output_dir)
 
293
        last_patch, last_encoding = get_last_revision(branch)
 
294
        assert encoding == last_encoding
 
295
        if last_patch is None:
 
296
            if branch.last_revision() != None:
 
297
                raise NotPreviousImport(branch.base)
 
298
        elif version is None:
227
299
            version = last_patch.version
228
300
    elif version is None:
229
301
        raise UserError("No version specified, and directory does not exist.")
230
302
 
231
303
    try:
232
304
        ancestors = version_ancestry(version)
 
305
        if not output_exists and reuse_history_from != []:
 
306
            for ancestor in reversed(ancestors):
 
307
                if last_patch is not None:
 
308
                    # found something to copy
 
309
                    break
 
310
                # try to grab a copy of ancestor
 
311
                # note that is not optimised: we could look for namespace
 
312
                # transitions and only look for the past after the 
 
313
                # transition.
 
314
                for history_root in reuse_history_from:
 
315
                    possible_source = os.path.join(history_root,
 
316
                        map_namespace(ancestor.version))
 
317
                    try:
 
318
                        source = Branch.open(possible_source)
 
319
                        rev_id = revision_id(ancestor, encoding)
 
320
                        if rev_id in source.revision_history():
 
321
                            do_branch(source, output_dir, rev_id)
 
322
                            last_patch = ancestor
 
323
                            break
 
324
                    except NotBranchError:
 
325
                        pass
233
326
    except NoSuchVersion, e:
234
 
        raise UserError(e)
 
327
        raise UserError(str(e))
235
328
 
236
329
    if last_patch:
237
330
        for i in range(len(ancestors)):
244
337
        # Strip off all of the ancestors which are already present
245
338
        # And get a directory starting with the latest ancestor
246
339
        latest_ancestor = ancestors[i]
247
 
        old_revno = find_branch(output_dir).revno()
 
340
        old_revno = Branch.open(output_dir).revno()
248
341
        ancestors = ancestors[i+1:]
249
342
    return ancestors, old_revno
250
343
 
251
 
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
252
 
                   dry_run=False, max_count=None, skip_symlinks=False):
 
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):
253
363
    """
254
364
    >>> q = test_environ()
 
365
    
 
366
    Progress bars output to stderr, but doctest does not capture that.
 
367
 
 
368
    >>> old_stderr = sys.stderr
 
369
    >>> sys.stderr = sys.stdout
 
370
 
255
371
    >>> result_path = os.path.join(q, "result")
256
372
    >>> commit_test_revisions()
257
373
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
258
 
    >>> import_version('/', version, fancy=False, dry_run=True)
 
374
    >>> old_ui = bzrlib.ui.ui_factory
 
375
    >>> bzrlib.ui.ui_factory = bzrlib.ui.text.TextUIFactory(
 
376
    ...     bar_type=bzrlib.progress.DotsProgressBar)
 
377
 
 
378
    >>> import_version('/', version, None, dry_run=True)
259
379
    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)
 
380
    NotPreviousImport: / is not the location of a previous import.
 
381
    >>> import_version(result_path, version, None, dry_run=True)
262
382
    Traceback (most recent call last):
263
383
    UserError: The version test@example.com/test--test--0.1 does not exist.
264
384
    >>> version = pybaz.Version("test@example.com/test--test--0")
265
 
    >>> import_version(result_path, version, fancy=False, dry_run=True)
266
 
    not fancy
267
 
    ....
 
385
    >>> import_version(result_path, version, None, dry_run=True) #doctest: +ELLIPSIS
 
386
    importing test@example.com/test--test--0 into ...
 
387
    ...
 
388
    revisions: ..........................................
268
389
    Dry run, not modifying output_dir
269
390
    Cleaning up
270
 
    >>> import_version(result_path, version, fancy=False)
271
 
    not fancy
272
 
    ....
 
391
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
 
392
    importing test@example.com/test--test--0 into ...
 
393
    ...
 
394
    revisions: .....................................................................
273
395
    Cleaning up
274
396
    Import complete.
275
 
    >>> import_version(result_path, version, fancy=False)
 
397
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
276
398
    Tree is up-to-date with test@example.com/test--test--0--patch-2
277
399
    >>> commit_more_test_revisions()
278
 
    >>> import_version(result_path, version, fancy=False)
279
 
    not fancy
280
 
    ..
 
400
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
 
401
    importing test@example.com/test--test--0 into ...
 
402
    revisions: ....................................................
281
403
    Cleaning up
282
404
    Import complete.
 
405
    >>> bzrlib.ui.ui_factory = old_ui
 
406
    >>> sys.stderr = old_stderr
283
407
    >>> teardown_environ(q)
284
408
    """
 
409
    progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
285
410
    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
 
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
293
425
 
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"
 
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
300
434
        try:
301
435
            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('.')
 
436
                    pb=progress_bar, encoding=encoding, fast=fast, 
 
437
                    verbose=verbose, dry_run=dry_run, max_count=max_count,
 
438
                    standalone=standalone):
 
439
                show_progress(progress_bar, result)
 
440
            if dry_run:
 
441
                progress_bar.note('Dry run, not modifying output_dir')
 
442
                return
 
443
    
 
444
            # Update the working tree of the branch
 
445
            try:
 
446
                wt = WorkingTree.open(output_dir)
 
447
            except NoWorkingTree:
 
448
                wt = None
 
449
            if wt is not None:
 
450
                wt.set_last_revision(wt.branch.last_revision())
 
451
                wt.set_root_id(BAZ_IMPORT_ROOT)
 
452
                wt.revert([])
 
453
    
308
454
        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
455
            
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
 
 
 
456
            progress_bar.note('Cleaning up')
 
457
            shutil.rmtree(tempdir)
 
458
        progress_bar.note("Import complete.")
339
459
    finally:
340
 
        print 'Cleaning up'
341
 
        shutil.rmtree(tempdir)
342
 
    print "Import complete."
 
460
        progress_bar.finished()
343
461
            
344
 
class UserError(Exception):
 
462
class UserError(BzrCommandError):
345
463
    def __init__(self, message):
346
464
        """Exception to throw when a user makes an impossible request
347
465
        :param message: The message to emit when printing this exception
348
466
        :type message: string
349
467
        """
350
 
        Exception.__init__(self, message)
351
 
 
352
 
def revision_id(arch_revision):
 
468
        BzrCommandError.__init__(self, message)
 
469
 
 
470
class NotPreviousImport(UserError):
 
471
    def __init__(self, path):
 
472
        UserError.__init__(self, "%s is not the location of a previous import."
 
473
                           % path)
 
474
 
 
475
 
 
476
def revision_id(arch_revision, encoding):
353
477
    """
354
478
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
355
479
    designates a revision imported with an experimental algorithm.  A number
357
481
 
358
482
    :param arch_revision: The Arch revision to generate an ID for.
359
483
 
360
 
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
361
 
    'Arch-x:you@example.com%cat--br--0--base-0'
 
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'
362
488
    """
363
 
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
 
489
    if encoding is None:
 
490
        encoding = ''
 
491
    else:
 
492
        encoding = '-' + encoding
 
493
    return "Arch-1%s:%s" % (encoding, str(arch_revision).replace('/', '%'))
364
494
 
365
495
class NotArchRevision(Exception):
366
496
    def __init__(self, revision_id):
370
500
 
371
501
def arch_revision(revision_id):
372
502
    """
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"))
380
 
    'jrandom@example.com/test--test--0--patch-5'
 
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'
381
517
    """
382
518
    if revision_id is None:
383
 
        return None
384
 
    if revision_id[:7] != 'Arch-x:':
 
519
        return None, None
 
520
    if revision_id[:7] not in ('Arch-1:', 'Arch-1-'):
385
521
        raise NotArchRevision(revision_id)
386
522
    else:
387
523
        try:
388
 
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
 
524
            encoding, arch_name = revision_id[6:].split(':', 1)
 
525
            arch_name = arch_name.replace('%', '/')
 
526
            if encoding == '':
 
527
                encoding = None
 
528
            else:
 
529
                encoding = encoding[1:]
 
530
            return pybaz.Revision(arch_name), encoding
389
531
        except pybaz.errors.NamespaceError, e:
390
532
            raise NotArchRevision(revision_id)
391
 
            
392
 
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
393
 
                        verbose=False, dry_run=False, max_count=None,
394
 
                        skip_symlinks=False):
 
533
 
 
534
 
 
535
def create_shared_repository(output_dir):
 
536
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
537
    bd.create_repository(shared=True)
 
538
 
 
539
def create_branch(output_dir):
 
540
    os.mkdir(output_dir)
 
541
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
542
    return bd.create_branch()
 
543
 
 
544
 
 
545
def create_checkout(source, to_location, revision_id=None):
 
546
    checkout = bzrdir.BzrDirMetaFormat1().initialize(to_location)
 
547
    bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
 
548
    return checkout.create_workingtree(revision_id)
 
549
 
 
550
 
 
551
def create_checkout_metadata(source, to_location, revision_id=None):
 
552
    if revision_id is None:
 
553
        revision_id = source.last_revision()
 
554
    wt = create_checkout(source, to_location, NULL_REVISION)
 
555
    wt.set_last_revision(revision_id)
 
556
    if revision_id not in (NULL_REVISION, None):
 
557
        wt._write_inventory(wt.basis_tree().inventory)
 
558
    return wt
 
559
 
 
560
 
 
561
def iter_import_version(output_dir, ancestors, tempdir, pb, encoding, 
 
562
                        fast=False, verbose=False, dry_run=False,
 
563
                        max_count=None, standalone=False):
395
564
    revdir = None
 
565
    log_encoding = 'ascii'
 
566
    if encoding is not None:
 
567
        log_encoding = encoding
396
568
 
397
569
    # Uncomment this for testing, it basically just has baz2bzr only update
398
570
    # 5 patches at a time
409
581
    #        print '\t%s' % a
410
582
 
411
583
    previous_version=None
 
584
    missing_ancestor = None
 
585
    if dry_run:
 
586
        dry_output_dir = os.path.join(tempdir, 'od')
 
587
        if os.path.exists(output_dir):
 
588
            shutil.copytree(output_dir, dry_output_dir)
 
589
        output_dir = dry_output_dir
 
590
 
 
591
    if os.path.exists(output_dir):
 
592
        target_branch = Branch.open(output_dir)
 
593
    else:
 
594
        if standalone:
 
595
            wt = BzrDir.create_standalone_workingtree(output_dir)
 
596
            target_branch = wt.branch
 
597
        else:
 
598
            target_branch = create_branch(output_dir)
412
599
 
413
600
    for i in range(len(ancestors)):
414
601
        revision = ancestors[i]
 
602
        rev_id = revision_id(revision, encoding)
 
603
        direct_merges = []
415
604
        if verbose:
416
605
            version = str(revision.version)
417
606
            if version != previous_version:
418
 
                clear_progress_bar()
419
 
                print '\rOn version: %s' % version
 
607
                pb.note('On version: %s' % version)
420
608
            yield Progress(str(revision.patchlevel), i, len(ancestors))
421
609
            previous_version = version
422
610
        else:
423
611
            yield Progress("revisions", i, len(ancestors))
 
612
 
 
613
        if target_branch.repository.has_revision(rev_id):
 
614
            target_branch.append_revision(rev_id)
 
615
            continue
424
616
        if revdir is None:
425
617
            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.initialize(revdir)
 
618
            try:
 
619
                tree, baz_inv, log = get_revision(revdir, revision)
 
620
            except pybaz.errors.ExecProblem, e:
 
621
                if ("%s" % e.args).find('could not connect') == -1:
 
622
                    raise
 
623
                missing_ancestor = revision
 
624
                revdir = None
 
625
                pb.note("unable to access ancestor %s, making into a merge."
 
626
                       % missing_ancestor)
 
627
                continue
 
628
            target_tree = create_checkout_metadata(target_branch, revdir)
 
629
            branch = target_tree.branch
441
630
        else:
442
631
            old = os.path.join(revdir, ".bzr")
443
632
            new = os.path.join(tempdir, ".bzr")
444
633
            os.rename(old, new)
445
 
            baz_inv, log = apply_revision(revdir, revision, 
446
 
                                          skip_symlinks=skip_symlinks)
 
634
            baz_inv, log = apply_revision(tree, revision)
447
635
            os.rename(new, old)
448
 
            branch = find_branch(revdir)
449
 
        timestamp = email.Utils.mktime_tz(log.date + (0,))
450
 
        rev_id = revision_id(revision)
 
636
            target_tree = WorkingTree.open(revdir)
 
637
            branch = target_tree.branch
 
638
        # cached so we can delete the log
 
639
        log_date = log.date
 
640
        log_summary = log.summary
 
641
        log_description = log.description
 
642
        is_continuation = log.continuation_of is not None
 
643
        log_creator = log.creator
 
644
        direct_merges = get_direct_merges(revdir, revision, log)
 
645
 
 
646
        timestamp = email.Utils.mktime_tz(log_date + (0,))
 
647
        if log_summary is None:
 
648
            log_summary = ""
 
649
        # log_descriptions of None and "" are ignored.
 
650
        if not is_continuation and log_description:
 
651
            log_message = "\n".join((log_summary, log_description))
 
652
        else:
 
653
            log_message = log_summary
 
654
        target_tree.lock_write()
451
655
        branch.lock_write()
452
656
        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)
 
657
            if missing_ancestor:
 
658
                # if we want it to be in revision-history, do that here.
 
659
                target_tree.set_parent_ids(
 
660
                    [revision_id(missing_ancestor, encoding)],
 
661
                    allow_leftmost_as_ghost=True)
 
662
                missing_ancestor = None
 
663
            for merged_rev in direct_merges:
 
664
                target_tree.add_pending_merge(revision_id(merged_rev, 
 
665
                                                          encoding))
 
666
            target_tree.set_root_id(BAZ_IMPORT_ROOT)
 
667
            target_tree.set_inventory(baz_inv)
 
668
            commitobj = Commit(reporter=ImportCommitReporter())
 
669
            commitobj.commit(working_tree=target_tree,
 
670
                message=log_message.decode(log_encoding, 'replace'),
 
671
                verbose=False, committer=log_creator, timestamp=timestamp,
 
672
                timezone=0, rev_id=rev_id, revprops={})
457
673
        finally:
458
 
            bzrlib.trace.silent = False   
 
674
            target_tree.unlock()
459
675
            branch.unlock()
460
676
    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)
 
677
 
 
678
def get_direct_merges(revdir, revision, log):
 
679
    continuation = log.continuation_of
 
680
    previous_version = revision.version
 
681
    if pybaz.WorkingTree(revdir).tree_version != previous_version:
 
682
        pybaz.WorkingTree(revdir).set_tree_version(previous_version)
 
683
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir, 
 
684
        revision.category.nonarch, revision.branch.nonarch, 
 
685
        revision.version.nonarch, revision.archive, revision.patchlevel)
 
686
    temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
 
687
    os.rename(log_path, temp_path)
 
688
    merges = list(iter_new_merges(revdir, revision.version))
 
689
    direct = direct_merges(merges, [continuation])
 
690
    os.rename(temp_path, log_path)
 
691
    return direct
 
692
 
 
693
def unlink_unversioned(wt):
 
694
    for unversioned in wt.extras():
 
695
        path = wt.abspath(unversioned)
466
696
        if os.path.isdir(path):
467
697
            shutil.rmtree(path)
468
698
        else:
469
699
            os.unlink(path)
470
700
 
471
701
def get_log(tree, revision):
472
 
    log = tree.iter_logs(version=revision.version, reverse=True).next()
 
702
    log = pybaz.Patchlog(revision, tree=tree)
473
703
    assert str(log.revision) == str(revision), (log.revision, revision)
474
704
    return log
475
705
 
476
 
def get_revision(revdir, revision, skip_symlinks=False):
477
 
    revision.get(revdir)
478
 
    tree = pybaz.tree_root(revdir)
 
706
def get_revision(revdir, revision):
 
707
    tree = revision.get(revdir)
479
708
    log = get_log(tree, revision)
480
709
    try:
481
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
 
710
        return tree, bzr_inventory_data(tree), log 
482
711
    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)
 
712
        raise UserError("Cannot convert %s because %s is a %s" % 
 
713
                        (revision,e.path, e.kind))
 
714
 
 
715
 
 
716
def apply_revision(tree, revision):
488
717
    revision.apply(tree)
489
718
    log = get_log(tree, revision)
490
719
    try:
491
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
 
720
        return bzr_inventory_data(tree), log
492
721
    except BadFileKind, e:
493
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
494
 
 
495
 
 
 
722
        raise UserError("Cannot convert %s because %s is a %s" % 
 
723
                        (revision,e.path, e.kind))
496
724
 
497
725
 
498
726
class BadFileKind(Exception):
504
732
        Exception.__init__(self, "File %s is of forbidden type %s" %
505
733
                           (os.path.join(tree_root, path), kind))
506
734
 
507
 
def bzr_inventory_data(tree, skip_symlinks=False):
 
735
 
 
736
def bzr_inventory_data(tree):
508
737
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
509
738
    inv_map = {}
510
739
    for arch_id, path in inv_iter:
511
 
        bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
 
740
        bzr_file_id = map_file_id(arch_id)
512
741
        inv_map[path] = bzr_file_id 
513
742
 
514
743
    bzr_inv = []
515
744
    for path, file_id in inv_map.iteritems():
516
745
        full_path = os.path.join(tree, path)
517
746
        kind = bzrlib.osutils.file_kind(full_path)
518
 
        if skip_symlinks and kind == "symlink":
519
 
            continue
520
 
        if kind not in ("file", "directory"):
 
747
        if kind not in ("file", "directory", "symlink"):
521
748
            raise BadFileKind(tree, path, kind)
522
749
        parent_dir = os.path.dirname(path)
523
750
        if parent_dir != "":
528
755
    bzr_inv.sort()
529
756
    return bzr_inv
530
757
 
 
758
 
 
759
def baz_import_branch(to_location, from_branch, fast, max_count, verbose, 
 
760
                      encoding, dry_run, reuse_history_list):
 
761
    to_location = os.path.realpath(str(to_location))
 
762
    if from_branch is not None:
 
763
        try:
 
764
            from_branch = pybaz.Version(from_branch)
 
765
        except pybaz.errors.NamespaceError:
 
766
            print "%s is not a valid Arch branch." % from_branch
 
767
            return 1
 
768
    if reuse_history_list is None:
 
769
        reuse_history_list = []
 
770
    import_version(to_location, from_branch, encoding, max_count=max_count, 
 
771
                   reuse_history_from=reuse_history_list)
 
772
 
 
773
 
531
774
class NotInABranch(Exception):
532
775
    def __init__(self, path):
533
776
        Exception.__init__(self, "%s is not in a branch." % path)
534
777
        self.path = path
535
778
 
536
779
 
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
 
    """
 
780
 
 
781
def baz_import(to_root_dir, from_archive, encoding, 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, encoding,
 
791
                   reuse_history_list, prefixes=prefixes)
 
792
 
 
793
 
 
794
def import_archive(to_root, from_archive, verbose,
 
795
                   encoding, 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()
546
817
    try:
547
 
        return Branch.open(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:
 
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))
561
826
            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)
 
827
                import_version(target, version, encoding,
 
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')