~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2006-10-25 13:08:35 UTC
  • Revision ID: abentley@panoramicfeedback.com-20061025130835-90663f6cd66311df
Release 0.12.0, update NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 by Aaron Bentley
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import errno
 
18
 
 
19
from bzrlib.bzrdir import BzrDir
 
20
import bzrlib.bzrdir as bzrdir
 
21
from bzrlib.errors import (BzrError,
 
22
                           NotBranchError,
 
23
                           NoWorkingTree,
 
24
                           BzrCommandError, 
 
25
                           NoSuchRevision,
 
26
                           NoRepositoryPresent,
 
27
                          )
 
28
from bzrlib.branch import Branch
 
29
from bzrlib.commit import Commit, NullCommitReporter
 
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
 
37
from errors import NoPyBaz
 
38
try:
 
39
    import pybaz
 
40
    import pybaz.errors
 
41
    from pybaz import NameParser as NameParser
 
42
    from pybaz.backends.baz import null_cmd
 
43
except ImportError:
 
44
    raise NoPyBaz
 
45
from fai import iter_new_merges, direct_merges
 
46
import tempfile
 
47
import os
 
48
import os.path
 
49
import shutil
 
50
import bzrlib
 
51
import bzrlib.trace
 
52
import bzrlib.merge
 
53
import bzrlib.inventory
 
54
import bzrlib.osutils
 
55
import sys
 
56
import email.Utils
 
57
from progress import *
 
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
 
 
69
def add_id(files, id=None):
 
70
    """Adds an explicit id to a list of files.
 
71
 
 
72
    :param files: the name of the file to add an id to
 
73
    :type files: list of str
 
74
    :param id: tag one file using the specified id, instead of generating id
 
75
    :type id: str
 
76
    """
 
77
    args = ["add-id"]
 
78
    if id is not None:
 
79
        args.extend(["--id", id])
 
80
    args.extend(files)
 
81
    return null_cmd(args)
 
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
 
 
90
def test_environ():
 
91
    """
 
92
    >>> q = test_environ()
 
93
    >>> os.path.exists(q)
 
94
    True
 
95
    >>> os.path.exists(os.path.join(q, "home", ".arch-params"))
 
96
    True
 
97
    >>> teardown_environ(q)
 
98
    >>> os.path.exists(q)
 
99
    False
 
100
    """
 
101
    global saved_dir
 
102
    saved_dir = os.getcwdu()
 
103
    tdir = tempfile.mkdtemp(prefix="testdir-")
 
104
    os.environ["HOME"] = os.path.join(tdir, "home")
 
105
    os.mkdir(os.environ["HOME"])
 
106
    arch_dir = os.path.join(tdir, "archive_dir")
 
107
    make_archive("test@example.com", arch_dir)
 
108
    work_dir = os.path.join(tdir, "work_dir")
 
109
    os.mkdir(work_dir)
 
110
    os.chdir(work_dir)
 
111
    pybaz.init_tree(work_dir, "test@example.com/test--test--0")
 
112
    lib_dir = os.path.join(tdir, "lib_dir")
 
113
    os.mkdir(lib_dir)
 
114
    pybaz.register_revision_library(lib_dir)
 
115
    pybaz.set_my_id("Test User<test@example.org>")
 
116
    return tdir
 
117
 
 
118
def add_file(path, text, id):
 
119
    """
 
120
    >>> q = test_environ()
 
121
    >>> add_file("path with space", "text", "lalala")
 
122
    >>> tree = pybaz.tree_root(".")
 
123
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
 
124
    >>> ("x_lalala", "path with space") in inv
 
125
    True
 
126
    >>> teardown_environ(q)
 
127
    """
 
128
    file(path, "wb").write(text)
 
129
    add_id([path], id)
 
130
 
 
131
 
 
132
def add_dir(path, id):
 
133
    """
 
134
    >>> q = test_environ()
 
135
    >>> add_dir("path with\(sp) space", "lalala")
 
136
    >>> tree = pybaz.tree_root(".")
 
137
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
 
138
    >>> ("x_lalala", "path with\(sp) space") in inv
 
139
    True
 
140
    >>> teardown_environ(q)
 
141
    """
 
142
    os.mkdir(path)
 
143
    add_id([path], id)
 
144
 
 
145
def teardown_environ(tdir):
 
146
    os.chdir(saved_dir)
 
147
    shutil.rmtree(tdir)
 
148
 
 
149
def timport(tree, summary):
 
150
    msg = tree.log_message()
 
151
    msg["summary"] = summary
 
152
    tree.import_(msg)
 
153
 
 
154
def commit(tree, summary):
 
155
    """
 
156
    >>> q = test_environ()
 
157
    >>> tree = pybaz.tree_root(".")
 
158
    >>> timport(tree, "import")
 
159
    >>> commit(tree, "commit")
 
160
    >>> logs = [str(l.revision) for l in tree.iter_logs()]
 
161
    >>> len(logs)
 
162
    2
 
163
    >>> logs[0]
 
164
    'test@example.com/test--test--0--base-0'
 
165
    >>> logs[1]
 
166
    'test@example.com/test--test--0--patch-1'
 
167
    >>> teardown_environ(q)
 
168
    """
 
169
    msg = tree.log_message()
 
170
    msg["summary"] = summary
 
171
    tree.commit(msg)
 
172
 
 
173
def commit_test_revisions():
 
174
    """
 
175
    >>> q = test_environ()
 
176
    >>> commit_test_revisions()
 
177
    >>> a = pybaz.Archive("test@example.com")
 
178
    >>> revisions = list(a.iter_revisions("test--test--0"))
 
179
    >>> len(revisions)
 
180
    3
 
181
    >>> str(revisions[2])
 
182
    'test@example.com/test--test--0--base-0'
 
183
    >>> str(revisions[1])
 
184
    'test@example.com/test--test--0--patch-1'
 
185
    >>> str(revisions[0])
 
186
    'test@example.com/test--test--0--patch-2'
 
187
    >>> teardown_environ(q)
 
188
    """
 
189
    tree = pybaz.tree_root(".")
 
190
    add_file("mainfile", "void main(void){}", "mainfile by aaron")
 
191
    timport(tree, "Created mainfile")
 
192
    file("mainfile", "wb").write("or something like that")
 
193
    commit(tree, "altered mainfile")
 
194
    add_file("ofile", "this is another file", "ofile by aaron")
 
195
    commit(tree, "altered mainfile")
 
196
 
 
197
 
 
198
def commit_more_test_revisions():
 
199
    """
 
200
    >>> q = test_environ()
 
201
    >>> commit_test_revisions()
 
202
    >>> commit_more_test_revisions()
 
203
    >>> a = pybaz.Archive("test@example.com")
 
204
    >>> revisions = list(a.iter_revisions("test--test--0"))
 
205
    >>> len(revisions)
 
206
    4
 
207
    >>> str(revisions[0])
 
208
    'test@example.com/test--test--0--patch-3'
 
209
    >>> teardown_environ(q)
 
210
    """
 
211
    tree = pybaz.tree_root(".")
 
212
    add_file("trainfile", "void train(void){}", "trainfile by aaron")
 
213
    commit(tree, "altered trainfile")
 
214
 
 
215
class NoSuchVersion(Exception):
 
216
    def __init__(self, version):
 
217
        Exception.__init__(self, "The version %s does not exist." % version)
 
218
        self.version = version
 
219
 
 
220
def version_ancestry(version):
 
221
    """
 
222
    >>> q = test_environ()
 
223
    >>> commit_test_revisions()
 
224
    >>> version = pybaz.Version("test@example.com/test--test--0")
 
225
    >>> ancestors = version_ancestry(version)
 
226
    >>> str(ancestors[0])
 
227
    'test@example.com/test--test--0--base-0'
 
228
    >>> str(ancestors[1])
 
229
    'test@example.com/test--test--0--patch-1'
 
230
    >>> version = pybaz.Version("test@example.com/test--test--0.5")
 
231
    >>> ancestors = version_ancestry(version)
 
232
    Traceback (most recent call last):
 
233
    NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
 
234
    >>> teardown_environ(q)
 
235
    """
 
236
    try:
 
237
        revision = version.iter_revisions(reverse=True).next()
 
238
    except StopIteration:
 
239
        return ()
 
240
    except:
 
241
        print version
 
242
        if not version.exists():
 
243
            raise NoSuchVersion(version)
 
244
        else:
 
245
            raise
 
246
    ancestors = list(revision.iter_ancestors(metoo=True))
 
247
    ancestors.reverse()
 
248
    return ancestors
 
249
 
 
250
def get_last_revision(branch):
 
251
    last_patch = branch.last_revision()
 
252
    try:
 
253
        return arch_revision(last_patch)
 
254
    except NotArchRevision:
 
255
        raise UserError(
 
256
            "Directory \"%s\" already exists, and the last revision is not"
 
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, reuse_history_from=[]):
 
285
    last_patch = None
 
286
    old_revno = None
 
287
    output_exists = os.path.exists(output_dir)
 
288
    if output_exists:
 
289
        # We are starting from an existing directory, figure out what
 
290
        # the current version is
 
291
        branch = Branch.open(output_dir)
 
292
        last_patch = get_last_revision(branch)
 
293
        if last_patch is None:
 
294
            if branch.last_revision() != None:
 
295
                raise NotPreviousImport(branch.base)
 
296
        elif version is None:
 
297
            version = last_patch.version
 
298
    elif version is None:
 
299
        raise UserError("No version specified, and directory does not exist.")
 
300
 
 
301
    try:
 
302
        ancestors = version_ancestry(version)
 
303
        if not output_exists and reuse_history_from != []:
 
304
            for ancestor in reversed(ancestors):
 
305
                if last_patch is not None:
 
306
                    # found something to copy
 
307
                    break
 
308
                # try to grab a copy of ancestor
 
309
                # note that is not optimised: we could look for namespace
 
310
                # transitions and only look for the past after the 
 
311
                # transition.
 
312
                for history_root in reuse_history_from:
 
313
                    possible_source = os.path.join(history_root,
 
314
                        map_namespace(ancestor.version))
 
315
                    try:
 
316
                        source = Branch.open(possible_source)
 
317
                        rev_id = revision_id(ancestor)
 
318
                        if rev_id in source.revision_history():
 
319
                            do_branch(source, output_dir, rev_id)
 
320
                            last_patch = ancestor
 
321
                            break
 
322
                    except NotBranchError:
 
323
                        pass
 
324
    except NoSuchVersion, e:
 
325
        raise UserError(str(e))
 
326
 
 
327
    if last_patch:
 
328
        for i in range(len(ancestors)):
 
329
            if ancestors[i] == last_patch:
 
330
                break
 
331
        else:
 
332
            raise UserError("Directory \"%s\" already exists, and the last "
 
333
                "revision (%s) is not in the ancestry of %s" % 
 
334
                (output_dir, last_patch, version))
 
335
        # Strip off all of the ancestors which are already present
 
336
        # And get a directory starting with the latest ancestor
 
337
        latest_ancestor = ancestors[i]
 
338
        old_revno = Branch.open(output_dir).revno()
 
339
        ancestors = ancestors[i+1:]
 
340
    return ancestors, old_revno
 
341
 
 
342
 
 
343
###class Importer(object):
 
344
###    """An importer.
 
345
###    
 
346
###    Currently this is used as a parameter object, though more behaviour is
 
347
###    possible later.
 
348
###    """
 
349
###
 
350
###    def __init__(self, output_dir, version, fast=False,
 
351
###                 verbose=False, dry_run=False, max_count=None, 
 
352
###                   reuse_history_from=[]):
 
353
###        self.output_dir = output_dir
 
354
###        self.version = version
 
355
###        self.
 
356
 
 
357
 
 
358
def import_version(output_dir, version, fast=False,
 
359
                   verbose=False, dry_run=False, max_count=None,
 
360
                   reuse_history_from=[], standalone=True):
 
361
    """
 
362
    >>> q = test_environ()
 
363
    
 
364
    Progress bars output to stderr, but doctest does not capture that.
 
365
 
 
366
    >>> old_stderr = sys.stderr
 
367
    >>> sys.stderr = sys.stdout
 
368
 
 
369
    >>> result_path = os.path.join(q, "result")
 
370
    >>> commit_test_revisions()
 
371
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
 
372
    >>> old_ui = bzrlib.ui.ui_factory
 
373
    >>> bzrlib.ui.ui_factory = bzrlib.ui.text.TextUIFactory(
 
374
    ...     bar_type=bzrlib.progress.DotsProgressBar)
 
375
 
 
376
    >>> import_version('/', version, dry_run=True)
 
377
    Traceback (most recent call last):
 
378
    NotPreviousImport: / is not the location of a previous import.
 
379
    >>> import_version(result_path, version, dry_run=True)
 
380
    Traceback (most recent call last):
 
381
    UserError: The version test@example.com/test--test--0.1 does not exist.
 
382
    >>> version = pybaz.Version("test@example.com/test--test--0")
 
383
    >>> import_version(result_path, version, dry_run=True) #doctest: +ELLIPSIS
 
384
    importing test@example.com/test--test--0 into ...
 
385
    ...
 
386
    revisions: ..........................................
 
387
    Dry run, not modifying output_dir
 
388
    Cleaning up
 
389
    >>> import_version(result_path, version) #doctest: +ELLIPSIS
 
390
    importing test@example.com/test--test--0 into ...
 
391
    ...
 
392
    revisions: .....................................................................
 
393
    Cleaning up
 
394
    Import complete.
 
395
    >>> import_version(result_path, version) #doctest: +ELLIPSIS
 
396
    Tree is up-to-date with test@example.com/test--test--0--patch-2
 
397
    >>> commit_more_test_revisions()
 
398
    >>> import_version(result_path, version) #doctest: +ELLIPSIS
 
399
    importing test@example.com/test--test--0 into ...
 
400
    revisions: ....................................................
 
401
    Cleaning up
 
402
    Import complete.
 
403
    >>> bzrlib.ui.ui_factory = old_ui
 
404
    >>> sys.stderr = old_stderr
 
405
    >>> teardown_environ(q)
 
406
    """
 
407
    progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
 
408
    try:
 
409
        try:
 
410
            ancestors, old_revno = get_remaining_revisions(output_dir, version,
 
411
                                                           reuse_history_from)
 
412
        except NotBranchError, e:
 
413
            raise NotPreviousImport(e.path)
 
414
        if old_revno is None and len(ancestors) == 0:
 
415
            progress_bar.note('Version %s has no revisions.' % version)
 
416
            return
 
417
        if len(ancestors) == 0:
 
418
            last_revision = get_last_revision(Branch.open(output_dir))
 
419
            progress_bar.note('Tree is up-to-date with %s' % last_revision)
 
420
            return
 
421
 
 
422
        progress_bar.note("importing %s into %s" % (version, output_dir))
 
423
    
 
424
        tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
 
425
                                   dir=os.path.dirname(output_dir))
 
426
        try:
 
427
            wt = WorkingTree.open(output_dir)
 
428
        except (NotBranchError, NoWorkingTree):
 
429
            wt = None
 
430
        try:
 
431
            for result in iter_import_version(output_dir, ancestors, tempdir,
 
432
                    pb=progress_bar,
 
433
                    fast=fast, verbose=verbose, dry_run=dry_run,
 
434
                    max_count=max_count, standalone=standalone):
 
435
                show_progress(progress_bar, result)
 
436
            if dry_run:
 
437
                progress_bar.note('Dry run, not modifying output_dir')
 
438
                return
 
439
    
 
440
            # Update the working tree of the branch
 
441
            try:
 
442
                wt = WorkingTree.open(output_dir)
 
443
            except NoWorkingTree:
 
444
                wt = None
 
445
            if wt is not None:
 
446
                wt.set_last_revision(wt.branch.last_revision())
 
447
                wt.set_root_id(BAZ_IMPORT_ROOT)
 
448
                wt.revert([])
 
449
    
 
450
        finally:
 
451
            
 
452
            progress_bar.note('Cleaning up')
 
453
            shutil.rmtree(tempdir)
 
454
        progress_bar.note("Import complete.")
 
455
    finally:
 
456
        progress_bar.finished()
 
457
            
 
458
class UserError(BzrCommandError):
 
459
    def __init__(self, message):
 
460
        """Exception to throw when a user makes an impossible request
 
461
        :param message: The message to emit when printing this exception
 
462
        :type message: string
 
463
        """
 
464
        BzrCommandError.__init__(self, message)
 
465
 
 
466
class NotPreviousImport(UserError):
 
467
    def __init__(self, path):
 
468
        UserError.__init__(self, "%s is not the location of a previous import."
 
469
                           % path)
 
470
 
 
471
 
 
472
def revision_id(arch_revision):
 
473
    """
 
474
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
 
475
    designates a revision imported with an experimental algorithm.  A number
 
476
    would indicate a particular standardized version.
 
477
 
 
478
    :param arch_revision: The Arch revision to generate an ID for.
 
479
 
 
480
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
 
481
    'Arch-1:you@example.com%cat--br--0--base-0'
 
482
    """
 
483
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
 
484
 
 
485
class NotArchRevision(Exception):
 
486
    def __init__(self, revision_id):
 
487
        msg = "The revision id %s does not look like it came from Arch."\
 
488
            % revision_id
 
489
        Exception.__init__(self, msg)
 
490
 
 
491
def arch_revision(revision_id):
 
492
    """
 
493
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
 
494
    Traceback (most recent call last):
 
495
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
 
496
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
 
497
    Traceback (most recent call last):
 
498
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
 
499
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
 
500
    'jrandom@example.com/test--test--0--patch-5'
 
501
    """
 
502
    if revision_id is None:
 
503
        return None
 
504
    if revision_id[:7] != 'Arch-1:':
 
505
        raise NotArchRevision(revision_id)
 
506
    else:
 
507
        try:
 
508
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
 
509
        except pybaz.errors.NamespaceError, e:
 
510
            raise NotArchRevision(revision_id)
 
511
 
 
512
 
 
513
def create_shared_repository(output_dir):
 
514
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
515
    bd.create_repository(shared=True)
 
516
 
 
517
def create_branch(output_dir):
 
518
    os.mkdir(output_dir)
 
519
    bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
 
520
    return bd.create_branch()
 
521
 
 
522
 
 
523
def create_checkout(source, to_location, revision_id=None):
 
524
    checkout = bzrdir.BzrDirMetaFormat1().initialize(to_location)
 
525
    bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
 
526
    return checkout.create_workingtree(revision_id)
 
527
 
 
528
 
 
529
def create_checkout_metadata(source, to_location, revision_id=None):
 
530
    if revision_id is None:
 
531
        revision_id = source.last_revision()
 
532
    wt = create_checkout(source, to_location, NULL_REVISION)
 
533
    wt.set_last_revision(revision_id)
 
534
    if revision_id not in (NULL_REVISION, None):
 
535
        wt._write_inventory(wt.basis_tree().inventory)
 
536
    return wt
 
537
 
 
538
 
 
539
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
 
540
                        verbose=False, dry_run=False, max_count=None,
 
541
                        standalone=False):
 
542
    revdir = None
 
543
 
 
544
    # Uncomment this for testing, it basically just has baz2bzr only update
 
545
    # 5 patches at a time
 
546
    if max_count:
 
547
        ancestors = ancestors[:max_count]
 
548
 
 
549
    # Not sure if I want this output. basically it tells you ahead of time
 
550
    # what it is going to do, but then later it tells you as it is doing it.
 
551
    # what probably would be best would be to collapse it into ranges, so that
 
552
    # this gives the simple view, and then later it gives the blow by blow.
 
553
    #if verbose:
 
554
    #    print 'Adding the following revisions:'
 
555
    #    for a in ancestors:
 
556
    #        print '\t%s' % a
 
557
 
 
558
    previous_version=None
 
559
    missing_ancestor = None
 
560
    if dry_run:
 
561
        dry_output_dir = os.path.join(tempdir, 'od')
 
562
        if os.path.exists(output_dir):
 
563
            shutil.copytree(output_dir, dry_output_dir)
 
564
        output_dir = dry_output_dir
 
565
 
 
566
    if os.path.exists(output_dir):
 
567
        target_branch = Branch.open(output_dir)
 
568
    else:
 
569
        if standalone:
 
570
            wt = BzrDir.create_standalone_workingtree(output_dir)
 
571
            target_branch = wt.branch
 
572
        else:
 
573
            target_branch = create_branch(output_dir)
 
574
 
 
575
    for i in range(len(ancestors)):
 
576
        revision = ancestors[i]
 
577
        rev_id = revision_id(revision)
 
578
        direct_merges = []
 
579
        if verbose:
 
580
            version = str(revision.version)
 
581
            if version != previous_version:
 
582
                pb.note('On version: %s' % version)
 
583
            yield Progress(str(revision.patchlevel), i, len(ancestors))
 
584
            previous_version = version
 
585
        else:
 
586
            yield Progress("revisions", i, len(ancestors))
 
587
 
 
588
        if target_branch.repository.has_revision(rev_id):
 
589
            target_branch.append_revision(rev_id)
 
590
            continue
 
591
        if revdir is None:
 
592
            revdir = os.path.join(tempdir, "rd")
 
593
            try:
 
594
                tree, baz_inv, log = get_revision(revdir, revision)
 
595
            except pybaz.errors.ExecProblem, e:
 
596
                if ("%s" % e.args).find('could not connect') == -1:
 
597
                    raise
 
598
                missing_ancestor = revision
 
599
                revdir = None
 
600
                pb.note("unable to access ancestor %s, making into a merge."
 
601
                       % missing_ancestor)
 
602
                continue
 
603
            target_tree = create_checkout_metadata(target_branch, revdir)
 
604
            branch = target_tree.branch
 
605
        else:
 
606
            old = os.path.join(revdir, ".bzr")
 
607
            new = os.path.join(tempdir, ".bzr")
 
608
            os.rename(old, new)
 
609
            baz_inv, log = apply_revision(tree, revision)
 
610
            os.rename(new, old)
 
611
            target_tree = WorkingTree.open(revdir)
 
612
            branch = target_tree.branch
 
613
        # cached so we can delete the log
 
614
        log_date = log.date
 
615
        log_summary = log.summary
 
616
        log_description = log.description
 
617
        is_continuation = log.continuation_of is not None
 
618
        log_creator = log.creator
 
619
        direct_merges = get_direct_merges(revdir, revision, log)
 
620
 
 
621
        timestamp = email.Utils.mktime_tz(log_date + (0,))
 
622
        if log_summary is None:
 
623
            log_summary = ""
 
624
        # log_descriptions of None and "" are ignored.
 
625
        if not is_continuation and log_description:
 
626
            log_message = "\n".join((log_summary, log_description))
 
627
        else:
 
628
            log_message = log_summary
 
629
        target_tree.lock_write()
 
630
        branch.lock_write()
 
631
        try:
 
632
            if missing_ancestor:
 
633
                # if we want it to be in revision-history, do that here.
 
634
                target_tree.set_parent_ids([revision_id(missing_ancestor)],
 
635
                                           allow_leftmost_as_ghost=True)
 
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_root_id(BAZ_IMPORT_ROOT)
 
640
            target_tree.set_inventory(baz_inv)
 
641
            commitobj = Commit(reporter=ImportCommitReporter())
 
642
            commitobj.commit(working_tree=target_tree,
 
643
                             message=log_message.decode('ascii', 'replace'), 
 
644
                             verbose=False, committer=log_creator,
 
645
                             timestamp=timestamp, timezone=0, rev_id=rev_id,
 
646
                             revprops={})
 
647
        finally:
 
648
            target_tree.unlock()
 
649
            branch.unlock()
 
650
    yield Progress("revisions", len(ancestors), len(ancestors))
 
651
 
 
652
def get_direct_merges(revdir, revision, log):
 
653
    continuation = log.continuation_of
 
654
    previous_version = revision.version
 
655
    if pybaz.WorkingTree(revdir).tree_version != previous_version:
 
656
        pybaz.WorkingTree(revdir).set_tree_version(previous_version)
 
657
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir, 
 
658
        revision.category.nonarch, revision.branch.nonarch, 
 
659
        revision.version.nonarch, revision.archive, revision.patchlevel)
 
660
    temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
 
661
    os.rename(log_path, temp_path)
 
662
    merges = list(iter_new_merges(revdir, revision.version))
 
663
    direct = direct_merges(merges, [continuation])
 
664
    os.rename(temp_path, log_path)
 
665
    return direct
 
666
 
 
667
def unlink_unversioned(wt):
 
668
    for unversioned in wt.extras():
 
669
        path = wt.abspath(unversioned)
 
670
        if os.path.isdir(path):
 
671
            shutil.rmtree(path)
 
672
        else:
 
673
            os.unlink(path)
 
674
 
 
675
def get_log(tree, revision):
 
676
    log = pybaz.Patchlog(revision, tree=tree)
 
677
    assert str(log.revision) == str(revision), (log.revision, revision)
 
678
    return log
 
679
 
 
680
def get_revision(revdir, revision):
 
681
    tree = revision.get(revdir)
 
682
    log = get_log(tree, revision)
 
683
    try:
 
684
        return tree, bzr_inventory_data(tree), log 
 
685
    except BadFileKind, e:
 
686
        raise UserError("Cannot convert %s because %s is a %s" % 
 
687
                        (revision,e.path, e.kind))
 
688
 
 
689
 
 
690
def apply_revision(tree, revision):
 
691
    revision.apply(tree)
 
692
    log = get_log(tree, revision)
 
693
    try:
 
694
        return bzr_inventory_data(tree), log
 
695
    except BadFileKind, e:
 
696
        raise UserError("Cannot convert %s because %s is a %s" % 
 
697
                        (revision,e.path, e.kind))
 
698
 
 
699
 
 
700
class BadFileKind(Exception):
 
701
    """The file kind is not permitted in bzr inventories"""
 
702
    def __init__(self, tree_root, path, kind):
 
703
        self.tree_root = tree_root
 
704
        self.path = path
 
705
        self.kind = kind
 
706
        Exception.__init__(self, "File %s is of forbidden type %s" %
 
707
                           (os.path.join(tree_root, path), kind))
 
708
 
 
709
 
 
710
def bzr_inventory_data(tree):
 
711
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
 
712
    inv_map = {}
 
713
    for arch_id, path in inv_iter:
 
714
        bzr_file_id = map_file_id(arch_id)
 
715
        inv_map[path] = bzr_file_id 
 
716
 
 
717
    bzr_inv = []
 
718
    for path, file_id in inv_map.iteritems():
 
719
        full_path = os.path.join(tree, path)
 
720
        kind = bzrlib.osutils.file_kind(full_path)
 
721
        if kind not in ("file", "directory", "symlink"):
 
722
            raise BadFileKind(tree, path, kind)
 
723
        parent_dir = os.path.dirname(path)
 
724
        if parent_dir != "":
 
725
            parent_id = inv_map[parent_dir]
 
726
        else:
 
727
            parent_id = bzrlib.inventory.ROOT_ID
 
728
        bzr_inv.append((path, file_id, parent_id, kind))
 
729
    bzr_inv.sort()
 
730
    return bzr_inv
 
731
 
 
732
 
 
733
def baz_import_branch(to_location, from_branch, fast, max_count, verbose, 
 
734
                      dry_run, reuse_history_list):
 
735
    to_location = os.path.realpath(str(to_location))
 
736
    if from_branch is not None:
 
737
        try:
 
738
            from_branch = pybaz.Version(from_branch)
 
739
        except pybaz.errors.NamespaceError:
 
740
            print "%s is not a valid Arch branch." % from_branch
 
741
            return 1
 
742
    if reuse_history_list is None:
 
743
        reuse_history_list = []
 
744
    import_version(to_location, from_branch, 
 
745
                   max_count=max_count, 
 
746
                   reuse_history_from=reuse_history_list)
 
747
 
 
748
 
 
749
class NotInABranch(Exception):
 
750
    def __init__(self, path):
 
751
        Exception.__init__(self, "%s is not in a branch." % path)
 
752
        self.path = path
 
753
 
 
754
 
 
755
 
 
756
def baz_import(to_root_dir, from_archive, verbose=False, reuse_history_list=[],
 
757
               prefixes=None):
 
758
    if reuse_history_list is None:
 
759
        reuse_history_list = []
 
760
    to_root = str(os.path.realpath(to_root_dir))
 
761
    if not os.path.exists(to_root):
 
762
        os.mkdir(to_root)
 
763
    if prefixes is not None:
 
764
        prefixes = prefixes.split(':')
 
765
    import_archive(to_root, from_archive, verbose,
 
766
                   reuse_history_list, prefixes=prefixes)
 
767
 
 
768
 
 
769
def import_archive(to_root, from_archive, verbose,
 
770
                   reuse_history_from=[], standalone=False,
 
771
                   prefixes=None):
 
772
    def selected(version):
 
773
        if prefixes is None:
 
774
            return True
 
775
        else:
 
776
            for prefix in prefixes:
 
777
                if version.nonarch.startswith(prefix):
 
778
                    return True
 
779
            return False
 
780
    real_to = os.path.realpath(to_root)
 
781
    history_locations = [real_to] + reuse_history_from
 
782
    if standalone is False:
 
783
        try:
 
784
            bd = BzrDir.open(to_root)
 
785
            bd.find_repository()
 
786
        except NotBranchError:
 
787
            create_shared_repository(to_root)
 
788
        except NoRepositoryPresent:
 
789
            raise BzrCommandError("Can't create repository at existing branch.")
 
790
    versions = list(pybaz.Archive(str(from_archive)).iter_versions())
 
791
    progress_bar = bzrlib.ui.ui_factory.nested_progress_bar()
 
792
    try:
 
793
        for num, version in enumerate(versions):
 
794
            progress_bar.update("Branch", num, len(versions))
 
795
            if not selected(version):
 
796
                print "Skipping %s" % version
 
797
                continue
 
798
            target = os.path.join(to_root, map_namespace(version))
 
799
            if not os.path.exists(os.path.dirname(target)):
 
800
                os.makedirs(os.path.dirname(target))
 
801
            try:
 
802
                import_version(target, version,
 
803
                               reuse_history_from=reuse_history_from, 
 
804
                               standalone=standalone)
 
805
            except pybaz.errors.ExecProblem,e:
 
806
                if str(e).find('The requested revision cannot be built.') != -1:
 
807
                    progress_bar.note(
 
808
                        "Skipping version %s as it cannot be built due"
 
809
                        " to a missing parent archive." % version)
 
810
                else:
 
811
                    raise
 
812
            except UserError, e:
 
813
                if str(e).find('already exists, and the last revision ') != -1:
 
814
                    progress_bar.note(
 
815
                        "Skipping version %s as it has had commits made"
 
816
                        " since it was converted to bzr." % version)
 
817
                else:
 
818
                    raise
 
819
    finally:
 
820
        progress_bar.finished()
 
821
 
 
822
 
 
823
def map_namespace(a_version):
 
824
    a_version = pybaz.Version("%s" % a_version)
 
825
    parser = NameParser(a_version)
 
826
    version = parser.get_version()
 
827
    branch = parser.get_branch()
 
828
    category = parser.get_category()
 
829
    if branch is None or branch == '':
 
830
        branch = "+trunk"
 
831
    if version == '0':
 
832
        return "%s/%s" % (category, branch)
 
833
    return "%s/%s/%s" % (category, version, branch)
 
834
 
 
835
 
 
836
def map_file_id(file_id):
 
837
    """Convert a baz file id to a bzr one."""
 
838
    return file_id.replace('%', '%25').replace('/', '%2f')