1
# Copyright (C) 2005 by Aaron Bentley
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.
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.
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
from bzrlib import Branch
17
from bzrlib.commands import Command
19
class NoPyBaz(Exception):
21
Exception.__init__(self, "PyBaz is not installed.")
27
from pybaz.backends.baz import null_cmd
33
from bzrlib.errors import BzrError
36
import bzrlib.inventory
40
from progress import *
42
def add_id(files, id=None):
43
"""Adds an explicit id to a list of files.
45
:param files: the name of the file to add an id to
46
:type files: list of str
47
:param id: tag one file using the specified id, instead of generating id
52
args.extend(["--id", id])
58
>>> q = test_environ()
61
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
63
>>> teardown_environ(q)
67
tdir = tempfile.mkdtemp(prefix="testdir-")
68
os.environ["HOME"] = os.path.join(tdir, "home")
69
os.mkdir(os.environ["HOME"])
70
arch_dir = os.path.join(tdir, "archive_dir")
71
pybaz.make_archive("test@example.com", arch_dir)
72
work_dir = os.path.join(tdir, "work_dir")
75
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
76
lib_dir = os.path.join(tdir, "lib_dir")
78
pybaz.register_revision_library(lib_dir)
79
pybaz.set_my_id("Test User<test@example.org>")
82
def add_file(path, text, id):
84
>>> q = test_environ()
85
>>> add_file("path with space", "text", "lalala")
86
>>> tree = pybaz.tree_root(".")
87
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
88
>>> ("x_lalala", "path with space") in inv
90
>>> teardown_environ(q)
92
file(path, "wb").write(text)
96
def add_dir(path, id):
98
>>> q = test_environ()
99
>>> add_dir("path with\(sp) space", "lalala")
100
>>> tree = pybaz.tree_root(".")
101
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
102
>>> ("x_lalala", "path with\(sp) space") in inv
104
>>> teardown_environ(q)
109
def teardown_environ(tdir):
113
def timport(tree, summary):
114
msg = tree.log_message()
115
msg["summary"] = summary
118
def commit(tree, summary):
120
>>> q = test_environ()
121
>>> tree = pybaz.tree_root(".")
122
>>> timport(tree, "import")
123
>>> commit(tree, "commit")
124
>>> logs = [str(l.revision) for l in tree.iter_logs()]
128
'test@example.com/test--test--0--base-0'
130
'test@example.com/test--test--0--patch-1'
131
>>> teardown_environ(q)
133
msg = tree.log_message()
134
msg["summary"] = summary
137
def commit_test_revisions():
139
>>> q = test_environ()
140
>>> commit_test_revisions()
141
>>> a = pybaz.Archive("test@example.com")
142
>>> revisions = list(a.iter_revisions("test--test--0"))
145
>>> str(revisions[2])
146
'test@example.com/test--test--0--base-0'
147
>>> str(revisions[1])
148
'test@example.com/test--test--0--patch-1'
149
>>> str(revisions[0])
150
'test@example.com/test--test--0--patch-2'
151
>>> teardown_environ(q)
153
tree = pybaz.tree_root(".")
154
add_file("mainfile", "void main(void){}", "mainfile by aaron")
155
timport(tree, "Created mainfile")
156
file("mainfile", "wb").write("or something like that")
157
commit(tree, "altered mainfile")
158
add_file("ofile", "this is another file", "ofile by aaron")
159
commit(tree, "altered mainfile")
162
def commit_more_test_revisions():
164
>>> q = test_environ()
165
>>> commit_test_revisions()
166
>>> commit_more_test_revisions()
167
>>> a = pybaz.Archive("test@example.com")
168
>>> revisions = list(a.iter_revisions("test--test--0"))
171
>>> str(revisions[0])
172
'test@example.com/test--test--0--patch-3'
173
>>> teardown_environ(q)
175
tree = pybaz.tree_root(".")
176
add_file("trainfile", "void train(void){}", "trainfile by aaron")
177
commit(tree, "altered trainfile")
179
class NoSuchVersion(Exception):
180
def __init__(self, version):
181
Exception.__init__(self, "The version %s does not exist." % version)
182
self.version = version
184
def version_ancestry(version):
186
>>> q = test_environ()
187
>>> commit_test_revisions()
188
>>> version = pybaz.Version("test@example.com/test--test--0")
189
>>> ancestors = version_ancestry(version)
190
>>> str(ancestors[0])
191
'test@example.com/test--test--0--base-0'
192
>>> str(ancestors[1])
193
'test@example.com/test--test--0--patch-1'
194
>>> version = pybaz.Version("test@example.com/test--test--0.5")
195
>>> ancestors = version_ancestry(version)
196
Traceback (most recent call last):
197
NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
198
>>> teardown_environ(q)
201
revision = version.iter_revisions(reverse=True).next()
203
if not version.exists():
204
raise NoSuchVersion(version)
207
ancestors = list(revision.iter_ancestors(metoo=True))
211
def get_last_revision(branch):
212
last_patch = branch.last_patch()
214
return arch_revision(last_patch)
215
except NotArchRevision:
217
"Directory \"%s\" already exists, and the last revision is not"
218
" an Arch revision (%s)" % (output_dir, last_patch))
221
def get_remaining_revisions(output_dir, version):
224
if os.path.exists(output_dir):
225
# We are starting from an existing directory, figure out what
226
# the current version is
227
branch = find_branch(output_dir)
228
last_patch = get_last_revision(branch)
230
version = last_patch.version
231
elif version is None:
232
raise UserError("No version specified, and directory does not exist.")
235
ancestors = version_ancestry(version)
236
except NoSuchVersion, e:
240
for i in range(len(ancestors)):
241
if ancestors[i] == last_patch:
244
raise UserError("Directory \"%s\" already exists, and the last "
245
"revision (%s) is not in the ancestry of %s" %
246
(output_dir, last_patch, version))
247
# Strip off all of the ancestors which are already present
248
# And get a directory starting with the latest ancestor
249
latest_ancestor = ancestors[i]
250
old_revno = find_branch(output_dir).revno()
251
ancestors = ancestors[i+1:]
252
return ancestors, old_revno
254
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
255
dry_run=False, max_count=None, skip_symlinks=False):
257
>>> q = test_environ()
258
>>> result_path = os.path.join(q, "result")
259
>>> commit_test_revisions()
260
>>> version = pybaz.Version("test@example.com/test--test--0.1")
261
>>> import_version('/', version, fancy=False, dry_run=True)
262
Traceback (most recent call last):
263
UserError: / exists, but is not a bzr branch.
264
>>> import_version(result_path, version, fancy=False, dry_run=True)
265
Traceback (most recent call last):
266
UserError: The version test@example.com/test--test--0.1 does not exist.
267
>>> version = pybaz.Version("test@example.com/test--test--0")
268
>>> import_version(result_path, version, fancy=False, dry_run=True)
271
Dry run, not modifying output_dir
273
>>> import_version(result_path, version, fancy=False)
278
>>> import_version(result_path, version, fancy=False)
279
Tree is up-to-date with test@example.com/test--test--0--patch-2
280
>>> commit_more_test_revisions()
281
>>> import_version(result_path, version, fancy=False)
286
>>> teardown_environ(q)
289
ancestors, old_revno = get_remaining_revisions(output_dir, version)
290
except NotInABranch, e:
291
raise UserError("%s exists, but is not a bzr branch." % e.path)
292
if len(ancestors) == 0:
293
last_revision = get_last_revision(find_branch(output_dir))
294
print 'Tree is up-to-date with %s' % last_revision
297
progress_bar = ProgressBar()
298
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
299
dir=os.path.dirname(output_dir))
304
for result in iter_import_version(output_dir, ancestors, tempdir,
305
fast=fast, verbose=verbose, dry_run=dry_run,
306
max_count=max_count, skip_symlinks=skip_symlinks):
308
show_progress(progress_bar, result)
310
sys.stdout.write('.')
315
sys.stdout.write('\n')
318
print 'Dry run, not modifying output_dir'
320
if os.path.exists(output_dir):
321
# Move the bzr control directory back, and update the working tree
322
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
324
bzr_dir = os.path.join(output_dir, '.bzr')
325
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
327
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
328
os.rename(new_bzr_dir, bzr_dir)
330
bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno),
331
check_clean=False, this_dir=output_dir,
334
# If something failed, move back the original bzr directory
335
os.rename(bzr_dir, new_bzr_dir)
336
os.rename(tmp_bzr_dir, bzr_dir)
339
revdir = os.path.join(tempdir, "rd")
340
os.rename(revdir, output_dir)
344
shutil.rmtree(tempdir)
345
print "Import complete."
347
class UserError(Exception):
348
def __init__(self, message):
349
"""Exception to throw when a user makes an impossible request
350
:param message: The message to emit when printing this exception
351
:type message: string
353
Exception.__init__(self, message)
355
def revision_id(arch_revision):
357
Generate a Bzr revision id from an Arch revision id. 'x' in the id
358
designates a revision imported with an experimental algorithm. A number
359
would indicate a particular standardized version.
361
:param arch_revision: The Arch revision to generate an ID for.
363
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
364
'Arch-x:you@example.com%cat--br--0--base-0'
366
return "Arch-x:%s" % str(arch_revision).replace('/', '%')
368
class NotArchRevision(Exception):
369
def __init__(self, revision_id):
370
msg = "The revision id %s does not look like it came from Arch."\
372
Exception.__init__(self, msg)
374
def arch_revision(revision_id):
376
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
377
Traceback (most recent call last):
378
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
379
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
380
Traceback (most recent call last):
381
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
382
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
383
'jrandom@example.com/test--test--0--patch-5'
385
if revision_id is None:
387
if revision_id[:7] != 'Arch-x:':
388
raise NotArchRevision(revision_id)
391
return pybaz.Revision(revision_id[7:].replace('%', '/'))
392
except pybaz.errors.NamespaceError, e:
393
raise NotArchRevision(revision_id)
395
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
396
verbose=False, dry_run=False, max_count=None,
397
skip_symlinks=False):
400
# Uncomment this for testing, it basically just has baz2bzr only update
401
# 5 patches at a time
403
ancestors = ancestors[:max_count]
405
# Not sure if I want this output. basically it tells you ahead of time
406
# what it is going to do, but then later it tells you as it is doing it.
407
# what probably would be best would be to collapse it into ranges, so that
408
# this gives the simple view, and then later it gives the blow by blow.
410
# print 'Adding the following revisions:'
411
# for a in ancestors:
414
previous_version=None
416
for i in range(len(ancestors)):
417
revision = ancestors[i]
419
version = str(revision.version)
420
if version != previous_version:
422
print '\rOn version: %s' % version
423
yield Progress(str(revision.patchlevel), i, len(ancestors))
424
previous_version = version
426
yield Progress("revisions", i, len(ancestors))
428
revdir = os.path.join(tempdir, "rd")
429
baz_inv, log = get_revision(revdir, revision,
430
skip_symlinks=skip_symlinks)
431
if os.path.exists(output_dir):
432
bzr_dir = os.path.join(output_dir, '.bzr')
433
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
434
# This would be much faster with a simple os.rename(), but if
435
# we fail, we have corrupted the original .bzr directory. Is
436
# that a big problem, as we can just back out the last
437
# revisions in .bzr/revision_history I don't really know
438
shutil.copytree(bzr_dir, new_bzr_dir)
439
# Now revdir should have a tree with the latest .bzr, and the
440
# next revision of the baz tree
441
branch = find_branch(revdir)
443
branch = bzrlib.Branch(revdir, init=True)
445
old = os.path.join(revdir, ".bzr")
446
new = os.path.join(tempdir, ".bzr")
448
baz_inv, log = apply_revision(revdir, revision,
449
skip_symlinks=skip_symlinks)
451
branch = find_branch(revdir)
452
timestamp = email.Utils.mktime_tz(log.date + (0,))
453
rev_id = revision_id(revision)
456
branch.set_inventory(baz_inv)
457
bzrlib.trace.silent = True
458
branch.commit(log.summary, verbose=False, committer=log.creator,
459
timestamp=timestamp, timezone=0, rev_id=rev_id)
461
bzrlib.trace.silent = False
463
yield Progress("revisions", len(ancestors), len(ancestors))
464
unlink_unversioned(branch, revdir)
466
def unlink_unversioned(branch, revdir):
467
for unversioned in branch.working_tree().extras():
468
path = os.path.join(revdir, unversioned)
469
if os.path.isdir(path):
474
def get_log(tree, revision):
475
log = tree.iter_logs(version=revision.version, reverse=True).next()
476
assert log.revision == revision
479
def get_revision(revdir, revision, skip_symlinks=False):
481
tree = pybaz.tree_root(revdir)
482
log = get_log(tree, revision)
484
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
485
except BadFileKind, e:
486
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
489
def apply_revision(revdir, revision, skip_symlinks=False):
490
tree = pybaz.tree_root(revdir)
492
log = get_log(tree, revision)
494
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
495
except BadFileKind, e:
496
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
501
class BadFileKind(Exception):
502
"""The file kind is not permitted in bzr inventories"""
503
def __init__(self, tree_root, path, kind):
504
self.tree_root = tree_root
507
Exception.__init__(self, "File %s is of forbidden type %s" %
508
(os.path.join(tree_root, path), kind))
510
def bzr_inventory_data(tree, skip_symlinks=False):
511
inv_iter = tree.iter_inventory_ids(source=True, both=True)
513
for file_id, path in inv_iter:
514
inv_map[path] = file_id
517
for path, file_id in inv_map.iteritems():
518
full_path = os.path.join(tree, path)
519
kind = bzrlib.osutils.file_kind(full_path)
520
if skip_symlinks and kind == "symlink":
522
if kind not in ("file", "directory"):
523
raise BadFileKind(tree, path, kind)
524
parent_dir = os.path.dirname(path)
526
parent_id = inv_map[parent_dir]
528
parent_id = bzrlib.inventory.ROOT_ID
529
bzr_inv.append((path, file_id, parent_id, kind))
533
class NotInABranch(Exception):
534
def __init__(self, path):
535
Exception.__init__(self, "%s is not in a branch." % path)
539
def find_branch(path):
542
Traceback (most recent call last):
543
NotInABranch: / is not in a branch.
544
>>> sb = bzrlib.ScratchBranch()
545
>>> isinstance(find_branch(sb.base), bzrlib.Branch)
549
return bzrlib.Branch(path)
551
if e.args[0].endswith("' is not in a branch"):
552
raise NotInABranch(path)
554
class cmd_baz_import(Command):
555
"""Import an Arch or Baz branch into a bzr branch"""
556
takes_args = ['to_location', 'from_branch?']
557
takes_options = ['verbose']
559
def run(self, to_location, from_branch=None, skip_symlinks=False,
560
fast=False, max_count=None, verbose=False, dry_run=False):
561
to_location = os.path.realpath(str(to_location))
562
if from_branch is not None:
564
from_branch = pybaz.Version(from_branch)
565
except pybaz.errors.NamespaceError:
566
print "%s is not a valid Arch branch." % from_branch
568
import_version(to_location, from_branch)