~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to obsolete/baz2bzr

  • Committer: Aaron Bentley
  • Date: 2006-05-03 19:39:28 UTC
  • mfrom: (147.4.37 push-to-rpush)
  • mto: This revision was merged to the branch mainline in revision 366.
  • Revision ID: abentley@panoramicfeedback.com-20060503193928-9cb76ec7fa7e881d
MergeĀ fromĀ Robert

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
#    along with this program; if not, write to the Free Software
18
18
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
19
 
 
20
import sys
 
21
from errors import NoPyBaz
20
22
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
 
23
    from baz_import import import_version, UserError
 
24
except NoPyBaz:
 
25
    print >> sys.stderr, "This command requires PyBaz.  Please ensure that it is installed."
26
26
    sys.exit(1)
27
 
from pybaz.backends.baz import null_cmd
28
 
import tempfile
29
 
import os
 
27
 
 
28
import pybaz
30
29
import os.path
31
 
import shutil
32
 
import bzrlib
33
 
import bzrlib.trace
34
 
import bzrlib.merge
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="baz2bzr-")
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
 
def version_ancestry(version):
159
 
    """
160
 
    >>> q = test_environ()
161
 
    >>> commit_test_revisions()
162
 
    >>> version = pybaz.Version("test@example.com/test--test--0")
163
 
    >>> ancestors = version_ancestry(version)
164
 
    >>> str(ancestors[0])
165
 
    'test@example.com/test--test--0--base-0'
166
 
    >>> str(ancestors[1])
167
 
    'test@example.com/test--test--0--patch-1'
168
 
    >>> teardown_environ(q)
169
 
    """
170
 
    revision = version.iter_revisions(reverse=True).next()
171
 
    ancestors = list(revision.iter_ancestors(metoo=True))
172
 
    ancestors.reverse()
173
 
    return ancestors
174
 
 
175
 
def get_remaining_revisions(output_dir, version):
176
 
    last_patch = None
177
 
    old_revno = None
178
 
    if os.path.exists(output_dir):
179
 
        # We are starting from an existing directory, figure out what
180
 
        # the current version is
181
 
        branch = bzrlib.Branch(output_dir)
182
 
        last_patch = branch.last_patch()
183
 
        try:
184
 
            last_patch = arch_revision(last_patch)
185
 
        except NotArchRevision:
186
 
            raise UserError(
187
 
                "Directory \"%s\" already exists, and the last revision is not"
188
 
                " an Arch revision (%s)" % (output_dir, last_patch))
189
 
        if version is None:
190
 
            version = last_patch.version
191
 
    elif version is None:
192
 
        raise UserError("No version specified, and directory does not exist.")
193
 
 
194
 
    ancestors = version_ancestry(version)
195
 
 
196
 
    if last_patch:
197
 
        for i in range(len(ancestors)):
198
 
            if ancestors[i] == last_patch:
199
 
                break
200
 
        else:
201
 
            raise UserError("Directory \"%s\" already exists, and the last "
202
 
                "revision (%s) is not in the ancestry of %s" % 
203
 
                (output_dir, last_patch, version))
204
 
        # Strip off all of the ancestors which are already present
205
 
        # And get a directory starting with the latest ancestor
206
 
        latest_ancestor = ancestors[i]
207
 
        old_revno = bzrlib.Branch(output_dir).revno()
208
 
        ancestors = ancestors[i+1:]
209
 
    return ancestors, old_revno
210
 
 
211
 
def import_version(output_dir, version, fancy=True, fast=False, verbose=False, 
212
 
                   dry_run=False, max_count=None, skip_symlinks=False):
213
 
    """
214
 
    >>> q = test_environ()
215
 
    >>> result_path = os.path.join(q, "result")
216
 
    >>> commit_test_revisions()
217
 
    >>> version = pybaz.Version("test@example.com/test--test--0")
218
 
    >>> import_version(result_path, version, fancy=False)
219
 
    not fancy
220
 
    ....
221
 
    Cleaning up
222
 
    Import complete.
223
 
    >>> teardown_environ(q)
224
 
    """
225
 
    ancestors, old_revno = get_remaining_revisions(output_dir, version)
226
 
    if len(ancestors) == 0:
227
 
        print '* Tree is up-to-date with %s' % last_patch
228
 
        return 0
229
 
 
230
 
    progress_bar = ProgressBar()
231
 
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
232
 
                               dir=os.path.dirname(output_dir))
233
 
    try:
234
 
        if not fancy:
235
 
            print "not fancy"
236
 
        for result in iter_import_version(output_dir, ancestors, tempdir,
237
 
                fast=fast, verbose=verbose, dry_run=dry_run, 
238
 
                max_count=max_count, skip_symlinks=skip_symlinks):
239
 
            if fancy:
240
 
                progress_bar(result)
241
 
            else:
242
 
                sys.stdout.write('.')
243
 
 
244
 
        if dry_run:
245
 
            print '**Dry run, not modifying output_dir'
246
 
            return 0
247
 
        if os.path.exists(output_dir):
248
 
            # Move the bzr control directory back, and update the working tree
249
 
            tmp_bzr_dir = os.path.join(tempdir, '.bzr')
250
 
            
251
 
            bzr_dir = os.path.join(output_dir, '.bzr')
252
 
            new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
253
 
 
254
 
            os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
255
 
            os.rename(new_bzr_dir, bzr_dir)
256
 
            try:
257
 
                # bzrlib.merge that exists in mainline does not have a this_dir component,
258
 
                # so we have to work in the local directory
259
 
                try:
260
 
                    pwd = os.getcwd()
261
 
                    os.chdir(output_dir)
262
 
                    bzrlib.merge.merge(('.', -1), ('.', old_revno))
263
 
                finally:
264
 
                    os.chdir(pwd)
265
 
            except:
266
 
                # If something failed, move back the original bzr directory
267
 
                os.rename(bzr_dir, new_bzr_dir)
268
 
                os.rename(tmp_bzr_dir, bzr_dir)
269
 
                raise
270
 
        else:
271
 
            os.rename(revdir, output_dir)
272
 
 
273
 
    finally:
274
 
        if fancy:
275
 
            clear_progress_bar()
276
 
        else:
277
 
            sys.stdout.write('\n')
278
 
        print 'Cleaning up'
279
 
        shutil.rmtree(tempdir)
280
 
    print "Import complete."
281
 
            
282
 
class UserError(Exception):
283
 
    def __init__(self, message):
284
 
        """Exception to throw when a user makes an impossible request
285
 
        :param message: The message to emit when printing this exception
286
 
        :type message: string
287
 
        """
288
 
        Exception.__init__(self, message)
289
 
 
290
 
def revision_id(arch_revision):
291
 
    """
292
 
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
293
 
    designates a revision imported with an experimental algorithm.  A number
294
 
    would indicate a particular standardized version.
295
 
 
296
 
    :param arch_revision: The Arch revision to generate an ID for.
297
 
 
298
 
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
299
 
    'Arch-x:you@example.com%cat--br--0--base-0'
300
 
    """
301
 
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
302
 
 
303
 
class NotArchRevision(Exception):
304
 
    def __init__(self, revision_id):
305
 
        msg = "The revision id %s does not look like it came from Arch."\
306
 
            % revision_id
307
 
        Exception.__init__(self, msg)
308
 
 
309
 
def arch_revision(revision_id):
310
 
    """
311
 
    >>> str(arch_revision("Arch-x:jrandom@example.com/test--test--0"))
312
 
    'jrandom@example.com/test--test--0'
313
 
    """
314
 
    if revision_id is None:
315
 
        return None
316
 
    if revision_id[:7] != 'Arch-x:':
317
 
        raise NotArchRevision(revision_id)
318
 
    else:
319
 
        try:
320
 
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
321
 
        except pybaz.errors.NamespaceError, e:
322
 
            raise NotArchRevision(revision_id)
323
 
            
324
 
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
325
 
                        verbose=False, dry_run=False, max_count=None,
326
 
                        skip_symlinks=False):
327
 
    revdir = None
328
 
 
329
 
    # Uncomment this for testing, it basically just has baz2bzr only update
330
 
    # 5 patches at a time
331
 
    if max_count:
332
 
        ancestors = ancestors[:max_count]
333
 
 
334
 
    # Not sure if I want this output. basically it tells you ahead of time
335
 
    # what it is going to do, but then later it tells you as it is doing it.
336
 
    # what probably would be best would be to collapse it into ranges, so that
337
 
    # this gives the simple view, and then later it gives the blow by blow.
338
 
    #if verbose:
339
 
    #    print 'Adding the following revisions:'
340
 
    #    for a in ancestors:
341
 
    #        print '\t%s' % a
342
 
 
343
 
    previous_version=None
344
 
 
345
 
    for i in range(len(ancestors)):
346
 
        revision = ancestors[i]
347
 
        if verbose:
348
 
            version = str(revision.version)
349
 
            if version != previous_version:
350
 
                clear_progress_bar()
351
 
                print '\rOn version: %s' % version
352
 
            yield Progress(str(revision.patchlevel), i, len(ancestors))
353
 
            previous_version = version
354
 
        else:
355
 
            yield Progress("revisions", i, len(ancestors))
356
 
        if revdir is None:
357
 
            revdir = os.path.join(tempdir, "rd")
358
 
            baz_inv, log = get_revision(revdir, revision, 
359
 
                                        skip_symlinks=skip_symlinks)
360
 
            if os.path.exists(output_dir):
361
 
                bzr_dir = os.path.join(output_dir, '.bzr')
362
 
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
363
 
                # This would be much faster with a simple os.rename(), but if
364
 
                # we fail, we have corrupted the original .bzr directory.  Is
365
 
                # that a big problem, as we can just back out the last
366
 
                # revisions in .bzr/revision_history I don't really know
367
 
                shutil.copytree(bzr_dir, new_bzr_dir)
368
 
                # Now revdir should have a tree with the latest .bzr, and the
369
 
                # next revision of the baz tree
370
 
                branch = bzrlib.Branch(revdir)
371
 
            else:
372
 
                branch = bzrlib.Branch(revdir, init=True)
373
 
        else:
374
 
            old = os.path.join(revdir, ".bzr")
375
 
            new = os.path.join(tempdir, ".bzr")
376
 
            os.rename(old, new)
377
 
            baz_inv, log = apply_revision(revdir, revision, 
378
 
                                          skip_symlinks=skip_symlinks)
379
 
            os.rename(new, old)
380
 
            branch = bzrlib.Branch(revdir)
381
 
        timestamp = email.Utils.mktime_tz(log.date + (0,))
382
 
        rev_id = revision_id(revision)
383
 
        branch.lock_write()
384
 
        try:
385
 
            branch.set_inventory(baz_inv)
386
 
            bzrlib.trace.silent = True
387
 
            branch.commit(log.summary, verbose=False, committer=log.creator,
388
 
                          timestamp=timestamp, timezone=0, rev_id=rev_id)
389
 
        finally:
390
 
            bzrlib.trace.silent = False   
391
 
            branch.unlock()
392
 
    yield Progress("revisions", len(ancestors), len(ancestors))
393
 
    unlink_unversioned(branch, revdir)
394
 
 
395
 
def unlink_unversioned(branch, revdir):
396
 
    for unversioned in branch.working_tree().extras():
397
 
        path = os.path.join(revdir, unversioned)
398
 
        if os.path.isdir(path):
399
 
            shutil.rmtree(path)
400
 
        else:
401
 
            os.unlink(path)
402
 
 
403
 
def get_log(tree, revision):
404
 
    log = tree.iter_logs(version=revision.version, reverse=True).next()
405
 
    assert log.revision == revision
406
 
    return log
407
 
 
408
 
def get_revision(revdir, revision, skip_symlinks=False):
409
 
    revision.get(revdir)
410
 
    tree = pybaz.tree_root(revdir)
411
 
    log = get_log(tree, revision)
412
 
    try:
413
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log 
414
 
    except BadFileKind, e:
415
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
416
 
 
417
 
 
418
 
def apply_revision(revdir, revision, skip_symlinks=False):
419
 
    tree = pybaz.tree_root(revdir)
420
 
    revision.apply(tree)
421
 
    log = get_log(tree, revision)
422
 
    try:
423
 
        return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
424
 
    except BadFileKind, e:
425
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
426
 
 
427
 
 
428
 
 
429
 
 
430
 
class BadFileKind(Exception):
431
 
    """The file kind is not permitted in bzr inventories"""
432
 
    def __init__(self, tree_root, path, kind):
433
 
        self.tree_root = tree_root
434
 
        self.path = path
435
 
        self.kind = kind
436
 
        Exception.__init__(self, "File %s is of forbidden type %s" %
437
 
                           (os.path.join(tree_root, path), kind))
438
 
 
439
 
def bzr_inventory_data(tree, skip_symlinks=False):
440
 
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
441
 
    inv_map = {}
442
 
    for file_id, path in inv_iter:
443
 
        inv_map[path] = file_id 
444
 
 
445
 
    bzr_inv = []
446
 
    for path, file_id in inv_map.iteritems():
447
 
        full_path = os.path.join(tree, path)
448
 
        kind = bzrlib.osutils.file_kind(full_path)
449
 
        if skip_symlinks and kind == "symlink":
450
 
            continue
451
 
        if kind not in ("file", "directory"):
452
 
            raise BadFileKind(tree, path, kind)
453
 
        parent_dir = os.path.dirname(path)
454
 
        if parent_dir != "":
455
 
            parent_id = inv_map[parent_dir]
456
 
        else:
457
 
            parent_id = bzrlib.inventory.ROOT_ID
458
 
        bzr_inv.append((path, file_id, parent_id, kind))
459
 
    bzr_inv.sort()
460
 
    return bzr_inv
461
30
 
462
31
def main(args):
463
32
    """Just the main() function for this script.
479
48
                      dest="skip_symlinks", 
480
49
                      help="Ignore any symlinks present in the Arch tree.")
481
50
 
482
 
    g = optparse.OptionGroup(parser, 'Progress options', 'Control how progress is given')
483
 
    g.add_option('--not-fancy', dest='fancy', action='store_false')
484
 
    g.add_option('--fancy', dest='fancy', action='store_true', default=True
485
 
        , help='Fancy or simple progress bar. (default: fancy)')
486
 
    parser.add_option_group(g)
487
 
 
488
51
    g = optparse.OptionGroup(parser, 'Test options', 'Options useful while testing process.')
489
52
    g.add_option('--test', action='store_true'
490
53
        , help='Run the self-tests and exit.')
502
65
 
503
66
    if opts.test:
504
67
        print "Running tests"
505
 
        import doctest
506
 
        nfail, ntests = doctest.testmod(verbose=opts.verbose)
 
68
        import doctest, baz_import
 
69
        nfail, ntests = doctest.testmod(baz_import, verbose=opts.verbose)
507
70
        if nfail > 0:
508
71
            return 1
509
72
        else:
510
73
            return 0
511
74
    if len(args) == 2:
512
 
        output_dir = os.path.realpath(args[1]) 
513
 
        version = pybaz.Version(args[0])
 
75
        version,output_dir = args
 
76
            
514
77
    elif len(args) == 1:
515
 
        output_dir = os.path.realpath(args[0])
 
78
        output_dir = args[0]
516
79
        version = None
517
80
    else:
518
81
        print 'Invalid number of arguments, try --help for more info'
519
82
        return 1
 
83
 
 
84
    output_dir = os.path.realpath(output_dir)
 
85
    if version is not None:
 
86
        try:
 
87
            version = pybaz.Version(version)
 
88
        except pybaz.errors.NamespaceError:
 
89
            print "%s is not a valid Arch branch." % version
 
90
            return 1
520
91
        
521
92
    try:
522
 
        
523
93
        import_version(output_dir, version,
524
94
            verbose=opts.verbose, fast=opts.fast,
525
 
            fancy=opts.fancy, dry_run=opts.dry_run,
526
 
            max_count=opts.max_count, skip_symlinks=opts.skip_symlinks)
 
95
            dry_run=opts.dry_run, max_count=opts.max_count,
 
96
            skip_symlinks=opts.skip_symlinks)
527
97
        return 0
528
98
    except UserError, e:
529
99
        print e
532
102
        print "Aborted."
533
103
        return 1
534
104
 
 
105
        
535
106
 
536
107
if __name__ == '__main__':
537
108
    sys.exit(main(sys.argv[1:]))