~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

Update for integration move of read_working_inventory from Branch to WorkingTree.

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 version is None:
 
277
            version = last_patch.version
 
278
    elif version is None:
 
279
        raise UserError("No version specified, and directory does not exist.")
 
280
 
 
281
    try:
 
282
        ancestors = version_ancestry(version)
 
283
        if not output_exists and reuse_history_from != []:
 
284
            for ancestor in reversed(ancestors):
 
285
                if last_patch is not None:
 
286
                    # found something to copy
 
287
                    break
 
288
                # try to grab a copy of ancestor
 
289
                # note that is not optimised: we could look for namespace
 
290
                # transitions and only look for the past after the 
 
291
                # transition.
 
292
                for history_root in reuse_history_from:
 
293
                    possible_source = os.path.join(history_root,
 
294
                        map_namespace(ancestor.version))
 
295
                    try:
 
296
                        source = Branch.open(possible_source)
 
297
                        rev_id = revision_id(ancestor)
 
298
                        if rev_id in source.revision_history():
 
299
                            do_branch(source, output_dir, rev_id)
 
300
                            last_patch = ancestor
 
301
                            break
 
302
                    except NotBranchError:
 
303
                        pass
 
304
    except NoSuchVersion, e:
 
305
        raise UserError(str(e))
 
306
 
 
307
    if last_patch:
 
308
        for i in range(len(ancestors)):
 
309
            if ancestors[i] == last_patch:
 
310
                break
 
311
        else:
 
312
            raise UserError("Directory \"%s\" already exists, and the last "
 
313
                "revision (%s) is not in the ancestry of %s" % 
 
314
                (output_dir, last_patch, version))
 
315
        # Strip off all of the ancestors which are already present
 
316
        # And get a directory starting with the latest ancestor
 
317
        latest_ancestor = ancestors[i]
 
318
        old_revno = Branch.open(output_dir).revno()
 
319
        ancestors = ancestors[i+1:]
 
320
    return ancestors, old_revno
 
321
 
 
322
 
 
323
###class Importer(object):
 
324
###    """An importer.
 
325
###    
 
326
###    Currently this is used as a parameter object, though more behaviour is
 
327
###    possible later.
 
328
###    """
 
329
###
 
330
###    def __init__(self, output_dir, version, printer, fancy=True, fast=False,
 
331
###                 verbose=False, dry_run=False, max_count=None, 
 
332
###                   reuse_history_from=[]):
 
333
###        self.output_dir = output_dir
 
334
###        self.version = version
 
335
###        self.
 
336
 
 
337
 
 
338
def import_version(output_dir, version, printer, fancy=True, fast=False,
 
339
                   verbose=False, dry_run=False, max_count=None,
 
340
                   reuse_history_from=[]):
 
341
    """
 
342
    >>> q = test_environ()
 
343
    >>> result_path = os.path.join(q, "result")
 
344
    >>> commit_test_revisions()
 
345
    >>> version = pybaz.Version("test@example.com/test--test--0.1")
 
346
    >>> def printer(message): print message
 
347
    >>> import_version('/', version, printer, fancy=False, dry_run=True)
 
348
    Traceback (most recent call last):
 
349
    UserError: / exists, but is not a bzr branch.
 
350
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
 
351
    Traceback (most recent call last):
 
352
    UserError: The version test@example.com/test--test--0.1 does not exist.
 
353
    >>> version = pybaz.Version("test@example.com/test--test--0")
 
354
    >>> import_version(result_path, version, printer, fancy=False, dry_run=True)
 
355
    not fancy
 
356
    ....
 
357
    Dry run, not modifying output_dir
 
358
    Cleaning up
 
359
    >>> import_version(result_path, version, printer, fancy=False)
 
360
    not fancy
 
361
    ....
 
362
    Cleaning up
 
363
    Import complete.
 
364
    >>> import_version(result_path, version, printer, fancy=False)
 
365
    Tree is up-to-date with test@example.com/test--test--0--patch-2
 
366
    >>> commit_more_test_revisions()
 
367
    >>> import_version(result_path, version, printer, fancy=False)
 
368
    not fancy
 
369
    ..
 
370
    Cleaning up
 
371
    Import complete.
 
372
    >>> teardown_environ(q)
 
373
    """
 
374
    try:
 
375
        ancestors, old_revno = get_remaining_revisions(output_dir, version,
 
376
                                                       reuse_history_from)
 
377
    except NotBranchError, e:
 
378
        raise UserError("%s exists, but is not a bzr branch." % output_dir)
 
379
    if old_revno is None and len(ancestors) == 0:
 
380
        print 'Version %s has no revisions.' % version
 
381
        return
 
382
    if len(ancestors) == 0:
 
383
        last_revision = get_last_revision(Branch.open(output_dir))
 
384
        print 'Tree is up-to-date with %s' % last_revision
 
385
        return
 
386
 
 
387
    progress_bar = ProgressBar()
 
388
    tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
 
389
                               dir=os.path.dirname(output_dir))
 
390
    try:
 
391
        if not fancy:
 
392
            print "not fancy"
 
393
        try:
 
394
            for result in iter_import_version(output_dir, ancestors, tempdir,
 
395
                    progress_bar, fast=fast, verbose=verbose, dry_run=dry_run,
 
396
                    max_count=max_count):
 
397
                if fancy:
 
398
                    show_progress(progress_bar, result)
 
399
                else:
 
400
                    sys.stdout.write('.')
 
401
        finally:
 
402
            if fancy:
 
403
                progress_bar.clear()
 
404
            else:
 
405
                sys.stdout.write('\n')
 
406
 
 
407
        if dry_run:
 
408
            print 'Dry run, not modifying output_dir'
 
409
            return
 
410
        if os.path.exists(output_dir):
 
411
            # Move the bzr control directory back, and update the working tree
 
412
            revdir = os.path.join(tempdir, "rd")
 
413
            if os.path.exists(revdir):
 
414
                # actual imports were done
 
415
                tmp_bzr_dir = os.path.join(tempdir, '.bzr')
 
416
                
 
417
                bzr_dir = os.path.join(output_dir, '.bzr')
 
418
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
419
    
 
420
                os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
 
421
                os.rename(new_bzr_dir, bzr_dir)
 
422
                try:
 
423
                    bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno), 
 
424
                                       check_clean=False, this_dir=output_dir, 
 
425
                                       ignore_zero=True)
 
426
                except:
 
427
                    # If something failed, move back the original bzr directory
 
428
                    os.rename(bzr_dir, new_bzr_dir)
 
429
                    os.rename(tmp_bzr_dir, bzr_dir)
 
430
                    raise
 
431
            else:
 
432
                # no imports - perhaps just append_revisions
 
433
                # should not fail:
 
434
                bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno), 
 
435
                                   check_clean=False, this_dir=output_dir, 
 
436
                                   ignore_zero=True)
 
437
        else:
 
438
            revdir = os.path.join(tempdir, "rd")
 
439
            os.rename(revdir, output_dir)
 
440
 
 
441
    finally:
 
442
        printer('Cleaning up')
 
443
        shutil.rmtree(tempdir)
 
444
    printer("Import complete.")
 
445
            
 
446
class UserError(BzrCommandError):
 
447
    def __init__(self, message):
 
448
        """Exception to throw when a user makes an impossible request
 
449
        :param message: The message to emit when printing this exception
 
450
        :type message: string
 
451
        """
 
452
        BzrCommandError.__init__(self, message)
 
453
 
 
454
def revision_id(arch_revision):
 
455
    """
 
456
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
 
457
    designates a revision imported with an experimental algorithm.  A number
 
458
    would indicate a particular standardized version.
 
459
 
 
460
    :param arch_revision: The Arch revision to generate an ID for.
 
461
 
 
462
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
 
463
    'Arch-1:you@example.com%cat--br--0--base-0'
 
464
    """
 
465
    return "Arch-1:%s" % str(arch_revision).replace('/', '%')
 
466
 
 
467
class NotArchRevision(Exception):
 
468
    def __init__(self, revision_id):
 
469
        msg = "The revision id %s does not look like it came from Arch."\
 
470
            % revision_id
 
471
        Exception.__init__(self, msg)
 
472
 
 
473
def arch_revision(revision_id):
 
474
    """
 
475
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
 
476
    Traceback (most recent call last):
 
477
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
 
478
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
 
479
    Traceback (most recent call last):
 
480
    NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
 
481
    >>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
 
482
    'jrandom@example.com/test--test--0--patch-5'
 
483
    """
 
484
    if revision_id is None:
 
485
        return None
 
486
    if revision_id[:7] != 'Arch-1:':
 
487
        raise NotArchRevision(revision_id)
 
488
    else:
 
489
        try:
 
490
            return pybaz.Revision(revision_id[7:].replace('%', '/'))
 
491
        except pybaz.errors.NamespaceError, e:
 
492
            raise NotArchRevision(revision_id)
 
493
            
 
494
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
 
495
                        verbose=False, dry_run=False, max_count=None):
 
496
    revdir = None
 
497
 
 
498
    # Uncomment this for testing, it basically just has baz2bzr only update
 
499
    # 5 patches at a time
 
500
    if max_count:
 
501
        ancestors = ancestors[:max_count]
 
502
 
 
503
    # Not sure if I want this output. basically it tells you ahead of time
 
504
    # what it is going to do, but then later it tells you as it is doing it.
 
505
    # what probably would be best would be to collapse it into ranges, so that
 
506
    # this gives the simple view, and then later it gives the blow by blow.
 
507
    #if verbose:
 
508
    #    print 'Adding the following revisions:'
 
509
    #    for a in ancestors:
 
510
    #        print '\t%s' % a
 
511
 
 
512
    previous_version=None
 
513
    missing_ancestor = None
 
514
 
 
515
    for i in range(len(ancestors)):
 
516
        revision = ancestors[i]
 
517
        rev_id = revision_id(revision)
 
518
        direct_merges = []
 
519
        if verbose:
 
520
            version = str(revision.version)
 
521
            if version != previous_version:
 
522
                clear_progress_bar()
 
523
                print '\rOn version: %s' % version
 
524
            yield Progress(str(revision.patchlevel), i, len(ancestors))
 
525
            previous_version = version
 
526
        else:
 
527
            yield Progress("revisions", i, len(ancestors))
 
528
        if revdir is None and os.path.exists(output_dir):
 
529
            # check for imported revisions and if present just append immediately
 
530
            branch = Branch.open(output_dir)
 
531
            if branch.has_revision(rev_id):
 
532
                branch.append_revision(rev_id)
 
533
                continue
 
534
        if revdir is None:
 
535
            revdir = os.path.join(tempdir, "rd")
 
536
            try:
 
537
                tree, baz_inv, log = get_revision(revdir, revision)
 
538
            except pybaz.errors.ExecProblem, e:
 
539
                if ("%s" % e.args).find('could not connect') == -1:
 
540
                    raise
 
541
                missing_ancestor = revision
 
542
                revdir = None
 
543
                print ("unable to access ancestor %s, making into a merge."
 
544
                       % missing_ancestor)
 
545
                continue
 
546
            if os.path.exists(output_dir):
 
547
                bzr_dir = os.path.join(output_dir, '.bzr')
 
548
                new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
 
549
                # This would be much faster with a simple os.rename(), but if
 
550
                # we fail, we have corrupted the original .bzr directory.  Is
 
551
                # that a big problem, as we can just back out the last
 
552
                # revisions in .bzr/revision_history I don't really know
 
553
                # RBC20051024 - yes, it would be a problem as we could not then
 
554
                # apply the corrupted revision.
 
555
                shutil.copytree(bzr_dir, new_bzr_dir)
 
556
                # Now revdir should have a tree with the latest .bzr, and the
 
557
                # next revision of the baz tree
 
558
                branch = Branch.open(revdir)
 
559
            else:
 
560
                branch = Branch.initialize(revdir)
 
561
        else:
 
562
            old = os.path.join(revdir, ".bzr")
 
563
            new = os.path.join(tempdir, ".bzr")
 
564
            os.rename(old, new)
 
565
            baz_inv, log = apply_revision(tree, revision)
 
566
            os.rename(new, old)
 
567
            branch = Branch.open(revdir)
 
568
        # cached so we can delete the log
 
569
        log_date = log.date
 
570
        log_summary = log.summary
 
571
        log_description = log.description
 
572
        is_continuation = log.continuation_of is not None
 
573
        log_creator = log.creator
 
574
        direct_merges = get_direct_merges(revdir, revision, log)
 
575
 
 
576
        timestamp = email.Utils.mktime_tz(log_date + (0,))
 
577
        if log_summary is None:
 
578
            log_summary = ""
 
579
        # log_descriptions of None and "" are ignored.
 
580
        if not is_continuation and log_description:
 
581
            log_message = "\n".join((log_summary, log_description))
 
582
        else:
 
583
            log_message = log_summary
 
584
        branch.lock_write()
 
585
        target_tree = WorkingTree(revdir ,branch=branch)
 
586
        target_tree.lock_write()
 
587
        try:
 
588
            if missing_ancestor:
 
589
                # if we want it to be in revision-history, do that here.
 
590
                branch.add_pending_merge(revision_id(missing_ancestor))
 
591
                missing_ancestor = None
 
592
            for merged_rev in direct_merges:
 
593
                branch.add_pending_merge(revision_id(merged_rev))
 
594
            target_tree.set_inventory(baz_inv)
 
595
            commitobj = Commit(reporter=ImportCommitReporter(pb))
 
596
            commitobj.commit(branch, log_message.decode('ascii', 'replace'), 
 
597
                             verbose=False, committer=log_creator,
 
598
                             timestamp=timestamp, timezone=0, rev_id=rev_id)
 
599
        finally:
 
600
            target_tree.unlock()
 
601
            branch.unlock()
 
602
    yield Progress("revisions", len(ancestors), len(ancestors))
 
603
    unlink_unversioned(branch, revdir)
 
604
 
 
605
def get_direct_merges(revdir, revision, log):
 
606
    continuation = log.continuation_of
 
607
    previous_version = revision.version
 
608
    if pybaz.WorkingTree(revdir).tree_version != previous_version:
 
609
        pybaz.WorkingTree(revdir).set_tree_version(previous_version)
 
610
    log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir, 
 
611
        revision.category.nonarch, revision.branch.nonarch, 
 
612
        revision.version.nonarch, revision.archive, revision.patchlevel)
 
613
    temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
 
614
    os.rename(log_path, temp_path)
 
615
    merges = list(iter_new_merges(revdir, revision.version))
 
616
    direct = direct_merges(merges, [continuation])
 
617
    os.rename(temp_path, log_path)
 
618
    return direct
 
619
 
 
620
def unlink_unversioned(branch, revdir):
 
621
    for unversioned in branch.working_tree().extras():
 
622
        path = os.path.join(revdir, unversioned)
 
623
        if os.path.isdir(path):
 
624
            shutil.rmtree(path)
 
625
        else:
 
626
            os.unlink(path)
 
627
 
 
628
def get_log(tree, revision):
 
629
    log = pybaz.Patchlog(revision, tree=tree)
 
630
    assert str(log.revision) == str(revision), (log.revision, revision)
 
631
    return log
 
632
 
 
633
def get_revision(revdir, revision):
 
634
    tree = revision.get(revdir)
 
635
    log = get_log(tree, revision)
 
636
    try:
 
637
        return tree, bzr_inventory_data(tree), log 
 
638
    except BadFileKind, e:
 
639
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
640
 
 
641
 
 
642
def apply_revision(tree, revision):
 
643
    revision.apply(tree)
 
644
    log = get_log(tree, revision)
 
645
    try:
 
646
        return bzr_inventory_data(tree), log
 
647
    except BadFileKind, e:
 
648
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
 
649
 
 
650
 
 
651
class BadFileKind(Exception):
 
652
    """The file kind is not permitted in bzr inventories"""
 
653
    def __init__(self, tree_root, path, kind):
 
654
        self.tree_root = tree_root
 
655
        self.path = path
 
656
        self.kind = kind
 
657
        Exception.__init__(self, "File %s is of forbidden type %s" %
 
658
                           (os.path.join(tree_root, path), kind))
 
659
 
 
660
 
 
661
def bzr_inventory_data(tree):
 
662
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
 
663
    inv_map = {}
 
664
    for arch_id, path in inv_iter:
 
665
        bzr_file_id = map_file_id(arch_id)
 
666
        inv_map[path] = bzr_file_id 
 
667
 
 
668
    bzr_inv = []
 
669
    for path, file_id in inv_map.iteritems():
 
670
        full_path = os.path.join(tree, path)
 
671
        kind = bzrlib.osutils.file_kind(full_path)
 
672
        if kind not in ("file", "directory", "symlink"):
 
673
            raise BadFileKind(tree, path, kind)
 
674
        parent_dir = os.path.dirname(path)
 
675
        if parent_dir != "":
 
676
            parent_id = inv_map[parent_dir]
 
677
        else:
 
678
            parent_id = bzrlib.inventory.ROOT_ID
 
679
        bzr_inv.append((path, file_id, parent_id, kind))
 
680
    bzr_inv.sort()
 
681
    return bzr_inv
 
682
 
 
683
_global_option('max-count', type = int)
 
684
class cmd_baz_import_branch(Command):
 
685
    """Import an Arch or Baz branch into a bzr branch"""
 
686
    takes_args = ['to_location', 'from_branch?', 'reuse_history*']
 
687
    takes_options = ['verbose', 'max-count']
 
688
 
 
689
    def printer(self, name):
 
690
        print name
 
691
 
 
692
    def run(self, to_location, from_branch=None, fast=False, max_count=None,
 
693
            verbose=False, dry_run=False, reuse_history_list=[]):
 
694
        to_location = os.path.realpath(str(to_location))
 
695
        if from_branch is not None:
 
696
            try:
 
697
                from_branch = pybaz.Version(from_branch)
 
698
            except pybaz.errors.NamespaceError:
 
699
                print "%s is not a valid Arch branch." % from_branch
 
700
                return 1
 
701
        if reuse_history_list is None:
 
702
            reuse_history_list = []
 
703
        import_version(to_location, from_branch, self.printer, 
 
704
                       max_count=max_count, reuse_history_from=reuse_history_list)
 
705
 
 
706
 
 
707
class cmd_baz_import(Command):
 
708
    """Import an Arch or Baz archive into bzr branches.
 
709
    
 
710
    reuse_history allows you to specify any previous imports you 
 
711
    have done of different archives, which this archive has branches
 
712
    tagged from. This will dramatically reduce the time to convert 
 
713
    the archive as it will not have to convert the history already
 
714
    converted in that other branch.
 
715
    """
 
716
    takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
 
717
    takes_options = ['verbose']
 
718
 
 
719
    def printer(self, name):
 
720
        print name
 
721
 
 
722
    def run(self, to_root_dir, from_archive, verbose=False,
 
723
            reuse_history_list=[]):
 
724
        if reuse_history_list is None:
 
725
            reuse_history_list = []
 
726
        to_root = str(os.path.realpath(to_root_dir))
 
727
        if not os.path.exists(to_root):
 
728
            os.mkdir(to_root)
 
729
        import_archive(to_root, from_archive, verbose, self.printer, 
 
730
                       reuse_history_list)
 
731
 
 
732
 
 
733
def import_archive(to_root, from_archive, verbose, printer,
 
734
                   reuse_history_from=[]):
 
735
    real_to = os.path.realpath(to_root)
 
736
    history_locations = [real_to] + reuse_history_from
 
737
    for version in pybaz.Archive(str(from_archive)).iter_versions():
 
738
        target = os.path.join(to_root, map_namespace(version))
 
739
        printer("importing %s into %s" % (version, target))
 
740
        if not os.path.exists(os.path.dirname(target)):
 
741
            os.makedirs(os.path.dirname(target))
 
742
        try:
 
743
            import_version(target, version, printer,
 
744
                           reuse_history_from=reuse_history_from)
 
745
        except pybaz.errors.ExecProblem,e:
 
746
            if str(e).find('The requested revision cannot be built.') != -1:
 
747
                printer("Skipping version %s as it cannot be built due"
 
748
                        " to a missing parent archive." % version)
 
749
            else:
 
750
                raise
 
751
        except UserError, e:
 
752
            if str(e).find('already exists, and the last revision ') != -1:
 
753
                printer("Skipping version %s as it has had commits made"
 
754
                        " since it was converted to bzr." % version)
 
755
            else:
 
756
                raise
 
757
 
 
758
 
 
759
def map_namespace(a_version):
 
760
    a_version = pybaz.Version("%s" % a_version)
 
761
    parser = NameParser(a_version)
 
762
    version = parser.get_version()
 
763
    branch = parser.get_branch()
 
764
    category = parser.get_category()
 
765
    if branch is None or branch == '':
 
766
        branch = "+trunk"
 
767
    if version == '0':
 
768
        return "%s/%s" % (category, branch)
 
769
    return "%s/%s/%s" % (category, version, branch)
 
770
 
 
771
def map_file_id(file_id):
 
772
    """Convert a baz file id to a bzr one."""
 
773
    return file_id.replace('%', '%25').replace('/', '%2f')