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