~abentley/bzrtools/bzrtools.dev

2 by abentley
Added baz2bzr file(doh!)
1
#!/usr/bin/env python
14 by abentley
GPLed the project, ignored files
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
2 by abentley
Added baz2bzr file(doh!)
20
try:
21
    import pybaz
61 by Aaron Bentley
Factored out bzr to baz id conversion
22
    import pybaz.errors
2 by abentley
Added baz2bzr file(doh!)
23
except ImportError:
24
    print "This command requires PyBaz.  Please ensure that it is installed."
25
    import sys
26
    sys.exit(1)
3 by abentley
Added add_file and add_dir functions
27
from pybaz.backends.baz import null_cmd
2 by abentley
Added baz2bzr file(doh!)
28
import tempfile
29
import os
30
import os.path
31
import shutil
7 by abentley
Completed initial construction
32
import bzrlib
45 by Aaron Bentley
Silenced commits
33
import bzrlib.trace
57 by Aaron Bentley
Added John Meinel's update-with-no-{arch} patch
34
import bzrlib.merge
8 by abentley
Added ability to run from the commandline
35
import sys
37 by Aaron Bentley
More log fixups for baz2bzr
36
import email.Utils
11 by abentley
refactored out progress.py
37
from progress import *
2 by abentley
Added baz2bzr file(doh!)
38
3 by abentley
Added add_file and add_dir functions
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
2 by abentley
Added baz2bzr file(doh!)
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="baz2bzr-")
65
    os.environ["HOME"] = os.path.join(tdir, "home")
66
    os.mkdir(os.environ["HOME"])
3 by abentley
Added add_file and add_dir functions
67
    arch_dir = os.path.join(tdir, "archive_dir")
68
    pybaz.make_archive("test@example.com", arch_dir)
2 by abentley
Added baz2bzr file(doh!)
69
    work_dir = os.path.join(tdir, "work_dir")
70
    os.mkdir(work_dir)
71
    os.chdir(work_dir)
3 by abentley
Added add_file and add_dir functions
72
    pybaz.init_tree(work_dir, "test@example.com/test--test--0")
2 by abentley
Added baz2bzr file(doh!)
73
    lib_dir = os.path.join(tdir, "lib_dir")
74
    os.mkdir(lib_dir)
75
    pybaz.register_revision_library(lib_dir)
6 by abentley
added commit, timport, commit_test_revisions
76
    pybaz.set_my_id("Test User<test@example.org>")
2 by abentley
Added baz2bzr file(doh!)
77
    return tdir
78
5 by abentley
changed add_file, add_dir interfaces
79
def add_file(path, text, id):
3 by abentley
Added add_file and add_dir functions
80
    """
81
    >>> q = test_environ()
5 by abentley
changed add_file, add_dir interfaces
82
    >>> add_file("path with space", "text", "lalala")
3 by abentley
Added add_file and add_dir functions
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)
5 by abentley
changed add_file, add_dir interfaces
90
    add_id([path], id)
91
92
93
def add_dir(path, id):
3 by abentley
Added add_file and add_dir functions
94
    """
95
    >>> q = test_environ()
5 by abentley
changed add_file, add_dir interfaces
96
    >>> add_dir("path with\(sp) space", "lalala")
3 by abentley
Added add_file and add_dir functions
97
    >>> tree = pybaz.tree_root(".")
98
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
4 by abentley
made test case trickier, by using pika escaping sequence in filename.
99
    >>> ("x_lalala", "path with\(sp) space") in inv
3 by abentley
Added add_file and add_dir functions
100
    True
101
    >>> teardown_environ(q)
102
    """
103
    os.mkdir(path)
5 by abentley
changed add_file, add_dir interfaces
104
    add_id([path], id)
3 by abentley
Added add_file and add_dir functions
105
2 by abentley
Added baz2bzr file(doh!)
106
def teardown_environ(tdir):
107
    os.chdir("/")
108
    shutil.rmtree(tdir)
109
6 by abentley
added commit, timport, commit_test_revisions
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)
7 by abentley
Completed initial construction
141
    3
142
    >>> str(revisions[2])
143
    'test@example.com/test--test--0--base-0'
6 by abentley
added commit, timport, commit_test_revisions
144
    >>> str(revisions[1])
7 by abentley
Completed initial construction
145
    'test@example.com/test--test--0--patch-1'
6 by abentley
added commit, timport, commit_test_revisions
146
    >>> str(revisions[0])
7 by abentley
Completed initial construction
147
    'test@example.com/test--test--0--patch-2'
6 by abentley
added commit, timport, commit_test_revisions
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")
7 by abentley
Completed initial construction
155
    add_file("ofile", "this is another file", "ofile by aaron")
156
    commit(tree, "altered mainfile")
157
68 by Aaron Bentley
got dry run and continued import under test
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
7 by abentley
Completed initial construction
176
def version_ancestry(version):
177
    """
178
    >>> q = test_environ()
179
    >>> commit_test_revisions()
180
    >>> version = pybaz.Version("test@example.com/test--test--0")
181
    >>> ancestors = version_ancestry(version)
182
    >>> str(ancestors[0])
183
    'test@example.com/test--test--0--base-0'
184
    >>> str(ancestors[1])
185
    'test@example.com/test--test--0--patch-1'
186
    >>> teardown_environ(q)
187
    """
35 by Aaron Bentley
Fixed, and got test cases passing
188
    revision = version.iter_revisions(reverse=True).next()
31 by Aaron Bentley
Further updates
189
    ancestors = list(revision.iter_ancestors(metoo=True))
190
    ancestors.reverse()
7 by abentley
Completed initial construction
191
    return ancestors
192
66 by Aaron Bentley
Continued symlink fix, cleaned up merge, error message
193
def get_last_revision(branch):
194
    last_patch = branch.last_patch()
195
    try:
196
        return arch_revision(last_patch)
197
    except NotArchRevision:
198
        raise UserError(
199
            "Directory \"%s\" already exists, and the last revision is not"
200
            " an Arch revision (%s)" % (output_dir, last_patch))
201
202
62 by Aaron Bentley
Refactored, made version optional
203
def get_remaining_revisions(output_dir, version):
204
    last_patch = None
205
    old_revno = None
206
    if os.path.exists(output_dir):
207
        # We are starting from an existing directory, figure out what
208
        # the current version is
209
        branch = bzrlib.Branch(output_dir)
66 by Aaron Bentley
Continued symlink fix, cleaned up merge, error message
210
        last_patch = get_last_revision(branch)
62 by Aaron Bentley
Refactored, made version optional
211
        if version is None:
212
            version = last_patch.version
213
    elif version is None:
214
        raise UserError("No version specified, and directory does not exist.")
215
216
    ancestors = version_ancestry(version)
217
218
    if last_patch:
219
        for i in range(len(ancestors)):
220
            if ancestors[i] == last_patch:
221
                break
222
        else:
223
            raise UserError("Directory \"%s\" already exists, and the last "
224
                "revision (%s) is not in the ancestry of %s" % 
225
                (output_dir, last_patch, version))
226
        # Strip off all of the ancestors which are already present
227
        # And get a directory starting with the latest ancestor
228
        latest_ancestor = ancestors[i]
229
        old_revno = bzrlib.Branch(output_dir).revno()
230
        ancestors = ancestors[i+1:]
231
    return ancestors, old_revno
10 by abentley
Refactored, made progress bar
232
60 by Aaron Bentley
Made symlink-skipping an option
233
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
234
                   dry_run=False, max_count=None, skip_symlinks=False):
7 by abentley
Completed initial construction
235
    """
236
    >>> q = test_environ()
237
    >>> result_path = os.path.join(q, "result")
238
    >>> commit_test_revisions()
239
    >>> version = pybaz.Version("test@example.com/test--test--0")
68 by Aaron Bentley
got dry run and continued import under test
240
    >>> import_version(result_path, version, fancy=False, dry_run=True)
241
    not fancy
242
    ....
243
    Dry run, not modifying output_dir
244
    Cleaning up
35 by Aaron Bentley
Fixed, and got test cases passing
245
    >>> import_version(result_path, version, fancy=False)
246
    not fancy
247
    ....
59 by Aaron Bentley
Applied John Meinel's options patch
248
    Cleaning up
35 by Aaron Bentley
Fixed, and got test cases passing
249
    Import complete.
68 by Aaron Bentley
got dry run and continued import under test
250
    >>> import_version(result_path, version, fancy=False)
251
    Tree is up-to-date with test@example.com/test--test--0--patch-2
7 by abentley
Completed initial construction
252
    >>> teardown_environ(q)
253
    """
62 by Aaron Bentley
Refactored, made version optional
254
    ancestors, old_revno = get_remaining_revisions(output_dir, version)
255
    if len(ancestors) == 0:
66 by Aaron Bentley
Continued symlink fix, cleaned up merge, error message
256
        last_revision = get_last_revision(bzrlib.Branch(output_dir))
68 by Aaron Bentley
got dry run and continued import under test
257
        print 'Tree is up-to-date with %s' % last_revision
258
        return
62 by Aaron Bentley
Refactored, made version optional
259
47 by Aaron Bentley
merged ETA changes
260
    progress_bar = ProgressBar()
51 by Aaron Bentley
Ensured temp dir is on same filesystem as output dir
261
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
262
                               dir=os.path.dirname(output_dir))
7 by abentley
Completed initial construction
263
    try:
35 by Aaron Bentley
Fixed, and got test cases passing
264
        if not fancy:
265
            print "not fancy"
68 by Aaron Bentley
got dry run and continued import under test
266
        try:
267
            for result in iter_import_version(output_dir, ancestors, tempdir,
268
                    fast=fast, verbose=verbose, dry_run=dry_run, 
269
                    max_count=max_count, skip_symlinks=skip_symlinks):
270
                if fancy:
271
                    progress_bar(result)
272
                else:
273
                    sys.stdout.write('.')
274
        finally:
35 by Aaron Bentley
Fixed, and got test cases passing
275
            if fancy:
68 by Aaron Bentley
got dry run and continued import under test
276
                clear_progress_bar()
35 by Aaron Bentley
Fixed, and got test cases passing
277
            else:
68 by Aaron Bentley
got dry run and continued import under test
278
                sys.stdout.write('\n')
62 by Aaron Bentley
Refactored, made version optional
279
280
        if dry_run:
68 by Aaron Bentley
got dry run and continued import under test
281
            print 'Dry run, not modifying output_dir'
282
            return
62 by Aaron Bentley
Refactored, made version optional
283
        if os.path.exists(output_dir):
284
            # Move the bzr control directory back, and update the working tree
285
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
286
            
287
            bzr_dir = os.path.join(output_dir, '.bzr')
288
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
289
290
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
291
            os.rename(new_bzr_dir, bzr_dir)
292
            try:
66 by Aaron Bentley
Continued symlink fix, cleaned up merge, error message
293
                bzrlib.merge.merge(('.', -1), ('.', old_revno), 
294
                                   check_clean=False, this_dir=output_dir, 
295
                                   ignore_zero=True)
62 by Aaron Bentley
Refactored, made version optional
296
            except:
297
                # If something failed, move back the original bzr directory
298
                os.rename(bzr_dir, new_bzr_dir)
299
                os.rename(tmp_bzr_dir, bzr_dir)
300
                raise
301
        else:
67 by Aaron Bentley
Ensured all tests pass
302
            revdir = os.path.join(tempdir, "rd")
62 by Aaron Bentley
Refactored, made version optional
303
            os.rename(revdir, output_dir)
304
29 by Aaron Bentley
Nicer handling of unsupported file types
305
    finally:
59 by Aaron Bentley
Applied John Meinel's options patch
306
        print 'Cleaning up'
7 by abentley
Completed initial construction
307
        shutil.rmtree(tempdir)
29 by Aaron Bentley
Nicer handling of unsupported file types
308
    print "Import complete."
10 by abentley
Refactored, made progress bar
309
            
12 by abentley
Error early when the user specifies an output directory that already exists
310
class UserError(Exception):
311
    def __init__(self, message):
312
        """Exception to throw when a user makes an impossible request
313
        :param message: The message to emit when printing this exception
314
        :type message: string
315
        """
316
        Exception.__init__(self, message)
317
46 by Aaron Bentley
Made bzr revision ids predictable
318
def revision_id(arch_revision):
319
    """
320
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
321
    designates a revision imported with an experimental algorithm.  A number
322
    would indicate a particular standardized version.
323
324
    :param arch_revision: The Arch revision to generate an ID for.
325
326
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
327
    'Arch-x:you@example.com%cat--br--0--base-0'
328
    """
329
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
330
61 by Aaron Bentley
Factored out bzr to baz id conversion
331
class NotArchRevision(Exception):
332
    def __init__(self, revision_id):
333
        msg = "The revision id %s does not look like it came from Arch."\
334
            % revision_id
335
        Exception.__init__(self, msg)
336
337
def arch_revision(revision_id):
338
    """
67 by Aaron Bentley
Ensured all tests pass
339
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
340
    Traceback (most recent call last):
341
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
342
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
343
    Traceback (most recent call last):
344
    NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
345
    >>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
346
    'jrandom@example.com/test--test--0--patch-5'
61 by Aaron Bentley
Factored out bzr to baz id conversion
347
    """
348
    if revision_id is None:
349
        return None
350
    if revision_id[:7] != 'Arch-x:':
351
        raise NotArchRevision(revision_id)
352
    else:
353
        try:
354
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
355
        except pybaz.errors.NamespaceError, e:
356
            raise NotArchRevision(revision_id)
357
            
62 by Aaron Bentley
Refactored, made version optional
358
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
60 by Aaron Bentley
Made symlink-skipping an option
359
                        verbose=False, dry_run=False, max_count=None,
360
                        skip_symlinks=False):
10 by abentley
Refactored, made progress bar
361
    revdir = None
57 by Aaron Bentley
Added John Meinel's update-with-no-{arch} patch
362
56 by Aaron Bentley
Applied John Meinel's 'update' patch
363
    # Uncomment this for testing, it basically just has baz2bzr only update
364
    # 5 patches at a time
59 by Aaron Bentley
Applied John Meinel's options patch
365
    if max_count:
366
        ancestors = ancestors[:max_count]
367
368
    # Not sure if I want this output. basically it tells you ahead of time
369
    # what it is going to do, but then later it tells you as it is doing it.
370
    # what probably would be best would be to collapse it into ranges, so that
371
    # this gives the simple view, and then later it gives the blow by blow.
372
    #if verbose:
373
    #    print 'Adding the following revisions:'
374
    #    for a in ancestors:
375
    #        print '\t%s' % a
376
377
    previous_version=None
56 by Aaron Bentley
Applied John Meinel's 'update' patch
378
10 by abentley
Refactored, made progress bar
379
    for i in range(len(ancestors)):
380
        revision = ancestors[i]
59 by Aaron Bentley
Applied John Meinel's options patch
381
        if verbose:
382
            version = str(revision.version)
383
            if version != previous_version:
384
                clear_progress_bar()
385
                print '\rOn version: %s' % version
386
            yield Progress(str(revision.patchlevel), i, len(ancestors))
387
            previous_version = version
388
        else:
389
            yield Progress("revisions", i, len(ancestors))
10 by abentley
Refactored, made progress bar
390
        if revdir is None:
391
            revdir = os.path.join(tempdir, "rd")
60 by Aaron Bentley
Made symlink-skipping an option
392
            baz_inv, log = get_revision(revdir, revision, 
393
                                        skip_symlinks=skip_symlinks)
62 by Aaron Bentley
Refactored, made version optional
394
            if os.path.exists(output_dir):
395
                bzr_dir = os.path.join(output_dir, '.bzr')
396
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
397
                # This would be much faster with a simple os.rename(), but if
398
                # we fail, we have corrupted the original .bzr directory.  Is
399
                # that a big problem, as we can just back out the last
400
                # revisions in .bzr/revision_history I don't really know
401
                shutil.copytree(bzr_dir, new_bzr_dir)
402
                # Now revdir should have a tree with the latest .bzr, and the
403
                # next revision of the baz tree
404
                branch = bzrlib.Branch(revdir)
405
            else:
406
                branch = bzrlib.Branch(revdir, init=True)
10 by abentley
Refactored, made progress bar
407
        else:
408
            old = os.path.join(revdir, ".bzr")
409
            new = os.path.join(tempdir, ".bzr")
410
            os.rename(old, new)
60 by Aaron Bentley
Made symlink-skipping an option
411
            baz_inv, log = apply_revision(revdir, revision, 
412
                                          skip_symlinks=skip_symlinks)
10 by abentley
Refactored, made progress bar
413
            os.rename(new, old)
414
            branch = bzrlib.Branch(revdir)
37 by Aaron Bentley
More log fixups for baz2bzr
415
        timestamp = email.Utils.mktime_tz(log.date + (0,))
46 by Aaron Bentley
Made bzr revision ids predictable
416
        rev_id = revision_id(revision)
52 by Aaron Bentley
Updated for new Branch locking
417
        branch.lock_write()
45 by Aaron Bentley
Silenced commits
418
        try:
52 by Aaron Bentley
Updated for new Branch locking
419
            branch.set_inventory(baz_inv)
420
            bzrlib.trace.silent = True
45 by Aaron Bentley
Silenced commits
421
            branch.commit(log.summary, verbose=False, committer=log.creator,
46 by Aaron Bentley
Made bzr revision ids predictable
422
                          timestamp=timestamp, timezone=0, rev_id=rev_id)
45 by Aaron Bentley
Silenced commits
423
        finally:
424
            bzrlib.trace.silent = False   
52 by Aaron Bentley
Updated for new Branch locking
425
            branch.unlock()
10 by abentley
Refactored, made progress bar
426
    yield Progress("revisions", len(ancestors), len(ancestors))
57 by Aaron Bentley
Added John Meinel's update-with-no-{arch} patch
427
    unlink_unversioned(branch, revdir)
10 by abentley
Refactored, made progress bar
428
429
def unlink_unversioned(branch, revdir):
430
    for unversioned in branch.working_tree().extras():
431
        path = os.path.join(revdir, unversioned)
432
        if os.path.isdir(path):
433
            shutil.rmtree(path)
434
        else:
435
            os.unlink(path)
7 by abentley
Completed initial construction
436
37 by Aaron Bentley
More log fixups for baz2bzr
437
def get_log(tree, revision):
438
    log = tree.iter_logs(version=revision.version, reverse=True).next()
439
    assert log.revision == revision
440
    return log
441
60 by Aaron Bentley
Made symlink-skipping an option
442
def get_revision(revdir, revision, skip_symlinks=False):
7 by abentley
Completed initial construction
443
    revision.get(revdir)
444
    tree = pybaz.tree_root(revdir)
37 by Aaron Bentley
More log fixups for baz2bzr
445
    log = get_log(tree, revision)
29 by Aaron Bentley
Nicer handling of unsupported file types
446
    try:
60 by Aaron Bentley
Made symlink-skipping an option
447
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
29 by Aaron Bentley
Nicer handling of unsupported file types
448
    except BadFileKind, e:
449
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
450
7 by abentley
Completed initial construction
451
60 by Aaron Bentley
Made symlink-skipping an option
452
def apply_revision(revdir, revision, skip_symlinks=False):
13 by abentley
Used replay to get next revision
453
    tree = pybaz.tree_root(revdir)
454
    revision.apply(tree)
37 by Aaron Bentley
More log fixups for baz2bzr
455
    log = get_log(tree, revision)
29 by Aaron Bentley
Nicer handling of unsupported file types
456
    try:
60 by Aaron Bentley
Made symlink-skipping an option
457
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
29 by Aaron Bentley
Nicer handling of unsupported file types
458
    except BadFileKind, e:
459
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
460
461
462
463
464
class BadFileKind(Exception):
465
    """The file kind is not permitted in bzr inventories"""
466
    def __init__(self, tree_root, path, kind):
467
        self.tree_root = tree_root
468
        self.path = path
469
        self.kind = kind
470
        Exception.__init__(self, "File %s is of forbidden type %s" %
471
                           (os.path.join(tree_root, path), kind))
13 by abentley
Used replay to get next revision
472
60 by Aaron Bentley
Made symlink-skipping an option
473
def bzr_inventory_data(tree, skip_symlinks=False):
7 by abentley
Completed initial construction
474
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
475
    inv_map = {}
476
    for file_id, path in inv_iter:
8 by abentley
Added ability to run from the commandline
477
        inv_map[path] = file_id 
7 by abentley
Completed initial construction
478
479
    bzr_inv = []
8 by abentley
Added ability to run from the commandline
480
    for path, file_id in inv_map.iteritems():
29 by Aaron Bentley
Nicer handling of unsupported file types
481
        full_path = os.path.join(tree, path)
482
        kind = bzrlib.osutils.file_kind(full_path)
60 by Aaron Bentley
Made symlink-skipping an option
483
        if skip_symlinks and kind == "symlink":
55 by Aaron Bentley
Skip symlinks while importing
484
            continue
29 by Aaron Bentley
Nicer handling of unsupported file types
485
        if kind not in ("file", "directory"):
486
            raise BadFileKind(tree, path, kind)
7 by abentley
Completed initial construction
487
        parent_dir = os.path.dirname(path)
488
        if parent_dir != "":
489
            parent_id = inv_map[parent_dir]
490
        else:
491
            parent_id = bzrlib.inventory.ROOT_ID
492
        bzr_inv.append((path, file_id, parent_id, kind))
493
    bzr_inv.sort()
494
    return bzr_inv
6 by abentley
added commit, timport, commit_test_revisions
495
59 by Aaron Bentley
Applied John Meinel's options patch
496
def main(args):
497
    """Just the main() function for this script.
498
499
    By separating it into a function, this can be called as a child from some other
500
    script.
501
502
    :param args: The arguments to this script. Essentially sys.argv[1:]
503
    """
504
    import optparse
62 by Aaron Bentley
Refactored, made version optional
505
    parser = optparse.OptionParser(usage='%prog [options] [VERSION] OUTDIR'
59 by Aaron Bentley
Applied John Meinel's options patch
506
        '\n  VERSION is the arch version to import.'
507
        '\n  OUTDIR can be an existing directory to be updated'
508
        '\n         or a new directory which will be created from scratch.')
509
    parser.add_option('--verbose', action='store_true'
510
        , help='Get chatty')
511
60 by Aaron Bentley
Made symlink-skipping an option
512
    parser.add_option('--skip-symlinks', action="store_true", 
513
                      dest="skip_symlinks", 
514
                      help="Ignore any symlinks present in the Arch tree.")
515
59 by Aaron Bentley
Applied John Meinel's options patch
516
    g = optparse.OptionGroup(parser, 'Test options', 'Options useful while testing process.')
517
    g.add_option('--test', action='store_true'
518
        , help='Run the self-tests and exit.')
519
    g.add_option('--dry-run', action='store_true'
520
        , help='Do the update, but don\'t copy the result to OUTDIR')
521
    g.add_option('--max-count', type='int', metavar='COUNT', default=None
522
        , help='At most, add COUNT patches.')
523
    g.add_option('--safe', action='store_false', dest='fast')
524
    g.add_option('--fast', action='store_true', default=False
525
        , help='By default the .bzr control directory will be copied, so that an error'
526
        ' does not modify the original. --fast allows the directory to be renamed instead.')
527
    parser.add_option_group(g)
528
529
    (opts, args) = parser.parse_args(args)
530
531
    if opts.test:
532
        print "Running tests"
533
        import doctest
534
        nfail, ntests = doctest.testmod(verbose=opts.verbose)
535
        if nfail > 0:
536
            return 1
537
        else:
538
            return 0
62 by Aaron Bentley
Refactored, made version optional
539
    if len(args) == 2:
540
        output_dir = os.path.realpath(args[1]) 
541
        version = pybaz.Version(args[0])
542
    elif len(args) == 1:
543
        output_dir = os.path.realpath(args[0])
544
        version = None
545
    else:
59 by Aaron Bentley
Applied John Meinel's options patch
546
        print 'Invalid number of arguments, try --help for more info'
547
        return 1
62 by Aaron Bentley
Refactored, made version optional
548
        
549
    try:
550
        
551
        import_version(output_dir, version,
552
            verbose=opts.verbose, fast=opts.fast,
66 by Aaron Bentley
Continued symlink fix, cleaned up merge, error message
553
            dry_run=opts.dry_run, max_count=opts.max_count,
554
            skip_symlinks=opts.skip_symlinks)
62 by Aaron Bentley
Refactored, made version optional
555
        return 0
556
    except UserError, e:
557
        print e
558
        return 1
559
    except KeyboardInterrupt:
560
        print "Aborted."
561
        return 1
59 by Aaron Bentley
Applied John Meinel's options patch
562
563
564
if __name__ == '__main__':
565
    sys.exit(main(sys.argv[1:]))
566