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