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
17
from bzrlib.errors import NotBranchError
18
from bzrlib.branch import Branch, find_branch
19
from bzrlib.commands import Command
20
from errors import NoPyBaz
24
from pybaz.backends.baz import null_cmd
32
from bzrlib.errors import BzrError
35
import bzrlib.inventory
39
from progress import *
41
def add_id(files, id=None):
42
"""Adds an explicit id to a list of files.
44
:param files: the name of the file to add an id to
45
:type files: list of str
46
:param id: tag one file using the specified id, instead of generating id
51
args.extend(["--id", id])
59
>>> q = test_environ()
62
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
64
>>> teardown_environ(q)
69
saved_dir = os.getcwdu()
70
tdir = tempfile.mkdtemp(prefix="testdir-")
71
os.environ["HOME"] = os.path.join(tdir, "home")
72
os.mkdir(os.environ["HOME"])
73
arch_dir = os.path.join(tdir, "archive_dir")
74
pybaz.make_archive("test@example.com", arch_dir)
75
work_dir = os.path.join(tdir, "work_dir")
78
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
79
lib_dir = os.path.join(tdir, "lib_dir")
81
pybaz.register_revision_library(lib_dir)
82
pybaz.set_my_id("Test User<test@example.org>")
85
def add_file(path, text, id):
87
>>> q = test_environ()
88
>>> add_file("path with space", "text", "lalala")
89
>>> tree = pybaz.tree_root(".")
90
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
91
>>> ("x_lalala", "path with space") in inv
93
>>> teardown_environ(q)
95
file(path, "wb").write(text)
99
def add_dir(path, id):
101
>>> q = test_environ()
102
>>> add_dir("path with\(sp) space", "lalala")
103
>>> tree = pybaz.tree_root(".")
104
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
105
>>> ("x_lalala", "path with\(sp) space") in inv
107
>>> teardown_environ(q)
112
def teardown_environ(tdir):
116
def timport(tree, summary):
117
msg = tree.log_message()
118
msg["summary"] = summary
121
def commit(tree, summary):
123
>>> q = test_environ()
124
>>> tree = pybaz.tree_root(".")
125
>>> timport(tree, "import")
126
>>> commit(tree, "commit")
127
>>> logs = [str(l.revision) for l in tree.iter_logs()]
131
'test@example.com/test--test--0--base-0'
133
'test@example.com/test--test--0--patch-1'
134
>>> teardown_environ(q)
136
msg = tree.log_message()
137
msg["summary"] = summary
140
def commit_test_revisions():
142
>>> q = test_environ()
143
>>> commit_test_revisions()
144
>>> a = pybaz.Archive("test@example.com")
145
>>> revisions = list(a.iter_revisions("test--test--0"))
148
>>> str(revisions[2])
149
'test@example.com/test--test--0--base-0'
150
>>> str(revisions[1])
151
'test@example.com/test--test--0--patch-1'
152
>>> str(revisions[0])
153
'test@example.com/test--test--0--patch-2'
154
>>> teardown_environ(q)
156
tree = pybaz.tree_root(".")
157
add_file("mainfile", "void main(void){}", "mainfile by aaron")
158
timport(tree, "Created mainfile")
159
file("mainfile", "wb").write("or something like that")
160
commit(tree, "altered mainfile")
161
add_file("ofile", "this is another file", "ofile by aaron")
162
commit(tree, "altered mainfile")
165
def commit_more_test_revisions():
167
>>> q = test_environ()
168
>>> commit_test_revisions()
169
>>> commit_more_test_revisions()
170
>>> a = pybaz.Archive("test@example.com")
171
>>> revisions = list(a.iter_revisions("test--test--0"))
174
>>> str(revisions[0])
175
'test@example.com/test--test--0--patch-3'
176
>>> teardown_environ(q)
178
tree = pybaz.tree_root(".")
179
add_file("trainfile", "void train(void){}", "trainfile by aaron")
180
commit(tree, "altered trainfile")
182
class NoSuchVersion(Exception):
183
def __init__(self, version):
184
Exception.__init__(self, "The version %s does not exist." % version)
185
self.version = version
187
def version_ancestry(version):
189
>>> q = test_environ()
190
>>> commit_test_revisions()
191
>>> version = pybaz.Version("test@example.com/test--test--0")
192
>>> ancestors = version_ancestry(version)
193
>>> str(ancestors[0])
194
'test@example.com/test--test--0--base-0'
195
>>> str(ancestors[1])
196
'test@example.com/test--test--0--patch-1'
197
>>> version = pybaz.Version("test@example.com/test--test--0.5")
198
>>> ancestors = version_ancestry(version)
199
Traceback (most recent call last):
200
NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
201
>>> teardown_environ(q)
204
revision = version.iter_revisions(reverse=True).next()
207
if not version.exists():
208
raise NoSuchVersion(version)
211
ancestors = list(revision.iter_ancestors(metoo=True))
215
def get_last_revision(branch):
216
last_patch = branch.last_patch()
218
return arch_revision(last_patch)
219
except NotArchRevision:
221
"Directory \"%s\" already exists, and the last revision is not"
222
" an Arch revision (%s)" % (output_dir, last_patch))
225
def get_remaining_revisions(output_dir, version):
228
if os.path.exists(output_dir):
229
# We are starting from an existing directory, figure out what
230
# the current version is
231
branch = find_branch(output_dir, find_root=False)
232
last_patch = get_last_revision(branch)
234
version = last_patch.version
235
elif version is None:
236
raise UserError("No version specified, and directory does not exist.")
239
ancestors = version_ancestry(version)
240
except NoSuchVersion, e:
244
for i in range(len(ancestors)):
245
if ancestors[i] == last_patch:
248
raise UserError("Directory \"%s\" already exists, and the last "
249
"revision (%s) is not in the ancestry of %s" %
250
(output_dir, last_patch, version))
251
# Strip off all of the ancestors which are already present
252
# And get a directory starting with the latest ancestor
253
latest_ancestor = ancestors[i]
254
old_revno = find_branch(output_dir, find_root=False).revno()
255
ancestors = ancestors[i+1:]
256
return ancestors, old_revno
258
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
259
dry_run=False, max_count=None, skip_symlinks=False):
261
>>> q = test_environ()
262
>>> result_path = os.path.join(q, "result")
263
>>> commit_test_revisions()
264
>>> version = pybaz.Version("test@example.com/test--test--0.1")
265
>>> import_version('/', version, fancy=False, dry_run=True)
266
Traceback (most recent call last):
267
UserError: / exists, but is not a bzr branch.
268
>>> import_version(result_path, version, fancy=False, dry_run=True)
269
Traceback (most recent call last):
270
UserError: The version test@example.com/test--test--0.1 does not exist.
271
>>> version = pybaz.Version("test@example.com/test--test--0")
272
>>> import_version(result_path, version, fancy=False, dry_run=True)
275
Dry run, not modifying output_dir
277
>>> import_version(result_path, version, fancy=False)
282
>>> import_version(result_path, version, fancy=False)
283
Tree is up-to-date with test@example.com/test--test--0--patch-2
284
>>> commit_more_test_revisions()
285
>>> import_version(result_path, version, fancy=False)
290
>>> teardown_environ(q)
293
ancestors, old_revno = get_remaining_revisions(output_dir, version)
294
except NotBranchError, e:
295
raise UserError("%s exists, but is not a bzr branch." % output_dir)
296
if len(ancestors) == 0:
297
last_revision = get_last_revision(find_branch(output_dir, find_root=False))
298
print 'Tree is up-to-date with %s' % last_revision
301
progress_bar = ProgressBar()
302
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
303
dir=os.path.dirname(output_dir))
308
for result in iter_import_version(output_dir, ancestors, tempdir,
309
fast=fast, verbose=verbose, dry_run=dry_run,
310
max_count=max_count, skip_symlinks=skip_symlinks):
312
show_progress(progress_bar, result)
314
sys.stdout.write('.')
319
sys.stdout.write('\n')
322
print 'Dry run, not modifying output_dir'
324
if os.path.exists(output_dir):
325
# Move the bzr control directory back, and update the working tree
326
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
328
bzr_dir = os.path.join(output_dir, '.bzr')
329
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
331
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
332
os.rename(new_bzr_dir, bzr_dir)
334
bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno),
335
check_clean=False, this_dir=output_dir,
338
# If something failed, move back the original bzr directory
339
os.rename(bzr_dir, new_bzr_dir)
340
os.rename(tmp_bzr_dir, bzr_dir)
343
revdir = os.path.join(tempdir, "rd")
344
os.rename(revdir, output_dir)
348
shutil.rmtree(tempdir)
349
print "Import complete."
351
class UserError(Exception):
352
def __init__(self, message):
353
"""Exception to throw when a user makes an impossible request
354
:param message: The message to emit when printing this exception
355
:type message: string
357
Exception.__init__(self, message)
359
def revision_id(arch_revision):
361
Generate a Bzr revision id from an Arch revision id. 'x' in the id
362
designates a revision imported with an experimental algorithm. A number
363
would indicate a particular standardized version.
365
:param arch_revision: The Arch revision to generate an ID for.
367
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
368
'Arch-1:you@example.com%cat--br--0--base-0'
370
return "Arch-1:%s" % str(arch_revision).replace('/', '%')
372
class NotArchRevision(Exception):
373
def __init__(self, revision_id):
374
msg = "The revision id %s does not look like it came from Arch."\
376
Exception.__init__(self, msg)
378
def arch_revision(revision_id):
380
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
381
Traceback (most recent call last):
382
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
383
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
384
Traceback (most recent call last):
385
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
386
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
387
'jrandom@example.com/test--test--0--patch-5'
389
if revision_id is None:
391
if revision_id[:7] != 'Arch-1:':
392
raise NotArchRevision(revision_id)
395
return pybaz.Revision(revision_id[7:].replace('%', '/'))
396
except pybaz.errors.NamespaceError, e:
397
raise NotArchRevision(revision_id)
399
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
400
verbose=False, dry_run=False, max_count=None,
401
skip_symlinks=False):
404
# Uncomment this for testing, it basically just has baz2bzr only update
405
# 5 patches at a time
407
ancestors = ancestors[:max_count]
409
# Not sure if I want this output. basically it tells you ahead of time
410
# what it is going to do, but then later it tells you as it is doing it.
411
# what probably would be best would be to collapse it into ranges, so that
412
# this gives the simple view, and then later it gives the blow by blow.
414
# print 'Adding the following revisions:'
415
# for a in ancestors:
418
previous_version=None
420
for i in range(len(ancestors)):
421
revision = ancestors[i]
424
version = str(revision.version)
425
if version != previous_version:
427
print '\rOn version: %s' % version
428
yield Progress(str(revision.patchlevel), i, len(ancestors))
429
previous_version = version
431
yield Progress("revisions", i, len(ancestors))
433
revdir = os.path.join(tempdir, "rd")
434
baz_inv, log = get_revision(revdir, revision,
435
skip_symlinks=skip_symlinks)
436
# cached so we can delete the log
438
log_summary = log.summary
439
log_creator = log.creator
440
if os.path.exists(output_dir):
441
bzr_dir = os.path.join(output_dir, '.bzr')
442
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
443
# This would be much faster with a simple os.rename(), but if
444
# we fail, we have corrupted the original .bzr directory. Is
445
# that a big problem, as we can just back out the last
446
# revisions in .bzr/revision_history I don't really know
447
shutil.copytree(bzr_dir, new_bzr_dir)
448
# Now revdir should have a tree with the latest .bzr, and the
449
# next revision of the baz tree
450
branch = find_branch(revdir, find_root=False)
452
branch = Branch(revdir, init=True)
454
old = os.path.join(revdir, ".bzr")
455
new = os.path.join(tempdir, ".bzr")
457
baz_inv, log = apply_revision(revdir, revision,
458
skip_symlinks=skip_symlinks)
460
log_summary = log.summary
461
log_creator = log.creator
462
direct_merges = get_direct_merges(revdir, revision)
464
branch = find_branch(revdir, find_root=False)
465
timestamp = email.Utils.mktime_tz(log_date + (0,))
466
rev_id = revision_id(revision)
469
for merge in direct_merges:
470
branch.add_pending_merge(revision_id(merge.revision))
471
branch.set_inventory(baz_inv)
472
bzrlib.trace.silent = True
473
branch.commit(log_summary, verbose=False, committer=log_creator,
474
timestamp=timestamp, timezone=0, rev_id=rev_id)
476
bzrlib.trace.silent = False
478
yield Progress("revisions", len(ancestors), len(ancestors))
479
unlink_unversioned(branch, revdir)
481
def get_direct_merges(revdir, revision):
482
from fai.cmdutil import pylon, direct_merges
483
if pybaz.WorkingTree(revdir).tree_version != revision.version:
484
pybaz.WorkingTree(revdir).set_tree_version(revision.version)
485
log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir,
486
revision.category.nonarch, revision.branch.nonarch,
487
revision.version.nonarch, revision.archive, revision.patchlevel)
488
temp_path = "templog"
489
os.rename(log_path, temp_path)
490
merges = list(pylon.iter_new_merges(revdir, revision.version))
491
direct = direct_merges (merges)
492
os.rename(temp_path, log_path)
495
def unlink_unversioned(branch, revdir):
496
for unversioned in branch.working_tree().extras():
497
path = os.path.join(revdir, unversioned)
498
if os.path.isdir(path):
503
def get_log(tree, revision):
504
log = tree.iter_logs(version=revision.version, reverse=True).next()
505
assert str(log.revision) == str(revision), (log.revision, revision)
508
def get_revision(revdir, revision, skip_symlinks=False):
510
tree = pybaz.tree_root(revdir)
511
log = get_log(tree, revision)
513
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
514
except BadFileKind, e:
515
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
518
def apply_revision(revdir, revision, skip_symlinks=False):
519
tree = pybaz.tree_root(revdir)
521
log = get_log(tree, revision)
523
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
524
except BadFileKind, e:
525
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
530
class BadFileKind(Exception):
531
"""The file kind is not permitted in bzr inventories"""
532
def __init__(self, tree_root, path, kind):
533
self.tree_root = tree_root
536
Exception.__init__(self, "File %s is of forbidden type %s" %
537
(os.path.join(tree_root, path), kind))
539
def bzr_inventory_data(tree, skip_symlinks=False):
540
inv_iter = tree.iter_inventory_ids(source=True, both=True)
542
for arch_id, path in inv_iter:
543
bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
544
inv_map[path] = bzr_file_id
547
for path, file_id in inv_map.iteritems():
548
full_path = os.path.join(tree, path)
549
kind = bzrlib.osutils.file_kind(full_path)
550
if skip_symlinks and kind == "symlink":
552
if kind not in ("file", "directory"):
553
raise BadFileKind(tree, path, kind)
554
parent_dir = os.path.dirname(path)
556
parent_id = inv_map[parent_dir]
558
parent_id = bzrlib.inventory.ROOT_ID
559
bzr_inv.append((path, file_id, parent_id, kind))
564
class cmd_baz_import(Command):
565
"""Import an Arch or Baz branch into a bzr branch"""
566
takes_args = ['to_location', 'from_branch?']
567
takes_options = ['verbose']
569
def run(self, to_location, from_branch=None, skip_symlinks=False,
570
fast=False, max_count=None, verbose=False, dry_run=False):
571
to_location = os.path.realpath(str(to_location))
572
if from_branch is not None:
574
from_branch = pybaz.Version(from_branch)
575
except pybaz.errors.NamespaceError:
576
print "%s is not a valid Arch branch." % from_branch
578
import_version(to_location, from_branch)