~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Michael Ellerman
  • Date: 2006-06-16 03:48:48 UTC
  • mfrom: (0.4.1 shelf)
  • mto: (0.3.4 shelf-dev)
  • mto: This revision was merged to the branch mainline in revision 396.
  • Revision ID: michael@ellerman.id.au-20060616034848-25d1c5bb8a043237
Use bzrlib.patches, thanks to Aaron.

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
 
                progress_bar.clear()
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
 
        to_location = os.path.realpath(str(to_location))
558
 
        if from_branch is not None:
559
 
            try:
560
 
                from_branch = pybaz.Version(version)
561
 
            except pybaz.errors.NamespaceError:
562
 
                print "%s is not a valid Arch branch." % from_branch
563
 
                return 1
564
 
        import_version(to_location, from_branch)