~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz2bzr

  • Committer: Aaron Bentley
  • Date: 2008-02-20 14:28:36 UTC
  • Revision ID: aaron@aaronbentley.com-20080220142836-jqsca0avvl2p3bar
Remove ImportReplacer hack

Show diffs side-by-side

added added

removed removed

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