[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: cvs2svn patch in the works...

From: Marko Macek <Marko.Macek_at_gmx.net>
Date: 2002-10-21 20:27:53 CEST

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

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.