~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2012-01-20 02:07:15 UTC
  • Revision ID: aaron@aaronbentley.com-20120120020715-ar6jbqnrjcuebggz
Tags: release-2.5
Update for 2.5 release.

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