17
17
# along with this program; if not, write to the Free Software
18
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from errors import NoPyBaz
23
from baz_import import import_version, UserError
25
print >> sys.stderr, "This command requires PyBaz. Please ensure that it is installed."
23
print "This command requires PyBaz. Please ensure that it is installed."
26
from pybaz.backends.baz import null_cmd
32
"""Just the main() function for this script.
34
By separating it into a function, this can be called as a child from some other
37
:param args: The arguments to this script. Essentially sys.argv[1:]
40
parser = optparse.OptionParser(usage='%prog [options] [VERSION] OUTDIR'
41
'\n VERSION is the arch version to import.'
42
'\n OUTDIR can be an existing directory to be updated'
43
'\n or a new directory which will be created from scratch.')
44
parser.add_option('--verbose', action='store_true'
47
parser.add_option('--skip-symlinks', action="store_true",
49
help="Ignore any symlinks present in the Arch tree.")
51
g = optparse.OptionGroup(parser, 'Test options', 'Options useful while testing process.')
52
g.add_option('--test', action='store_true'
53
, help='Run the self-tests and exit.')
54
g.add_option('--dry-run', action='store_true'
55
, help='Do the update, but don\'t copy the result to OUTDIR')
56
g.add_option('--max-count', type='int', metavar='COUNT', default=None
57
, help='At most, add COUNT patches.')
58
g.add_option('--safe', action='store_false', dest='fast')
59
g.add_option('--fast', action='store_true', default=False
60
, help='By default the .bzr control directory will be copied, so that an error'
61
' does not modify the original. --fast allows the directory to be renamed instead.')
62
parser.add_option_group(g)
64
(opts, args) = parser.parse_args(args)
68
import doctest, baz_import
69
nfail, ntests = doctest.testmod(baz_import, verbose=opts.verbose)
36
from progress import *
38
def add_id(files, id=None):
39
"""Adds an explicit id to a list of files.
41
:param files: the name of the file to add an id to
42
:type files: list of str
43
:param id: tag one file using the specified id, instead of generating id
48
args.extend(["--id", id])
54
>>> q = test_environ()
57
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
59
>>> teardown_environ(q)
63
tdir = tempfile.mkdtemp(prefix="baz2bzr-")
64
os.environ["HOME"] = os.path.join(tdir, "home")
65
os.mkdir(os.environ["HOME"])
66
arch_dir = os.path.join(tdir, "archive_dir")
67
pybaz.make_archive("test@example.com", arch_dir)
68
work_dir = os.path.join(tdir, "work_dir")
71
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
72
lib_dir = os.path.join(tdir, "lib_dir")
74
pybaz.register_revision_library(lib_dir)
75
pybaz.set_my_id("Test User<test@example.org>")
78
def add_file(path, text, id):
80
>>> q = test_environ()
81
>>> add_file("path with space", "text", "lalala")
82
>>> tree = pybaz.tree_root(".")
83
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
84
>>> ("x_lalala", "path with space") in inv
86
>>> teardown_environ(q)
88
file(path, "wb").write(text)
92
def add_dir(path, id):
94
>>> q = test_environ()
95
>>> add_dir("path with\(sp) space", "lalala")
96
>>> tree = pybaz.tree_root(".")
97
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
98
>>> ("x_lalala", "path with\(sp) space") in inv
100
>>> teardown_environ(q)
105
def teardown_environ(tdir):
109
def timport(tree, summary):
110
msg = tree.log_message()
111
msg["summary"] = summary
114
def commit(tree, summary):
116
>>> q = test_environ()
117
>>> tree = pybaz.tree_root(".")
118
>>> timport(tree, "import")
119
>>> commit(tree, "commit")
120
>>> logs = [str(l.revision) for l in tree.iter_logs()]
124
'test@example.com/test--test--0--base-0'
126
'test@example.com/test--test--0--patch-1'
127
>>> teardown_environ(q)
129
msg = tree.log_message()
130
msg["summary"] = summary
133
def commit_test_revisions():
135
>>> q = test_environ()
136
>>> commit_test_revisions()
137
>>> a = pybaz.Archive("test@example.com")
138
>>> revisions = list(a.iter_revisions("test--test--0"))
141
>>> str(revisions[2])
142
'test@example.com/test--test--0--base-0'
143
>>> str(revisions[1])
144
'test@example.com/test--test--0--patch-1'
145
>>> str(revisions[0])
146
'test@example.com/test--test--0--patch-2'
147
>>> teardown_environ(q)
149
tree = pybaz.tree_root(".")
150
add_file("mainfile", "void main(void){}", "mainfile by aaron")
151
timport(tree, "Created mainfile")
152
file("mainfile", "wb").write("or something like that")
153
commit(tree, "altered mainfile")
154
add_file("ofile", "this is another file", "ofile by aaron")
155
commit(tree, "altered mainfile")
157
def version_ancestry(version):
159
>>> q = test_environ()
160
>>> commit_test_revisions()
161
>>> version = pybaz.Version("test@example.com/test--test--0")
162
>>> ancestors = version_ancestry(version)
163
>>> str(ancestors[0])
164
'test@example.com/test--test--0--base-0'
165
>>> str(ancestors[1])
166
'test@example.com/test--test--0--patch-1'
167
>>> teardown_environ(q)
169
revision = version.iter_revisions(reverse=True).next()
170
ancestors = list(revision.iter_ancestors(metoo=True))
175
def import_version(output_dir, version, fancy=True):
177
>>> q = test_environ()
178
>>> result_path = os.path.join(q, "result")
179
>>> commit_test_revisions()
180
>>> version = pybaz.Version("test@example.com/test--test--0")
181
>>> import_version(result_path, version, fancy=False)
185
>>> teardown_environ(q)
187
progress_bar = ProgressBar()
188
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
189
dir=os.path.dirname(output_dir))
193
for result in iter_import_version(output_dir, version, tempdir):
197
sys.stdout.write('.')
75
version,output_dir = args
202
sys.stdout.write('\n')
203
shutil.rmtree(tempdir)
204
print "Import complete."
206
class UserError(Exception):
207
def __init__(self, message):
208
"""Exception to throw when a user makes an impossible request
209
:param message: The message to emit when printing this exception
210
:type message: string
212
Exception.__init__(self, message)
214
def revision_id(arch_revision):
216
Generate a Bzr revision id from an Arch revision id. 'x' in the id
217
designates a revision imported with an experimental algorithm. A number
218
would indicate a particular standardized version.
220
:param arch_revision: The Arch revision to generate an ID for.
222
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
223
'Arch-x:you@example.com%cat--br--0--base-0'
225
return "Arch-x:%s" % str(arch_revision).replace('/', '%')
227
def iter_import_version(output_dir, version, tempdir):
229
ancestors = version_ancestry(version)
231
if os.path.exists(output_dir):
232
# We are starting from an existing directory, figure out what
233
# the current version is
234
branch = bzrlib.Branch(output_dir)
235
last_patch = branch.last_patch()
236
if last_patch is not None:
237
if last_patch[:7] != 'Arch-x:':
238
raise UserError("Directory \"%s\" already exists, and the last patch is not an arch patch (%s)" % (output_dir, last_patch))
240
last_patch = last_patch[7:].replace('%', '/')
243
for i in range(len(ancestors)):
244
if str(ancestors[i]) == last_patch:
247
raise UserError("Directory \"%s\" already exists, and the last patch (%s) is not in the revision history" % (output_dir, last_patch))
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 = bzrlib.Branch(output_dir).revno()
252
ancestors = ancestors[i+1:]
254
if len(ancestors) == 0:
255
print '* Tree is up-to-date with %s' % last_patch
258
revdir = os.path.join(tempdir, "rd")
259
baz_inv, log = get_revision(revdir, latest_ancestor, skip_symlink=True)
260
bzr_dir = os.path.join(output_dir, '.bzr')
261
new_bzr_dir = os.path.join(revdir, '.bzr')
262
# This would be much faster with a simple os.rename(), but if we fail,
263
# we have corrupted the original .bzr directory.
264
# Is that a big problem, as we can just back out the last revisions in
265
# .bzr/revision_history
266
# I don't really know
267
shutil.copytree(bzr_dir, new_bzr_dir)
268
# Now revdir should have a tree with the latest .bzr, and the correct
269
# version of the baz tree
271
# Uncomment this for testing, it basically just has baz2bzr only update
272
# 5 patches at a time
273
#ancestors = ancestors[:5]
275
for i in range(len(ancestors)):
276
revision = ancestors[i]
277
yield Progress("revisions", i, len(ancestors))
279
revdir = os.path.join(tempdir, "rd")
280
baz_inv, log = get_revision(revdir, revision, skip_symlink=True)
281
branch = bzrlib.Branch(revdir, init=True)
283
old = os.path.join(revdir, ".bzr")
284
new = os.path.join(tempdir, ".bzr")
286
baz_inv, log = apply_revision(revdir, revision, skip_symlink=True)
288
branch = bzrlib.Branch(revdir)
289
timestamp = email.Utils.mktime_tz(log.date + (0,))
290
rev_id = revision_id(revision)
293
branch.set_inventory(baz_inv)
294
bzrlib.trace.silent = True
295
branch.commit(log.summary, verbose=False, committer=log.creator,
296
timestamp=timestamp, timezone=0, rev_id=rev_id)
298
bzrlib.trace.silent = False
300
yield Progress("revisions", len(ancestors), len(ancestors))
301
unlink_unversioned(branch, revdir)
302
if os.path.exists(output_dir):
303
# Move the bzr control directory back, and update the working tree
304
tmp_bzr_dir = os.path.join(tempdir, '.bzr')
305
os.rename(bzr_dir, tmp_bzr_dir) # Move the original bzr out of the way
306
os.rename(new_bzr_dir, bzr_dir)
308
# bzrlib.merge that exists in mainline does not have a this_dir component,
309
# so we have to work in the local directory
313
bzrlib.merge.merge(('.', -1), ('.', old_revno))
317
# If something failed, move back the original bzr directory
318
os.rename(bzr_dir, new_bzr_dir)
319
os.rename(tmp_bzr_dir, bzr_dir)
81
print 'Invalid number of arguments, try --help for more info'
84
output_dir = os.path.realpath(output_dir)
85
if version is not None:
87
version = pybaz.Version(version)
88
except pybaz.errors.NamespaceError:
89
print "%s is not a valid Arch branch." % version
93
import_version(output_dir, version,
94
verbose=opts.verbose, fast=opts.fast,
95
dry_run=opts.dry_run, max_count=opts.max_count,
96
skip_symlinks=opts.skip_symlinks)
322
os.rename(revdir, output_dir)
324
def unlink_unversioned(branch, revdir):
325
for unversioned in branch.working_tree().extras():
326
path = os.path.join(revdir, unversioned)
327
if os.path.isdir(path):
332
def get_log(tree, revision):
333
log = tree.iter_logs(version=revision.version, reverse=True).next()
334
assert log.revision == revision
337
def get_revision(revdir, revision, skip_symlink=False):
339
tree = pybaz.tree_root(revdir)
340
log = get_log(tree, revision)
342
return bzr_inventory_data(tree, skip_symlink=skip_symlink), log
343
except BadFileKind, e:
344
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
347
def apply_revision(revdir, revision, skip_symlink=False):
348
tree = pybaz.tree_root(revdir)
350
log = get_log(tree, revision)
352
return bzr_inventory_data(tree, skip_symlink=skip_symlink), log
353
except BadFileKind, e:
354
raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
359
class BadFileKind(Exception):
360
"""The file kind is not permitted in bzr inventories"""
361
def __init__(self, tree_root, path, kind):
362
self.tree_root = tree_root
365
Exception.__init__(self, "File %s is of forbidden type %s" %
366
(os.path.join(tree_root, path), kind))
368
def bzr_inventory_data(tree, skip_symlink=False):
369
inv_iter = tree.iter_inventory_ids(source=True, both=True)
371
for file_id, path in inv_iter:
372
inv_map[path] = file_id
375
for path, file_id in inv_map.iteritems():
376
full_path = os.path.join(tree, path)
377
kind = bzrlib.osutils.file_kind(full_path)
378
if skip_symlink and kind == "symlink":
380
if kind not in ("file", "directory"):
381
raise BadFileKind(tree, path, kind)
382
parent_dir = os.path.dirname(path)
384
parent_id = inv_map[parent_dir]
386
parent_id = bzrlib.inventory.ROOT_ID
387
bzr_inv.append((path, file_id, parent_id, kind))
391
if len(sys.argv) == 2 and sys.argv[1] == "test":
392
print "Running tests"
395
elif len(sys.argv) == 3:
397
output_dir = os.path.realpath(sys.argv[2])
398
import_version(output_dir, pybaz.Version(sys.argv[1]))
98
399
except UserError, e:
101
401
except KeyboardInterrupt:
107
if __name__ == '__main__':
108
sys.exit(main(sys.argv[1:]))
404
print "usage: %s VERSION OUTDIR" % os.path.basename(sys.argv[0])