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
33
from bzrlib.errors import BzrError
38
from progress import *
40
def add_id(files, id=None):
41
"""Adds an explicit id to a list of files.
43
:param files: the name of the file to add an id to
44
:type files: list of str
45
:param id: tag one file using the specified id, instead of generating id
50
args.extend(["--id", id])
56
>>> q = test_environ()
59
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
61
>>> teardown_environ(q)
65
tdir = tempfile.mkdtemp(prefix="testdir-")
66
os.environ["HOME"] = os.path.join(tdir, "home")
67
os.mkdir(os.environ["HOME"])
68
arch_dir = os.path.join(tdir, "archive_dir")
69
pybaz.make_archive("test@example.com", arch_dir)
70
work_dir = os.path.join(tdir, "work_dir")
73
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
74
lib_dir = os.path.join(tdir, "lib_dir")
76
pybaz.register_revision_library(lib_dir)
77
pybaz.set_my_id("Test User<test@example.org>")
80
def add_file(path, text, id):
82
>>> q = test_environ()
83
>>> add_file("path with space", "text", "lalala")
84
>>> tree = pybaz.tree_root(".")
85
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
86
>>> ("x_lalala", "path with space") in inv
88
>>> teardown_environ(q)
90
file(path, "wb").write(text)
94
def add_dir(path, id):
96
>>> q = test_environ()
97
>>> add_dir("path with\(sp) space", "lalala")
98
>>> tree = pybaz.tree_root(".")
99
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
100
>>> ("x_lalala", "path with\(sp) space") in inv
102
>>> teardown_environ(q)
107
def teardown_environ(tdir):
111
def timport(tree, summary):
112
msg = tree.log_message()
113
msg["summary"] = summary
116
def commit(tree, summary):
118
>>> q = test_environ()
119
>>> tree = pybaz.tree_root(".")
120
>>> timport(tree, "import")
121
>>> commit(tree, "commit")
122
>>> logs = [str(l.revision) for l in tree.iter_logs()]
126
'test@example.com/test--test--0--base-0'
128
'test@example.com/test--test--0--patch-1'
129
>>> teardown_environ(q)
131
msg = tree.log_message()
132
msg["summary"] = summary
135
def commit_test_revisions():
137
>>> q = test_environ()
138
>>> commit_test_revisions()
139
>>> a = pybaz.Archive("test@example.com")
140
>>> revisions = list(a.iter_revisions("test--test--0"))
143
>>> str(revisions[2])
144
'test@example.com/test--test--0--base-0'
145
>>> str(revisions[1])
146
'test@example.com/test--test--0--patch-1'
147
>>> str(revisions[0])
148
'test@example.com/test--test--0--patch-2'
149
>>> teardown_environ(q)
151
tree = pybaz.tree_root(".")
152
add_file("mainfile", "void main(void){}", "mainfile by aaron")
153
timport(tree, "Created mainfile")
154
file("mainfile", "wb").write("or something like that")
155
commit(tree, "altered mainfile")
156
add_file("ofile", "this is another file", "ofile by aaron")
157
commit(tree, "altered mainfile")
160
def commit_more_test_revisions():
162
>>> q = test_environ()
163
>>> commit_test_revisions()
164
>>> commit_more_test_revisions()
165
>>> a = pybaz.Archive("test@example.com")
166
>>> revisions = list(a.iter_revisions("test--test--0"))
169
>>> str(revisions[0])
170
'test@example.com/test--test--0--patch-3'
171
>>> teardown_environ(q)
173
tree = pybaz.tree_root(".")
174
add_file("trainfile", "void train(void){}", "trainfile by aaron")
175
commit(tree, "altered trainfile")
177
def version_ancestry(version):
179
>>> q = test_environ()
180
>>> commit_test_revisions()
181
>>> version = pybaz.Version("test@example.com/test--test--0")
182
>>> ancestors = version_ancestry(version)
183
>>> str(ancestors[0])
184
'test@example.com/test--test--0--base-0'
185
>>> str(ancestors[1])
186
'test@example.com/test--test--0--patch-1'
187
>>> teardown_environ(q)
189
revision = version.iter_revisions(reverse=True).next()
190
ancestors = list(revision.iter_ancestors(metoo=True))
194
def get_last_revision(branch):
195
last_patch = branch.last_patch()
197
return arch_revision(last_patch)
198
except NotArchRevision:
200
"Directory \"%s\" already exists, and the last revision is not"
201
" an Arch revision (%s)" % (output_dir, last_patch))
204
def get_remaining_revisions(output_dir, version):
207
if os.path.exists(output_dir):
208
# We are starting from an existing directory, figure out what
209
# the current version is
210
branch = find_branch(output_dir)
211
last_patch = get_last_revision(branch)
213
version = last_patch.version
214
elif version is None:
215
raise UserError("No version specified, and directory does not exist.")
217
ancestors = version_ancestry(version)
220
for i in range(len(ancestors)):
221
if ancestors[i] == last_patch:
224
raise UserError("Directory \"%s\" already exists, and the last "
225
"revision (%s) is not in the ancestry of %s" %
226
(output_dir, last_patch, version))
227
# Strip off all of the ancestors which are already present
228
# And get a directory starting with the latest ancestor
229
latest_ancestor = ancestors[i]
230
old_revno = find_branch(output_dir).revno()
231
ancestors = ancestors[i+1:]
232
return ancestors, old_revno
234
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
235
dry_run=False, max_count=None, skip_symlinks=False):
237
>>> q = test_environ()
238
>>> result_path = os.path.join(q, "result")
239
>>> commit_test_revisions()
240
>>> version = pybaz.Version("test@example.com/test--test--0")
241
>>> import_version('/', version, fancy=False, dry_run=True)
242
Traceback (most recent call last):
243
UserError: / exists, but is not a bzr branch.
244
>>> import_version(result_path, version, fancy=False, dry_run=True)
247
Dry run, not modifying output_dir
249
>>> import_version(result_path, version, fancy=False)
254
>>> import_version(result_path, version, fancy=False)
255
Tree is up-to-date with test@example.com/test--test--0--patch-2
256
>>> commit_more_test_revisions()
257
>>> import_version(result_path, version, fancy=False)
262
>>> teardown_environ(q)
265
ancestors, old_revno = get_remaining_revisions(output_dir, version)
266
except NotInABranch, e:
267
raise UserError("%s exists, but is not a bzr branch." % e.path)
268
if len(ancestors) == 0:
269
last_revision = get_last_revision(find_branch(output_dir))
270
print 'Tree is up-to-date with %s' % last_revision
273
progress_bar = ProgressBar()
274
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
275
dir=os.path.dirname(output_dir))
280
for result in iter_import_version(output_dir, ancestors, tempdir,
281
fast=fast, verbose=verbose, dry_run=dry_run,
282
max_count=max_count, skip_symlinks=skip_symlinks):
286
sys.stdout.write('.')
291
sys.stdout.write('\n')
294
print 'Dry run, not modifying output_dir'
296
if os.path.exists(output_dir):
297
# Move the bzr control directory back, and update the working tree
298
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
300
bzr_dir = os.path.join(output_dir, '.bzr')
301
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
303
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
304
os.rename(new_bzr_dir, bzr_dir)
306
bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno),
307
check_clean=False, this_dir=output_dir,
310
# If something failed, move back the original bzr directory
311
os.rename(bzr_dir, new_bzr_dir)
312
os.rename(tmp_bzr_dir, bzr_dir)
315
revdir = os.path.join(tempdir, "rd")
316
os.rename(revdir, output_dir)
320
shutil.rmtree(tempdir)
321
print "Import complete."
323
class UserError(Exception):
324
def __init__(self, message):
325
"""Exception to throw when a user makes an impossible request
326
:param message: The message to emit when printing this exception
327
:type message: string
329
Exception.__init__(self, message)
331
def revision_id(arch_revision):
333
Generate a Bzr revision id from an Arch revision id. 'x' in the id
334
designates a revision imported with an experimental algorithm. A number
335
would indicate a particular standardized version.
337
:param arch_revision: The Arch revision to generate an ID for.
339
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
340
'Arch-x:you@example.com%cat--br--0--base-0'
342
return "Arch-x:%s" % str(arch_revision).replace('/', '%')
344
class NotArchRevision(Exception):
345
def __init__(self, revision_id):
346
msg = "The revision id %s does not look like it came from Arch."\
348
Exception.__init__(self, msg)
350
def arch_revision(revision_id):
352
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
353
Traceback (most recent call last):
354
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
355
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
356
Traceback (most recent call last):
357
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
358
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
359
'jrandom@example.com/test--test--0--patch-5'
361
if revision_id is None:
363
if revision_id[:7] != 'Arch-x:':
364
raise NotArchRevision(revision_id)
367
return pybaz.Revision(revision_id[7:].replace('%', '/'))
368
except pybaz.errors.NamespaceError, e:
369
raise NotArchRevision(revision_id)
371
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
372
verbose=False, dry_run=False, max_count=None,
373
skip_symlinks=False):
376
# Uncomment this for testing, it basically just has baz2bzr only update
377
# 5 patches at a time
379
ancestors = ancestors[:max_count]
381
# Not sure if I want this output. basically it tells you ahead of time
382
# what it is going to do, but then later it tells you as it is doing it.
383
# what probably would be best would be to collapse it into ranges, so that
384
# this gives the simple view, and then later it gives the blow by blow.
386
# print 'Adding the following revisions:'
387
# for a in ancestors:
390
previous_version=None
392
for i in range(len(ancestors)):
393
revision = ancestors[i]
395
version = str(revision.version)
396
if version != previous_version:
398
print '\rOn version: %s' % version
399
yield Progress(str(revision.patchlevel), i, len(ancestors))
400
previous_version = version
402
yield Progress("revisions", i, len(ancestors))
404
revdir = os.path.join(tempdir, "rd")
405
baz_inv, log = get_revision(revdir, revision,
406
skip_symlinks=skip_symlinks)
407
if os.path.exists(output_dir):
408
bzr_dir = os.path.join(output_dir, '.bzr')
409
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
410
# This would be much faster with a simple os.rename(), but if
411
# we fail, we have corrupted the original .bzr directory. Is
412
# that a big problem, as we can just back out the last
413
# revisions in .bzr/revision_history I don't really know
414
shutil.copytree(bzr_dir, new_bzr_dir)
415
# Now revdir should have a tree with the latest .bzr, and the
416
# next revision of the baz tree
417
branch = find_branch(revdir)
419
branch = bzrlib.Branch(revdir, init=True)
421
old = os.path.join(revdir, ".bzr")
422
new = os.path.join(tempdir, ".bzr")
424
baz_inv, log = apply_revision(revdir, revision,
425
skip_symlinks=skip_symlinks)
427
branch = find_branch(revdir)
428
timestamp = email.Utils.mktime_tz(log.date + (0,))
429
rev_id = revision_id(revision)
432
branch.set_inventory(baz_inv)
433
bzrlib.trace.silent = True
434
branch.commit(log.summary, verbose=False, committer=log.creator,
435
timestamp=timestamp, timezone=0, rev_id=rev_id)
437
bzrlib.trace.silent = False
439
yield Progress("revisions", len(ancestors), len(ancestors))
440
unlink_unversioned(branch, revdir)
442
def unlink_unversioned(branch, revdir):
443
for unversioned in branch.working_tree().extras():
444
path = os.path.join(revdir, unversioned)
445
if os.path.isdir(path):
450
def get_log(tree, revision):
451
log = tree.iter_logs(version=revision.version, reverse=True).next()
452
assert log.revision == revision
455
def get_revision(revdir, revision, skip_symlinks=False):
457
tree = pybaz.tree_root(revdir)
458
log = get_log(tree, revision)
460
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
461
except BadFileKind, e:
462
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
465
def apply_revision(revdir, revision, skip_symlinks=False):
466
tree = pybaz.tree_root(revdir)
468
log = get_log(tree, revision)
470
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
471
except BadFileKind, e:
472
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
477
class BadFileKind(Exception):
478
"""The file kind is not permitted in bzr inventories"""
479
def __init__(self, tree_root, path, kind):
480
self.tree_root = tree_root
483
Exception.__init__(self, "File %s is of forbidden type %s" %
484
(os.path.join(tree_root, path), kind))
486
def bzr_inventory_data(tree, skip_symlinks=False):
487
inv_iter = tree.iter_inventory_ids(source=True, both=True)
489
for file_id, path in inv_iter:
490
inv_map[path] = file_id
493
for path, file_id in inv_map.iteritems():
494
full_path = os.path.join(tree, path)
495
kind = bzrlib.osutils.file_kind(full_path)
496
if skip_symlinks and kind == "symlink":
498
if kind not in ("file", "directory"):
499
raise BadFileKind(tree, path, kind)
500
parent_dir = os.path.dirname(path)
502
parent_id = inv_map[parent_dir]
504
parent_id = bzrlib.inventory.ROOT_ID
505
bzr_inv.append((path, file_id, parent_id, kind))
510
"""Just the main() function for this script.
512
By separating it into a function, this can be called as a child from some other
515
:param args: The arguments to this script. Essentially sys.argv[1:]
518
parser = optparse.OptionParser(usage='%prog [options] [VERSION] OUTDIR'
519
'\n VERSION is the arch version to import.'
520
'\n OUTDIR can be an existing directory to be updated'
521
'\n or a new directory which will be created from scratch.')
522
parser.add_option('--verbose', action='store_true'
525
parser.add_option('--skip-symlinks', action="store_true",
526
dest="skip_symlinks",
527
help="Ignore any symlinks present in the Arch tree.")
529
g = optparse.OptionGroup(parser, 'Test options', 'Options useful while testing process.')
530
g.add_option('--test', action='store_true'
531
, help='Run the self-tests and exit.')
532
g.add_option('--dry-run', action='store_true'
533
, help='Do the update, but don\'t copy the result to OUTDIR')
534
g.add_option('--max-count', type='int', metavar='COUNT', default=None
535
, help='At most, add COUNT patches.')
536
g.add_option('--safe', action='store_false', dest='fast')
537
g.add_option('--fast', action='store_true', default=False
538
, help='By default the .bzr control directory will be copied, so that an error'
539
' does not modify the original. --fast allows the directory to be renamed instead.')
540
parser.add_option_group(g)
542
(opts, args) = parser.parse_args(args)
545
print "Running tests"
547
nfail, ntests = doctest.testmod(verbose=opts.verbose)
553
output_dir = os.path.realpath(args[1])
554
version = pybaz.Version(args[0])
556
output_dir = os.path.realpath(args[0])
559
print 'Invalid number of arguments, try --help for more info'
564
import_version(output_dir, version,
565
verbose=opts.verbose, fast=opts.fast,
566
dry_run=opts.dry_run, max_count=opts.max_count,
567
skip_symlinks=opts.skip_symlinks)
572
except KeyboardInterrupt:
576
class NotInABranch(Exception):
577
def __init__(self, path):
578
Exception.__init__(self, "%s is not in a branch." % path)
582
def find_branch(path):
585
Traceback (most recent call last):
586
NotInABranch: / is not in a branch.
587
>>> sb = bzrlib.ScratchBranch()
588
>>> isinstance(find_branch(sb.base), bzrlib.Branch)
592
return bzrlib.Branch(path)
594
if e.args[0].endswith("' is not in a branch"):
595
raise NotInABranch(path)
598
if __name__ == '__main__':
599
sys.exit(main(sys.argv[1:]))