Merge lp:~jelmer/bzr-fastimport/setup.py-fixes into lp:~bzr/bzr-fastimport/fastimport.dev

Proposed by Jelmer Vernooij
Status: Rejected
Rejected by: Ian Clatworthy
Proposed branch: lp:~jelmer/bzr-fastimport/setup.py-fixes
Merge into: lp:~bzr/bzr-fastimport/fastimport.dev
Diff against target: 225 lines
1 file modified
exporters/svn-fast-export.py (+220/-0)
To merge this branch: bzr merge lp:~jelmer/bzr-fastimport/setup.py-fixes
Reviewer Review Type Date Requested Status
Ian Clatworthy Disapprove
Review via email: mp+13557@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

This adds a subvertpy-based svn-fast-export as well, and adds support for the executable bit and symbolic links.

Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

After an IRC discussion, we've agreed to leave Jelmer's fast-export code in subvertpy. I'll tweak fast-export-from-svn to look for it in preference to the current one.

review: Disapprove

Unmerged revisions

257. By Jelmer Vernooij

Support symbolic links in svn-fast-export.py.

256. By Jelmer Vernooij

svn-fast-export: Add support for executable bit.

255. By Jelmer Vernooij

Backport Ted's patch.

254. By Jelmer Vernooij

Use subvertpy bindings in svn-fast-export.py.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed file 'exporters/svn-fast-export.py' => 'exporters/svn-fast-export-old.py'
2=== added file 'exporters/svn-fast-export.py'
3--- exporters/svn-fast-export.py 1970-01-01 00:00:00 +0000
4+++ exporters/svn-fast-export.py 2009-10-19 09:40:31 +0000
5@@ -0,0 +1,220 @@
6+#!/usr/bin/python
7+#
8+# svn-fast-export.py
9+# ----------
10+# Walk through each revision of a local Subversion repository and export it
11+# in a stream that git-fast-import can consume.
12+#
13+# Author: Chris Lee <clee@kde.org>
14+# License: MIT <http://www.opensource.org/licenses/mit-license.php>
15+#
16+# Adapted for subvertpy by Jelmer Vernooij <jelmer@samba.org>
17+
18+trunk_path = '/trunk/'
19+branches_path = '/branches/'
20+tags_path = '/tags/'
21+address = 'localhost'
22+
23+from cStringIO import StringIO
24+import sys, os.path
25+from optparse import OptionParser
26+import stat
27+from time import mktime, strptime
28+
29+from subvertpy.repos import PATH_CHANGE_DELETE, Repository
30+
31+ct_short = ['M', 'A', 'D', 'R', 'X']
32+
33+def dump_file_blob(root, stream, stream_length):
34+ sys.stdout.write("data %s\n" % stream_length)
35+ sys.stdout.flush()
36+ sys.stdout.write(stream.read())
37+ sys.stdout.write("\n")
38+
39+
40+class Matcher(object):
41+
42+ branch = None
43+
44+ def __init__(self, trunk_path):
45+ self.trunk_path = trunk_path
46+
47+ def branchname(self):
48+ return self.branch
49+
50+ def __str__(self):
51+ return super(Matcher, self).__str__() + ":" + self.trunk_path
52+
53+ @staticmethod
54+ def getMatcher(trunk_path):
55+ if trunk_path.startswith("regex:"):
56+ return RegexStringMatcher(trunk_path)
57+ else:
58+ return StaticStringMatcher(trunk_path)
59+
60+
61+class StaticStringMatcher(Matcher):
62+
63+ branch = "master"
64+
65+ def matches(self, path):
66+ return path.startswith(trunk_path)
67+
68+ def replace(self, path):
69+ return path.replace(self.trunk_path, '')
70+
71+
72+class RegexStringMatcher(Matcher):
73+
74+ def __init__(self, trunk_path):
75+ super(RegexStringMatcher, self).__init__(trunk_path)
76+ import re
77+ self.matcher = re.compile(self.trunk_path[len("regex:"):])
78+
79+ def matches(self, path):
80+ match = self.matcher.match(path)
81+ if match:
82+ self.branch = match.group(1)
83+ return True
84+ else:
85+ return False
86+
87+ def replace(self, path):
88+ return self.matcher.sub("\g<2>", path)
89+
90+MATCHER = None
91+
92+def export_revision(rev, fs):
93+ sys.stderr.write("Exporting revision %s... " % rev)
94+
95+ # Open a root object representing the youngest (HEAD) revision.
96+ root = fs.revision_root(rev)
97+
98+ # And the list of what changed in this revision.
99+ changes = root.paths_changed()
100+
101+ i = 1
102+ marks = {}
103+ file_changes = []
104+
105+ for path, (node_id, change_type, text_changed, prop_changed) in changes.iteritems():
106+ if root.is_dir(path):
107+ continue
108+ if not MATCHER.matches(path):
109+ # We don't handle branches. Or tags. Yet.
110+ pass
111+ else:
112+ if change_type == PATH_CHANGE_DELETE:
113+ file_changes.append("D %s" % MATCHER.replace(path))
114+ else:
115+ props = root.proplist(path)
116+ marks[i] = MATCHER.replace(path)
117+ if props.get("svn:special", ""):
118+ contents = root.file_content(path).read()
119+ if not contents.startswith("link "):
120+ sys.stderr.write("special file '%s' is not a symlink, ignoring...\n" % path)
121+ continue
122+ mode = stat.S_IFLNK
123+ stream = StringIO(contents[len("link "):])
124+ stream_length = len(stream.getvalue())
125+ else:
126+ if props.get("svn:executable", ""):
127+ mode = 0755
128+ else:
129+ mode = 0644
130+ stream_length = root.file_length(path)
131+ stream = root.file_content(path)
132+ file_changes.append("M %o :%s %s" % (mode, i, marks[i]))
133+ sys.stdout.write("blob\nmark :%s\n" % i)
134+ dump_file_blob(root, stream, stream_length)
135+ stream.close()
136+ i += 1
137+
138+ # Get the commit author and message
139+ props = fs.revision_proplist(rev)
140+
141+ # Do the recursive crawl.
142+ if props.has_key('svn:author'):
143+ author = "%s <%s@%s>" % (props['svn:author'], props['svn:author'], address)
144+ else:
145+ author = 'nobody <nobody@localhost>'
146+
147+ if len(file_changes) == 0:
148+ sys.stderr.write("skipping.\n")
149+ return
150+
151+ svndate = props['svn:date'][0:-8]
152+ commit_time = mktime(strptime(svndate, '%Y-%m-%dT%H:%M:%S'))
153+ sys.stdout.write("commit refs/heads/%s\n" % MATCHER.branchname())
154+ sys.stdout.write("committer %s %s -0000\n" % (author, int(commit_time)))
155+ sys.stdout.write("data %s\n" % len(props['svn:log']))
156+ sys.stdout.write(props['svn:log'])
157+ sys.stdout.write("\n")
158+ sys.stdout.write('\n'.join(file_changes))
159+ sys.stdout.write("\n\n")
160+ sys.stderr.write("done!\n")
161+
162+
163+def crawl_revisions(repos_path, first_rev=None, final_rev=None):
164+ """Open the repository at REPOS_PATH, and recursively crawl all its
165+ revisions."""
166+
167+ # Open the repository at REPOS_PATH, and get a reference to its
168+ # versioning filesystem.
169+ fs_obj = Repository(repos_path).fs()
170+
171+ # Query the current youngest revision.
172+ if first_rev is None:
173+ first_rev = 1
174+ if final_rev is None:
175+ final_rev = fs_obj.youngest_revision()
176+ for rev in xrange(first_rev, final_rev + 1):
177+ export_revision(rev, fs_obj)
178+
179+
180+if __name__ == '__main__':
181+ usage = '%prog [options] REPOS_PATH'
182+ parser = OptionParser()
183+ parser.set_usage(usage)
184+ parser.add_option('-f', '--final-rev', help='Final revision to import',
185+ dest='final_rev', metavar='FINAL_REV', type='int')
186+ parser.add_option('-r', '--first-rev', help='First revision to import',
187+ dest='first_rev', metavar='FIRST_REV', type='int')
188+ parser.add_option('-t', '--trunk-path', help="Path in repo to /trunk, may be `regex:/cvs/(trunk)/proj1/(.*)`\nFirst group is used as branchname, second to match files",
189+ dest='trunk_path', metavar='TRUNK_PATH')
190+ parser.add_option('-b', '--branches-path', help='Path in repo to /branches',
191+ dest='branches_path', metavar='BRANCHES_PATH')
192+ parser.add_option('-T', '--tags-path', help='Path in repo to /tags',
193+ dest='tags_path', metavar='TAGS_PATH')
194+ parser.add_option('-a', '--address', help='Domain to put on users for their mail address',
195+ dest='address', metavar='hostname', type='string')
196+ (options, args) = parser.parse_args()
197+
198+ if options.trunk_path != None:
199+ trunk_path = options.trunk_path
200+ if options.branches_path != None:
201+ branches_path = options.branches_path
202+ if options.tags_path != None:
203+ tags_path = options.tags_path
204+ if options.address != None:
205+ address = options.address
206+
207+ MATCHER = Matcher.getMatcher(trunk_path)
208+ sys.stderr.write("%s\n" % MATCHER)
209+ if len(args) != 1:
210+ parser.print_help()
211+ sys.exit(2)
212+
213+ # Canonicalize (enough for Subversion, at least) the repository path.
214+ repos_path = os.path.normpath(args[0])
215+ if repos_path == '.':
216+ repos_path = ''
217+
218+ try:
219+ import msvcrt
220+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
221+ except ImportError:
222+ pass
223+
224+ crawl_revisions(repos_path, first_rev=options.first_rev,
225+ final_rev=options.final_rev)

Subscribers

People subscribed via source and target branches

to all changes: