~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2007-06-12 20:36:46 UTC
  • mto: This revision was merged to the branch mainline in revision 543.
  • Revision ID: abentley@panoramicfeedback.com-20070612203646-m4syd874y4km1u6x
Tags: release-0.17.1
Prepare for 0.17.1 release

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, NotBranchError, BzrCommandError
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)
 
292
        branch = Branch.open(output_dir)
 
293
        last_patch, last_encoding = get_last_revision(branch)
 
294
        assert encoding == last_encoding
226
295
        if last_patch is None:
227
 
            raise NotPreviousImport(branch.base)
228
 
        if version is None:
 
296
            if branch.last_revision() != None:
 
297
                raise NotPreviousImport(branch.base)
 
298
        elif version is None:
229
299
            version = last_patch.version
230
300
    elif version is None:
231
301
        raise UserError("No version specified, and directory does not exist.")
232
302
 
233
303
    try:
234
304
        ancestors = version_ancestry(version)
235
 
        if len(ancestors) > 0 and not ancestors[0].archive.is_registered():
236
 
            ancestors = ancestors[1:]
 
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
237
326
    except NoSuchVersion, e:
238
 
        raise UserError(e)
 
327
        raise UserError(str(e))
239
328
 
240
329
    if last_patch:
241
330
        for i in range(len(ancestors)):
243
332
                break
244
333
        else:
245
334
            raise UserError("Directory \"%s\" already exists, and the last "
246
 
                "revision (%s) is not in the ancestry of %s" % 
 
335
                "revision (%s) is not in the ancestry of %s" %
247
336
                (output_dir, last_patch, version))
248
337
        # Strip off all of the ancestors which are already present
249
338
        # And get a directory starting with the latest ancestor
250
339
        latest_ancestor = ancestors[i]
251
 
        old_revno = find_branch(output_dir).revno()
 
340
        old_revno = Branch.open(output_dir).revno()
252
341
        ancestors = ancestors[i+1:]
253
342
    return ancestors, old_revno
254
343
 
255
 
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
256
 
                   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):
257
363
    """
258
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
 
259
371
    >>> result_path = os.path.join(q, "result")
260
372
    >>> commit_test_revisions()
261
373
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
262
 
    >>> 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)
263
379
    Traceback (most recent call last):
264
 
    UserError: / exists, but is not a bzr branch.
265
 
    >>> 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)
266
382
    Traceback (most recent call last):
267
383
    UserError: The version test@example.com/test--test--0.1 does not exist.
268
384
    >>> version = pybaz.Version("test@example.com/test--test--0")
269
 
    >>> import_version(result_path, version, fancy=False, dry_run=True)
270
 
    not fancy
271
 
    ....
 
385
    >>> import_version(result_path, version, None, dry_run=True) #doctest: +ELLIPSIS
 
386
    importing test@example.com/test--test--0 into ...
 
387
    ...
 
388
    revisions: ..........................................
272
389
    Dry run, not modifying output_dir
273
390
    Cleaning up
274
 
    >>> import_version(result_path, version, fancy=False)
275
 
    not fancy
276
 
    ....
 
391
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
 
392
    importing test@example.com/test--test--0 into ...
 
393
    ...
 
394
    revisions: .....................................................................
277
395
    Cleaning up
278
396
    Import complete.
279
 
    >>> import_version(result_path, version, fancy=False)
 
397
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
280
398
    Tree is up-to-date with test@example.com/test--test--0--patch-2
281
399
    >>> commit_more_test_revisions()
282
 
    >>> import_version(result_path, version, fancy=False)
283
 
    not fancy
284
 
    ..
 
400
    >>> import_version(result_path, version, None) #doctest: +ELLIPSIS
 
401
    importing test@example.com/test--test--0 into ...
 
402
    revisions: ....................................................
285
403
    Cleaning up
286
404
    Import complete.
 
405
    >>> bzrlib.ui.ui_factory = old_ui
 
406
    >>> sys.stderr = old_stderr
287
407
    >>> teardown_environ(q)
288
408
    """
289
 
    try:
290
 
        ancestors, old_revno = get_remaining_revisions(output_dir, version)
291
 
    except NotInABranch, e:
292
 
        raise NotPreviousImport(e.path)
293
 
    if len(ancestors) == 0:
294
 
        last_revision = get_last_revision(find_branch(output_dir))
295
 
        print 'Tree is up-to-date with %s' % last_revision
296
 
        return
297
 
 
298
 
    progress_bar = ProgressBar()
299
 
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
300
 
                               dir=os.path.dirname(output_dir))
301
 
    try:
302
 
        if not fancy:
303
 
            print "not fancy"
 
409
    progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
 
410
    try:
 
411
        try:
 
412
            ancestors, old_revno = get_remaining_revisions(output_dir, version,
 
413
                                                           encoding,
 
414
                                                           reuse_history_from)
 
415
        except NotBranchError, e:
 
416
            raise NotPreviousImport(e.path)
 
417
        if old_revno is None and len(ancestors) == 0:
 
418
            progress_bar.note('Version %s has no revisions.' % version)
 
419
            return
 
420
        if len(ancestors) == 0:
 
421
            last_revision, last_encoding = \
 
422
                get_last_revision(Branch.open(output_dir))
 
423
            progress_bar.note('Tree is up-to-date with %s' % last_revision)
 
424
            return
 
425
 
 
426
        progress_bar.note("importing %s into %s" % (version, output_dir))
 
427
 
 
428
        tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
 
429
                                   dir=os.path.dirname(output_dir))
 
430
        try:
 
431
            wt = WorkingTree.open(output_dir)
 
432
        except (NotBranchError, NoWorkingTree):
 
433
            wt = None
304
434
        try:
305
435
            for result in iter_import_version(output_dir, ancestors, tempdir,
306
 
                    fast=fast, verbose=verbose, dry_run=dry_run, 
307
 
                    max_count=max_count, skip_symlinks=skip_symlinks):
308
 
                if fancy:
309
 
                    show_progress(progress_bar, result)
310
 
                else:
311
 
                    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
 
312
454
        finally:
313
 
            if fancy:
314
 
                progress_bar.clear()
315
 
            else:
316
 
                sys.stdout.write('\n')
317
 
 
318
 
        if dry_run:
319
 
            print 'Dry run, not modifying output_dir'
320
 
            return
321
 
        if os.path.exists(output_dir):
322
 
            # Move the bzr control directory back, and update the working tree
323
 
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
324
 
            
325
 
            bzr_dir = os.path.join(output_dir, '.bzr')
326
 
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
327
 
 
328
 
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
329
 
            os.rename(new_bzr_dir, bzr_dir)
330
 
            try:
331
 
                bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno), 
332
 
                                   check_clean=False, this_dir=output_dir, 
333
 
                                   ignore_zero=True)
334
 
            except:
335
 
                # If something failed, move back the original bzr directory
336
 
                os.rename(bzr_dir, new_bzr_dir)
337
 
                os.rename(tmp_bzr_dir, bzr_dir)
338
 
                raise
339
 
        else:
340
 
            revdir = os.path.join(tempdir, "rd")
341
 
            os.rename(revdir, output_dir)
342
 
 
 
455
 
 
456
            progress_bar.note('Cleaning up')
 
457
            shutil.rmtree(tempdir)
 
458
        progress_bar.note("Import complete.")
343
459
    finally:
344
 
        print 'Cleaning up'
345
 
        shutil.rmtree(tempdir)
346
 
    print "Import complete."
347
 
            
 
460
        progress_bar.finished()
 
461
 
348
462
class UserError(BzrCommandError):
349
463
    def __init__(self, message):
350
464
        """Exception to throw when a user makes an impossible request
359
473
                           % path)
360
474
 
361
475
 
362
 
def revision_id(arch_revision):
 
476
def revision_id(arch_revision, encoding):
363
477
    """
364
478
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
365
479
    designates a revision imported with an experimental algorithm.  A number
367
481
 
368
482
    :param arch_revision: The Arch revision to generate an ID for.
369
483
 
370
 
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
371
 
    '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'
372
488
    """
373
 
    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('/', '%'))
374
494
 
375
495
class NotArchRevision(Exception):
376
496
    def __init__(self, revision_id):
380
500
 
381
501
def arch_revision(revision_id):
382
502
    """
383
 
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
384
 
    Traceback (most recent call last):
385
 
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
386
 
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
387
 
    Traceback (most recent call last):
388
 
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
389
 
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
390
 
    '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'
391
517
    """
392
518
    if revision_id is None:
393
 
        return None
394
 
    if revision_id[:7] != 'Arch-x:':
 
519
        return None, None
 
520
    if revision_id[:7] not in ('Arch-1:', 'Arch-1-'):
395
521
        raise NotArchRevision(revision_id)
396
522
    else:
397
523
        try:
398
 
            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
399
531
        except pybaz.errors.NamespaceError, e:
400
532
            raise NotArchRevision(revision_id)
401
 
            
402
 
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
403
 
                        verbose=False, dry_run=False, max_count=None,
404
 
                        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.lock_write()
 
556
    try:
 
557
        wt.set_last_revision(revision_id)
 
558
        wt.flush()
 
559
        if revision_id not in (NULL_REVISION, None):
 
560
            basis = wt.basis_tree()
 
561
            basis.lock_read()
 
562
            try:
 
563
                wt._write_inventory(basis.inventory)
 
564
            finally:
 
565
                basis.unlock()
 
566
    finally:
 
567
        wt.unlock()
 
568
    return wt
 
569
 
 
570
 
 
571
def iter_import_version(output_dir, ancestors, tempdir, pb, encoding,
 
572
                        fast=False, verbose=False, dry_run=False,
 
573
                        max_count=None, standalone=False):
405
574
    revdir = None
 
575
    log_encoding = 'ascii'
 
576
    if encoding is not None:
 
577
        log_encoding = encoding
406
578
 
407
579
    # Uncomment this for testing, it basically just has baz2bzr only update
408
580
    # 5 patches at a time
419
591
    #        print '\t%s' % a
420
592
 
421
593
    previous_version=None
 
594
    missing_ancestor = None
 
595
    if dry_run:
 
596
        dry_output_dir = os.path.join(tempdir, 'od')
 
597
        if os.path.exists(output_dir):
 
598
            shutil.copytree(output_dir, dry_output_dir)
 
599
        output_dir = dry_output_dir
 
600
 
 
601
    if os.path.exists(output_dir):
 
602
        target_branch = Branch.open(output_dir)
 
603
    else:
 
604
        if standalone:
 
605
            wt = BzrDir.create_standalone_workingtree(output_dir)
 
606
            target_branch = wt.branch
 
607
        else:
 
608
            target_branch = create_branch(output_dir)
422
609
 
423
610
    for i in range(len(ancestors)):
424
611
        revision = ancestors[i]
 
612
        rev_id = revision_id(revision, encoding)
 
613
        direct_merges = []
425
614
        if verbose:
426
615
            version = str(revision.version)
427
616
            if version != previous_version:
428
 
                clear_progress_bar()
429
 
                print '\rOn version: %s' % version
 
617
                pb.note('On version: %s' % version)
430
618
            yield Progress(str(revision.patchlevel), i, len(ancestors))
431
619
            previous_version = version
432
620
        else:
433
621
            yield Progress("revisions", i, len(ancestors))
 
622
 
 
623
        if target_branch.repository.has_revision(rev_id):
 
624
            target_branch.append_revision(rev_id)
 
625
            continue
434
626
        if revdir is None:
435
627
            revdir = os.path.join(tempdir, "rd")
436
 
            baz_inv, log = get_revision(revdir, revision, 
437
 
                                        skip_symlinks=skip_symlinks)
438
 
            if os.path.exists(output_dir):
439
 
                bzr_dir = os.path.join(output_dir, '.bzr')
440
 
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
441
 
                # This would be much faster with a simple os.rename(), but if
442
 
                # we fail, we have corrupted the original .bzr directory.  Is
443
 
                # that a big problem, as we can just back out the last
444
 
                # revisions in .bzr/revision_history I don't really know
445
 
                shutil.copytree(bzr_dir, new_bzr_dir)
446
 
                # Now revdir should have a tree with the latest .bzr, and the
447
 
                # next revision of the baz tree
448
 
                branch = find_branch(revdir)
449
 
            else:
450
 
                branch = Branch.initialize(revdir)
 
628
            try:
 
629
                tree, baz_inv, log = get_revision(revdir, revision)
 
630
            except pybaz.errors.ExecProblem, e:
 
631
                if ("%s" % e.args).find('could not connect') == -1:
 
632
                    raise
 
633
                missing_ancestor = revision
 
634
                revdir = None
 
635
                pb.note("unable to access ancestor %s, making into a merge."
 
636
                       % missing_ancestor)
 
637
                continue
 
638
            target_tree = create_checkout_metadata(target_branch, revdir)
 
639
            branch = target_tree.branch
451
640
        else:
452
641
            old = os.path.join(revdir, ".bzr")
453
642
            new = os.path.join(tempdir, ".bzr")
454
643
            os.rename(old, new)
455
 
            baz_inv, log = apply_revision(revdir, revision, 
456
 
                                          skip_symlinks=skip_symlinks)
 
644
            baz_inv, log = apply_revision(tree, revision)
457
645
            os.rename(new, old)
458
 
            branch = find_branch(revdir)
459
 
        timestamp = email.Utils.mktime_tz(log.date + (0,))
460
 
        rev_id = revision_id(revision)
 
646
            target_tree = WorkingTree.open(revdir)
 
647
            branch = target_tree.branch
 
648
        # cached so we can delete the log
 
649
        log_date = log.date
 
650
        log_summary = log.summary
 
651
        log_description = log.description
 
652
        is_continuation = log.continuation_of is not None
 
653
        log_creator = log.creator
 
654
        direct_merges = get_direct_merges(revdir, revision, log)
 
655
 
 
656
        timestamp = email.Utils.mktime_tz(log_date + (0,))
 
657
        if log_summary is None:
 
658
            log_summary = ""
 
659
        # log_descriptions of None and "" are ignored.
 
660
        if not is_continuation and log_description:
 
661
            log_message = "\n".join((log_summary, log_description))
 
662
        else:
 
663
            log_message = log_summary
 
664
        target_tree.lock_write()
461
665
        branch.lock_write()
462
666
        try:
463
 
            branch.working_tree().set_inventory(baz_inv)
464
 
            bzrlib.trace.silent = True
465
 
            wt = branch.working_tree()
466
 
            wt.commit(log.summary, verbose=False, committer=log.creator,
467
 
                      timestamp=timestamp, timezone=0, rev_id=rev_id)
 
667
            if missing_ancestor:
 
668
                # if we want it to be in revision-history, do that here.
 
669
                target_tree.set_parent_ids(
 
670
                    [revision_id(missing_ancestor, encoding)],
 
671
                    allow_leftmost_as_ghost=True)
 
672
                missing_ancestor = None
 
673
            for merged_rev in direct_merges:
 
674
                target_tree.add_pending_merge(revision_id(merged_rev,
 
675
                                                          encoding))
 
676
            target_tree.set_root_id(BAZ_IMPORT_ROOT)
 
677
            target_tree.flush()
 
678
            target_tree.set_inventory(baz_inv)
 
679
            commitobj = Commit(reporter=ImportCommitReporter())
 
680
            commitobj.commit(working_tree=target_tree,
 
681
                message=log_message.decode(log_encoding, 'replace'),
 
682
                verbose=False, committer=log_creator, timestamp=timestamp,
 
683
                timezone=0, rev_id=rev_id, revprops={})
468
684
        finally:
469
 
            bzrlib.trace.silent = False   
 
685
            target_tree.unlock()
470
686
            branch.unlock()
471
687
    yield Progress("revisions", len(ancestors), len(ancestors))
472
 
    unlink_unversioned(branch, revdir)
473
 
 
474
 
def unlink_unversioned(branch, revdir):
475
 
    for unversioned in branch.working_tree().extras():
476
 
        path = os.path.join(revdir, unversioned)
 
688
 
 
689
def get_direct_merges(revdir, revision, log):
 
690
    continuation = log.continuation_of
 
691
    previous_version = revision.version
 
692
    if pybaz.WorkingTree(revdir).tree_version != previous_version:
 
693
        pybaz.WorkingTree(revdir).set_tree_version(previous_version)
 
694
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir,
 
695
        revision.category.nonarch, revision.branch.nonarch,
 
696
        revision.version.nonarch, revision.archive, revision.patchlevel)
 
697
    temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
 
698
    os.rename(log_path, temp_path)
 
699
    merges = list(iter_new_merges(revdir, revision.version))
 
700
    direct = direct_merges(merges, [continuation])
 
701
    os.rename(temp_path, log_path)
 
702
    return direct
 
703
 
 
704
def unlink_unversioned(wt):
 
705
    for unversioned in wt.extras():
 
706
        path = wt.abspath(unversioned)
477
707
        if os.path.isdir(path):
478
708
            shutil.rmtree(path)
479
709
        else:
480
710
            os.unlink(path)
481
711
 
482
712
def get_log(tree, revision):
483
 
    log = tree.iter_logs(version=revision.version, reverse=True).next()
 
713
    log = pybaz.Patchlog(revision, tree=tree)
484
714
    assert str(log.revision) == str(revision), (log.revision, revision)
485
715
    return log
486
716
 
487
 
def get_revision(revdir, revision, skip_symlinks=False):
488
 
    revision.get(revdir)
489
 
    tree = pybaz.tree_root(revdir)
 
717
def get_revision(revdir, revision):
 
718
    tree = revision.get(revdir)
490
719
    log = get_log(tree, revision)
491
720
    try:
492
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
 
721
        return tree, bzr_inventory_data(tree), log
493
722
    except BadFileKind, e:
494
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
495
 
 
496
 
 
497
 
def apply_revision(revdir, revision, skip_symlinks=False):
498
 
    tree = pybaz.tree_root(revdir)
 
723
        raise UserError("Cannot convert %s because %s is a %s" %
 
724
                        (revision,e.path, e.kind))
 
725
 
 
726
 
 
727
def apply_revision(tree, revision):
499
728
    revision.apply(tree)
500
729
    log = get_log(tree, revision)
501
730
    try:
502
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
 
731
        return bzr_inventory_data(tree), log
503
732
    except BadFileKind, e:
504
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
505
 
 
506
 
 
 
733
        raise UserError("Cannot convert %s because %s is a %s" %
 
734
                        (revision,e.path, e.kind))
507
735
 
508
736
 
509
737
class BadFileKind(Exception):
515
743
        Exception.__init__(self, "File %s is of forbidden type %s" %
516
744
                           (os.path.join(tree_root, path), kind))
517
745
 
518
 
def bzr_inventory_data(tree, skip_symlinks=False):
 
746
 
 
747
def bzr_inventory_data(tree):
519
748
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
520
749
    inv_map = {}
521
750
    for arch_id, path in inv_iter:
522
 
        bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
523
 
        inv_map[path] = bzr_file_id 
 
751
        bzr_file_id = map_file_id(arch_id)
 
752
        inv_map[path] = bzr_file_id
524
753
 
525
754
    bzr_inv = []
526
755
    for path, file_id in inv_map.iteritems():
527
756
        full_path = os.path.join(tree, path)
528
757
        kind = bzrlib.osutils.file_kind(full_path)
529
 
        if skip_symlinks and kind == "symlink":
530
 
            continue
531
 
        if kind not in ("file", "directory"):
 
758
        if kind not in ("file", "directory", "symlink"):
532
759
            raise BadFileKind(tree, path, kind)
533
760
        parent_dir = os.path.dirname(path)
534
761
        if parent_dir != "":
539
766
    bzr_inv.sort()
540
767
    return bzr_inv
541
768
 
 
769
 
 
770
def baz_import_branch(to_location, from_branch, fast, max_count, verbose,
 
771
                      encoding, dry_run, reuse_history_list):
 
772
    to_location = os.path.realpath(str(to_location))
 
773
    if from_branch is not None:
 
774
        try:
 
775
            from_branch = pybaz.Version(from_branch)
 
776
        except pybaz.errors.NamespaceError:
 
777
            print "%s is not a valid Arch branch." % from_branch
 
778
            return 1
 
779
    if reuse_history_list is None:
 
780
        reuse_history_list = []
 
781
    import_version(to_location, from_branch, encoding, max_count=max_count,
 
782
                   reuse_history_from=reuse_history_list)
 
783
 
 
784
 
542
785
class NotInABranch(Exception):
543
786
    def __init__(self, path):
544
787
        Exception.__init__(self, "%s is not in a branch." % path)
545
788
        self.path = path
546
789
 
547
790
 
548
 
def find_branch(path):
549
 
    """
550
 
    >>> find_branch('/')
551
 
    Traceback (most recent call last):
552
 
    NotInABranch: / is not in a branch.
553
 
    >>> sb = bzrlib.ScratchBranch()
554
 
    >>> isinstance(find_branch(sb.base), Branch)
555
 
    True
556
 
    """
 
791
 
 
792
def baz_import(to_root_dir, from_archive, encoding, verbose=False,
 
793
               reuse_history_list=[], prefixes=None):
 
794
    if reuse_history_list is None:
 
795
        reuse_history_list = []
 
796
    to_root = str(os.path.realpath(to_root_dir))
 
797
    if not os.path.exists(to_root):
 
798
        os.mkdir(to_root)
 
799
    if prefixes is not None:
 
800
        prefixes = prefixes.split(':')
 
801
    import_archive(to_root, from_archive, verbose, encoding,
 
802
                   reuse_history_list, prefixes=prefixes)
 
803
 
 
804
 
 
805
def import_archive(to_root, from_archive, verbose,
 
806
                   encoding, reuse_history_from=[], standalone=False,
 
807
                   prefixes=None):
 
808
    def selected(version):
 
809
        if prefixes is None:
 
810
            return True
 
811
        else:
 
812
            for prefix in prefixes:
 
813
                if version.nonarch.startswith(prefix):
 
814
                    return True
 
815
            return False
 
816
    real_to = os.path.realpath(to_root)
 
817
    history_locations = [real_to] + reuse_history_from
 
818
    if standalone is False:
 
819
        try:
 
820
            bd = BzrDir.open(to_root)
 
821
            bd.find_repository()
 
822
        except NotBranchError:
 
823
            create_shared_repository(to_root)
 
824
        except NoRepositoryPresent:
 
825
            raise BzrCommandError("Can't create repository at existing branch.")
 
826
    versions = list(pybaz.Archive(str(from_archive)).iter_versions())
 
827
    progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
557
828
    try:
558
 
        return Branch.open(path)
559
 
    except NotBranchError, e:
560
 
        raise NotInABranch(path)
561
 
 
562
 
class cmd_baz_import(Command):
563
 
    """Import an Arch or Baz branch into a bzr branch"""
564
 
    takes_args = ['to_location', 'from_branch?']
565
 
    takes_options = ['verbose']
566
 
 
567
 
    def run(self, to_location, from_branch=None, skip_symlinks=False, 
568
 
            fast=False, max_count=None, verbose=False, dry_run=False):
569
 
        to_location = os.path.realpath(str(to_location))
570
 
        if from_branch is not None:
 
829
        for num, version in enumerate(versions):
 
830
            progress_bar.update("Branch", num, len(versions))
 
831
            if not selected(version):
 
832
                print "Skipping %s" % version
 
833
                continue
 
834
            target = os.path.join(to_root, map_namespace(version))
 
835
            if not os.path.exists(os.path.dirname(target)):
 
836
                os.makedirs(os.path.dirname(target))
571
837
            try:
572
 
                from_branch = pybaz.Version(from_branch)
573
 
            except pybaz.errors.NamespaceError:
574
 
                print "%s is not a valid Arch branch." % from_branch
575
 
                return 1
576
 
        import_version(to_location, from_branch)
 
838
                import_version(target, version, encoding,
 
839
                               reuse_history_from=reuse_history_from,
 
840
                               standalone=standalone)
 
841
            except pybaz.errors.ExecProblem,e:
 
842
                if str(e).find('The requested revision cannot be built.') != -1:
 
843
                    progress_bar.note(
 
844
                        "Skipping version %s as it cannot be built due"
 
845
                        " to a missing parent archive." % version)
 
846
                else:
 
847
                    raise
 
848
            except UserError, e:
 
849
                if str(e).find('already exists, and the last revision ') != -1:
 
850
                    progress_bar.note(
 
851
                        "Skipping version %s as it has had commits made"
 
852
                        " since it was converted to bzr." % version)
 
853
                else:
 
854
                    raise
 
855
    finally:
 
856
        progress_bar.finished()
 
857
 
 
858
 
 
859
def map_namespace(a_version):
 
860
    a_version = pybaz.Version("%s" % a_version)
 
861
    parser = NameParser(a_version)
 
862
    version = parser.get_version()
 
863
    branch = parser.get_branch()
 
864
    category = parser.get_category()
 
865
    if branch is None or branch == '':
 
866
        branch = "+trunk"
 
867
    if version == '0':
 
868
        return "%s/%s" % (category, branch)
 
869
    return "%s/%s/%s" % (category, version, branch)
 
870
 
 
871
 
 
872
def map_file_id(file_id):
 
873
    """Convert a baz file id to a bzr one."""
 
874
    return file_id.replace('%', '%25').replace('/', '%2f')