3
# Copyright (C) 2005 Aaron Bentley
4
# <aaron.bentley@utoronto.ca>
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
print "This command requires PyBaz. Please ensure that it is installed."
27
from pybaz.backends.baz import null_cmd
37
from progress import *
39
def add_id(files, id=None):
40
"""Adds an explicit id to a list of files.
42
:param files: the name of the file to add an id to
43
:type files: list of str
44
:param id: tag one file using the specified id, instead of generating id
49
args.extend(["--id", id])
55
>>> q = test_environ()
58
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
60
>>> teardown_environ(q)
64
tdir = tempfile.mkdtemp(prefix="baz2bzr-")
65
os.environ["HOME"] = os.path.join(tdir, "home")
66
os.mkdir(os.environ["HOME"])
67
arch_dir = os.path.join(tdir, "archive_dir")
68
pybaz.make_archive("test@example.com", arch_dir)
69
work_dir = os.path.join(tdir, "work_dir")
72
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
73
lib_dir = os.path.join(tdir, "lib_dir")
75
pybaz.register_revision_library(lib_dir)
76
pybaz.set_my_id("Test User<test@example.org>")
79
def add_file(path, text, id):
81
>>> q = test_environ()
82
>>> add_file("path with space", "text", "lalala")
83
>>> tree = pybaz.tree_root(".")
84
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
85
>>> ("x_lalala", "path with space") in inv
87
>>> teardown_environ(q)
89
file(path, "wb").write(text)
93
def add_dir(path, id):
95
>>> q = test_environ()
96
>>> add_dir("path with\(sp) space", "lalala")
97
>>> tree = pybaz.tree_root(".")
98
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
99
>>> ("x_lalala", "path with\(sp) space") in inv
101
>>> teardown_environ(q)
106
def teardown_environ(tdir):
110
def timport(tree, summary):
111
msg = tree.log_message()
112
msg["summary"] = summary
115
def commit(tree, summary):
117
>>> q = test_environ()
118
>>> tree = pybaz.tree_root(".")
119
>>> timport(tree, "import")
120
>>> commit(tree, "commit")
121
>>> logs = [str(l.revision) for l in tree.iter_logs()]
125
'test@example.com/test--test--0--base-0'
127
'test@example.com/test--test--0--patch-1'
128
>>> teardown_environ(q)
130
msg = tree.log_message()
131
msg["summary"] = summary
134
def commit_test_revisions():
136
>>> q = test_environ()
137
>>> commit_test_revisions()
138
>>> a = pybaz.Archive("test@example.com")
139
>>> revisions = list(a.iter_revisions("test--test--0"))
142
>>> str(revisions[2])
143
'test@example.com/test--test--0--base-0'
144
>>> str(revisions[1])
145
'test@example.com/test--test--0--patch-1'
146
>>> str(revisions[0])
147
'test@example.com/test--test--0--patch-2'
148
>>> teardown_environ(q)
150
tree = pybaz.tree_root(".")
151
add_file("mainfile", "void main(void){}", "mainfile by aaron")
152
timport(tree, "Created mainfile")
153
file("mainfile", "wb").write("or something like that")
154
commit(tree, "altered mainfile")
155
add_file("ofile", "this is another file", "ofile by aaron")
156
commit(tree, "altered mainfile")
158
def version_ancestry(version):
160
>>> q = test_environ()
161
>>> commit_test_revisions()
162
>>> version = pybaz.Version("test@example.com/test--test--0")
163
>>> ancestors = version_ancestry(version)
164
>>> str(ancestors[0])
165
'test@example.com/test--test--0--base-0'
166
>>> str(ancestors[1])
167
'test@example.com/test--test--0--patch-1'
168
>>> teardown_environ(q)
170
revision = version.iter_revisions(reverse=True).next()
171
ancestors = list(revision.iter_ancestors(metoo=True))
175
def get_last_revision(branch):
176
last_patch = branch.last_patch()
178
return arch_revision(last_patch)
179
except NotArchRevision:
181
"Directory \"%s\" already exists, and the last revision is not"
182
" an Arch revision (%s)" % (output_dir, last_patch))
185
def get_remaining_revisions(output_dir, version):
188
if os.path.exists(output_dir):
189
# We are starting from an existing directory, figure out what
190
# the current version is
191
branch = bzrlib.Branch(output_dir)
192
last_patch = get_last_revision(branch)
194
version = last_patch.version
195
elif version is None:
196
raise UserError("No version specified, and directory does not exist.")
198
ancestors = version_ancestry(version)
201
for i in range(len(ancestors)):
202
if ancestors[i] == last_patch:
205
raise UserError("Directory \"%s\" already exists, and the last "
206
"revision (%s) is not in the ancestry of %s" %
207
(output_dir, last_patch, version))
208
# Strip off all of the ancestors which are already present
209
# And get a directory starting with the latest ancestor
210
latest_ancestor = ancestors[i]
211
old_revno = bzrlib.Branch(output_dir).revno()
212
ancestors = ancestors[i+1:]
213
return ancestors, old_revno
215
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
216
dry_run=False, max_count=None, skip_symlinks=False):
218
>>> q = test_environ()
219
>>> result_path = os.path.join(q, "result")
220
>>> commit_test_revisions()
221
>>> version = pybaz.Version("test@example.com/test--test--0")
222
>>> import_version(result_path, version, fancy=False)
227
>>> teardown_environ(q)
229
ancestors, old_revno = get_remaining_revisions(output_dir, version)
230
if len(ancestors) == 0:
231
last_revision = get_last_revision(bzrlib.Branch(output_dir))
232
print '* Tree is up-to-date with %s' % last_revision
235
progress_bar = ProgressBar()
236
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
237
dir=os.path.dirname(output_dir))
241
for result in iter_import_version(output_dir, ancestors, tempdir,
242
fast=fast, verbose=verbose, dry_run=dry_run,
243
max_count=max_count, skip_symlinks=skip_symlinks):
247
sys.stdout.write('.')
250
print '**Dry run, not modifying output_dir'
252
if os.path.exists(output_dir):
253
# Move the bzr control directory back, and update the working tree
254
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
256
bzr_dir = os.path.join(output_dir, '.bzr')
257
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
259
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
260
os.rename(new_bzr_dir, bzr_dir)
262
# bzrlib.merge that exists in mainline does not have a this_dir component,
263
# so we have to work in the local directory
264
bzrlib.merge.merge(('.', -1), ('.', old_revno),
265
check_clean=False, this_dir=output_dir,
268
# If something failed, move back the original bzr directory
269
os.rename(bzr_dir, new_bzr_dir)
270
os.rename(tmp_bzr_dir, bzr_dir)
273
os.rename(revdir, output_dir)
279
sys.stdout.write('\n')
281
shutil.rmtree(tempdir)
282
print "Import complete."
284
class UserError(Exception):
285
def __init__(self, message):
286
"""Exception to throw when a user makes an impossible request
287
:param message: The message to emit when printing this exception
288
:type message: string
290
Exception.__init__(self, message)
292
def revision_id(arch_revision):
294
Generate a Bzr revision id from an Arch revision id. 'x' in the id
295
designates a revision imported with an experimental algorithm. A number
296
would indicate a particular standardized version.
298
:param arch_revision: The Arch revision to generate an ID for.
300
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
301
'Arch-x:you@example.com%cat--br--0--base-0'
303
return "Arch-x:%s" % str(arch_revision).replace('/', '%')
305
class NotArchRevision(Exception):
306
def __init__(self, revision_id):
307
msg = "The revision id %s does not look like it came from Arch."\
309
Exception.__init__(self, msg)
311
def arch_revision(revision_id):
313
>>> str(arch_revision("Arch-x:jrandom@example.com/test--test--0"))
314
'jrandom@example.com/test--test--0'
316
if revision_id is None:
318
if revision_id[:7] != 'Arch-x:':
319
raise NotArchRevision(revision_id)
322
return pybaz.Revision(revision_id[7:].replace('%', '/'))
323
except pybaz.errors.NamespaceError, e:
324
raise NotArchRevision(revision_id)
326
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
327
verbose=False, dry_run=False, max_count=None,
328
skip_symlinks=False):
331
# Uncomment this for testing, it basically just has baz2bzr only update
332
# 5 patches at a time
334
ancestors = ancestors[:max_count]
336
# Not sure if I want this output. basically it tells you ahead of time
337
# what it is going to do, but then later it tells you as it is doing it.
338
# what probably would be best would be to collapse it into ranges, so that
339
# this gives the simple view, and then later it gives the blow by blow.
341
# print 'Adding the following revisions:'
342
# for a in ancestors:
345
previous_version=None
347
for i in range(len(ancestors)):
348
revision = ancestors[i]
350
version = str(revision.version)
351
if version != previous_version:
353
print '\rOn version: %s' % version
354
yield Progress(str(revision.patchlevel), i, len(ancestors))
355
previous_version = version
357
yield Progress("revisions", i, len(ancestors))
359
revdir = os.path.join(tempdir, "rd")
360
baz_inv, log = get_revision(revdir, revision,
361
skip_symlinks=skip_symlinks)
362
if os.path.exists(output_dir):
363
bzr_dir = os.path.join(output_dir, '.bzr')
364
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
365
# This would be much faster with a simple os.rename(), but if
366
# we fail, we have corrupted the original .bzr directory. Is
367
# that a big problem, as we can just back out the last
368
# revisions in .bzr/revision_history I don't really know
369
shutil.copytree(bzr_dir, new_bzr_dir)
370
# Now revdir should have a tree with the latest .bzr, and the
371
# next revision of the baz tree
372
branch = bzrlib.Branch(revdir)
374
branch = bzrlib.Branch(revdir, init=True)
376
old = os.path.join(revdir, ".bzr")
377
new = os.path.join(tempdir, ".bzr")
379
baz_inv, log = apply_revision(revdir, revision,
380
skip_symlinks=skip_symlinks)
382
branch = bzrlib.Branch(revdir)
383
timestamp = email.Utils.mktime_tz(log.date + (0,))
384
rev_id = revision_id(revision)
387
branch.set_inventory(baz_inv)
388
bzrlib.trace.silent = True
389
branch.commit(log.summary, verbose=False, committer=log.creator,
390
timestamp=timestamp, timezone=0, rev_id=rev_id)
392
bzrlib.trace.silent = False
394
yield Progress("revisions", len(ancestors), len(ancestors))
395
unlink_unversioned(branch, revdir)
397
def unlink_unversioned(branch, revdir):
398
for unversioned in branch.working_tree().extras():
399
path = os.path.join(revdir, unversioned)
400
if os.path.isdir(path):
405
def get_log(tree, revision):
406
log = tree.iter_logs(version=revision.version, reverse=True).next()
407
assert log.revision == revision
410
def get_revision(revdir, revision, skip_symlinks=False):
412
tree = pybaz.tree_root(revdir)
413
log = get_log(tree, revision)
415
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
416
except BadFileKind, e:
417
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
420
def apply_revision(revdir, revision, skip_symlinks=False):
421
tree = pybaz.tree_root(revdir)
423
log = get_log(tree, revision)
425
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
426
except BadFileKind, e:
427
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
432
class BadFileKind(Exception):
433
"""The file kind is not permitted in bzr inventories"""
434
def __init__(self, tree_root, path, kind):
435
self.tree_root = tree_root
438
Exception.__init__(self, "File %s is of forbidden type %s" %
439
(os.path.join(tree_root, path), kind))
441
def bzr_inventory_data(tree, skip_symlinks=False):
442
inv_iter = tree.iter_inventory_ids(source=True, both=True)
444
for file_id, path in inv_iter:
445
inv_map[path] = file_id
448
for path, file_id in inv_map.iteritems():
449
full_path = os.path.join(tree, path)
450
kind = bzrlib.osutils.file_kind(full_path)
451
if skip_symlinks and kind == "symlink":
453
if kind not in ("file", "directory"):
454
raise BadFileKind(tree, path, kind)
455
parent_dir = os.path.dirname(path)
457
parent_id = inv_map[parent_dir]
459
parent_id = bzrlib.inventory.ROOT_ID
460
bzr_inv.append((path, file_id, parent_id, kind))
465
"""Just the main() function for this script.
467
By separating it into a function, this can be called as a child from some other
470
:param args: The arguments to this script. Essentially sys.argv[1:]
473
parser = optparse.OptionParser(usage='%prog [options] [VERSION] OUTDIR'
474
'\n VERSION is the arch version to import.'
475
'\n OUTDIR can be an existing directory to be updated'
476
'\n or a new directory which will be created from scratch.')
477
parser.add_option('--verbose', action='store_true'
480
parser.add_option('--skip-symlinks', action="store_true",
481
dest="skip_symlinks",
482
help="Ignore any symlinks present in the Arch tree.")
484
g = optparse.OptionGroup(parser, 'Test options', 'Options useful while testing process.')
485
g.add_option('--test', action='store_true'
486
, help='Run the self-tests and exit.')
487
g.add_option('--dry-run', action='store_true'
488
, help='Do the update, but don\'t copy the result to OUTDIR')
489
g.add_option('--max-count', type='int', metavar='COUNT', default=None
490
, help='At most, add COUNT patches.')
491
g.add_option('--safe', action='store_false', dest='fast')
492
g.add_option('--fast', action='store_true', default=False
493
, help='By default the .bzr control directory will be copied, so that an error'
494
' does not modify the original. --fast allows the directory to be renamed instead.')
495
parser.add_option_group(g)
497
(opts, args) = parser.parse_args(args)
500
print "Running tests"
502
nfail, ntests = doctest.testmod(verbose=opts.verbose)
508
output_dir = os.path.realpath(args[1])
509
version = pybaz.Version(args[0])
511
output_dir = os.path.realpath(args[0])
514
print 'Invalid number of arguments, try --help for more info'
519
import_version(output_dir, version,
520
verbose=opts.verbose, fast=opts.fast,
521
dry_run=opts.dry_run, max_count=opts.max_count,
522
skip_symlinks=opts.skip_symlinks)
527
except KeyboardInterrupt:
532
if __name__ == '__main__':
533
sys.exit(main(sys.argv[1:]))