~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-06-14 15:52:58 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050614155258-a6efa8c45a25fd6c
Moved most baz2bzr code to baz_import, added Python plugin

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