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)