~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Aaron Bentley
  • Date: 2005-11-08 17:14:39 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20051108171439-6835d23881be10b8
Added force-reweave-inventory from Daniel Silverstone

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_revision()
 
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
        if len(ancestors) > 0 and not ancestors[0].archive.is_registered():
 
234
            ancestors = ancestors[1:]
 
235
    except NoSuchVersion, e:
 
236
        raise UserError(e)
 
237
 
 
238
    if last_patch:
 
239
        for i in range(len(ancestors)):
 
240
            if ancestors[i] == last_patch:
 
241
                break
 
242
        else:
 
243
            raise UserError("Directory \"%s\" already exists, and the last "
 
244
                "revision (%s) is not in the ancestry of %s" % 
 
245
                (output_dir, last_patch, version))
 
246
        # Strip off all of the ancestors which are already present
 
247
        # And get a directory starting with the latest ancestor
 
248
        latest_ancestor = ancestors[i]
 
249
        old_revno = find_branch(output_dir).revno()
 
250
        ancestors = ancestors[i+1:]
 
251
    return ancestors, old_revno
 
252
 
 
253
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
 
254
                   dry_run=False, max_count=None, skip_symlinks=False):
 
255
    """
 
256
    >>> q = test_environ()
 
257
    >>> result_path = os.path.join(q, "result")
 
258
    >>> commit_test_revisions()
 
259
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
 
260
    >>> import_version('/', version, fancy=False, dry_run=True)
 
261
    Traceback (most recent call last):
 
262
    UserError: / exists, but is not a bzr branch.
 
263
    >>> import_version(result_path, version, fancy=False, dry_run=True)
 
264
    Traceback (most recent call last):
 
265
    UserError: The version test@example.com/test--test--0.1 does not exist.
 
266
    >>> version = pybaz.Version("test@example.com/test--test--0")
 
267
    >>> import_version(result_path, version, fancy=False, dry_run=True)
 
268
    not fancy
 
269
    ....
 
270
    Dry run, not modifying output_dir
 
271
    Cleaning up
 
272
    >>> import_version(result_path, version, fancy=False)
 
273
    not fancy
 
274
    ....
 
275
    Cleaning up
 
276
    Import complete.
 
277
    >>> import_version(result_path, version, fancy=False)
 
278
    Tree is up-to-date with test@example.com/test--test--0--patch-2
 
279
    >>> commit_more_test_revisions()
 
280
    >>> import_version(result_path, version, fancy=False)
 
281
    not fancy
 
282
    ..
 
283
    Cleaning up
 
284
    Import complete.
 
285
    >>> teardown_environ(q)
 
286
    """
 
287
    try:
 
288
        ancestors, old_revno = get_remaining_revisions(output_dir, version)
 
289
    except NotInABranch, e:
 
290
        raise UserError("%s exists, but is not a bzr branch." % e.path)
 
291
    if len(ancestors) == 0:
 
292
        last_revision = get_last_revision(find_branch(output_dir))
 
293
        print 'Tree is up-to-date with %s' % last_revision
 
294
        return
 
295
 
 
296
    progress_bar = ProgressBar()
 
297
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
 
298
                               dir=os.path.dirname(output_dir))
 
299
    try:
 
300
        if not fancy:
 
301
            print "not fancy"
 
302
        try:
 
303
            for result in iter_import_version(output_dir, ancestors, tempdir,
 
304
                    fast=fast, verbose=verbose, dry_run=dry_run, 
 
305
                    max_count=max_count, skip_symlinks=skip_symlinks):
 
306
                if fancy:
 
307
                    show_progress(progress_bar, result)
 
308
                else:
 
309
                    sys.stdout.write('.')
 
310
        finally:
 
311
            if fancy:
 
312
                progress_bar.clear()
 
313
            else:
 
314
                sys.stdout.write('\n')
 
315
 
 
316
        if dry_run:
 
317
            print 'Dry run, not modifying output_dir'
 
318
            return
 
319
        if os.path.exists(output_dir):
 
320
            # Move the bzr control directory back, and update the working tree
 
321
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
 
322
            
 
323
            bzr_dir = os.path.join(output_dir, '.bzr')
 
324
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
325
 
 
326
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
 
327
            os.rename(new_bzr_dir, bzr_dir)
 
328
            try:
 
329
                bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno), 
 
330
                                   check_clean=False, this_dir=output_dir, 
 
331
                                   ignore_zero=True)
 
332
            except:
 
333
                # If something failed, move back the original bzr directory
 
334
                os.rename(bzr_dir, new_bzr_dir)
 
335
                os.rename(tmp_bzr_dir, bzr_dir)
 
336
                raise
 
337
        else:
 
338
            revdir = os.path.join(tempdir, "rd")
 
339
            os.rename(revdir, output_dir)
 
340
 
 
341
    finally:
 
342
        print 'Cleaning up'
 
343
        shutil.rmtree(tempdir)
 
344
    print "Import complete."
 
345
            
 
346
class UserError(Exception):
 
347
    def __init__(self, message):
 
348
        """Exception to throw when a user makes an impossible request
 
349
        :param message: The message to emit when printing this exception
 
350
        :type message: string
 
351
        """
 
352
        Exception.__init__(self, message)
 
353
 
 
354
def revision_id(arch_revision):
 
355
    """
 
356
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
 
357
    designates a revision imported with an experimental algorithm.  A number
 
358
    would indicate a particular standardized version.
 
359
 
 
360
    :param arch_revision: The Arch revision to generate an ID for.
 
361
 
 
362
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
 
363
    'Arch-x:you@example.com%cat--br--0--base-0'
 
364
    """
 
365
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
 
366
 
 
367
class NotArchRevision(Exception):
 
368
    def __init__(self, revision_id):
 
369
        msg = "The revision id %s does not look like it came from Arch."\
 
370
            % revision_id
 
371
        Exception.__init__(self, msg)
 
372
 
 
373
def arch_revision(revision_id):
 
374
    """
 
375
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
 
376
    Traceback (most recent call last):
 
377
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
 
378
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
 
379
    Traceback (most recent call last):
 
380
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
 
381
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
 
382
    'jrandom@example.com/test--test--0--patch-5'
 
383
    """
 
384
    if revision_id is None:
 
385
        return None
 
386
    if revision_id[:7] != 'Arch-x:':
 
387
        raise NotArchRevision(revision_id)
 
388
    else:
 
389
        try:
 
390
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
 
391
        except pybaz.errors.NamespaceError, e:
 
392
            raise NotArchRevision(revision_id)
 
393
            
 
394
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
 
395
                        verbose=False, dry_run=False, max_count=None,
 
396
                        skip_symlinks=False):
 
397
    revdir = None
 
398
 
 
399
    # Uncomment this for testing, it basically just has baz2bzr only update
 
400
    # 5 patches at a time
 
401
    if max_count:
 
402
        ancestors = ancestors[:max_count]
 
403
 
 
404
    # Not sure if I want this output. basically it tells you ahead of time
 
405
    # what it is going to do, but then later it tells you as it is doing it.
 
406
    # what probably would be best would be to collapse it into ranges, so that
 
407
    # this gives the simple view, and then later it gives the blow by blow.
 
408
    #if verbose:
 
409
    #    print 'Adding the following revisions:'
 
410
    #    for a in ancestors:
 
411
    #        print '\t%s' % a
 
412
 
 
413
    previous_version=None
 
414
 
 
415
    for i in range(len(ancestors)):
 
416
        revision = ancestors[i]
 
417
        if verbose:
 
418
            version = str(revision.version)
 
419
            if version != previous_version:
 
420
                clear_progress_bar()
 
421
                print '\rOn version: %s' % version
 
422
            yield Progress(str(revision.patchlevel), i, len(ancestors))
 
423
            previous_version = version
 
424
        else:
 
425
            yield Progress("revisions", i, len(ancestors))
 
426
        if revdir is None:
 
427
            revdir = os.path.join(tempdir, "rd")
 
428
            baz_inv, log = get_revision(revdir, revision, 
 
429
                                        skip_symlinks=skip_symlinks)
 
430
            if os.path.exists(output_dir):
 
431
                bzr_dir = os.path.join(output_dir, '.bzr')
 
432
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
433
                # This would be much faster with a simple os.rename(), but if
 
434
                # we fail, we have corrupted the original .bzr directory.  Is
 
435
                # that a big problem, as we can just back out the last
 
436
                # revisions in .bzr/revision_history I don't really know
 
437
                shutil.copytree(bzr_dir, new_bzr_dir)
 
438
                # Now revdir should have a tree with the latest .bzr, and the
 
439
                # next revision of the baz tree
 
440
                branch = find_branch(revdir)
 
441
            else:
 
442
                branch = Branch.initialize(revdir)
 
443
        else:
 
444
            old = os.path.join(revdir, ".bzr")
 
445
            new = os.path.join(tempdir, ".bzr")
 
446
            os.rename(old, new)
 
447
            baz_inv, log = apply_revision(revdir, revision, 
 
448
                                          skip_symlinks=skip_symlinks)
 
449
            os.rename(new, old)
 
450
            branch = find_branch(revdir)
 
451
        timestamp = email.Utils.mktime_tz(log.date + (0,))
 
452
        rev_id = revision_id(revision)
 
453
        branch.lock_write()
 
454
        try:
 
455
            branch.working_tree().set_inventory(baz_inv)
 
456
            bzrlib.trace.silent = True
 
457
            branch.commit(log.summary, verbose=False, committer=log.creator,
 
458
                          timestamp=timestamp, timezone=0, rev_id=rev_id)
 
459
        finally:
 
460
            bzrlib.trace.silent = False   
 
461
            branch.unlock()
 
462
    yield Progress("revisions", len(ancestors), len(ancestors))
 
463
    unlink_unversioned(branch, revdir)
 
464
 
 
465
def unlink_unversioned(branch, revdir):
 
466
    for unversioned in branch.working_tree().extras():
 
467
        path = os.path.join(revdir, unversioned)
 
468
        if os.path.isdir(path):
 
469
            shutil.rmtree(path)
 
470
        else:
 
471
            os.unlink(path)
 
472
 
 
473
def get_log(tree, revision):
 
474
    log = tree.iter_logs(version=revision.version, reverse=True).next()
 
475
    assert str(log.revision) == str(revision), (log.revision, revision)
 
476
    return log
 
477
 
 
478
def get_revision(revdir, revision, skip_symlinks=False):
 
479
    revision.get(revdir)
 
480
    tree = pybaz.tree_root(revdir)
 
481
    log = get_log(tree, revision)
 
482
    try:
 
483
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
 
484
    except BadFileKind, e:
 
485
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
486
 
 
487
 
 
488
def apply_revision(revdir, revision, skip_symlinks=False):
 
489
    tree = pybaz.tree_root(revdir)
 
490
    revision.apply(tree)
 
491
    log = get_log(tree, revision)
 
492
    try:
 
493
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
 
494
    except BadFileKind, e:
 
495
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
496
 
 
497
 
 
498
 
 
499
 
 
500
class BadFileKind(Exception):
 
501
    """The file kind is not permitted in bzr inventories"""
 
502
    def __init__(self, tree_root, path, kind):
 
503
        self.tree_root = tree_root
 
504
        self.path = path
 
505
        self.kind = kind
 
506
        Exception.__init__(self, "File %s is of forbidden type %s" %
 
507
                           (os.path.join(tree_root, path), kind))
 
508
 
 
509
def bzr_inventory_data(tree, skip_symlinks=False):
 
510
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
 
511
    inv_map = {}
 
512
    for arch_id, path in inv_iter:
 
513
        bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
 
514
        inv_map[path] = bzr_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), Branch)
 
546
    True
 
547
    """
 
548
    try:
 
549
        return Branch.open(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)