~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-07-25 14:17:15 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050725141715-d410836f6e4a32c0
Got baz2bzr/annotate working now that ProgressBar is a function

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