~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Michael Ellerman
  • Date: 2005-10-26 09:49:08 UTC
  • mto: (0.3.1 shelf-dev) (325.1.2 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 246.
  • Revision ID: michael@ellerman.id.au-20051026094908-ddd0626e7e5e12a1
Shelf() takes a location which specifies where to open the branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Aaron Bentley
2
 
 
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
 
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
 
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
import errno
18
 
 
19
 
from bzrlib.errors import BzrError
20
 
from bzrlib.errors import NotBranchError, BzrCommandError, NoSuchRevision
21
 
from bzrlib.branch import Branch
22
 
from bzrlib.clone import copy_branch
23
 
from bzrlib.commit import Commit, NullCommitReporter
24
 
from bzrlib.commands import Command
25
 
from bzrlib.option import _global_option
26
 
from bzrlib.workingtree import WorkingTree
27
 
from errors import NoPyBaz
28
 
try:
29
 
    import pybaz
30
 
    import pybaz.errors
31
 
    from pybaz import NameParser as NameParser
32
 
    from pybaz.backends.baz import null_cmd
33
 
except ImportError:
34
 
    raise NoPyBaz
35
 
from fai import iter_new_merges, direct_merges
36
 
import tempfile
37
 
import os
38
 
import os.path
39
 
import shutil
40
 
import bzrlib
41
 
import bzrlib.trace
42
 
import bzrlib.merge
43
 
import bzrlib.inventory
44
 
import bzrlib.osutils
45
 
import sys
46
 
import email.Utils
47
 
from progress import *
48
 
 
49
 
class ImportCommitReporter(NullCommitReporter):
50
 
    def __init__(self, pb):
51
 
        self.pb = pb
52
 
 
53
 
    def escaped(self, escape_count, message):
54
 
        self.pb.clear()
55
 
        bzrlib.trace.warning("replaced %d control characters in message" %
56
 
                             escape_count)
57
 
 
58
 
def add_id(files, id=None):
59
 
    """Adds an explicit id to a list of files.
60
 
 
61
 
    :param files: the name of the file to add an id to
62
 
    :type files: list of str
63
 
    :param id: tag one file using the specified id, instead of generating id
64
 
    :type id: str
65
 
    """
66
 
    args = ["add-id"]
67
 
    if id is not None:
68
 
        args.extend(["--id", id])
69
 
    args.extend(files)
70
 
    return null_cmd(args)
71
 
 
72
 
saved_dir = None
73
 
 
74
 
def test_environ():
75
 
    """
76
 
    >>> q = test_environ()
77
 
    >>> os.path.exists(q)
78
 
    True
79
 
    >>> os.path.exists(os.path.join(q, "home", ".arch-params"))
80
 
    True
81
 
    >>> teardown_environ(q)
82
 
    >>> os.path.exists(q)
83
 
    False
84
 
    """
85
 
    global saved_dir
86
 
    saved_dir = os.getcwdu()
87
 
    tdir = tempfile.mkdtemp(prefix="testdir-")
88
 
    os.environ["HOME"] = os.path.join(tdir, "home")
89
 
    os.mkdir(os.environ["HOME"])
90
 
    arch_dir = os.path.join(tdir, "archive_dir")
91
 
    pybaz.make_archive("test@example.com", arch_dir)
92
 
    work_dir = os.path.join(tdir, "work_dir")
93
 
    os.mkdir(work_dir)
94
 
    os.chdir(work_dir)
95
 
    pybaz.init_tree(work_dir, "test@example.com/test--test--0")
96
 
    lib_dir = os.path.join(tdir, "lib_dir")
97
 
    os.mkdir(lib_dir)
98
 
    pybaz.register_revision_library(lib_dir)
99
 
    pybaz.set_my_id("Test User<test@example.org>")
100
 
    return tdir
101
 
 
102
 
def add_file(path, text, id):
103
 
    """
104
 
    >>> q = test_environ()
105
 
    >>> add_file("path with space", "text", "lalala")
106
 
    >>> tree = pybaz.tree_root(".")
107
 
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
108
 
    >>> ("x_lalala", "path with space") in inv
109
 
    True
110
 
    >>> teardown_environ(q)
111
 
    """
112
 
    file(path, "wb").write(text)
113
 
    add_id([path], id)
114
 
 
115
 
 
116
 
def add_dir(path, id):
117
 
    """
118
 
    >>> q = test_environ()
119
 
    >>> add_dir("path with\(sp) space", "lalala")
120
 
    >>> tree = pybaz.tree_root(".")
121
 
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
122
 
    >>> ("x_lalala", "path with\(sp) space") in inv
123
 
    True
124
 
    >>> teardown_environ(q)
125
 
    """
126
 
    os.mkdir(path)
127
 
    add_id([path], id)
128
 
 
129
 
def teardown_environ(tdir):
130
 
    os.chdir(saved_dir)
131
 
    shutil.rmtree(tdir)
132
 
 
133
 
def timport(tree, summary):
134
 
    msg = tree.log_message()
135
 
    msg["summary"] = summary
136
 
    tree.import_(msg)
137
 
 
138
 
def commit(tree, summary):
139
 
    """
140
 
    >>> q = test_environ()
141
 
    >>> tree = pybaz.tree_root(".")
142
 
    >>> timport(tree, "import")
143
 
    >>> commit(tree, "commit")
144
 
    >>> logs = [str(l.revision) for l in tree.iter_logs()]
145
 
    >>> len(logs)
146
 
    2
147
 
    >>> logs[0]
148
 
    'test@example.com/test--test--0--base-0'
149
 
    >>> logs[1]
150
 
    'test@example.com/test--test--0--patch-1'
151
 
    >>> teardown_environ(q)
152
 
    """
153
 
    msg = tree.log_message()
154
 
    msg["summary"] = summary
155
 
    tree.commit(msg)
156
 
 
157
 
def commit_test_revisions():
158
 
    """
159
 
    >>> q = test_environ()
160
 
    >>> commit_test_revisions()
161
 
    >>> a = pybaz.Archive("test@example.com")
162
 
    >>> revisions = list(a.iter_revisions("test--test--0"))
163
 
    >>> len(revisions)
164
 
    3
165
 
    >>> str(revisions[2])
166
 
    'test@example.com/test--test--0--base-0'
167
 
    >>> str(revisions[1])
168
 
    'test@example.com/test--test--0--patch-1'
169
 
    >>> str(revisions[0])
170
 
    'test@example.com/test--test--0--patch-2'
171
 
    >>> teardown_environ(q)
172
 
    """
173
 
    tree = pybaz.tree_root(".")
174
 
    add_file("mainfile", "void main(void){}", "mainfile by aaron")
175
 
    timport(tree, "Created mainfile")
176
 
    file("mainfile", "wb").write("or something like that")
177
 
    commit(tree, "altered mainfile")
178
 
    add_file("ofile", "this is another file", "ofile by aaron")
179
 
    commit(tree, "altered mainfile")
180
 
 
181
 
 
182
 
def commit_more_test_revisions():
183
 
    """
184
 
    >>> q = test_environ()
185
 
    >>> commit_test_revisions()
186
 
    >>> commit_more_test_revisions()
187
 
    >>> a = pybaz.Archive("test@example.com")
188
 
    >>> revisions = list(a.iter_revisions("test--test--0"))
189
 
    >>> len(revisions)
190
 
    4
191
 
    >>> str(revisions[0])
192
 
    'test@example.com/test--test--0--patch-3'
193
 
    >>> teardown_environ(q)
194
 
    """
195
 
    tree = pybaz.tree_root(".")
196
 
    add_file("trainfile", "void train(void){}", "trainfile by aaron")
197
 
    commit(tree, "altered trainfile")
198
 
 
199
 
class NoSuchVersion(Exception):
200
 
    def __init__(self, version):
201
 
        Exception.__init__(self, "The version %s does not exist." % version)
202
 
        self.version = version
203
 
 
204
 
def version_ancestry(version):
205
 
    """
206
 
    >>> q = test_environ()
207
 
    >>> commit_test_revisions()
208
 
    >>> version = pybaz.Version("test@example.com/test--test--0")
209
 
    >>> ancestors = version_ancestry(version)
210
 
    >>> str(ancestors[0])
211
 
    'test@example.com/test--test--0--base-0'
212
 
    >>> str(ancestors[1])
213
 
    'test@example.com/test--test--0--patch-1'
214
 
    >>> version = pybaz.Version("test@example.com/test--test--0.5")
215
 
    >>> ancestors = version_ancestry(version)
216
 
    Traceback (most recent call last):
217
 
    NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
218
 
    >>> teardown_environ(q)
219
 
    """
220
 
    try:
221
 
        revision = version.iter_revisions(reverse=True).next()
222
 
    except StopIteration:
223
 
        return ()
224
 
    except:
225
 
        print version
226
 
        if not version.exists():
227
 
            raise NoSuchVersion(version)
228
 
        else:
229
 
            raise
230
 
    ancestors = list(revision.iter_ancestors(metoo=True))
231
 
    ancestors.reverse()
232
 
    return ancestors
233
 
 
234
 
def get_last_revision(branch):
235
 
    last_patch = branch.last_revision()
236
 
    try:
237
 
        return arch_revision(last_patch)
238
 
    except NotArchRevision:
239
 
        raise UserError(
240
 
            "Directory \"%s\" already exists, and the last revision is not"
241
 
            " an Arch revision (%s)" % (branch.base, last_patch))
242
 
 
243
 
def do_branch(br_from, to_location, revision_id):
244
 
    """Derived from branch in builtins."""
245
 
    br_from.lock_read()
246
 
    try:
247
 
        try:
248
 
            os.mkdir(to_location)
249
 
        except OSError, e:
250
 
            if e.errno == errno.EEXIST:
251
 
                raise UserError('Target directory "%s" already'
252
 
                                      ' exists.' % to_location)
253
 
            if e.errno == errno.ENOENT:
254
 
                raise UserError('Parent of "%s" does not exist.' %
255
 
                                      to_location)
256
 
            else:
257
 
                raise
258
 
        try:
259
 
            copy_branch(br_from, to_location, revision_id, None)
260
 
        except NoSuchRevision:
261
 
            rmtree(to_location)
262
 
            msg = "The branch %s has no revision %s." % (from_location, revision_id)
263
 
            raise UserError(msg)
264
 
    finally:
265
 
        br_from.unlock()
266
 
 
267
 
def get_remaining_revisions(output_dir, version, reuse_history_from=[]):
268
 
    last_patch = None
269
 
    old_revno = None
270
 
    output_exists = os.path.exists(output_dir)
271
 
    if output_exists:
272
 
        # We are starting from an existing directory, figure out what
273
 
        # the current version is
274
 
        branch = Branch.open(output_dir)
275
 
        last_patch = get_last_revision(branch)
276
 
        if last_patch is None:
277
 
            raise NotPreviousImport(branch.base)
278
 
        if version is None:
279
 
            version = last_patch.version
280
 
    elif version is None:
281
 
        raise UserError("No version specified, and directory does not exist.")
282
 
 
283
 
    try:
284
 
        ancestors = version_ancestry(version)
285
 
        if not output_exists and reuse_history_from != []:
286
 
            for ancestor in reversed(ancestors):
287
 
                if last_patch is not None:
288
 
                    # found something to copy
289
 
                    break
290
 
                # try to grab a copy of ancestor
291
 
                # note that is not optimised: we could look for namespace
292
 
                # transitions and only look for the past after the 
293
 
                # transition.
294
 
                for history_root in reuse_history_from:
295
 
                    possible_source = os.path.join(history_root,
296
 
                        map_namespace(ancestor.version))
297
 
                    try:
298
 
                        source = Branch.open(possible_source)
299
 
                        rev_id = revision_id(ancestor)
300
 
                        if rev_id in source.revision_history():
301
 
                            do_branch(source, output_dir, rev_id)
302
 
                            last_patch = ancestor
303
 
                            break
304
 
                    except NotBranchError:
305
 
                        pass
306
 
    except NoSuchVersion, e:
307
 
        raise UserError(str(e))
308
 
 
309
 
    if last_patch:
310
 
        for i in range(len(ancestors)):
311
 
            if ancestors[i] == last_patch:
312
 
                break
313
 
        else:
314
 
            raise UserError("Directory \"%s\" already exists, and the last "
315
 
                "revision (%s) is not in the ancestry of %s" % 
316
 
                (output_dir, last_patch, version))
317
 
        # Strip off all of the ancestors which are already present
318
 
        # And get a directory starting with the latest ancestor
319
 
        latest_ancestor = ancestors[i]
320
 
        old_revno = Branch.open(output_dir).revno()
321
 
        ancestors = ancestors[i+1:]
322
 
    return ancestors, old_revno
323
 
 
324
 
 
325
 
###class Importer(object):
326
 
###    """An importer.
327
 
###    
328
 
###    Currently this is used as a parameter object, though more behaviour is
329
 
###    possible later.
330
 
###    """
331
 
###
332
 
###    def __init__(self, output_dir, version, printer, fancy=True, fast=False,
333
 
###                 verbose=False, dry_run=False, max_count=None, 
334
 
###                   reuse_history_from=[]):
335
 
###        self.output_dir = output_dir
336
 
###        self.version = version
337
 
###        self.
338
 
 
339
 
 
340
 
def import_version(output_dir, version, printer, fancy=True, fast=False,
341
 
                   verbose=False, dry_run=False, max_count=None,
342
 
                   reuse_history_from=[]):
343
 
    """
344
 
    >>> q = test_environ()
345
 
    >>> result_path = os.path.join(q, "result")
346
 
    >>> commit_test_revisions()
347
 
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
348
 
    >>> def printer(message): print message
349
 
    >>> import_version('/', version, printer, fancy=False, dry_run=True)
350
 
    Traceback (most recent call last):
351
 
    NotPreviousImport: / is not the location of a previous import.
352
 
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
353
 
    Traceback (most recent call last):
354
 
    UserError: The version test@example.com/test--test--0.1 does not exist.
355
 
    >>> version = pybaz.Version("test@example.com/test--test--0")
356
 
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
357
 
    not fancy
358
 
    ....
359
 
    Dry run, not modifying output_dir
360
 
    Cleaning up
361
 
    >>> import_version(result_path, version, printer, fancy=False)
362
 
    not fancy
363
 
    ....
364
 
    Cleaning up
365
 
    Import complete.
366
 
    >>> import_version(result_path, version, printer, fancy=False)
367
 
    Tree is up-to-date with test@example.com/test--test--0--patch-2
368
 
    >>> commit_more_test_revisions()
369
 
    >>> import_version(result_path, version, printer, fancy=False)
370
 
    not fancy
371
 
    ..
372
 
    Cleaning up
373
 
    Import complete.
374
 
    >>> teardown_environ(q)
375
 
    """
376
 
    try:
377
 
        ancestors, old_revno = get_remaining_revisions(output_dir, version,
378
 
                                                       reuse_history_from)
379
 
    except NotBranchError, e:
380
 
        raise NotPreviousImport(e.path)
381
 
    if old_revno is None and len(ancestors) == 0:
382
 
        print 'Version %s has no revisions.' % version
383
 
        return
384
 
    if len(ancestors) == 0:
385
 
        last_revision = get_last_revision(Branch.open(output_dir))
386
 
        print 'Tree is up-to-date with %s' % last_revision
387
 
        return
388
 
 
389
 
    progress_bar = ProgressBar()
390
 
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
391
 
                               dir=os.path.dirname(output_dir))
392
 
    try:
393
 
        if not fancy:
394
 
            print "not fancy"
395
 
        try:
396
 
            for result in iter_import_version(output_dir, ancestors, tempdir,
397
 
                    progress_bar, fast=fast, verbose=verbose, dry_run=dry_run,
398
 
                    max_count=max_count):
399
 
                if fancy:
400
 
                    show_progress(progress_bar, result)
401
 
                else:
402
 
                    sys.stdout.write('.')
403
 
        finally:
404
 
            if fancy:
405
 
                progress_bar.clear()
406
 
            else:
407
 
                sys.stdout.write('\n')
408
 
 
409
 
        if dry_run:
410
 
            print 'Dry run, not modifying output_dir'
411
 
            return
412
 
        if os.path.exists(output_dir):
413
 
            # Move the bzr control directory back, and update the working tree
414
 
            revdir = os.path.join(tempdir, "rd")
415
 
            if os.path.exists(revdir):
416
 
                # actual imports were done
417
 
                tmp_bzr_dir = os.path.join(tempdir, '.bzr')
418
 
                
419
 
                bzr_dir = os.path.join(output_dir, '.bzr')
420
 
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
421
 
    
422
 
                os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
423
 
                os.rename(new_bzr_dir, bzr_dir)
424
 
                try:
425
 
                    bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno), 
426
 
                                       check_clean=False, this_dir=output_dir, 
427
 
                                       ignore_zero=True)
428
 
                except:
429
 
                    # If something failed, move back the original bzr directory
430
 
                    os.rename(bzr_dir, new_bzr_dir)
431
 
                    os.rename(tmp_bzr_dir, bzr_dir)
432
 
                    raise
433
 
            else:
434
 
                # no imports - perhaps just append_revisions
435
 
                # should not fail:
436
 
                bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno), 
437
 
                                   check_clean=False, this_dir=output_dir, 
438
 
                                   ignore_zero=True)
439
 
        else:
440
 
            revdir = os.path.join(tempdir, "rd")
441
 
            os.rename(revdir, output_dir)
442
 
 
443
 
    finally:
444
 
        printer('Cleaning up')
445
 
        shutil.rmtree(tempdir)
446
 
    printer("Import complete.")
447
 
            
448
 
class UserError(BzrCommandError):
449
 
    def __init__(self, message):
450
 
        """Exception to throw when a user makes an impossible request
451
 
        :param message: The message to emit when printing this exception
452
 
        :type message: string
453
 
        """
454
 
        BzrCommandError.__init__(self, message)
455
 
 
456
 
class NotPreviousImport(UserError):
457
 
    def __init__(self, path):
458
 
        UserError.__init__(self, "%s is not the location of a previous import."
459
 
                           % path)
460
 
 
461
 
 
462
 
def revision_id(arch_revision):
463
 
    """
464
 
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
465
 
    designates a revision imported with an experimental algorithm.  A number
466
 
    would indicate a particular standardized version.
467
 
 
468
 
    :param arch_revision: The Arch revision to generate an ID for.
469
 
 
470
 
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
471
 
    'Arch-1:you@example.com%cat--br--0--base-0'
472
 
    """
473
 
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
474
 
 
475
 
class NotArchRevision(Exception):
476
 
    def __init__(self, revision_id):
477
 
        msg = "The revision id %s does not look like it came from Arch."\
478
 
            % revision_id
479
 
        Exception.__init__(self, msg)
480
 
 
481
 
def arch_revision(revision_id):
482
 
    """
483
 
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
484
 
    Traceback (most recent call last):
485
 
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
486
 
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
487
 
    Traceback (most recent call last):
488
 
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
489
 
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
490
 
    'jrandom@example.com/test--test--0--patch-5'
491
 
    """
492
 
    if revision_id is None:
493
 
        return None
494
 
    if revision_id[:7] != 'Arch-1:':
495
 
        raise NotArchRevision(revision_id)
496
 
    else:
497
 
        try:
498
 
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
499
 
        except pybaz.errors.NamespaceError, e:
500
 
            raise NotArchRevision(revision_id)
501
 
            
502
 
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
503
 
                        verbose=False, dry_run=False, max_count=None):
504
 
    revdir = None
505
 
 
506
 
    # Uncomment this for testing, it basically just has baz2bzr only update
507
 
    # 5 patches at a time
508
 
    if max_count:
509
 
        ancestors = ancestors[:max_count]
510
 
 
511
 
    # Not sure if I want this output. basically it tells you ahead of time
512
 
    # what it is going to do, but then later it tells you as it is doing it.
513
 
    # what probably would be best would be to collapse it into ranges, so that
514
 
    # this gives the simple view, and then later it gives the blow by blow.
515
 
    #if verbose:
516
 
    #    print 'Adding the following revisions:'
517
 
    #    for a in ancestors:
518
 
    #        print '\t%s' % a
519
 
 
520
 
    previous_version=None
521
 
    missing_ancestor = None
522
 
 
523
 
    for i in range(len(ancestors)):
524
 
        revision = ancestors[i]
525
 
        rev_id = revision_id(revision)
526
 
        direct_merges = []
527
 
        if verbose:
528
 
            version = str(revision.version)
529
 
            if version != previous_version:
530
 
                clear_progress_bar()
531
 
                print '\rOn version: %s' % version
532
 
            yield Progress(str(revision.patchlevel), i, len(ancestors))
533
 
            previous_version = version
534
 
        else:
535
 
            yield Progress("revisions", i, len(ancestors))
536
 
        if revdir is None and os.path.exists(output_dir):
537
 
            # check for imported revisions and if present just append immediately
538
 
            branch = Branch.open(output_dir)
539
 
            if branch.has_revision(rev_id):
540
 
                branch.append_revision(rev_id)
541
 
                continue
542
 
        if revdir is None:
543
 
            revdir = os.path.join(tempdir, "rd")
544
 
            try:
545
 
                tree, baz_inv, log = get_revision(revdir, revision)
546
 
            except pybaz.errors.ExecProblem, e:
547
 
                if ("%s" % e.args).find('could not connect') == -1:
548
 
                    raise
549
 
                missing_ancestor = revision
550
 
                revdir = None
551
 
                print ("unable to access ancestor %s, making into a merge."
552
 
                       % missing_ancestor)
553
 
                continue
554
 
            if os.path.exists(output_dir):
555
 
                bzr_dir = os.path.join(output_dir, '.bzr')
556
 
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
557
 
                # This would be much faster with a simple os.rename(), but if
558
 
                # we fail, we have corrupted the original .bzr directory.  Is
559
 
                # that a big problem, as we can just back out the last
560
 
                # revisions in .bzr/revision_history I don't really know
561
 
                # RBC20051024 - yes, it would be a problem as we could not then
562
 
                # apply the corrupted revision.
563
 
                shutil.copytree(bzr_dir, new_bzr_dir)
564
 
                # Now revdir should have a tree with the latest .bzr, and the
565
 
                # next revision of the baz tree
566
 
                branch = Branch.open(revdir)
567
 
            else:
568
 
                branch = Branch.initialize(revdir)
569
 
        else:
570
 
            old = os.path.join(revdir, ".bzr")
571
 
            new = os.path.join(tempdir, ".bzr")
572
 
            os.rename(old, new)
573
 
            baz_inv, log = apply_revision(tree, revision)
574
 
            os.rename(new, old)
575
 
            branch = Branch.open(revdir)
576
 
        # cached so we can delete the log
577
 
        log_date = log.date
578
 
        log_summary = log.summary
579
 
        log_description = log.description
580
 
        is_continuation = log.continuation_of is not None
581
 
        log_creator = log.creator
582
 
        direct_merges = get_direct_merges(revdir, revision, log)
583
 
 
584
 
        timestamp = email.Utils.mktime_tz(log_date + (0,))
585
 
        if log_summary is None:
586
 
            log_summary = ""
587
 
        # log_descriptions of None and "" are ignored.
588
 
        if not is_continuation and log_description:
589
 
            log_message = "\n".join((log_summary, log_description))
590
 
        else:
591
 
            log_message = log_summary
592
 
        branch.lock_write()
593
 
        target_tree = WorkingTree(revdir ,branch=branch)
594
 
        target_tree.lock_write()
595
 
        try:
596
 
            if missing_ancestor:
597
 
                # if we want it to be in revision-history, do that here.
598
 
                target_tree.add_pending_merge(revision_id(missing_ancestor))
599
 
                missing_ancestor = None
600
 
            for merged_rev in direct_merges:
601
 
                target_tree.add_pending_merge(revision_id(merged_rev))
602
 
            target_tree.set_inventory(baz_inv)
603
 
            commitobj = Commit(reporter=ImportCommitReporter(pb))
604
 
            commitobj.commit(branch, log_message.decode('ascii', 'replace'), 
605
 
                             verbose=False, committer=log_creator,
606
 
                             timestamp=timestamp, timezone=0, rev_id=rev_id)
607
 
        finally:
608
 
            target_tree.unlock()
609
 
            branch.unlock()
610
 
    yield Progress("revisions", len(ancestors), len(ancestors))
611
 
    unlink_unversioned(branch, revdir)
612
 
 
613
 
def get_direct_merges(revdir, revision, log):
614
 
    continuation = log.continuation_of
615
 
    previous_version = revision.version
616
 
    if pybaz.WorkingTree(revdir).tree_version != previous_version:
617
 
        pybaz.WorkingTree(revdir).set_tree_version(previous_version)
618
 
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir, 
619
 
        revision.category.nonarch, revision.branch.nonarch, 
620
 
        revision.version.nonarch, revision.archive, revision.patchlevel)
621
 
    temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
622
 
    os.rename(log_path, temp_path)
623
 
    merges = list(iter_new_merges(revdir, revision.version))
624
 
    direct = direct_merges(merges, [continuation])
625
 
    os.rename(temp_path, log_path)
626
 
    return direct
627
 
 
628
 
def unlink_unversioned(branch, revdir):
629
 
    for unversioned in branch.working_tree().extras():
630
 
        path = os.path.join(revdir, unversioned)
631
 
        if os.path.isdir(path):
632
 
            shutil.rmtree(path)
633
 
        else:
634
 
            os.unlink(path)
635
 
 
636
 
def get_log(tree, revision):
637
 
    log = pybaz.Patchlog(revision, tree=tree)
638
 
    assert str(log.revision) == str(revision), (log.revision, revision)
639
 
    return log
640
 
 
641
 
def get_revision(revdir, revision):
642
 
    tree = revision.get(revdir)
643
 
    log = get_log(tree, revision)
644
 
    try:
645
 
        return tree, bzr_inventory_data(tree), log 
646
 
    except BadFileKind, e:
647
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
648
 
 
649
 
 
650
 
def apply_revision(tree, revision):
651
 
    revision.apply(tree)
652
 
    log = get_log(tree, revision)
653
 
    try:
654
 
        return bzr_inventory_data(tree), log
655
 
    except BadFileKind, e:
656
 
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
657
 
 
658
 
 
659
 
class BadFileKind(Exception):
660
 
    """The file kind is not permitted in bzr inventories"""
661
 
    def __init__(self, tree_root, path, kind):
662
 
        self.tree_root = tree_root
663
 
        self.path = path
664
 
        self.kind = kind
665
 
        Exception.__init__(self, "File %s is of forbidden type %s" %
666
 
                           (os.path.join(tree_root, path), kind))
667
 
 
668
 
 
669
 
def bzr_inventory_data(tree):
670
 
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
671
 
    inv_map = {}
672
 
    for arch_id, path in inv_iter:
673
 
        bzr_file_id = map_file_id(arch_id)
674
 
        inv_map[path] = bzr_file_id 
675
 
 
676
 
    bzr_inv = []
677
 
    for path, file_id in inv_map.iteritems():
678
 
        full_path = os.path.join(tree, path)
679
 
        kind = bzrlib.osutils.file_kind(full_path)
680
 
        if kind not in ("file", "directory", "symlink"):
681
 
            raise BadFileKind(tree, path, kind)
682
 
        parent_dir = os.path.dirname(path)
683
 
        if parent_dir != "":
684
 
            parent_id = inv_map[parent_dir]
685
 
        else:
686
 
            parent_id = bzrlib.inventory.ROOT_ID
687
 
        bzr_inv.append((path, file_id, parent_id, kind))
688
 
    bzr_inv.sort()
689
 
    return bzr_inv
690
 
 
691
 
_global_option('max-count', type = int)
692
 
class cmd_baz_import_branch(Command):
693
 
    """Import an Arch or Baz branch into a bzr branch"""
694
 
    takes_args = ['to_location', 'from_branch?', 'reuse_history*']
695
 
    takes_options = ['verbose', 'max-count']
696
 
 
697
 
    def printer(self, name):
698
 
        print name
699
 
 
700
 
    def run(self, to_location, from_branch=None, fast=False, max_count=None,
701
 
            verbose=False, dry_run=False, reuse_history_list=[]):
702
 
        to_location = os.path.realpath(str(to_location))
703
 
        if from_branch is not None:
704
 
            try:
705
 
                from_branch = pybaz.Version(from_branch)
706
 
            except pybaz.errors.NamespaceError:
707
 
                print "%s is not a valid Arch branch." % from_branch
708
 
                return 1
709
 
        if reuse_history_list is None:
710
 
            reuse_history_list = []
711
 
        import_version(to_location, from_branch, self.printer, 
712
 
                       max_count=max_count, 
713
 
                       reuse_history_from=reuse_history_list)
714
 
 
715
 
 
716
 
class NotInABranch(Exception):
717
 
    def __init__(self, path):
718
 
        Exception.__init__(self, "%s is not in a branch." % path)
719
 
        self.path = path
720
 
 
721
 
 
722
 
class cmd_baz_import(Command):
723
 
    """Import an Arch or Baz archive into bzr branches.
724
 
    
725
 
    reuse_history allows you to specify any previous imports you 
726
 
    have done of different archives, which this archive has branches
727
 
    tagged from. This will dramatically reduce the time to convert 
728
 
    the archive as it will not have to convert the history already
729
 
    converted in that other branch.
730
 
    """
731
 
    takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
732
 
    takes_options = ['verbose']
733
 
 
734
 
    def printer(self, name):
735
 
        print name
736
 
 
737
 
    def run(self, to_root_dir, from_archive, verbose=False,
738
 
            reuse_history_list=[]):
739
 
        if reuse_history_list is None:
740
 
            reuse_history_list = []
741
 
        to_root = str(os.path.realpath(to_root_dir))
742
 
        if not os.path.exists(to_root):
743
 
            os.mkdir(to_root)
744
 
        import_archive(to_root, from_archive, verbose, self.printer, 
745
 
                       reuse_history_list)
746
 
 
747
 
 
748
 
def import_archive(to_root, from_archive, verbose, printer,
749
 
                   reuse_history_from=[]):
750
 
    real_to = os.path.realpath(to_root)
751
 
    history_locations = [real_to] + reuse_history_from
752
 
    for version in pybaz.Archive(str(from_archive)).iter_versions():
753
 
        target = os.path.join(to_root, map_namespace(version))
754
 
        printer("importing %s into %s" % (version, target))
755
 
        if not os.path.exists(os.path.dirname(target)):
756
 
            os.makedirs(os.path.dirname(target))
757
 
        try:
758
 
            import_version(target, version, printer,
759
 
                           reuse_history_from=reuse_history_from)
760
 
        except pybaz.errors.ExecProblem,e:
761
 
            if str(e).find('The requested revision cannot be built.') != -1:
762
 
                printer("Skipping version %s as it cannot be built due"
763
 
                        " to a missing parent archive." % version)
764
 
            else:
765
 
                raise
766
 
        except UserError, e:
767
 
            if str(e).find('already exists, and the last revision ') != -1:
768
 
                printer("Skipping version %s as it has had commits made"
769
 
                        " since it was converted to bzr." % version)
770
 
            else:
771
 
                raise
772
 
 
773
 
 
774
 
def map_namespace(a_version):
775
 
    a_version = pybaz.Version("%s" % a_version)
776
 
    parser = NameParser(a_version)
777
 
    version = parser.get_version()
778
 
    branch = parser.get_branch()
779
 
    category = parser.get_category()
780
 
    if branch is None or branch == '':
781
 
        branch = "+trunk"
782
 
    if version == '0':
783
 
        return "%s/%s" % (category, branch)
784
 
    return "%s/%s/%s" % (category, version, branch)
785
 
 
786
 
def map_file_id(file_id):
787
 
    """Convert a baz file id to a bzr one."""
788
 
    return file_id.replace('%', '%25').replace('/', '%2f')