~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Robert Collins
  • Date: 2005-09-28 05:43:19 UTC
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050928054319-2c2e9e3048bbc215
find_branch -> open_containing change

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