Hi!
Branko Čibej wrote:
> A co-worker of mine has taught cvs2svn to convert tags and branches.
Well, here it is, based on revision 3371
Changes:
- now requires an existing svn repository instead of creating one
- added command line options to specify /trunk, /tags and /branches
It works by creating a copy of the file in /tags (and /branches)
immediatelly after the file revision is commited to the /trunk (or
/branches).
It wasn't heavily tested (I mostly used the icewm repository).
I have no idea about the vendor branch handling (need test cases)
Issues:
- currently breaks the multi-pass mode, because it uses some
in-core data which needs to be moved to the temp files
- some stuff marked with XXX needs review
- the CONFLICTS: output is strange.
- needs an option to convert a single branch only, to a specified PATH.
- it would be nice to have an option to specify a module, resulting in
paths like $MODULES/{trunk,tags,branches}
Comments? Bugs?
Mark
P.S. Many thanks to people creating binary packages. They saved me lots
of time.
--- /usr/bin/cvs2svn 2002-10-15 23:32:02.000000000 +0200
+++ cvs2svn 2002-10-21 20:02:08.000000000 +0200
@@ -24,6 +24,8 @@
trunk_rev = re.compile('^[0-9]+\\.[0-9]+$')
+branch_tag = re.compile('^[0-9.]+\\.0\\.[0-9]+$')
+vendor_tag = re.compile('^[0-9]+\\.[0-9]+\\.[0-9]+$') # XXX?
DATAFILE = 'cvs2svn-data'
REVS_SUFFIX = '.revs'
@@ -44,6 +46,66 @@
verbose = 1
+g_keys = {}
+
+g_tags = {}
+g_branches = {}
+
+g_branch_names = {}
+
+def get_key(f, r):
+ key = f + "--" + r
+ if g_keys.has_key(key):
+ return g_keys[key]
+ g_keys[key] = key
+ return key
+
+def set_branch_name(fname, revision, name):
+ branch_key = get_key(fname, revision)
+ ###print "set_branch", branch_key, name
+ g_branch_names[branch_key] = name
+
+def add_branch_point(fname, revision, target_rev):
+ print "add_branch_point", fname, revision, target_rev
+ branch_key = get_key(fname, revision)
+ if not g_branches.has_key(branch_key):
+ g_branches[branch_key] = [];
+ g_branches[branch_key].append(target_rev);
+
+def add_vendor_name(fname, revision, name):
+ ###print "set_vendor_name", fname, revision, name
+ set_branch_name(fname, revision, name)
+
+ ### add_branch_point(fname, revision, branch_rev); # XXX
+
+def add_branch_name(fname, revision, name):
+ ###print "set_branch_name", fname, revision, name
+ branch_key = get_key(fname, revision)
+ last_dot = revision.rfind(".");
+ branch_rev = revision[:revision.rindex(".")];
+ last2_dot = branch_rev.rfind(".");
+ branch_rev = branch_rev[:last2_dot] + revision[last_dot:];
+ set_branch_name(fname, branch_rev, name)
+
+ add_branch_point(fname, branch_rev[:last2_dot], branch_rev);
+
+def get_branch_name(f, r):
+ ### print "get_branch_name", f, r
+ brev = r[:r.rindex(".")];
+ branch_key = get_key(f, brev)
+ if g_branch_names.has_key(branch_key):
+ branch_name = g_branch_names[branch_key]
+ else:
+ branch_name = None
+ return branch_name
+
+def get_branch_path(ctx, f, r):
+ branch_name = get_branch_name(f, r)
+ if branch_name == None:
+ base_name = ctx.trunk_base
+ else:
+ base_name = ctx.branches_base + '/' + branch_name
+ return base_name
class CollectData(rcsparse.Sink):
def __init__(self, cvsroot, log_fname_base):
@@ -62,6 +124,15 @@
def define_tag(self, name, revision):
self.tags.write('%s %s %s\n' % (name, revision, self.fname))
+ tag_key = get_key(self.fname, revision)
+ if branch_tag.match(revision):
+ add_branch_name(self.fname, revision, name)
+ elif vendor_tag.match(revision):
+ add_vendor_name(self.fname, revision, name)
+ else:
+ if not g_tags.has_key(tag_key):
+ g_tags[tag_key] = [];
+ g_tags[tag_key].append(name);
def define_revision(self, revision, timestamp, author, state,
branches, next):
@@ -315,12 +386,14 @@
for f, r in self.changes:
# compute a repository path. ensure we have a leading "/" and drop
# the ,v from the file name
- repos_path = '/' + relative_name(ctx.cvsroot, f[:-2])
+ base_name = get_branch_path(ctx, f, r)
+ repos_path = base_name + '/' + relative_name(ctx.cvsroot, f[:-2])
print ' changing %s : %s' % (r, repos_path)
for f, r in self.deletes:
# compute a repository path. ensure we have a leading "/" and drop
# the ,v from the file name
- repos_path = '/' + relative_name(ctx.cvsroot, f[:-2])
+ base_name = get_branch_path(ctx, f, r)
+ repos_path = base_name + '/' + relative_name(ctx.cvsroot, f[:-2])
print ' deleting %s : %s' % (r, repos_path)
print ' (skipped; dry run enabled)'
return
@@ -338,9 +411,10 @@
f_pool = util.svn_pool_create(c_pool)
for f, r in self.changes:
+ base_name = get_branch_path(ctx, f, r)
# compute a repository path. ensure we have a leading "/" and drop
# the ,v from the file name
- repos_path = '/' + relative_name(ctx.cvsroot, f[:-2])
+ repos_path = base_name + '/' + relative_name(ctx.cvsroot, f[:-2])
#print 'DEBUG:', repos_path
print ' changing %s : %s' % (r, repos_path)
@@ -413,9 +487,10 @@
lastcommit = (repos_path, r)
for f, r in self.deletes:
+ base_name = get_branch_path(ctx, f, r)
# compute a repository path. ensure we have a leading "/" and drop
# the ,v from the file name
- repos_path = '/' + relative_name(ctx.cvsroot, f[:-2])
+ repos_path = base_name + '/' + relative_name(ctx.cvsroot, f[:-2])
print ' deleting %s : %s' % (r, repos_path)
@@ -436,7 +511,7 @@
conflicts, new_rev = fs.commit_txn(txn)
- # set the time to the proper (past) time
+ # set the time to the proper (past) time
fs.change_rev_prop(t_fs, new_rev, 'svn:date', date, c_pool)
### how come conflicts is a newline?
@@ -444,6 +519,87 @@
print ' CONFLICTS:', `conflicts`
print ' new revision:', new_rev
+ # make a new transaction for the tags
+ rev = fs.youngest_rev(t_fs, c_pool)
+ txn = fs.begin_txn(t_fs, rev, c_pool)
+ root = fs.txn_root(txn, c_pool)
+
+ for f, r in self.changes:
+ tag_key = get_key(f, r)
+ if g_tags.has_key(tag_key):
+ for tag in g_tags[tag_key]:
+ tag_path = ctx.tags_base + '/' + tag + '/' + relative_name(ctx.cvsroot, f[:-2])
+
+ base_name = get_branch_path(ctx, f, r)
+ repos_path = base_name + '/' + relative_name(ctx.cvsroot, f[:-2])
+
+ print "tagging", tag, "for", tag_path, "from", repos_path
+
+ t_root = fs.revision_root(t_fs, rev, f_pool);
+
+ ### hmm. need to clarify OS path separators vs FS path separators
+ dirname = os.path.dirname(tag_path)
+ if dirname != '/':
+ # get the components of the path (skipping the leading '/')
+ parts = string.split(dirname[1:], os.sep)
+ for i in range(1, len(parts) + 1):
+ # reassemble the pieces, adding a leading slash
+ parent_dir = '/' + string.join(parts[:i], '/')
+ if fs.check_path(root, parent_dir, f_pool) == svn_node_none:
+ print ' making dir:', parent_dir
+ fs.make_dir(root, parent_dir, f_pool) ### XXX COPY FROM BRANCH?
+
+ fs.copy(t_root, repos_path, root, tag_path, f_pool)
+
+ # clear the pool after each copy
+ util.svn_pool_clear(f_pool)
+ del g_tags[tag_key]
+
+ for f, r in self.changes:
+ branch_key = get_key(f, r)
+ if g_branches.has_key(branch_key):
+ for branch_rev in g_branches[branch_key]:
+ branch_base = get_branch_path(ctx, f, branch_rev + ".1")
+ branch_path = branch_base + '/' + relative_name(ctx.cvsroot, f[:-2])
+ base_name = get_branch_path(ctx, f, r)
+ repos_path = base_name + '/' + relative_name(ctx.cvsroot, f[:-2])
+
+ print "branching", branch_rev, "for", branch_path, "from", repos_path
+
+ t_root = fs.revision_root(t_fs, rev, f_pool);
+
+ ### hmm. need to clarify OS path separators vs FS path separators
+ dirname = os.path.dirname(branch_path)
+ if dirname != '/':
+ # get the components of the path (skipping the leading '/')
+ parts = string.split(dirname[1:], os.sep)
+ for i in range(1, len(parts) + 1):
+ # reassemble the pieces, adding a leading slash
+ parent_dir = '/' + string.join(parts[:i], '/')
+ if fs.check_path(root, parent_dir, f_pool) == svn_node_none:
+ print ' making dir:', parent_dir
+ fs.make_dir(root, parent_dir, f_pool) ### XXX COPY FROM BRANCH?
+
+ fs.copy(t_root, repos_path, root, branch_path, f_pool)
+
+ # clear the pool after each copy
+ util.svn_pool_clear(f_pool)
+ del g_branches[branch_key]
+
+ for f, r in self.deletes:
+ branch_key = get_key(f, r)
+ if g_branches.has_key(branch_key):
+ for branch_rev in g_branches[branch_key]:
+ branch_base = get_branch_path(ctx, f, branch_rev + ".1")
+ branch_path = branch_base + '/' + relative_name(ctx.cvsroot, f[:-2])
+ print "file created on branch:", branch_rev, "for", branch_path
+ del g_branches[branch_key]
+
+ conflicts, new_rev = fs.commit_txn(txn)
+ if conflicts != '\n':
+ print ' CONFLICTS:', `conflicts`
+ print ' new revision:', new_rev
+
# done with the commit and file pools
util.svn_pool_destroy(c_pool)
@@ -549,7 +705,7 @@
def pass4(ctx):
# create the target repository
if not ctx.dry_run:
- t_repos = _repos.svn_repos_create(ctx.target, ctx.pool)
+ t_repos = _repos.svn_repos_open(ctx.target, ctx.pool)
t_fs = _repos.svn_repos_fs(t_repos)
else:
t_fs = t_repos = None
@@ -562,10 +718,10 @@
timestamp, id, op, rev, fname = parse_revs_line(line)
### only handle changes on the trunk for now
- if not trunk_rev.match(rev):
- ### technically, the timestamp on this could/should cause a flush.
- ### don't worry about it; the next item will handle it
- continue
+# if not trunk_rev.match(rev):
+# ### technically, the timestamp on this could/should cause a flush.
+# ### don't worry about it; the next item will handle it
+# continue
# scan for commits to process
process = [ ]
@@ -627,6 +783,12 @@
for i in range(start_pass, len(_passes)+1):
print 'pass %d: %d seconds' % (i, int(times[i] - times[i-1]))
print ' total:', int(times[len(_passes)] - times[start_pass-1]), 'seconds'
+
+ if g_tags != {}:
+ print "warning: remaining tags: ", repr(g_tags)
+ if g_branches != {}:
+ print "warning: remaining branches: ", repr(g_branches)
+ ### print "branche names: ", repr(g_branch_names)
def usage():
print 'USAGE: %s [-n] [-v] [-s svn-repos-path] [-p pass] cvs-repos-path' \
@@ -634,12 +796,18 @@
print ' -n dry run. parse CVS repos, but do not construct SVN repos.'
print ' -v verbose.'
print ' -s PATH path for new SVN repos.'
+ print ' -m PATH relative path for trunk (default: /trunk)'
+ print ' -b PATH relative path for branches (default: /tags)'
+ print ' -t PATH relative path for tags (default: /branches)'
+# TODO
+# print ' -M PATH relative path, prepended to all 3 above'
+
print ' -p NUM start at pass NUM of %d.' % len(_passes)
sys.exit(1)
def main():
try:
- opts, args = getopt.getopt(sys.argv[1:], 'p:s:vn')
+ opts, args = getopt.getopt(sys.argv[1:], 'p:s:m:b:t:vn')
except getopt.GetoptError:
usage()
if len(args) != 1:
@@ -652,6 +820,9 @@
ctx.log_fname_base = DATAFILE
ctx.verbose = 0
ctx.dry_run = 0
+ ctx.trunk_base = "/trunk"
+ ctx.tags_base = "/tags"
+ ctx.branches_base = "/branches"
start_pass = 1
@@ -668,6 +839,12 @@
ctx.dry_run = 1
elif opt == '-s':
ctx.target = value
+ elif opt == '-m':
+ ctx.trunk_base = value
+ elif opt == '-b':
+ ctx.branches_base = value
+ elif opt == '-t':
+ ctx.tags_base = value
util.run_app(convert, ctx, start_pass=start_pass)
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Mon Oct 21 20:47:22 2002