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
16
from bzrlib.branch import Branch
17
from bzrlib.commands import Command
18
from errors import NoPyBaz
22
from pybaz.backends.baz import null_cmd
30
from bzrlib.errors import BzrError, NotBranchError, BzrCommandError
33
import bzrlib.inventory
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="testdir-")
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")
159
def commit_more_test_revisions():
161
>>> q = test_environ()
162
>>> commit_test_revisions()
163
>>> commit_more_test_revisions()
164
>>> a = pybaz.Archive("test@example.com")
165
>>> revisions = list(a.iter_revisions("test--test--0"))
168
>>> str(revisions[0])
169
'test@example.com/test--test--0--patch-3'
170
>>> teardown_environ(q)
172
tree = pybaz.tree_root(".")
173
add_file("trainfile", "void train(void){}", "trainfile by aaron")
174
commit(tree, "altered trainfile")
176
class NoSuchVersion(Exception):
177
def __init__(self, version):
178
Exception.__init__(self, "The version %s does not exist." % version)
179
self.version = version
181
def version_ancestry(version):
183
>>> q = test_environ()
184
>>> commit_test_revisions()
185
>>> version = pybaz.Version("test@example.com/test--test--0")
186
>>> ancestors = version_ancestry(version)
187
>>> str(ancestors[0])
188
'test@example.com/test--test--0--base-0'
189
>>> str(ancestors[1])
190
'test@example.com/test--test--0--patch-1'
191
>>> version = pybaz.Version("test@example.com/test--test--0.5")
192
>>> ancestors = version_ancestry(version)
193
Traceback (most recent call last):
194
NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
195
>>> teardown_environ(q)
198
revision = version.iter_revisions(reverse=True).next()
200
if not version.exists():
201
raise NoSuchVersion(version)
204
ancestors = list(revision.iter_ancestors(metoo=True))
208
def get_last_revision(branch):
209
last_patch = branch.last_revision()
211
return arch_revision(last_patch)
212
except NotArchRevision:
214
"Directory \"%s\" already exists, and the last revision is not"
215
" an Arch revision (%s)" % (output_dir, last_patch))
218
def get_remaining_revisions(output_dir, version):
221
if os.path.exists(output_dir):
222
# We are starting from an existing directory, figure out what
223
# the current version is
224
branch = find_branch(output_dir)
225
last_patch = get_last_revision(branch)
226
if last_patch is None:
227
raise NotPreviousImport(branch.base)
229
version = last_patch.version
230
elif version is None:
231
raise UserError("No version specified, and directory does not exist.")
234
ancestors = version_ancestry(version)
235
if len(ancestors) > 0 and not ancestors[0].archive.is_registered():
236
ancestors = ancestors[1:]
237
except NoSuchVersion, e:
241
for i in range(len(ancestors)):
242
if ancestors[i] == last_patch:
245
raise UserError("Directory \"%s\" already exists, and the last "
246
"revision (%s) is not in the ancestry of %s" %
247
(output_dir, last_patch, version))
248
# Strip off all of the ancestors which are already present
249
# And get a directory starting with the latest ancestor
250
latest_ancestor = ancestors[i]
251
old_revno = find_branch(output_dir).revno()
252
ancestors = ancestors[i+1:]
253
return ancestors, old_revno
255
def import_version(output_dir, version, fancy=True, fast=False, verbose=False,
256
dry_run=False, max_count=None, skip_symlinks=False):
258
>>> q = test_environ()
259
>>> result_path = os.path.join(q, "result")
260
>>> commit_test_revisions()
261
>>> version = pybaz.Version("test@example.com/test--test--0.1")
262
>>> import_version('/', version, fancy=False, dry_run=True)
263
Traceback (most recent call last):
264
UserError: / exists, but is not a bzr branch.
265
>>> import_version(result_path, version, fancy=False, dry_run=True)
266
Traceback (most recent call last):
267
UserError: The version test@example.com/test--test--0.1 does not exist.
268
>>> version = pybaz.Version("test@example.com/test--test--0")
269
>>> import_version(result_path, version, fancy=False, dry_run=True)
272
Dry run, not modifying output_dir
274
>>> import_version(result_path, version, fancy=False)
279
>>> import_version(result_path, version, fancy=False)
280
Tree is up-to-date with test@example.com/test--test--0--patch-2
281
>>> commit_more_test_revisions()
282
>>> import_version(result_path, version, fancy=False)
287
>>> teardown_environ(q)
290
ancestors, old_revno = get_remaining_revisions(output_dir, version)
291
except NotInABranch, e:
292
raise NotPreviousImport(e.path)
293
if len(ancestors) == 0:
294
last_revision = get_last_revision(find_branch(output_dir))
295
print 'Tree is up-to-date with %s' % last_revision
298
progress_bar = ProgressBar()
299
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
300
dir=os.path.dirname(output_dir))
305
for result in iter_import_version(output_dir, ancestors, tempdir,
306
fast=fast, verbose=verbose, dry_run=dry_run,
307
max_count=max_count, skip_symlinks=skip_symlinks):
309
show_progress(progress_bar, result)
311
sys.stdout.write('.')
316
sys.stdout.write('\n')
319
print 'Dry run, not modifying output_dir'
321
if os.path.exists(output_dir):
322
# Move the bzr control directory back, and update the working tree
323
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
325
bzr_dir = os.path.join(output_dir, '.bzr')
326
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
328
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
329
os.rename(new_bzr_dir, bzr_dir)
331
bzrlib.merge.merge((output_dir, -1), (output_dir, old_revno),
332
check_clean=False, this_dir=output_dir,
335
# If something failed, move back the original bzr directory
336
os.rename(bzr_dir, new_bzr_dir)
337
os.rename(tmp_bzr_dir, bzr_dir)
340
revdir = os.path.join(tempdir, "rd")
341
os.rename(revdir, output_dir)
345
shutil.rmtree(tempdir)
346
print "Import complete."
348
class UserError(BzrCommandError):
349
def __init__(self, message):
350
"""Exception to throw when a user makes an impossible request
351
:param message: The message to emit when printing this exception
352
:type message: string
354
BzrCommandError.__init__(self, message)
356
class NotPreviousImport(UserError):
357
def __init__(self, path):
358
UserError.__init__(self, "%s is not the location of a previous import."
362
def revision_id(arch_revision):
364
Generate a Bzr revision id from an Arch revision id. 'x' in the id
365
designates a revision imported with an experimental algorithm. A number
366
would indicate a particular standardized version.
368
:param arch_revision: The Arch revision to generate an ID for.
370
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
371
'Arch-x:you@example.com%cat--br--0--base-0'
373
return "Arch-x:%s" % str(arch_revision).replace('/', '%')
375
class NotArchRevision(Exception):
376
def __init__(self, revision_id):
377
msg = "The revision id %s does not look like it came from Arch."\
379
Exception.__init__(self, msg)
381
def arch_revision(revision_id):
383
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0"))
384
Traceback (most recent call last):
385
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0 does not look like it came from Arch.
386
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--base-5"))
387
Traceback (most recent call last):
388
NotArchRevision: The revision id Arch-x:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
389
>>> str(arch_revision("Arch-x:jrandom@example.com%test--test--0--patch-5"))
390
'jrandom@example.com/test--test--0--patch-5'
392
if revision_id is None:
394
if revision_id[:7] != 'Arch-x:':
395
raise NotArchRevision(revision_id)
398
return pybaz.Revision(revision_id[7:].replace('%', '/'))
399
except pybaz.errors.NamespaceError, e:
400
raise NotArchRevision(revision_id)
402
def iter_import_version(output_dir, ancestors, tempdir, fast=False,
403
verbose=False, dry_run=False, max_count=None,
404
skip_symlinks=False):
407
# Uncomment this for testing, it basically just has baz2bzr only update
408
# 5 patches at a time
410
ancestors = ancestors[:max_count]
412
# Not sure if I want this output. basically it tells you ahead of time
413
# what it is going to do, but then later it tells you as it is doing it.
414
# what probably would be best would be to collapse it into ranges, so that
415
# this gives the simple view, and then later it gives the blow by blow.
417
# print 'Adding the following revisions:'
418
# for a in ancestors:
421
previous_version=None
423
for i in range(len(ancestors)):
424
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")
436
baz_inv, log = get_revision(revdir, revision,
437
skip_symlinks=skip_symlinks)
438
if os.path.exists(output_dir):
439
bzr_dir = os.path.join(output_dir, '.bzr')
440
new_bzr_dir = os.path.join(tempdir, "rd", '.bzr')
441
# This would be much faster with a simple os.rename(), but if
442
# we fail, we have corrupted the original .bzr directory. Is
443
# that a big problem, as we can just back out the last
444
# revisions in .bzr/revision_history I don't really know
445
shutil.copytree(bzr_dir, new_bzr_dir)
446
# Now revdir should have a tree with the latest .bzr, and the
447
# next revision of the baz tree
448
branch = find_branch(revdir)
450
branch = Branch.initialize(revdir)
452
old = os.path.join(revdir, ".bzr")
453
new = os.path.join(tempdir, ".bzr")
455
baz_inv, log = apply_revision(revdir, revision,
456
skip_symlinks=skip_symlinks)
458
branch = find_branch(revdir)
459
timestamp = email.Utils.mktime_tz(log.date + (0,))
460
rev_id = revision_id(revision)
463
branch.working_tree().set_inventory(baz_inv)
464
bzrlib.trace.silent = True
465
branch.commit(log.summary, verbose=False, committer=log.creator,
466
timestamp=timestamp, timezone=0, rev_id=rev_id)
468
bzrlib.trace.silent = False
470
yield Progress("revisions", len(ancestors), len(ancestors))
471
unlink_unversioned(branch, revdir)
473
def unlink_unversioned(branch, revdir):
474
for unversioned in branch.working_tree().extras():
475
path = os.path.join(revdir, unversioned)
476
if os.path.isdir(path):
481
def get_log(tree, revision):
482
log = tree.iter_logs(version=revision.version, reverse=True).next()
483
assert str(log.revision) == str(revision), (log.revision, revision)
486
def get_revision(revdir, revision, skip_symlinks=False):
488
tree = pybaz.tree_root(revdir)
489
log = get_log(tree, revision)
491
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
492
except BadFileKind, e:
493
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
496
def apply_revision(revdir, revision, skip_symlinks=False):
497
tree = pybaz.tree_root(revdir)
499
log = get_log(tree, revision)
501
return bzr_inventory_data(tree, skip_symlinks=skip_symlinks), log
502
except BadFileKind, e:
503
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
508
class BadFileKind(Exception):
509
"""The file kind is not permitted in bzr inventories"""
510
def __init__(self, tree_root, path, kind):
511
self.tree_root = tree_root
514
Exception.__init__(self, "File %s is of forbidden type %s" %
515
(os.path.join(tree_root, path), kind))
517
def bzr_inventory_data(tree, skip_symlinks=False):
518
inv_iter = tree.iter_inventory_ids(source=True, both=True)
520
for arch_id, path in inv_iter:
521
bzr_file_id = arch_id.replace('%', '%25').replace('/', '%2f')
522
inv_map[path] = bzr_file_id
525
for path, file_id in inv_map.iteritems():
526
full_path = os.path.join(tree, path)
527
kind = bzrlib.osutils.file_kind(full_path)
528
if skip_symlinks and kind == "symlink":
530
if kind not in ("file", "directory"):
531
raise BadFileKind(tree, path, kind)
532
parent_dir = os.path.dirname(path)
534
parent_id = inv_map[parent_dir]
536
parent_id = bzrlib.inventory.ROOT_ID
537
bzr_inv.append((path, file_id, parent_id, kind))
541
class NotInABranch(Exception):
542
def __init__(self, path):
543
Exception.__init__(self, "%s is not in a branch." % path)
547
def find_branch(path):
550
Traceback (most recent call last):
551
NotInABranch: / is not in a branch.
552
>>> sb = bzrlib.ScratchBranch()
553
>>> isinstance(find_branch(sb.base), Branch)
557
return Branch.open(path)
558
except NotBranchError, e:
559
raise NotInABranch(path)
561
class cmd_baz_import(Command):
562
"""Import an Arch or Baz branch into a bzr branch"""
563
takes_args = ['to_location', 'from_branch?']
564
takes_options = ['verbose']
566
def run(self, to_location, from_branch=None, skip_symlinks=False,
567
fast=False, max_count=None, verbose=False, dry_run=False):
568
to_location = os.path.realpath(str(to_location))
569
if from_branch is not None:
571
from_branch = pybaz.Version(from_branch)
572
except pybaz.errors.NamespaceError:
573
print "%s is not a valid Arch branch." % from_branch
575
import_version(to_location, from_branch)