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
22
print "This command requires PyBaz. Please ensure that it is installed."
25
from pybaz.backends.baz import null_cmd
31
from bzrlib.errors import BzrError
36
from progress import *
38
def add_id(files, id=None):
39
"""Adds an explicit id to a list of files.
41
:param files: the name of the file to add an id to
42
:type files: list of str
43
:param id: tag one file using the specified id, instead of generating id
48
args.extend(["--id", id])
54
>>> q = test_environ()
57
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
59
>>> teardown_environ(q)
63
tdir = tempfile.mkdtemp(prefix="testdir-")
64
os.environ["HOME"] = os.path.join(tdir, "home")
65
os.mkdir(os.environ["HOME"])
66
arch_dir = os.path.join(tdir, "archive_dir")
67
pybaz.make_archive("test@example.com", arch_dir)
68
work_dir = os.path.join(tdir, "work_dir")
71
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
72
lib_dir = os.path.join(tdir, "lib_dir")
74
pybaz.register_revision_library(lib_dir)
75
pybaz.set_my_id("Test User<test@example.org>")
78
def add_file(path, text, id):
80
>>> q = test_environ()
81
>>> add_file("path with space", "text", "lalala")
82
>>> tree = pybaz.tree_root(".")
83
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
84
>>> ("x_lalala", "path with space") in inv
86
>>> teardown_environ(q)
88
file(path, "wb").write(text)
92
def add_dir(path, id):
94
>>> q = test_environ()
95
>>> add_dir("path with\(sp) space", "lalala")
96
>>> tree = pybaz.tree_root(".")
97
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
98
>>> ("x_lalala", "path with\(sp) space") in inv
100
>>> teardown_environ(q)
105
def teardown_environ(tdir):
109
def timport(tree, summary):
110
msg = tree.log_message()
111
msg["summary"] = summary
114
def commit(tree, summary):
116
>>> q = test_environ()
117
>>> tree = pybaz.tree_root(".")
118
>>> timport(tree, "import")
119
>>> commit(tree, "commit")
120
>>> logs = [str(l.revision) for l in tree.iter_logs()]
124
'test@example.com/test--test--0--base-0'
126
'test@example.com/test--test--0--patch-1'
127
>>> teardown_environ(q)
129
msg = tree.log_message()
130
msg["summary"] = summary
133
def commit_test_revisions():
135
>>> q = test_environ()
136
>>> commit_test_revisions()
137
>>> a = pybaz.Archive("test@example.com")
138
>>> revisions = list(a.iter_revisions("test--test--0"))
141
>>> str(revisions[2])
142
'test@example.com/test--test--0--base-0'
143
>>> str(revisions[1])
144
'test@example.com/test--test--0--patch-1'
145
>>> str(revisions[0])
146
'test@example.com/test--test--0--patch-2'
147
>>> teardown_environ(q)
149
tree = pybaz.tree_root(".")
150
add_file("mainfile", "void main(void){}", "mainfile by aaron")
151
timport(tree, "Created mainfile")
152
file("mainfile", "wb").write("or something like that")
153
commit(tree, "altered mainfile")
154
add_file("ofile", "this is another file", "ofile by aaron")
155
commit(tree, "altered mainfile")
158
def commit_more_test_revisions():
160
>>> q = test_environ()
161
>>> commit_test_revisions()
162
>>> commit_more_test_revisions()
163
>>> a = pybaz.Archive("test@example.com")
164
>>> revisions = list(a.iter_revisions("test--test--0"))
167
>>> str(revisions[0])
168
'test@example.com/test--test--0--patch-3'
169
>>> teardown_environ(q)
171
tree = pybaz.tree_root(".")
172
add_file("trainfile", "void train(void){}", "trainfile by aaron")
173
commit(tree, "altered trainfile")
175
class NoSuchVersion(Exception):
176
def __init__(self, version):
177
Exception.__init__(self, "The version %s does not exist." % version)
178
self.version = version
180
def version_ancestry(version):
182
>>> q = test_environ()
183
>>> commit_test_revisions()
184
>>> version = pybaz.Version("test@example.com/test--test--0")
185
>>> ancestors = version_ancestry(version)
186
>>> str(ancestors[0])
187
'test@example.com/test--test--0--base-0'
188
>>> str(ancestors[1])
189
'test@example.com/test--test--0--patch-1'
190
>>> version = pybaz.Version("test@example.com/test--test--0.5")
191
>>> ancestors = version_ancestry(version)
192
Traceback (most recent call last):
193
NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
194
>>> teardown_environ(q)
197
revision = version.iter_revisions(reverse=True).next()
199
if not version.exists():
200
raise NoSuchVersion(version)
203
ancestors = list(revision.iter_ancestors(metoo=True))
207
def get_last_revision(branch):
208
last_patch = branch.last_patch()
210
return arch_revision(last_patch)
211
except NotArchRevision:
213
"Directory \"%s\" already exists, and the last revision is not"
214
" an Arch revision (%s)" % (output_dir, last_patch))
217
def get_remaining_revisions(output_dir, version):
220
if os.path.exists(output_dir):
221
# We are starting from an existing directory, figure out what
222
# the current version is
223
branch = find_branch(output_dir)
224
last_patch = get_last_revision(branch)
226
version = last_patch.version
227
elif version is None:
228
raise UserError("No version specified, and directory does not exist.")
231
ancestors = version_ancestry(version)
232
except NoSuchVersion, e:
236
for i in range(len(ancestors)):
237
if ancestors[i] == last_patch:
240
raise UserError("Directory \"%s\" already exists, and the last "
241
"revision (%s) is not in the ancestry of %s" %
242
(output_dir, last_patch, version))
243
# Strip off all of the ancestors which are already present
244
# And get a directory starting with the latest ancestor
245
latest_ancestor = ancestors[i]
246
old_revno = find_branch(output_dir).revno()
247
ancestors = ancestors[i+1:]
248
return ancestors, old_revno
250
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
251
dry_run=False, max_count=None, skip_symlinks=False):
253
>>> q = test_environ()
254
>>> result_path = os.path.join(q, "result")
255
>>> commit_test_revisions()
256
>>> version = pybaz.Version("test@example.com/test--test--0.1")
257
>>> import_version('/', version, fancy=False, dry_run=True)
258
Traceback (most recent call last):
259
UserError: / exists, but is not a bzr branch.
260
>>> import_version(result_path, version, fancy=False, dry_run=True)
261
Traceback (most recent call last):
262
UserError: The version test@example.com/test--test--0.1 does not exist.
263
>>> version = pybaz.Version("test@example.com/test--test--0")
264
>>> import_version(result_path, version, fancy=False, dry_run=True)
267
Dry run, not modifying output_dir
269
>>> import_version(result_path, version, fancy=False)
274
>>> import_version(result_path, version, fancy=False)
275
Tree is up-to-date with test@example.com/test--test--0--patch-2
276
>>> commit_more_test_revisions()
277
>>> import_version(result_path, version, fancy=False)
282
>>> teardown_environ(q)
285
ancestors, old_revno = get_remaining_revisions(output_dir, version)
286
except NotInABranch, e:
287
raise UserError("%s exists, but is not a bzr branch." % e.path)
288
if len(ancestors) == 0:
289
last_revision = get_last_revision(find_branch(output_dir))
290
print 'Tree is up-to-date with %s' % last_revision
293
progress_bar = ProgressBar()
294
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
295
dir=os.path.dirname(output_dir))
300
for result in iter_import_version(output_dir, ancestors, tempdir,
301
fast=fast, verbose=verbose, dry_run=dry_run,
302
max_count=max_count, skip_symlinks=skip_symlinks):
306
sys.stdout.write('.')
311
sys.stdout.write('\n')
314
print 'Dry run, not modifying output_dir'
316
if os.path.exists(output_dir):
317
# Move the bzr control directory back, and update the working tree
318
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
320
bzr_dir = os.path.join(output_dir, '.bzr')
321
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
323
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
324
os.rename(new_bzr_dir, bzr_dir)
326
bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno),
327
check_clean=False, this_dir=output_dir,
330
# If something failed, move back the original bzr directory
331
os.rename(bzr_dir, new_bzr_dir)
332
os.rename(tmp_bzr_dir, bzr_dir)
335
revdir = os.path.join(tempdir, "rd")
336
os.rename(revdir, output_dir)
340
shutil.rmtree(tempdir)
341
print "Import complete."
343
class UserError(Exception):
344
def __init__(self, message):
345
"""Exception to throw when a user makes an impossible request
346
:param message: The message to emit when printing this exception
347
:type message: string
349
Exception.__init__(self, message)
351
def revision_id(arch_revision):
353
Generate a Bzr revision id from an Arch revision id. 'x' in the id
354
designates a revision imported with an experimental algorithm. A number
355
would indicate a particular standardized version.
357
:param arch_revision: The Arch revision to generate an ID for.
359
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
360
'Arch-x:you@example.com%cat--br--0--base-0'
362
return "Arch-x:%s" % str(arch_revision).replace('/', '%')
364
class NotArchRevision(Exception):
365
def __init__(self, revision_id):
366
msg = "The revision id %s does not look like it came from Arch."\
368
Exception.__init__(self, msg)
370
def arch_revision(revision_id):
372
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
373
Traceback (most recent call last):
374
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
375
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
376
Traceback (most recent call last):
377
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
378
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
379
'jrandom@example.com/test--test--0--patch-5'
381
if revision_id is None:
383
if revision_id[:7] != 'Arch-x:':
384
raise NotArchRevision(revision_id)
387
return pybaz.Revision(revision_id[7:].replace('%', '/'))
388
except pybaz.errors.NamespaceError, e:
389
raise NotArchRevision(revision_id)
391
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
392
verbose=False, dry_run=False, max_count=None,
393
skip_symlinks=False):
396
# Uncomment this for testing, it basically just has baz2bzr only update
397
# 5 patches at a time
399
ancestors = ancestors[:max_count]
401
# Not sure if I want this output. basically it tells you ahead of time
402
# what it is going to do, but then later it tells you as it is doing it.
403
# what probably would be best would be to collapse it into ranges, so that
404
# this gives the simple view, and then later it gives the blow by blow.
406
# print 'Adding the following revisions:'
407
# for a in ancestors:
410
previous_version=None
412
for i in range(len(ancestors)):
413
revision = ancestors[i]
415
version = str(revision.version)
416
if version != previous_version:
418
print '\rOn version: %s' % version
419
yield Progress(str(revision.patchlevel), i, len(ancestors))
420
previous_version = version
422
yield Progress("revisions", i, len(ancestors))
424
revdir = os.path.join(tempdir, "rd")
425
baz_inv, log = get_revision(revdir, revision,
426
skip_symlinks=skip_symlinks)
427
if os.path.exists(output_dir):
428
bzr_dir = os.path.join(output_dir, '.bzr')
429
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
430
# This would be much faster with a simple os.rename(), but if
431
# we fail, we have corrupted the original .bzr directory. Is
432
# that a big problem, as we can just back out the last
433
# revisions in .bzr/revision_history I don't really know
434
shutil.copytree(bzr_dir, new_bzr_dir)
435
# Now revdir should have a tree with the latest .bzr, and the
436
# next revision of the baz tree
437
branch = find_branch(revdir)
439
branch = bzrlib.Branch(revdir, init=True)
441
old = os.path.join(revdir, ".bzr")
442
new = os.path.join(tempdir, ".bzr")
444
baz_inv, log = apply_revision(revdir, revision,
445
skip_symlinks=skip_symlinks)
447
branch = find_branch(revdir)
448
timestamp = email.Utils.mktime_tz(log.date + (0,))
449
rev_id = revision_id(revision)
452
branch.set_inventory(baz_inv)
453
bzrlib.trace.silent = True
454
branch.commit(log.summary, verbose=False, committer=log.creator,
455
timestamp=timestamp, timezone=0, rev_id=rev_id)
457
bzrlib.trace.silent = False
459
yield Progress("revisions", len(ancestors), len(ancestors))
460
unlink_unversioned(branch, revdir)
462
def unlink_unversioned(branch, revdir):
463
for unversioned in branch.working_tree().extras():
464
path = os.path.join(revdir, unversioned)
465
if os.path.isdir(path):
470
def get_log(tree, revision):
471
log = tree.iter_logs(version=revision.version, reverse=True).next()
472
assert log.revision == revision
475
def get_revision(revdir, revision, skip_symlinks=False):
477
tree = pybaz.tree_root(revdir)
478
log = get_log(tree, revision)
480
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
481
except BadFileKind, e:
482
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
485
def apply_revision(revdir, revision, skip_symlinks=False):
486
tree = pybaz.tree_root(revdir)
488
log = get_log(tree, revision)
490
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
491
except BadFileKind, e:
492
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
497
class BadFileKind(Exception):
498
"""The file kind is not permitted in bzr inventories"""
499
def __init__(self, tree_root, path, kind):
500
self.tree_root = tree_root
503
Exception.__init__(self, "File %s is of forbidden type %s" %
504
(os.path.join(tree_root, path), kind))
506
def bzr_inventory_data(tree, skip_symlinks=False):
507
inv_iter = tree.iter_inventory_ids(source=True, both=True)
509
for file_id, path in inv_iter:
510
inv_map[path] = file_id
513
for path, file_id in inv_map.iteritems():
514
full_path = os.path.join(tree, path)
515
kind = bzrlib.osutils.file_kind(full_path)
516
if skip_symlinks and kind == "symlink":
518
if kind not in ("file", "directory"):
519
raise BadFileKind(tree, path, kind)
520
parent_dir = os.path.dirname(path)
522
parent_id = inv_map[parent_dir]
524
parent_id = bzrlib.inventory.ROOT_ID
525
bzr_inv.append((path, file_id, parent_id, kind))
529
class NotInABranch(Exception):
530
def __init__(self, path):
531
Exception.__init__(self, "%s is not in a branch." % path)
535
def find_branch(path):
538
Traceback (most recent call last):
539
NotInABranch: / is not in a branch.
540
>>> sb = bzrlib.ScratchBranch()
541
>>> isinstance(find_branch(sb.base), bzrlib.Branch)
545
return bzrlib.Branch(path)
547
if e.args[0].endswith("' is not in a branch"):
548
raise NotInABranch(path)
550
class cmd_baz_import(Command):
551
"""Import an Arch or Baz branch into a bzr branch"""
552
takes_args = ['to_location', 'from_branch?']
553
takes_options = ['verbose']
555
def run(self, to_location, from_branch=None, skip_symlinks=False,
556
fast=False, max_count=None, verbose=False, dry_run=False):
557
to_location = os.path.realpath(str(to_location))
558
if from_branch is not None:
560
from_branch = pybaz.Version(version)
561
except pybaz.errors.NamespaceError:
562
print "%s is not a valid Arch branch." % from_branch
564
import_version(to_location, from_branch)