Mercurial > hgsubversion
comparison wrappers.py @ 304:ce676eff002b
First merge, totally untested.
author | Dan Villiom Podlaski Christiansen <danchr@gmail.com> |
---|---|
date | Fri, 01 May 2009 10:28:59 +0200 |
parents | f8f9a2993705 |
children | 1d48d9a34c19 |
comparison
equal
deleted
inserted
replaced
303:f423a8780832 | 304:ce676eff002b |
---|---|
1 import os | |
2 | |
3 from hgext import rebase as hgrebase | |
4 | |
5 from mercurial import cmdutil as hgcmdutil | |
6 from mercurial import commands | |
7 from mercurial import patch | |
8 from mercurial import hg | |
9 from mercurial import util as hgutil | |
10 from mercurial import node | |
11 from mercurial import i18n | |
12 | |
13 from svn import core | |
14 from svn import delta | |
15 | |
16 import cmdutil | |
17 import hg_delta_editor | |
18 import stupid as stupidmod | |
19 import svnwrap | |
20 import util | |
21 | |
22 def parent(orig, ui, repo, *args, **opts): | |
23 """show Mercurial & Subversion parents of the working dir or revision | |
24 """ | |
25 if not opts.get('svn', False): | |
26 return orig(ui, repo, *args, **opts) | |
27 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
28 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
29 hge.revmap.iterkeys())) | |
30 ha = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes) | |
31 if ha.node() == node.nullid: | |
32 raise hgutil.Abort('No parent svn revision!') | |
33 displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) | |
34 displayer.show(ha) | |
35 return 0 | |
36 | |
37 | |
38 def outgoing(orig, ui, repo, dest=None, *args, **opts): | |
39 """show changesets not found in the Subversion repository | |
40 """ | |
41 svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default') | |
42 if not (cmdutil.issvnurl(svnurl) or opts.get('svn', False)): | |
43 return orig(ui, repo, dest, *args, **opts) | |
44 | |
45 # split off #rev; TODO implement --revision/#rev support | |
46 svnurl, revs, checkout = hg.parseurl(svnurl, opts.get('rev')) | |
47 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
48 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
49 hge.revmap.iterkeys())) | |
50 o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, | |
51 repo.parents()[0].node()) | |
52 if not (o_r and len(o_r)): | |
53 ui.status('no changes found\n') | |
54 return 0 | |
55 displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) | |
56 for node in reversed(o_r): | |
57 displayer.show(repo[node]) | |
58 | |
59 | |
60 def diff(orig, ui, repo, *args, **opts): | |
61 """show a diff of the most recent revision against its parent from svn | |
62 """ | |
63 if not opts.get('svn', False) or opts.get('change', None): | |
64 return orig(ui, repo, *args, **opts) | |
65 svn_commit_hashes = {} | |
66 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
67 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
68 hge.revmap.iterkeys())) | |
69 if not opts.get('rev', None): | |
70 parent = repo.parents()[0] | |
71 o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, | |
72 parent.node()) | |
73 if o_r: | |
74 parent = repo[o_r[-1]].parents()[0] | |
75 opts['rev'] = ['%s:.' % node.hex(parent.node()), ] | |
76 node1, node2 = hgcmdutil.revpair(repo, opts['rev']) | |
77 baserev, _junk = svn_commit_hashes.get(node1, (-1, 'junk', )) | |
78 newrev, _junk = svn_commit_hashes.get(node2, (-1, 'junk', )) | |
79 it = patch.diff(repo, node1, node2, | |
80 opts=patch.diffopts(ui, opts={'git': True, | |
81 'show_function': False, | |
82 'ignore_all_space': False, | |
83 'ignore_space_change': False, | |
84 'ignore_blank_lines': False, | |
85 'unified': True, | |
86 'text': False, | |
87 })) | |
88 ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev)) | |
89 | |
90 | |
91 def push(orig, ui, repo, dest=None, *args, **opts): | |
92 """push revisions starting at a specified head back to Subversion. | |
93 """ | |
94 opts.pop('svn', None) # unused in this case | |
95 svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default') | |
96 if not cmdutil.issvnurl(svnurl): | |
97 return orig(ui, repo, dest=dest, *args, **opts) | |
98 old_encoding = util.swap_out_encoding() | |
99 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
100 svnurl = util.normalize_url(svnurl) | |
101 # split of #rev; TODO: implement --rev/#rev support | |
102 svnurl, revs, checkout = hg.parseurl(svnurl, opts.get('rev')) | |
103 if svnurl != hge.url: | |
104 raise hgutil.Abort('wrong subversion url!') | |
105 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
106 hge.revmap.iterkeys())) | |
107 user, passwd = util.getuserpass(opts) | |
108 # Strategy: | |
109 # 1. Find all outgoing commits from this head | |
110 if len(repo.parents()) != 1: | |
111 ui.status('Cowardly refusing to push branch merge\n') | |
112 return 1 | |
113 workingrev = repo.parents()[0] | |
114 ui.status('searching for changes\n') | |
115 outgoing = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, workingrev.node()) | |
116 if not (outgoing and len(outgoing)): | |
117 ui.status('no changes found\n') | |
118 return 0 | |
119 while outgoing: | |
120 oldest = outgoing.pop(-1) | |
121 old_ctx = repo[oldest] | |
122 if len(old_ctx.parents()) != 1: | |
123 ui.status('Found a branch merge, this needs discussion and ' | |
124 'implementation.\n') | |
125 return 1 | |
126 base_n = old_ctx.parents()[0].node() | |
127 old_children = repo[base_n].children() | |
128 svnbranch = repo[base_n].branch() | |
129 oldtip = base_n | |
130 samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch | |
131 and c.node() in svn_commit_hashes] | |
132 while samebranchchildren: | |
133 oldtip = samebranchchildren[0].node() | |
134 samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch | |
135 and c.node() in svn_commit_hashes] | |
136 # 2. Commit oldest revision that needs to be pushed | |
137 base_revision = svn_commit_hashes[base_n][0] | |
138 try: | |
139 cmdutil.commit_from_rev(ui, repo, old_ctx, hge, svnurl, | |
140 base_revision, user, passwd) | |
141 except cmdutil.NoFilesException: | |
142 ui.warn("Could not push revision %s because it had no changes in svn.\n" % | |
143 old_ctx) | |
144 return 1 | |
145 # 3. Fetch revisions from svn | |
146 # TODO this probably should pass in the source explicitly | |
147 r = pull(None, ui, repo, svn=True, stupid=opts.get('svn_stupid', False), | |
148 username=user, password=passwd) | |
149 assert not r or r == 0 | |
150 # 4. Find the new head of the target branch | |
151 repo = hg.repository(ui, hge.path) | |
152 oldtipctx = repo[oldtip] | |
153 replacement = [c for c in oldtipctx.children() if c not in old_children | |
154 and c.branch() == oldtipctx.branch()] | |
155 assert len(replacement) == 1, 'Replacement node came back as: %r' % replacement | |
156 replacement = replacement[0] | |
157 # 5. Rebase all children of the currently-pushing rev to the new branch | |
158 heads = repo.heads(old_ctx.node()) | |
159 for needs_transplant in heads: | |
160 def extrafn(ctx, extra): | |
161 if ctx.node() == oldest: | |
162 return | |
163 extra['branch'] = ctx.branch() | |
164 rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn, | |
165 svnsourcerev=needs_transplant, **opts) | |
166 repo = hg.repository(ui, hge.path) | |
167 for child in repo[replacement.node()].children(): | |
168 rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) | |
169 if rebasesrc in outgoing: | |
170 while rebasesrc in outgoing: | |
171 rebsrcindex = outgoing.index(rebasesrc) | |
172 outgoing = (outgoing[0:rebsrcindex] + | |
173 [child.node(), ] + outgoing[rebsrcindex+1:]) | |
174 children = [c for c in child.children() if c.branch() == child.branch()] | |
175 if children: | |
176 child = children[0] | |
177 rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) | |
178 hge = hg_delta_editor.HgChangeReceiver(hge.path, ui_=ui) | |
179 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) | |
180 util.swap_out_encoding(old_encoding) | |
181 return 0 | |
182 | |
183 | |
184 def clone(orig, ui, source, dest=None, *args, **opts): | |
185 '''clone Subversion repository to a local Mercurial repository. | |
186 | |
187 If no destination directory name is specified, it defaults to the | |
188 basename of the source plus "-hg". | |
189 | |
190 You can specify multiple paths for the location of tags using comma | |
191 separated values. | |
192 ''' | |
193 svnurl = ui.expandpath(source) | |
194 if not cmdutil.issvnurl(svnurl): | |
195 return orig(ui, source=source, dest=dest, *args, **opts) | |
196 | |
197 if not dest: | |
198 dest = hg.defaultdest(hg.parseurl(source)[0]) + '-hg' | |
199 ui.status("Assuming destination %s\n" % dest) | |
200 | |
201 if os.path.exists(dest): | |
202 raise hgutil.Abort("destination '%s' already exists" % dest) | |
203 url = util.normalize_url(svnurl) | |
204 res = -1 | |
205 try: | |
206 try: | |
207 res = pull(None, ui, None, source=url, svn=None, | |
208 svn_stupid=opts.pop('svn_stupid', False), | |
209 create_new_dest=dest, **opts) | |
210 except core.SubversionException, e: | |
211 if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED: | |
212 raise hgutil.Abort('It appears svn does not trust the ssl cert for this site.\n' | |
213 'Please try running svn ls on that url first.') | |
214 raise | |
215 finally: | |
216 if os.path.exists(dest): | |
217 repo = hg.repository(ui, dest) | |
218 fp = repo.opener("hgrc", "w", text=True) | |
219 fp.write("[paths]\n") | |
220 # percent needs to be escaped for ConfigParser | |
221 fp.write("default = %(url)s\nsvn = %(url)s\n" % {'url': svnurl}) | |
222 fp.close() | |
223 if (res is None or res == 0) and not opts.get('noupdate', False): | |
224 # Split off #rev | |
225 url, revs, checkout = hg.parseurl(svnurl) | |
226 for test in (checkout, 'default', 'tip'): | |
227 try: | |
228 uprev = repo.lookup(test) | |
229 break | |
230 except: | |
231 continue | |
232 commands.update(ui, repo, uprev) | |
233 | |
234 return res | |
235 | |
236 | |
237 def pull(orig, ui, repo, source="default", *args, **opts): | |
238 """pull new revisions from Subversion | |
239 | |
240 Also takes svn, svn_stupid, and create_new_dest kwargs. | |
241 """ | |
242 svn = opts.pop('svn', None) | |
243 svn_stupid = opts.pop('svn_stupid', False) | |
244 create_new_dest = opts.pop('create_new_dest', False) | |
245 url = ((repo and repo.ui) or ui).expandpath(source) | |
246 if not (cmdutil.issvnurl(url) or svn or create_new_dest): | |
247 return orig(ui, repo, source=source, *args, **opts) | |
248 svn_url = url | |
249 svn_url = util.normalize_url(svn_url) | |
250 # Split off #rev; TODO: implement --rev/#rev support limiting the pulled/cloned revisions | |
251 svn_url, revs, checkout = hg.parseurl(svn_url, opts.get('rev')) | |
252 old_encoding = util.swap_out_encoding() | |
253 # TODO implement skipto support | |
254 skipto_rev = 0 | |
255 have_replay = not svn_stupid | |
256 if have_replay and not callable( | |
257 delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover | |
258 ui.status('You are using old Subversion SWIG bindings. Replay will not' | |
259 ' work until you upgrade to 1.5.0 or newer. Falling back to' | |
260 ' a slower method that may be buggier. Please upgrade, or' | |
261 ' contribute a patch to use the ctypes bindings instead' | |
262 ' of SWIG.\n') | |
263 have_replay = False | |
264 initializing_repo = False | |
265 user, passwd = util.getuserpass(opts) | |
266 svn = svnwrap.SubversionRepo(svn_url, user, passwd) | |
267 author_host = "@%s" % svn.uuid | |
268 tag_locations = ['tags', ] | |
269 authors = opts.pop('svn_authors', None) | |
270 filemap = opts.pop('svn_filemap', None) | |
271 if repo: | |
272 hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo, | |
273 subdir=svn.subdir, | |
274 author_host=author_host, | |
275 tag_locations=tag_locations, | |
276 authors=authors, | |
277 filemap=filemap) | |
278 else: | |
279 hg_editor = hg_delta_editor.HgChangeReceiver(ui_=ui, | |
280 path=create_new_dest, | |
281 subdir=svn.subdir, | |
282 author_host=author_host, | |
283 tag_locations=tag_locations, | |
284 authors=authors, | |
285 filemap=filemap) | |
286 if os.path.exists(hg_editor.uuid_file): | |
287 uuid = open(hg_editor.uuid_file).read() | |
288 assert uuid == svn.uuid | |
289 start = hg_editor.last_known_revision() | |
290 else: | |
291 open(hg_editor.uuid_file, 'w').write(svn.uuid) | |
292 open(hg_editor.svn_url_file, 'w').write(svn_url) | |
293 initializing_repo = True | |
294 start = skipto_rev | |
295 | |
296 if initializing_repo and start > 0: | |
297 raise hgutil.Abort('Revision skipping at repository initialization ' | |
298 'remains unimplemented.') | |
299 | |
300 revisions = 0 | |
301 if not initializing_repo: | |
302 oldheads = len(repo.changelog.heads()) | |
303 | |
304 # start converting revisions | |
305 for r in svn.revisions(start=start): | |
306 valid = True | |
307 hg_editor.update_branch_tag_map_for_rev(r) | |
308 for p in r.paths: | |
309 if hg_editor._is_path_valid(p): | |
310 valid = True | |
311 break | |
312 if valid: | |
313 # got a 502? Try more than once! | |
314 tries = 0 | |
315 converted = False | |
316 while not converted: | |
317 try: | |
318 util.describe_revision(ui, r) | |
319 if have_replay: | |
320 try: | |
321 cmdutil.replay_convert_rev(hg_editor, svn, r) | |
322 except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover | |
323 ui.status('%s\n' % e.message) | |
324 stupidmod.print_your_svn_is_old_message(ui) | |
325 have_replay = False | |
326 stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) | |
327 else: | |
328 stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) | |
329 converted = True | |
330 except core.SubversionException, e: #pragma: no cover | |
331 if (e.apr_err == core.SVN_ERR_RA_DAV_REQUEST_FAILED | |
332 and '502' in str(e) | |
333 and tries < 3): | |
334 tries += 1 | |
335 ui.status('Got a 502, retrying (%s)\n' % tries) | |
336 else: | |
337 raise hgutil.Abort(*e.args) | |
338 revisions += 1 | |
339 util.swap_out_encoding(old_encoding) | |
340 | |
341 if revisions == 0: | |
342 ui.status(i18n._("no changes found\n")) | |
343 return | |
344 else: | |
345 ui.status("added %d svn revisions\n" % revisions) | |
346 if not initializing_repo: | |
347 newheads = len(repo.changelog.heads()) | |
348 # postincoming needs to know if heads were added or removed | |
349 # calculation based on mercurial.localrepo.addchangegroup | |
350 # 0 means no changes, 1 no new heads, > 1 new heads, < 0 heads removed | |
351 modheads = newheads - oldheads + (newheads < oldheads and -1 or 1) | |
352 commands.postincoming(ui, repo, modheads, opts.get('update'), checkout) | |
353 | |
354 | |
355 def rebase(orig, ui, repo, **opts): | |
356 """rebase current unpushed revisions onto the Subversion head | |
357 | |
358 This moves a line of development from making its own head to the top of | |
359 Subversion development, linearizing the changes. In order to make sure you | |
360 rebase on top of the current top of Subversion work, you should probably run | |
361 'hg svn pull' before running this. | |
362 | |
363 Also looks for svnextrafn and svnsourcerev in **opts. | |
364 """ | |
365 if not opts.get('svn', False): | |
366 return orig(ui, repo, **opts) | |
367 def extrafn2(ctx, extra): | |
368 """defined here so we can add things easily. | |
369 """ | |
370 extra['branch'] = ctx.branch() | |
371 extrafn = opts.get('svnextrafn', extrafn2) | |
372 sourcerev = opts.get('svnsourcerev', repo.parents()[0].node()) | |
373 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
374 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
375 hge.revmap.iterkeys())) | |
376 o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, sourcerev=sourcerev) | |
377 if not o_r: | |
378 ui.status('Nothing to rebase!\n') | |
379 return 0 | |
380 if len(repo[sourcerev].children()): | |
381 ui.status('Refusing to rebase non-head commit like a coward\n') | |
382 return 0 | |
383 parent_rev = repo[o_r[-1]].parents()[0] | |
384 target_rev = parent_rev | |
385 p_n = parent_rev.node() | |
386 exhausted_choices = False | |
387 while target_rev.children() and not exhausted_choices: | |
388 for c in target_rev.children(): | |
389 exhausted_choices = True | |
390 n = c.node() | |
391 if (n in svn_commit_hashes and | |
392 svn_commit_hashes[n][1] == svn_commit_hashes[p_n][1]): | |
393 target_rev = c | |
394 exhausted_choices = False | |
395 break | |
396 if parent_rev == target_rev: | |
397 ui.status('Already up to date!\n') | |
398 return 0 | |
399 return orig(ui, repo, dest=node.hex(target_rev.node()), | |
400 base=node.hex(sourcerev), | |
401 extrafn=extrafn) |