~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2006-08-02 03:23:09 UTC
  • mto: This revision was merged to the branch mainline in revision 425.
  • Revision ID: aaron.bentley@utoronto.ca-20060802032309-6ad0139e61304b19
Etienne Goyer: remove unused shebangs, update packaging

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