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 import NameParser as NameParser
25
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])
60
>>> q = test_environ()
63
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
65
>>> teardown_environ(q)
70
saved_dir = os.getcwdu()
71
tdir = tempfile.mkdtemp(prefix="testdir-")
72
os.environ["HOME"] = os.path.join(tdir, "home")
73
os.mkdir(os.environ["HOME"])
74
arch_dir = os.path.join(tdir, "archive_dir")
75
pybaz.make_archive("test@example.com", arch_dir)
76
work_dir = os.path.join(tdir, "work_dir")
79
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
80
lib_dir = os.path.join(tdir, "lib_dir")
82
pybaz.register_revision_library(lib_dir)
83
pybaz.set_my_id("Test User<test@example.org>")
86
def add_file(path, text, id):
88
>>> q = test_environ()
89
>>> add_file("path with space", "text", "lalala")
90
>>> tree = pybaz.tree_root(".")
91
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
92
>>> ("x_lalala", "path with space") in inv
94
>>> teardown_environ(q)
96
file(path, "wb").write(text)
100
def add_dir(path, id):
102
>>> q = test_environ()
103
>>> add_dir("path with\(sp) space", "lalala")
104
>>> tree = pybaz.tree_root(".")
105
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
106
>>> ("x_lalala", "path with\(sp) space") in inv
108
>>> teardown_environ(q)
113
def teardown_environ(tdir):
117
def timport(tree, summary):
118
msg = tree.log_message()
119
msg["summary"] = summary
122
def commit(tree, summary):
124
>>> q = test_environ()
125
>>> tree = pybaz.tree_root(".")
126
>>> timport(tree, "import")
127
>>> commit(tree, "commit")
128
>>> logs = [str(l.revision) for l in tree.iter_logs()]
132
'test@example.com/test--test--0--base-0'
134
'test@example.com/test--test--0--patch-1'
135
>>> teardown_environ(q)
137
msg = tree.log_message()
138
msg["summary"] = summary
141
def commit_test_revisions():
143
>>> q = test_environ()
144
>>> commit_test_revisions()
145
>>> a = pybaz.Archive("test@example.com")
146
>>> revisions = list(a.iter_revisions("test--test--0"))
149
>>> str(revisions[2])
150
'test@example.com/test--test--0--base-0'
151
>>> str(revisions[1])
152
'test@example.com/test--test--0--patch-1'
153
>>> str(revisions[0])
154
'test@example.com/test--test--0--patch-2'
155
>>> teardown_environ(q)
157
tree = pybaz.tree_root(".")
158
add_file("mainfile", "void main(void){}", "mainfile by aaron")
159
timport(tree, "Created mainfile")
160
file("mainfile", "wb").write("or something like that")
161
commit(tree, "altered mainfile")
162
add_file("ofile", "this is another file", "ofile by aaron")
163
commit(tree, "altered mainfile")
166
def commit_more_test_revisions():
168
>>> q = test_environ()
169
>>> commit_test_revisions()
170
>>> commit_more_test_revisions()
171
>>> a = pybaz.Archive("test@example.com")
172
>>> revisions = list(a.iter_revisions("test--test--0"))
175
>>> str(revisions[0])
176
'test@example.com/test--test--0--patch-3'
177
>>> teardown_environ(q)
179
tree = pybaz.tree_root(".")
180
add_file("trainfile", "void train(void){}", "trainfile by aaron")
181
commit(tree, "altered trainfile")
183
class NoSuchVersion(Exception):
184
def __init__(self, version):
185
Exception.__init__(self, "The version %s does not exist." % version)
186
self.version = version
188
def version_ancestry(version):
190
>>> q = test_environ()
191
>>> commit_test_revisions()
192
>>> version = pybaz.Version("test@example.com/test--test--0")
193
>>> ancestors = version_ancestry(version)
194
>>> str(ancestors[0])
195
'test@example.com/test--test--0--base-0'
196
>>> str(ancestors[1])
197
'test@example.com/test--test--0--patch-1'
198
>>> version = pybaz.Version("test@example.com/test--test--0.5")
199
>>> ancestors = version_ancestry(version)
200
Traceback (most recent call last):
201
NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
202
>>> teardown_environ(q)
205
revision = version.iter_revisions(reverse=True).next()
208
if not version.exists():
209
raise NoSuchVersion(version)
212
ancestors = list(revision.iter_ancestors(metoo=True))
216
def get_last_revision(branch):
217
last_patch = branch.last_patch()
219
return arch_revision(last_patch)
220
except NotArchRevision:
222
"Directory \"%s\" already exists, and the last revision is not"
223
" an Arch revision (%s)" % (output_dir, last_patch))
226
def get_remaining_revisions(output_dir, version):
229
if os.path.exists(output_dir):
230
# We are starting from an existing directory, figure out what
231
# the current version is
232
branch = find_branch(output_dir, find_root=False)
233
last_patch = get_last_revision(branch)
235
version = last_patch.version
236
elif version is None:
237
raise UserError("No version specified, and directory does not exist.")
240
ancestors = version_ancestry(version)
241
except NoSuchVersion, e:
245
for i in range(len(ancestors)):
246
if ancestors[i] == last_patch:
249
raise UserError("Directory \"%s\" already exists, and the last "
250
"revision (%s) is not in the ancestry of %s" %
251
(output_dir, last_patch, version))
252
# Strip off all of the ancestors which are already present
253
# And get a directory starting with the latest ancestor
254
latest_ancestor = ancestors[i]
255
old_revno = find_branch(output_dir, find_root=False).revno()
256
ancestors = ancestors[i+1:]
257
return ancestors, old_revno
259
def import_version(output_dir, version, printer, fancy=True, fast=False,
260
verbose=False, dry_run=False, max_count=None):
262
>>> q = test_environ()
263
>>> result_path = os.path.join(q, "result")
264
>>> commit_test_revisions()
265
>>> version = pybaz.Version("test@example.com/test--test--0.1")
266
>>> def printer(message): print message
267
>>> import_version('/', version, printer, fancy=False, dry_run=True)
268
Traceback (most recent call last):
269
UserError: / exists, but is not a bzr branch.
270
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
271
Traceback (most recent call last):
272
UserError: The version test@example.com/test--test--0.1 does not exist.
273
>>> version = pybaz.Version("test@example.com/test--test--0")
274
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
277
Dry run, not modifying output_dir
279
>>> import_version(result_path, version, printer, fancy=False)
284
>>> import_version(result_path, version, printer, fancy=False)
285
Tree is up-to-date with test@example.com/test--test--0--patch-2
286
>>> commit_more_test_revisions()
287
>>> import_version(result_path, version, printer, fancy=False)
292
>>> teardown_environ(q)
295
ancestors, old_revno = get_remaining_revisions(output_dir, version)
296
except NotBranchError, e:
297
raise UserError("%s exists, but is not a bzr branch." % output_dir)
298
if len(ancestors) == 0:
299
last_revision = get_last_revision(find_branch(output_dir, find_root=False))
300
print 'Tree is up-to-date with %s' % last_revision
303
progress_bar = ProgressBar()
304
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
305
dir=os.path.dirname(output_dir))
310
for result in iter_import_version(output_dir, ancestors, tempdir,
311
fast=fast, verbose=verbose, dry_run=dry_run,
312
max_count=max_count):
314
show_progress(progress_bar, result)
316
sys.stdout.write('.')
321
sys.stdout.write('\n')
324
print 'Dry run, not modifying output_dir'
326
if os.path.exists(output_dir):
327
# Move the bzr control directory back, and update the working tree
328
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
330
bzr_dir = os.path.join(output_dir, '.bzr')
331
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
333
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
334
os.rename(new_bzr_dir, bzr_dir)
336
bzrlib.merge.merge((output_dir, -1), (output_dir, None), # old_revno),
337
check_clean=False, this_dir=output_dir,
340
# If something failed, move back the original bzr directory
341
os.rename(bzr_dir, new_bzr_dir)
342
os.rename(tmp_bzr_dir, bzr_dir)
345
revdir = os.path.join(tempdir, "rd")
346
os.rename(revdir, output_dir)
349
printer('Cleaning up')
350
shutil.rmtree(tempdir)
351
printer("Import complete.")
353
class UserError(Exception):
354
def __init__(self, message):
355
"""Exception to throw when a user makes an impossible request
356
:param message: The message to emit when printing this exception
357
:type message: string
359
Exception.__init__(self, message)
361
def revision_id(arch_revision):
363
Generate a Bzr revision id from an Arch revision id. 'x' in the id
364
designates a revision imported with an experimental algorithm. A number
365
would indicate a particular standardized version.
367
:param arch_revision: The Arch revision to generate an ID for.
369
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
370
'Arch-1:you@example.com%cat--br--0--base-0'
372
return "Arch-1:%s" % str(arch_revision).replace('/', '%')
374
class NotArchRevision(Exception):
375
def __init__(self, revision_id):
376
msg = "The revision id %s does not look like it came from Arch."\
378
Exception.__init__(self, msg)
380
def arch_revision(revision_id):
382
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
383
Traceback (most recent call last):
384
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
385
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
386
Traceback (most recent call last):
387
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
388
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
389
'jrandom@example.com/test--test--0--patch-5'
391
if revision_id is None:
393
if revision_id[:7] != 'Arch-1:':
394
raise NotArchRevision(revision_id)
397
return pybaz.Revision(revision_id[7:].replace('%', '/'))
398
except pybaz.errors.NamespaceError, e:
399
raise NotArchRevision(revision_id)
401
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
402
verbose=False, dry_run=False, max_count=None):
405
# Uncomment this for testing, it basically just has baz2bzr only update
406
# 5 patches at a time
408
ancestors = ancestors[:max_count]
410
# Not sure if I want this output. basically it tells you ahead of time
411
# what it is going to do, but then later it tells you as it is doing it.
412
# what probably would be best would be to collapse it into ranges, so that
413
# this gives the simple view, and then later it gives the blow by blow.
415
# print 'Adding the following revisions:'
416
# for a in ancestors:
419
previous_version=None
420
missing_ancestor = None
422
for i in range(len(ancestors)):
423
revision = ancestors[i]
426
version = str(revision.version)
427
if version != previous_version:
429
print '\rOn version: %s' % version
430
yield Progress(str(revision.patchlevel), i, len(ancestors))
431
previous_version = version
433
yield Progress("revisions", i, len(ancestors))
435
revdir = os.path.join(tempdir, "rd")
437
baz_inv, log = get_revision(revdir, revision)
438
except pybaz.errors.ExecProblem, e:
439
if ("%s" % e.args).find('could not connect') == -1:
441
missing_ancestor = revision
443
print ("unable to access ancestor %s, making into a merge."
446
# cached so we can delete the log
448
log_summary = log.summary
449
log_creator = log.creator
450
if os.path.exists(output_dir):
451
bzr_dir = os.path.join(output_dir, '.bzr')
452
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
453
# This would be much faster with a simple os.rename(), but if
454
# we fail, we have corrupted the original .bzr directory. Is
455
# that a big problem, as we can just back out the last
456
# revisions in .bzr/revision_history I don't really know
457
shutil.copytree(bzr_dir, new_bzr_dir)
458
# Now revdir should have a tree with the latest .bzr, and the
459
# next revision of the baz tree
460
branch = find_branch(revdir, find_root=False)
462
branch = Branch(revdir, init=True)
464
old = os.path.join(revdir, ".bzr")
465
new = os.path.join(tempdir, ".bzr")
467
baz_inv, log = apply_revision(revdir, revision)
469
log_summary = log.summary
470
log_creator = log.creator
471
direct_merges = get_direct_merges(revdir, revision)
473
branch = find_branch(revdir, find_root=False)
474
timestamp = email.Utils.mktime_tz(log_date + (0,))
475
rev_id = revision_id(revision)
479
# if we want it to be in revision-history, do that here.
480
branch.add_pending_merge(revision_id(missing_ancestor))
481
missing_ancestor = None
482
for merge in direct_merges:
483
branch.add_pending_merge(revision_id(merge.revision))
484
branch.set_inventory(baz_inv)
485
bzrlib.trace.silent = True
486
branch.commit(log_summary, verbose=False, committer=log_creator,
487
timestamp=timestamp, timezone=0, rev_id=rev_id)
489
bzrlib.trace.silent = False
491
yield Progress("revisions", len(ancestors), len(ancestors))
492
unlink_unversioned(branch, revdir)
494
def get_direct_merges(revdir, revision):
495
from fai import iter_new_merges, direct_merges
496
if pybaz.WorkingTree(revdir).tree_version != revision.version:
497
pybaz.WorkingTree(revdir).set_tree_version(revision.version)
498
log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir,
499
revision.category.nonarch, revision.branch.nonarch,
500
revision.version.nonarch, revision.archive, revision.patchlevel)
501
temp_path = tempfile.mktemp()
502
os.rename(log_path, temp_path)
503
merges = list(iter_new_merges(revdir, revision.version))
504
direct = direct_merges (merges)
505
os.rename(temp_path, log_path)
508
def unlink_unversioned(branch, revdir):
509
for unversioned in branch.working_tree().extras():
510
path = os.path.join(revdir, unversioned)
511
if os.path.isdir(path):
516
def get_log(tree, revision):
517
log = tree.iter_logs(version=revision.version, reverse=True).next()
518
assert str(log.revision) == str(revision), (log.revision, revision)
521
def get_revision(revdir, revision):
523
tree = pybaz.tree_root(revdir)
524
log = get_log(tree, revision)
526
return bzr_inventory_data(tree), log
527
except BadFileKind, e:
528
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
531
def apply_revision(revdir, revision):
532
tree = pybaz.tree_root(revdir)
534
log = get_log(tree, revision)
536
return bzr_inventory_data(tree), log
537
except BadFileKind, e:
538
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
543
class BadFileKind(Exception):
544
"""The file kind is not permitted in bzr inventories"""
545
def __init__(self, tree_root, path, kind):
546
self.tree_root = tree_root
549
Exception.__init__(self, "File %s is of forbidden type %s" %
550
(os.path.join(tree_root, path), kind))
552
def bzr_inventory_data(tree):
553
inv_iter = tree.iter_inventory_ids(source=True, both=True)
555
for arch_id, path in inv_iter:
556
bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
557
inv_map[path] = bzr_file_id
560
for path, file_id in inv_map.iteritems():
561
full_path = os.path.join(tree, path)
562
kind = bzrlib.osutils.file_kind(full_path)
563
if kind not in ("file", "directory", "symlink"):
564
raise BadFileKind(tree, path, kind)
565
parent_dir = os.path.dirname(path)
567
parent_id = inv_map[parent_dir]
569
parent_id = bzrlib.inventory.ROOT_ID
570
bzr_inv.append((path, file_id, parent_id, kind))
575
class cmd_baz_import_branch(Command):
576
"""Import an Arch or Baz branch into a bzr branch"""
577
takes_args = ['to_location', 'from_branch?']
578
takes_options = ['verbose']
580
def printer(self, name):
583
def run(self, to_location, from_branch=None, fast=False, max_count=None,
584
verbose=False, dry_run=False):
585
to_location = os.path.realpath(str(to_location))
586
if from_branch is not None:
588
from_branch = pybaz.Version(from_branch)
589
except pybaz.errors.NamespaceError:
590
print "%s is not a valid Arch branch." % from_branch
592
import_version(to_location, from_branch, self.printer)
594
class cmd_baz_import(Command):
595
"""Import an Arch or Baz archive into bzr branches."""
596
takes_args = ['to_root_dir', 'from_archive']
597
takes_options = ['verbose']
599
def printer(self, name):
602
def run(self, to_root_dir, from_archive, verbose=False):
603
to_root = str(os.path.realpath(to_root_dir))
604
if not os.path.exists(to_root):
606
import_archive(to_root, from_archive, verbose, self.printer)
608
def import_archive(to_root, from_archive, verbose, printer):
609
for version in pybaz.Archive(str(from_archive)).iter_versions():
610
target = os.path.join(to_root, map_namespace(version))
611
printer("importing %s into %s" % (version, target))
612
if not os.path.exists(os.path.dirname(target)):
613
os.makedirs(os.path.dirname(target))
614
import_version(target, version, printer)
616
def map_namespace(a_version):
617
a_version = pybaz.Version("%s" % a_version)
618
parser = NameParser(a_version)
619
version = parser.get_version()
620
branch = parser.get_branch()
621
category = parser.get_category()
622
if branch is None or branch == '':
625
return "%s/%s" % (category, branch)
626
return "%s/%s/%s" % (category, version, branch)