~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to baz_import.py

  • Committer: Alexander Belchenko
  • Date: 2007-06-12 18:31:58 UTC
  • mto: This revision was merged to the branch mainline in revision 637.
  • Revision ID: bialix@ukr.net-20070612183158-sqt205eb1s910jca
AUTHORS

Show diffs side-by-side

added added

removed removed

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