annotate extra/fast-export/svn-fast-export.py @ 1561:6074fffd8a1d feature_1136

No, a bare repo is better
author Chris Cannam
date Thu, 14 Jan 2016 12:03:06 +0000
parents e9e55585ebf2
children
rev   line source
chris@1544 1 #!/usr/bin/python
chris@1544 2 #
chris@1544 3 # svn-fast-export.py
chris@1544 4 # ----------
chris@1544 5 # Walk through each revision of a local Subversion repository and export it
chris@1544 6 # in a stream that git-fast-import can consume.
chris@1544 7 #
chris@1544 8 # Author: Chris Lee <clee@kde.org>
chris@1544 9 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
chris@1544 10
chris@1544 11 trunk_path = '/trunk/'
chris@1544 12 branches_path = '/branches/'
chris@1544 13 tags_path = '/tags/'
chris@1544 14
chris@1544 15 first_rev = 1
chris@1544 16 final_rev = 0
chris@1544 17
chris@1544 18 import sys, os.path
chris@1544 19 from optparse import OptionParser
chris@1544 20 from time import mktime, strptime
chris@1544 21 from svn.fs import svn_fs_file_length, svn_fs_file_contents, svn_fs_is_dir, svn_fs_revision_root, svn_fs_youngest_rev, svn_fs_revision_proplist, svn_fs_paths_changed
chris@1544 22 from svn.core import svn_pool_create, svn_pool_clear, svn_pool_destroy, svn_stream_for_stdout, svn_stream_copy, svn_stream_close, run_app
chris@1544 23 from svn.repos import svn_repos_open, svn_repos_fs
chris@1544 24
chris@1544 25 ct_short = ['M', 'A', 'D', 'R', 'X']
chris@1544 26
chris@1544 27 def dump_file_blob(root, full_path, pool):
chris@1544 28 stream_length = svn_fs_file_length(root, full_path, pool)
chris@1544 29 stream = svn_fs_file_contents(root, full_path, pool)
chris@1544 30 sys.stdout.write("data %s\n" % stream_length)
chris@1544 31 sys.stdout.flush()
chris@1544 32 ostream = svn_stream_for_stdout(pool)
chris@1544 33 svn_stream_copy(stream, ostream, pool)
chris@1544 34 svn_stream_close(ostream)
chris@1544 35 sys.stdout.write("\n")
chris@1544 36
chris@1544 37
chris@1544 38 def export_revision(rev, repo, fs, pool):
chris@1544 39 sys.stderr.write("Exporting revision %s... " % rev)
chris@1544 40
chris@1544 41 revpool = svn_pool_create(pool)
chris@1544 42 svn_pool_clear(revpool)
chris@1544 43
chris@1544 44 # Open a root object representing the youngest (HEAD) revision.
chris@1544 45 root = svn_fs_revision_root(fs, rev, revpool)
chris@1544 46
chris@1544 47 # And the list of what changed in this revision.
chris@1544 48 changes = svn_fs_paths_changed(root, revpool)
chris@1544 49
chris@1544 50 i = 1
chris@1544 51 marks = {}
chris@1544 52 file_changes = []
chris@1544 53
chris@1544 54 for path, change_type in changes.iteritems():
chris@1544 55 c_t = ct_short[change_type.change_kind]
chris@1544 56 if svn_fs_is_dir(root, path, revpool):
chris@1544 57 continue
chris@1544 58
chris@1544 59 if not path.startswith(trunk_path):
chris@1544 60 # We don't handle branches. Or tags. Yet.
chris@1544 61 pass
chris@1544 62 else:
chris@1544 63 if c_t == 'D':
chris@1544 64 file_changes.append("D %s" % path.replace(trunk_path, ''))
chris@1544 65 else:
chris@1544 66 marks[i] = path.replace(trunk_path, '')
chris@1544 67 file_changes.append("M 644 :%s %s" % (i, marks[i]))
chris@1544 68 sys.stdout.write("blob\nmark :%s\n" % i)
chris@1544 69 dump_file_blob(root, path, revpool)
chris@1544 70 i += 1
chris@1544 71
chris@1544 72 # Get the commit author and message
chris@1544 73 props = svn_fs_revision_proplist(fs, rev, revpool)
chris@1544 74
chris@1544 75 # Do the recursive crawl.
chris@1544 76 if props.has_key('svn:author'):
chris@1544 77 author = "%s <%s@localhost>" % (props['svn:author'], props['svn:author'])
chris@1544 78 else:
chris@1544 79 author = 'nobody <nobody@localhost>'
chris@1544 80
chris@1544 81 if len(file_changes) == 0:
chris@1544 82 svn_pool_destroy(revpool)
chris@1544 83 sys.stderr.write("skipping.\n")
chris@1544 84 return
chris@1544 85
chris@1544 86 svndate = props['svn:date'][0:-8]
chris@1544 87 commit_time = mktime(strptime(svndate, '%Y-%m-%dT%H:%M:%S'))
chris@1544 88 sys.stdout.write("commit refs/heads/master\n")
chris@1544 89 sys.stdout.write("committer %s %s -0000\n" % (author, int(commit_time)))
chris@1544 90 sys.stdout.write("data %s\n" % len(props['svn:log']))
chris@1544 91 sys.stdout.write(props['svn:log'])
chris@1544 92 sys.stdout.write("\n")
chris@1544 93 sys.stdout.write('\n'.join(file_changes))
chris@1544 94 sys.stdout.write("\n\n")
chris@1544 95
chris@1544 96 svn_pool_destroy(revpool)
chris@1544 97
chris@1544 98 sys.stderr.write("done!\n")
chris@1544 99
chris@1544 100 #if rev % 1000 == 0:
chris@1544 101 # sys.stderr.write("gc: %s objects\n" % len(gc.get_objects()))
chris@1544 102 # sleep(5)
chris@1544 103
chris@1544 104
chris@1544 105 def crawl_revisions(pool, repos_path):
chris@1544 106 """Open the repository at REPOS_PATH, and recursively crawl all its
chris@1544 107 revisions."""
chris@1544 108 global final_rev
chris@1544 109
chris@1544 110 # Open the repository at REPOS_PATH, and get a reference to its
chris@1544 111 # versioning filesystem.
chris@1544 112 repos_obj = svn_repos_open(repos_path, pool)
chris@1544 113 fs_obj = svn_repos_fs(repos_obj)
chris@1544 114
chris@1544 115 # Query the current youngest revision.
chris@1544 116 youngest_rev = svn_fs_youngest_rev(fs_obj, pool)
chris@1544 117
chris@1544 118
chris@1544 119 first_rev = 1
chris@1544 120 if final_rev == 0:
chris@1544 121 final_rev = youngest_rev
chris@1544 122 for rev in xrange(first_rev, final_rev + 1):
chris@1544 123 export_revision(rev, repos_obj, fs_obj, pool)
chris@1544 124
chris@1544 125
chris@1544 126 if __name__ == '__main__':
chris@1544 127 usage = '%prog [options] REPOS_PATH'
chris@1544 128 parser = OptionParser()
chris@1544 129 parser.set_usage(usage)
chris@1544 130 parser.add_option('-f', '--final-rev', help='Final revision to import',
chris@1544 131 dest='final_rev', metavar='FINAL_REV', type='int')
chris@1544 132 parser.add_option('-t', '--trunk-path', help='Path in repo to /trunk',
chris@1544 133 dest='trunk_path', metavar='TRUNK_PATH')
chris@1544 134 parser.add_option('-b', '--branches-path', help='Path in repo to /branches',
chris@1544 135 dest='branches_path', metavar='BRANCHES_PATH')
chris@1544 136 parser.add_option('-T', '--tags-path', help='Path in repo to /tags',
chris@1544 137 dest='tags_path', metavar='TAGS_PATH')
chris@1544 138 (options, args) = parser.parse_args()
chris@1544 139
chris@1544 140 if options.trunk_path != None:
chris@1544 141 trunk_path = options.trunk_path
chris@1544 142 if options.branches_path != None:
chris@1544 143 branches_path = options.branches_path
chris@1544 144 if options.tags_path != None:
chris@1544 145 tags_path = options.tags_path
chris@1544 146 if options.final_rev != None:
chris@1544 147 final_rev = options.final_rev
chris@1544 148
chris@1544 149 if len(args) != 1:
chris@1544 150 parser.print_help()
chris@1544 151 sys.exit(2)
chris@1544 152
chris@1544 153 # Canonicalize (enough for Subversion, at least) the repository path.
chris@1544 154 repos_path = os.path.normpath(args[0])
chris@1544 155 if repos_path == '.':
chris@1544 156 repos_path = ''
chris@1544 157
chris@1544 158 # Call the app-wrapper, which takes care of APR initialization/shutdown
chris@1544 159 # and the creation and cleanup of our top-level memory pool.
chris@1544 160 run_app(crawl_revisions, repos_path)