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_remaining_revisions(output_dir, version):
178
if os.path.exists(output_dir):
179
# We are starting from an existing directory, figure out what
180
# the current version is
181
branch = bzrlib.Branch(output_dir)
182
last_patch = branch.last_patch()
184
last_patch = arch_revision(last_patch)
185
except NotArchRevision:
187
"Directory \"%s\" already exists, and the last revision is not"
188
" an Arch revision (%s)" % (output_dir, last_patch))
190
version = last_patch.version
191
elif version is None:
192
raise UserError("No version specified, and directory does not exist.")
194
ancestors = version_ancestry(version)
197
for i in range(len(ancestors)):
198
if ancestors[i] == last_patch:
201
raise UserError("Directory \"%s\" already exists, and the last "
202
"revision (%s) is not in the ancestry of %s" %
203
(output_dir, last_patch, version))
204
# Strip off all of the ancestors which are already present
205
# And get a directory starting with the latest ancestor
206
latest_ancestor = ancestors[i]
207
old_revno = bzrlib.Branch(output_dir).revno()
208
ancestors = ancestors[i+1:]
209
return ancestors, old_revno
211
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
212
dry_run=False, max_count=None, skip_symlinks=False):
214
>>> q = test_environ()
215
>>> result_path = os.path.join(q, "result")
216
>>> commit_test_revisions()
217
>>> version = pybaz.Version("test@example.com/test--test--0")
218
>>> import_version(result_path, version, fancy=False)
223
>>> teardown_environ(q)
225
ancestors, old_revno = get_remaining_revisions(output_dir, version)
226
if len(ancestors) == 0:
227
print '* Tree is up-to-date with %s' % last_patch
230
progress_bar = ProgressBar()
231
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
232
dir=os.path.dirname(output_dir))
236
for result in iter_import_version(output_dir, ancestors, tempdir,
237
fast=fast, verbose=verbose, dry_run=dry_run,
238
max_count=max_count, skip_symlinks=skip_symlinks):
242
sys.stdout.write('.')
245
print '**Dry run, not modifying output_dir'
247
if os.path.exists(output_dir):
248
# Move the bzr control directory back, and update the working tree
249
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
251
bzr_dir = os.path.join(output_dir, '.bzr')
252
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
254
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
255
os.rename(new_bzr_dir, bzr_dir)
257
# bzrlib.merge that exists in mainline does not have a this_dir component,
258
# so we have to work in the local directory
262
bzrlib.merge.merge(('.', -1), ('.', old_revno))
266
# If something failed, move back the original bzr directory
267
os.rename(bzr_dir, new_bzr_dir)
268
os.rename(tmp_bzr_dir, bzr_dir)
271
os.rename(revdir, output_dir)
277
sys.stdout.write('\n')
279
shutil.rmtree(tempdir)
280
print "Import complete."
282
class UserError(Exception):
283
def __init__(self, message):
284
"""Exception to throw when a user makes an impossible request
285
:param message: The message to emit when printing this exception
286
:type message: string
288
Exception.__init__(self, message)
290
def revision_id(arch_revision):
292
Generate a Bzr revision id from an Arch revision id. 'x' in the id
293
designates a revision imported with an experimental algorithm. A number
294
would indicate a particular standardized version.
296
:param arch_revision: The Arch revision to generate an ID for.
298
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
299
'Arch-x:you@example.com%cat--br--0--base-0'
301
return "Arch-x:%s" % str(arch_revision).replace('/', '%')
303
class NotArchRevision(Exception):
304
def __init__(self, revision_id):
305
msg = "The revision id %s does not look like it came from Arch."\
307
Exception.__init__(self, msg)
309
def arch_revision(revision_id):
311
>>> str(arch_revision("Arch-x:jrandom@example.com/test--test--0"))
312
'jrandom@example.com/test--test--0'
314
if revision_id is None:
316
if revision_id[:7] != 'Arch-x:':
317
raise NotArchRevision(revision_id)
320
return pybaz.Revision(revision_id[7:].replace('%', '/'))
321
except pybaz.errors.NamespaceError, e:
322
raise NotArchRevision(revision_id)
324
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
325
verbose=False, dry_run=False, max_count=None,
326
skip_symlinks=False):
329
# Uncomment this for testing, it basically just has baz2bzr only update
330
# 5 patches at a time
332
ancestors = ancestors[:max_count]
334
# Not sure if I want this output. basically it tells you ahead of time
335
# what it is going to do, but then later it tells you as it is doing it.
336
# what probably would be best would be to collapse it into ranges, so that
337
# this gives the simple view, and then later it gives the blow by blow.
339
# print 'Adding the following revisions:'
340
# for a in ancestors:
343
previous_version=None
345
for i in range(len(ancestors)):
346
revision = ancestors[i]
348
version = str(revision.version)
349
if version != previous_version:
351
print '\rOn version: %s' % version
352
yield Progress(str(revision.patchlevel), i, len(ancestors))
353
previous_version = version
355
yield Progress("revisions", i, len(ancestors))
357
revdir = os.path.join(tempdir, "rd")
358
baz_inv, log = get_revision(revdir, revision,
359
skip_symlinks=skip_symlinks)
360
if os.path.exists(output_dir):
361
bzr_dir = os.path.join(output_dir, '.bzr')
362
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
363
# This would be much faster with a simple os.rename(), but if
364
# we fail, we have corrupted the original .bzr directory. Is
365
# that a big problem, as we can just back out the last
366
# revisions in .bzr/revision_history I don't really know
367
shutil.copytree(bzr_dir, new_bzr_dir)
368
# Now revdir should have a tree with the latest .bzr, and the
369
# next revision of the baz tree
370
branch = bzrlib.Branch(revdir)
372
branch = bzrlib.Branch(revdir, init=True)
374
old = os.path.join(revdir, ".bzr")
375
new = os.path.join(tempdir, ".bzr")
377
baz_inv, log = apply_revision(revdir, revision,
378
skip_symlinks=skip_symlinks)
380
branch = bzrlib.Branch(revdir)
381
timestamp = email.Utils.mktime_tz(log.date + (0,))
382
rev_id = revision_id(revision)
385
branch.set_inventory(baz_inv)
386
bzrlib.trace.silent = True
387
branch.commit(log.summary, verbose=False, committer=log.creator,
388
timestamp=timestamp, timezone=0, rev_id=rev_id)
390
bzrlib.trace.silent = False
392
yield Progress("revisions", len(ancestors), len(ancestors))
393
unlink_unversioned(branch, revdir)
395
def unlink_unversioned(branch, revdir):
396
for unversioned in branch.working_tree().extras():
397
path = os.path.join(revdir, unversioned)
398
if os.path.isdir(path):
403
def get_log(tree, revision):
404
log = tree.iter_logs(version=revision.version, reverse=True).next()
405
assert log.revision == revision
408
def get_revision(revdir, revision, skip_symlinks=False):
410
tree = pybaz.tree_root(revdir)
411
log = get_log(tree, revision)
413
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
414
except BadFileKind, e:
415
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
418
def apply_revision(revdir, revision, skip_symlinks=False):
419
tree = pybaz.tree_root(revdir)
421
log = get_log(tree, revision)
423
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
424
except BadFileKind, e:
425
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
430
class BadFileKind(Exception):
431
"""The file kind is not permitted in bzr inventories"""
432
def __init__(self, tree_root, path, kind):
433
self.tree_root = tree_root
436
Exception.__init__(self, "File %s is of forbidden type %s" %
437
(os.path.join(tree_root, path), kind))
439
def bzr_inventory_data(tree, skip_symlinks=False):
440
inv_iter = tree.iter_inventory_ids(source=True, both=True)
442
for file_id, path in inv_iter:
443
inv_map[path] = file_id
446
for path, file_id in inv_map.iteritems():
447
full_path = os.path.join(tree, path)
448
kind = bzrlib.osutils.file_kind(full_path)
449
if skip_symlinks and kind == "symlink":
451
if kind not in ("file", "directory"):
452
raise BadFileKind(tree, path, kind)
453
parent_dir = os.path.dirname(path)
455
parent_id = inv_map[parent_dir]
457
parent_id = bzrlib.inventory.ROOT_ID
458
bzr_inv.append((path, file_id, parent_id, kind))
463
"""Just the main() function for this script.
465
By separating it into a function, this can be called as a child from some other
468
:param args: The arguments to this script. Essentially sys.argv[1:]
471
parser = optparse.OptionParser(usage='%prog [options] [VERSION] OUTDIR'
472
'\n VERSION is the arch version to import.'
473
'\n OUTDIR can be an existing directory to be updated'
474
'\n or a new directory which will be created from scratch.')
475
parser.add_option('--verbose', action='store_true'
478
parser.add_option('--skip-symlinks', action="store_true",
479
dest="skip_symlinks",
480
help="Ignore any symlinks present in the Arch tree.")
482
g = optparse.OptionGroup(parser, 'Progress options', 'Control how progress is given')
483
g.add_option('--not-fancy', dest='fancy', action='store_false')
484
g.add_option('--fancy', dest='fancy', action='store_true', default=True
485
, help='Fancy or simple progress bar. (default: fancy)')
486
parser.add_option_group(g)
488
g = optparse.OptionGroup(parser, 'Test options', 'Options useful while testing process.')
489
g.add_option('--test', action='store_true'
490
, help='Run the self-tests and exit.')
491
g.add_option('--dry-run', action='store_true'
492
, help='Do the update, but don\'t copy the result to OUTDIR')
493
g.add_option('--max-count', type='int', metavar='COUNT', default=None
494
, help='At most, add COUNT patches.')
495
g.add_option('--safe', action='store_false', dest='fast')
496
g.add_option('--fast', action='store_true', default=False
497
, help='By default the .bzr control directory will be copied, so that an error'
498
' does not modify the original. --fast allows the directory to be renamed instead.')
499
parser.add_option_group(g)
501
(opts, args) = parser.parse_args(args)
504
print "Running tests"
506
nfail, ntests = doctest.testmod(verbose=opts.verbose)
512
output_dir = os.path.realpath(args[1])
513
version = pybaz.Version(args[0])
515
output_dir = os.path.realpath(args[0])
518
print 'Invalid number of arguments, try --help for more info'
523
import_version(output_dir, version,
524
verbose=opts.verbose, fast=opts.fast,
525
fancy=opts.fancy, dry_run=opts.dry_run,
526
max_count=opts.max_count, skip_symlinks=opts.skip_symlinks)
531
except KeyboardInterrupt:
536
if __name__ == '__main__':
537
sys.exit(main(sys.argv[1:]))