~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-10-03 17:44:39 UTC
  • mto: (147.2.17)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: abentley@panoramicfeedback.com-20051003174439-e83c9f189855a62a
Quieten commits

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 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
from bzrlib.errors import NotBranchError
 
18
from bzrlib.branch import Branch
 
19
from bzrlib.commit import Commit, NullCommitReporter
 
20
from bzrlib.commands import Command
 
21
from errors import NoPyBaz
 
22
try:
 
23
    import pybaz
 
24
    import pybaz.errors
 
25
    from pybaz import NameParser as NameParser
 
26
    from pybaz.backends.baz import null_cmd
 
27
except ImportError:
 
28
    raise NoPyBaz
 
29
from fai import iter_new_merges, direct_merges
 
30
import tempfile
 
31
import os
 
32
import os.path
 
33
import shutil
 
34
import bzrlib
 
35
from bzrlib.errors import BzrError
 
36
import bzrlib.trace
 
37
import bzrlib.merge
 
38
import bzrlib.inventory
 
39
import bzrlib.osutils
 
40
import sys
 
41
import email.Utils
 
42
from progress import *
 
43
 
 
44
class ImportCommitReporter(NullCommitReporter):
 
45
    def __init__(self, pb):
 
46
        self.pb = pb
 
47
 
 
48
    def escaped(self, escape_count, message):
 
49
        self.pb.clear()
 
50
        bzrlib.trace.warning("replaced %d control characters in message" %
 
51
                             escape_count)
 
52
 
 
53
def add_id(files, id=None):
 
54
    """Adds an explicit id to a list of files.
 
55
 
 
56
    :param files: the name of the file to add an id to
 
57
    :type files: list of str
 
58
    :param id: tag one file using the specified id, instead of generating id
 
59
    :type id: str
 
60
    """
 
61
    args = ["add-id"]
 
62
    if id is not None:
 
63
        args.extend(["--id", id])
 
64
    args.extend(files)
 
65
    return null_cmd(args)
 
66
 
 
67
saved_dir = None
 
68
 
 
69
def test_environ():
 
70
    """
 
71
    >>> q = test_environ()
 
72
    >>> os.path.exists(q)
 
73
    True
 
74
    >>> os.path.exists(os.path.join(q, "home", ".arch-params"))
 
75
    True
 
76
    >>> teardown_environ(q)
 
77
    >>> os.path.exists(q)
 
78
    False
 
79
    """
 
80
    global saved_dir
 
81
    saved_dir = os.getcwdu()
 
82
    tdir = tempfile.mkdtemp(prefix="testdir-")
 
83
    os.environ["HOME"] = os.path.join(tdir, "home")
 
84
    os.mkdir(os.environ["HOME"])
 
85
    arch_dir = os.path.join(tdir, "archive_dir")
 
86
    pybaz.make_archive("test@example.com", arch_dir)
 
87
    work_dir = os.path.join(tdir, "work_dir")
 
88
    os.mkdir(work_dir)
 
89
    os.chdir(work_dir)
 
90
    pybaz.init_tree(work_dir, "test@example.com/test--test--0")
 
91
    lib_dir = os.path.join(tdir, "lib_dir")
 
92
    os.mkdir(lib_dir)
 
93
    pybaz.register_revision_library(lib_dir)
 
94
    pybaz.set_my_id("Test User<test@example.org>")
 
95
    return tdir
 
96
 
 
97
def add_file(path, text, id):
 
98
    """
 
99
    >>> q = test_environ()
 
100
    >>> add_file("path with space", "text", "lalala")
 
101
    >>> tree = pybaz.tree_root(".")
 
102
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
 
103
    >>> ("x_lalala", "path with space") in inv
 
104
    True
 
105
    >>> teardown_environ(q)
 
106
    """
 
107
    file(path, "wb").write(text)
 
108
    add_id([path], id)
 
109
 
 
110
 
 
111
def add_dir(path, id):
 
112
    """
 
113
    >>> q = test_environ()
 
114
    >>> add_dir("path with\(sp) space", "lalala")
 
115
    >>> tree = pybaz.tree_root(".")
 
116
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
 
117
    >>> ("x_lalala", "path with\(sp) space") in inv
 
118
    True
 
119
    >>> teardown_environ(q)
 
120
    """
 
121
    os.mkdir(path)
 
122
    add_id([path], id)
 
123
 
 
124
def teardown_environ(tdir):
 
125
    os.chdir(saved_dir)
 
126
    shutil.rmtree(tdir)
 
127
 
 
128
def timport(tree, summary):
 
129
    msg = tree.log_message()
 
130
    msg["summary"] = summary
 
131
    tree.import_(msg)
 
132
 
 
133
def commit(tree, summary):
 
134
    """
 
135
    >>> q = test_environ()
 
136
    >>> tree = pybaz.tree_root(".")
 
137
    >>> timport(tree, "import")
 
138
    >>> commit(tree, "commit")
 
139
    >>> logs = [str(l.revision) for l in tree.iter_logs()]
 
140
    >>> len(logs)
 
141
    2
 
142
    >>> logs[0]
 
143
    'test@example.com/test--test--0--base-0'
 
144
    >>> logs[1]
 
145
    'test@example.com/test--test--0--patch-1'
 
146
    >>> teardown_environ(q)
 
147
    """
 
148
    msg = tree.log_message()
 
149
    msg["summary"] = summary
 
150
    tree.commit(msg)
 
151
 
 
152
def commit_test_revisions():
 
153
    """
 
154
    >>> q = test_environ()
 
155
    >>> commit_test_revisions()
 
156
    >>> a = pybaz.Archive("test@example.com")
 
157
    >>> revisions = list(a.iter_revisions("test--test--0"))
 
158
    >>> len(revisions)
 
159
    3
 
160
    >>> str(revisions[2])
 
161
    'test@example.com/test--test--0--base-0'
 
162
    >>> str(revisions[1])
 
163
    'test@example.com/test--test--0--patch-1'
 
164
    >>> str(revisions[0])
 
165
    'test@example.com/test--test--0--patch-2'
 
166
    >>> teardown_environ(q)
 
167
    """
 
168
    tree = pybaz.tree_root(".")
 
169
    add_file("mainfile", "void main(void){}", "mainfile by aaron")
 
170
    timport(tree, "Created mainfile")
 
171
    file("mainfile", "wb").write("or something like that")
 
172
    commit(tree, "altered mainfile")
 
173
    add_file("ofile", "this is another file", "ofile by aaron")
 
174
    commit(tree, "altered mainfile")
 
175
 
 
176
 
 
177
def commit_more_test_revisions():
 
178
    """
 
179
    >>> q = test_environ()
 
180
    >>> commit_test_revisions()
 
181
    >>> commit_more_test_revisions()
 
182
    >>> a = pybaz.Archive("test@example.com")
 
183
    >>> revisions = list(a.iter_revisions("test--test--0"))
 
184
    >>> len(revisions)
 
185
    4
 
186
    >>> str(revisions[0])
 
187
    'test@example.com/test--test--0--patch-3'
 
188
    >>> teardown_environ(q)
 
189
    """
 
190
    tree = pybaz.tree_root(".")
 
191
    add_file("trainfile", "void train(void){}", "trainfile by aaron")
 
192
    commit(tree, "altered trainfile")
 
193
 
 
194
class NoSuchVersion(Exception):
 
195
    def __init__(self, version):
 
196
        Exception.__init__(self, "The version %s does not exist." % version)
 
197
        self.version = version
 
198
 
 
199
def version_ancestry(version):
 
200
    """
 
201
    >>> q = test_environ()
 
202
    >>> commit_test_revisions()
 
203
    >>> version = pybaz.Version("test@example.com/test--test--0")
 
204
    >>> ancestors = version_ancestry(version)
 
205
    >>> str(ancestors[0])
 
206
    'test@example.com/test--test--0--base-0'
 
207
    >>> str(ancestors[1])
 
208
    'test@example.com/test--test--0--patch-1'
 
209
    >>> version = pybaz.Version("test@example.com/test--test--0.5")
 
210
    >>> ancestors = version_ancestry(version)
 
211
    Traceback (most recent call last):
 
212
    NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
 
213
    >>> teardown_environ(q)
 
214
    """
 
215
    try:
 
216
        revision = version.iter_revisions(reverse=True).next()
 
217
    except StopIteration:
 
218
        return ()
 
219
    except:
 
220
        print version
 
221
        if not version.exists():
 
222
            raise NoSuchVersion(version)
 
223
        else:
 
224
            raise
 
225
    ancestors = list(revision.iter_ancestors(metoo=True))
 
226
    ancestors.reverse()
 
227
    return ancestors
 
228
 
 
229
def get_last_revision(branch):
 
230
    last_patch = branch.last_revision()
 
231
    try:
 
232
        return arch_revision(last_patch)
 
233
    except NotArchRevision:
 
234
        raise UserError(
 
235
            "Directory \"%s\" already exists, and the last revision is not"
 
236
            " an Arch revision (%s)" % (output_dir, last_patch))
 
237
 
 
238
 
 
239
def get_remaining_revisions(output_dir, version):
 
240
    last_patch = None
 
241
    old_revno = None
 
242
    if os.path.exists(output_dir):
 
243
        # We are starting from an existing directory, figure out what
 
244
        # the current version is
 
245
        branch = Branch.open(output_dir)
 
246
        last_patch = get_last_revision(branch)
 
247
        if version is None:
 
248
            version = last_patch.version
 
249
    elif version is None:
 
250
        raise UserError("No version specified, and directory does not exist.")
 
251
 
 
252
    try:
 
253
        ancestors = version_ancestry(version)
 
254
    except NoSuchVersion, e:
 
255
        raise UserError(e)
 
256
 
 
257
    if last_patch:
 
258
        for i in range(len(ancestors)):
 
259
            if ancestors[i] == last_patch:
 
260
                break
 
261
        else:
 
262
            raise UserError("Directory \"%s\" already exists, and the last "
 
263
                "revision (%s) is not in the ancestry of %s" % 
 
264
                (output_dir, last_patch, version))
 
265
        # Strip off all of the ancestors which are already present
 
266
        # And get a directory starting with the latest ancestor
 
267
        latest_ancestor = ancestors[i]
 
268
        old_revno = Branch.open(output_dir).revno()
 
269
        ancestors = ancestors[i+1:]
 
270
    return ancestors, old_revno
 
271
 
 
272
def import_version(output_dir, version, printer, fancy=True, fast=False,
 
273
                   verbose=False, dry_run=False, max_count=None):
 
274
    """
 
275
    >>> q = test_environ()
 
276
    >>> result_path = os.path.join(q, "result")
 
277
    >>> commit_test_revisions()
 
278
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
 
279
    >>> def printer(message): print message
 
280
    >>> import_version('/', version, printer, fancy=False, dry_run=True)
 
281
    Traceback (most recent call last):
 
282
    UserError: / exists, but is not a bzr branch.
 
283
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
 
284
    Traceback (most recent call last):
 
285
    UserError: The version test@example.com/test--test--0.1 does not exist.
 
286
    >>> version = pybaz.Version("test@example.com/test--test--0")
 
287
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
 
288
    not fancy
 
289
    ....
 
290
    Dry run, not modifying output_dir
 
291
    Cleaning up
 
292
    >>> import_version(result_path, version, printer, fancy=False)
 
293
    not fancy
 
294
    ....
 
295
    Cleaning up
 
296
    Import complete.
 
297
    >>> import_version(result_path, version, printer, fancy=False)
 
298
    Tree is up-to-date with test@example.com/test--test--0--patch-2
 
299
    >>> commit_more_test_revisions()
 
300
    >>> import_version(result_path, version, printer, fancy=False)
 
301
    not fancy
 
302
    ..
 
303
    Cleaning up
 
304
    Import complete.
 
305
    >>> teardown_environ(q)
 
306
    """
 
307
    try:
 
308
        ancestors, old_revno = get_remaining_revisions(output_dir, version)
 
309
    except NotBranchError, e:
 
310
        raise UserError("%s exists, but is not a bzr branch." % output_dir)
 
311
    if old_revno is None and len(ancestors) == 0:
 
312
        print 'Version %s has no revisions.' % version
 
313
        return
 
314
    if len(ancestors) == 0:
 
315
        last_revision = get_last_revision(Branch.open(output_dir))
 
316
        print 'Tree is up-to-date with %s' % last_revision
 
317
        return
 
318
 
 
319
    progress_bar = ProgressBar()
 
320
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
 
321
                               dir=os.path.dirname(output_dir))
 
322
    try:
 
323
        if not fancy:
 
324
            print "not fancy"
 
325
        try:
 
326
            for result in iter_import_version(output_dir, ancestors, tempdir,
 
327
                    progress_bar, fast=fast, verbose=verbose, dry_run=dry_run,
 
328
                    max_count=max_count):
 
329
                if fancy:
 
330
                    show_progress(progress_bar, result)
 
331
                else:
 
332
                    sys.stdout.write('.')
 
333
        finally:
 
334
            if fancy:
 
335
                progress_bar.clear()
 
336
            else:
 
337
                sys.stdout.write('\n')
 
338
 
 
339
        if dry_run:
 
340
            print 'Dry run, not modifying output_dir'
 
341
            return
 
342
        if os.path.exists(output_dir):
 
343
            # Move the bzr control directory back, and update the working tree
 
344
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
 
345
            
 
346
            bzr_dir = os.path.join(output_dir, '.bzr')
 
347
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
348
 
 
349
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
 
350
            os.rename(new_bzr_dir, bzr_dir)
 
351
            try:
 
352
                bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno), 
 
353
                                   check_clean=False, this_dir=output_dir, 
 
354
                                   ignore_zero=True)
 
355
            except:
 
356
                # If something failed, move back the original bzr directory
 
357
                os.rename(bzr_dir, new_bzr_dir)
 
358
                os.rename(tmp_bzr_dir, bzr_dir)
 
359
                raise
 
360
        else:
 
361
            revdir = os.path.join(tempdir, "rd")
 
362
            os.rename(revdir, output_dir)
 
363
 
 
364
    finally:
 
365
        printer('Cleaning up')
 
366
        shutil.rmtree(tempdir)
 
367
    printer("Import complete.")
 
368
            
 
369
class UserError(Exception):
 
370
    def __init__(self, message):
 
371
        """Exception to throw when a user makes an impossible request
 
372
        :param message: The message to emit when printing this exception
 
373
        :type message: string
 
374
        """
 
375
        Exception.__init__(self, message)
 
376
 
 
377
def revision_id(arch_revision):
 
378
    """
 
379
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
 
380
    designates a revision imported with an experimental algorithm.  A number
 
381
    would indicate a particular standardized version.
 
382
 
 
383
    :param arch_revision: The Arch revision to generate an ID for.
 
384
 
 
385
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
 
386
    'Arch-1:you@example.com%cat--br--0--base-0'
 
387
    """
 
388
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
 
389
 
 
390
class NotArchRevision(Exception):
 
391
    def __init__(self, revision_id):
 
392
        msg = "The revision id %s does not look like it came from Arch."\
 
393
            % revision_id
 
394
        Exception.__init__(self, msg)
 
395
 
 
396
def arch_revision(revision_id):
 
397
    """
 
398
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
 
399
    Traceback (most recent call last):
 
400
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
 
401
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
 
402
    Traceback (most recent call last):
 
403
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
 
404
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
 
405
    'jrandom@example.com/test--test--0--patch-5'
 
406
    """
 
407
    if revision_id is None:
 
408
        return None
 
409
    if revision_id[:7] != 'Arch-1:':
 
410
        raise NotArchRevision(revision_id)
 
411
    else:
 
412
        try:
 
413
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
 
414
        except pybaz.errors.NamespaceError, e:
 
415
            raise NotArchRevision(revision_id)
 
416
            
 
417
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
 
418
                        verbose=False, dry_run=False, max_count=None):
 
419
    revdir = None
 
420
 
 
421
    # Uncomment this for testing, it basically just has baz2bzr only update
 
422
    # 5 patches at a time
 
423
    if max_count:
 
424
        ancestors = ancestors[:max_count]
 
425
 
 
426
    # Not sure if I want this output. basically it tells you ahead of time
 
427
    # what it is going to do, but then later it tells you as it is doing it.
 
428
    # what probably would be best would be to collapse it into ranges, so that
 
429
    # this gives the simple view, and then later it gives the blow by blow.
 
430
    #if verbose:
 
431
    #    print 'Adding the following revisions:'
 
432
    #    for a in ancestors:
 
433
    #        print '\t%s' % a
 
434
 
 
435
    previous_version=None
 
436
    missing_ancestor = None
 
437
 
 
438
    for i in range(len(ancestors)):
 
439
        revision = ancestors[i]
 
440
        direct_merges = []
 
441
        if verbose:
 
442
            version = str(revision.version)
 
443
            if version != previous_version:
 
444
                clear_progress_bar()
 
445
                print '\rOn version: %s' % version
 
446
            yield Progress(str(revision.patchlevel), i, len(ancestors))
 
447
            previous_version = version
 
448
        else:
 
449
            yield Progress("revisions", i, len(ancestors))
 
450
        if revdir is None:
 
451
            revdir = os.path.join(tempdir, "rd")
 
452
            try:
 
453
                baz_inv, log = get_revision(revdir, revision)
 
454
            except pybaz.errors.ExecProblem, e:
 
455
                if ("%s" % e.args).find('could not connect') == -1:
 
456
                    raise
 
457
                missing_ancestor = revision
 
458
                revdir = None
 
459
                print ("unable to access ancestor %s, making into a merge."
 
460
                       % missing_ancestor)
 
461
                continue
 
462
            # cached so we can delete the log
 
463
            log_date = log.date
 
464
            log_summary = log.summary
 
465
            log_creator = log.creator
 
466
            if os.path.exists(output_dir):
 
467
                bzr_dir = os.path.join(output_dir, '.bzr')
 
468
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
469
                # This would be much faster with a simple os.rename(), but if
 
470
                # we fail, we have corrupted the original .bzr directory.  Is
 
471
                # that a big problem, as we can just back out the last
 
472
                # revisions in .bzr/revision_history I don't really know
 
473
                shutil.copytree(bzr_dir, new_bzr_dir)
 
474
                # Now revdir should have a tree with the latest .bzr, and the
 
475
                # next revision of the baz tree
 
476
                branch = Branch.open(revdir)
 
477
            else:
 
478
                branch = Branch.initialize(revdir)
 
479
        else:
 
480
            old = os.path.join(revdir, ".bzr")
 
481
            new = os.path.join(tempdir, ".bzr")
 
482
            os.rename(old, new)
 
483
            baz_inv, log = apply_revision(revdir, revision)
 
484
            log_date = log.date
 
485
            log_summary = log.summary
 
486
            log_creator = log.creator
 
487
            direct_merges = get_direct_merges(revdir, revision)
 
488
            os.rename(new, old)
 
489
            branch = Branch.open(revdir)
 
490
        timestamp = email.Utils.mktime_tz(log_date + (0,))
 
491
        rev_id = revision_id(revision)
 
492
        if log_summary is None:
 
493
            log_summary = ""
 
494
        branch.lock_write()
 
495
        try:
 
496
            if missing_ancestor:
 
497
                # if we want it to be in revision-history, do that here.
 
498
                branch.add_pending_merge(revision_id(missing_ancestor))
 
499
                missing_ancestor = None
 
500
            for merge in direct_merges:
 
501
                branch.add_pending_merge(revision_id(merge.revision))
 
502
            branch.set_inventory(baz_inv)
 
503
            commitobj = Commit(reporter=ImportCommitReporter(pb))
 
504
            commitobj.commit(branch, log_summary, verbose=False, 
 
505
                             committer=log_creator, timestamp=timestamp,
 
506
                             timezone=0, rev_id=rev_id)
 
507
        finally:
 
508
            branch.unlock()
 
509
    yield Progress("revisions", len(ancestors), len(ancestors))
 
510
    unlink_unversioned(branch, revdir)
 
511
 
 
512
def get_direct_merges(revdir, revision):
 
513
    if pybaz.WorkingTree(revdir).tree_version != revision.version:
 
514
        pybaz.WorkingTree(revdir).set_tree_version(revision.version)
 
515
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir, 
 
516
        revision.category.nonarch, revision.branch.nonarch, 
 
517
        revision.version.nonarch, revision.archive, revision.patchlevel)
 
518
    temp_path = tempfile.mktemp()
 
519
    os.rename(log_path, temp_path)
 
520
    merges = list(iter_new_merges(revdir, revision.version))
 
521
    direct = direct_merges (merges)
 
522
    os.rename(temp_path, log_path)
 
523
    return direct
 
524
 
 
525
def unlink_unversioned(branch, revdir):
 
526
    for unversioned in branch.working_tree().extras():
 
527
        path = os.path.join(revdir, unversioned)
 
528
        if os.path.isdir(path):
 
529
            shutil.rmtree(path)
 
530
        else:
 
531
            os.unlink(path)
 
532
 
 
533
def get_log(tree, revision):
 
534
    log = tree.iter_logs(version=revision.version, reverse=True).next()
 
535
    assert str(log.revision) == str(revision), (log.revision, revision)
 
536
    return log
 
537
 
 
538
def get_revision(revdir, revision):
 
539
    revision.get(revdir)
 
540
    tree = pybaz.tree_root(revdir)
 
541
    log = get_log(tree, revision)
 
542
    try:
 
543
        return bzr_inventory_data(tree), log 
 
544
    except BadFileKind, e:
 
545
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
546
 
 
547
 
 
548
def apply_revision(revdir, revision):
 
549
    tree = pybaz.tree_root(revdir)
 
550
    revision.apply(tree)
 
551
    log = get_log(tree, revision)
 
552
    try:
 
553
        return bzr_inventory_data(tree), log
 
554
    except BadFileKind, e:
 
555
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
556
 
 
557
 
 
558
class BadFileKind(Exception):
 
559
    """The file kind is not permitted in bzr inventories"""
 
560
    def __init__(self, tree_root, path, kind):
 
561
        self.tree_root = tree_root
 
562
        self.path = path
 
563
        self.kind = kind
 
564
        Exception.__init__(self, "File %s is of forbidden type %s" %
 
565
                           (os.path.join(tree_root, path), kind))
 
566
 
 
567
 
 
568
def bzr_inventory_data(tree):
 
569
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
 
570
    inv_map = {}
 
571
    for arch_id, path in inv_iter:
 
572
        bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
 
573
        inv_map[path] = bzr_file_id 
 
574
 
 
575
    bzr_inv = []
 
576
    for path, file_id in inv_map.iteritems():
 
577
        full_path = os.path.join(tree, path)
 
578
        kind = bzrlib.osutils.file_kind(full_path)
 
579
        if kind not in ("file", "directory", "symlink"):
 
580
            raise BadFileKind(tree, path, kind)
 
581
        parent_dir = os.path.dirname(path)
 
582
        if parent_dir != "":
 
583
            parent_id = inv_map[parent_dir]
 
584
        else:
 
585
            parent_id = bzrlib.inventory.ROOT_ID
 
586
        bzr_inv.append((path, file_id, parent_id, kind))
 
587
    bzr_inv.sort()
 
588
    return bzr_inv
 
589
 
 
590
 
 
591
class cmd_baz_import_branch(Command):
 
592
    """Import an Arch or Baz branch into a bzr branch"""
 
593
    takes_args = ['to_location', 'from_branch?']
 
594
    takes_options = ['verbose']
 
595
 
 
596
    def printer(self, name):
 
597
        print name
 
598
 
 
599
    def run(self, to_location, from_branch=None, fast=False, max_count=None,
 
600
            verbose=False, dry_run=False):
 
601
        to_location = os.path.realpath(str(to_location))
 
602
        if from_branch is not None:
 
603
            try:
 
604
                from_branch = pybaz.Version(from_branch)
 
605
            except pybaz.errors.NamespaceError:
 
606
                print "%s is not a valid Arch branch." % from_branch
 
607
                return 1
 
608
        import_version(to_location, from_branch, self.printer)
 
609
 
 
610
 
 
611
class cmd_baz_import(Command):
 
612
    """Import an Arch or Baz archive into bzr branches."""
 
613
    takes_args = ['to_root_dir', 'from_archive']
 
614
    takes_options = ['verbose']
 
615
 
 
616
    def printer(self, name):
 
617
        print name
 
618
 
 
619
    def run(self, to_root_dir, from_archive, verbose=False):
 
620
        to_root = str(os.path.realpath(to_root_dir))
 
621
        if not os.path.exists(to_root):
 
622
            os.mkdir(to_root)
 
623
        import_archive(to_root, from_archive, verbose, self.printer)
 
624
 
 
625
 
 
626
def import_archive(to_root, from_archive, verbose, printer):
 
627
    for version in pybaz.Archive(str(from_archive)).iter_versions():
 
628
        target = os.path.join(to_root, map_namespace(version))
 
629
        printer("importing %s into %s" % (version, target))
 
630
        if not os.path.exists(os.path.dirname(target)):
 
631
            os.makedirs(os.path.dirname(target))
 
632
        import_version(target, version, printer)
 
633
 
 
634
 
 
635
def map_namespace(a_version):
 
636
    a_version = pybaz.Version("%s" % a_version)
 
637
    parser = NameParser(a_version)
 
638
    version = parser.get_version()
 
639
    branch = parser.get_branch()
 
640
    category = parser.get_category()
 
641
    if branch is None or branch == '':
 
642
        branch = "+trunk"
 
643
    if version == '0':
 
644
        return "%s/%s" % (category, branch)
 
645
    return "%s/%s/%s" % (category, version, branch)