~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Robert Collins
  • Date: 2005-09-07 13:02:59 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-20050907130259-35bb613478f3ec74
start adding baz_import unit test cases

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)