Mercurial > hgsubversion
comparison wrappers.py @ 257:ffccf0080e54
Move wrappers for hg commands to their own module.
| author | Augie Fackler <durin42@gmail.com> |
|---|---|
| date | Fri, 10 Apr 2009 22:38:29 -0500 |
| parents | |
| children | 87dc4d0dd048 |
comparison
equal
deleted
inserted
replaced
| 256:7932d098cb5f | 257:ffccf0080e54 |
|---|---|
| 1 import os | |
| 2 | |
| 3 from mercurial import cmdutil as hgcmdutil | |
| 4 from mercurial import commands | |
| 5 from mercurial import patch | |
| 6 from mercurial import hg | |
| 7 from mercurial import util as hgutil | |
| 8 from mercurial import node | |
| 9 | |
| 10 from svn import core | |
| 11 from svn import delta | |
| 12 | |
| 13 import cmdutil | |
| 14 import hg_delta_editor | |
| 15 import stupid as stupidmod | |
| 16 import svnwrap | |
| 17 import util | |
| 18 import utility_commands | |
| 19 | |
| 20 def parent(orig, ui, repo, *args, **opts): | |
| 21 """show Mercurial & Subversion parents of the working dir or revision | |
| 22 """ | |
| 23 if not opts.get('svn', False): | |
| 24 return orig(ui, repo, *args, **opts) | |
| 25 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
| 26 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
| 27 hge.revmap.iterkeys())) | |
| 28 ha = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes) | |
| 29 if ha.node() == node.nullid: | |
| 30 raise hgutil.Abort('No parent svn revision!') | |
| 31 displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) | |
| 32 displayer.show(ha) | |
| 33 return 0 | |
| 34 | |
| 35 | |
| 36 def outgoing(orig, ui, repo, dest=None, *args, **opts): | |
| 37 """show changesets not found in the Subversion repository | |
| 38 """ | |
| 39 svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default') | |
| 40 if not (cmdutil.issvnurl(svnurl) or opts.get('svn', False)): | |
| 41 return orig(ui, repo, dest, *args, **opts) | |
| 42 | |
| 43 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
| 44 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
| 45 hge.revmap.iterkeys())) | |
| 46 o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, | |
| 47 repo.parents()[0].node()) | |
| 48 if not (o_r and len(o_r)): | |
| 49 ui.status('no changes found\n') | |
| 50 return 0 | |
| 51 displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) | |
| 52 for node in reversed(o_r): | |
| 53 displayer.show(repo[node]) | |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 def diff(orig, ui, repo, *args, **opts): | |
| 59 """show a diff of the most recent revision against its parent from svn | |
| 60 """ | |
| 61 if not opts.get('svn', False) or opts.get('change', None): | |
| 62 return orig(ui, repo, *args, **opts) | |
| 63 svn_commit_hashes = {} | |
| 64 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
| 65 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
| 66 hge.revmap.iterkeys())) | |
| 67 if not opts.get('rev', None): | |
| 68 parent = repo.parents()[0] | |
| 69 o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, | |
| 70 parent.node()) | |
| 71 if o_r: | |
| 72 parent = repo[o_r[-1]].parents()[0] | |
| 73 opts['rev'] = ['%s:.' % node.hex(parent.node()), ] | |
| 74 node1, node2 = hgcmdutil.revpair(repo, opts['rev']) | |
| 75 baserev, _junk = svn_commit_hashes.get(node1, (-1, 'junk', )) | |
| 76 newrev, _junk = svn_commit_hashes.get(node2, (-1, 'junk', )) | |
| 77 it = patch.diff(repo, node1, node2, | |
| 78 opts=patch.diffopts(ui, opts={'git': True, | |
| 79 'show_function': False, | |
| 80 'ignore_all_space': False, | |
| 81 'ignore_space_change': False, | |
| 82 'ignore_blank_lines': False, | |
| 83 'unified': True, | |
| 84 'text': False, | |
| 85 })) | |
| 86 ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev)) | |
| 87 | |
| 88 | |
| 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 svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default') | |
| 95 if not cmdutil.issvnurl(svnurl): | |
| 96 orig(ui, repo, dest=dest, *args, **opts) | |
| 97 old_encoding = util.swap_out_encoding() | |
| 98 hge = hg_delta_editor.HgChangeReceiver(repo=repo) | |
| 99 svnurl = util.normalize_url(svnurl) | |
| 100 if svnurl != hge.url: | |
| 101 raise hgutil.Abort('wrong subversion url!') | |
| 102 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), | |
| 103 hge.revmap.iterkeys())) | |
| 104 user = opts.get('username', hgutil.getuser()) | |
| 105 passwd = opts.get('password', '') | |
| 106 | |
| 107 # Strategy: | |
| 108 # 1. Find all outgoing commits from this head | |
| 109 if len(repo.parents()) != 1: | |
| 110 ui.status('Cowardly refusing to push branch merge') | |
| 111 return 1 | |
| 112 workingrev = repo.parents()[0] | |
| 113 outgoing = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, workingrev.node()) | |
| 114 if not (outgoing and len(outgoing)): | |
| 115 ui.status('No revisions to push.') | |
| 116 return 0 | |
| 117 while outgoing: | |
| 118 oldest = outgoing.pop(-1) | |
| 119 old_ctx = repo[oldest] | |
| 120 if len(old_ctx.parents()) != 1: | |
| 121 ui.status('Found a branch merge, this needs discussion and ' | |
| 122 'implementation.') | |
| 123 return 1 | |
| 124 base_n = old_ctx.parents()[0].node() | |
| 125 old_children = repo[base_n].children() | |
| 126 svnbranch = repo[base_n].branch() | |
| 127 oldtip = base_n | |
| 128 samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch | |
| 129 and c.node() in svn_commit_hashes] | |
| 130 while samebranchchildren: | |
| 131 oldtip = samebranchchildren[0].node() | |
| 132 samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch | |
| 133 and c.node() in svn_commit_hashes] | |
| 134 # 2. Commit oldest revision that needs to be pushed | |
| 135 base_revision = svn_commit_hashes[base_n][0] | |
| 136 try: | |
| 137 cmdutil.commit_from_rev(ui, repo, old_ctx, hge, svnurl, | |
| 138 base_revision, user, passwd) | |
| 139 except cmdutil.NoFilesException: | |
| 140 ui.warn("Could not push revision %s because it had no changes in svn.\n" % | |
| 141 old_ctx) | |
| 142 return 1 | |
| 143 # 3. Fetch revisions from svn | |
| 144 r = pull(None, ui, repo, True, stupid=opts.get('svn_stupid', False), | |
| 145 username=user, password=passwd) | |
| 146 assert not r or r == 0 | |
| 147 # 4. Find the new head of the target branch | |
| 148 repo = hg.repository(ui, hge.path) | |
| 149 oldtipctx = repo[oldtip] | |
| 150 replacement = [c for c in oldtipctx.children() if c not in old_children | |
| 151 and c.branch() == oldtipctx.branch()] | |
| 152 assert len(replacement) == 1, 'Replacement node came back as: %r' % replacement | |
| 153 replacement = replacement[0] | |
| 154 # 5. Rebase all children of the currently-pushing rev to the new branch | |
| 155 heads = repo.heads(old_ctx.node()) | |
| 156 for needs_transplant in heads: | |
| 157 def extrafn(ctx, extra): | |
| 158 if ctx.node() == oldest: | |
| 159 return | |
| 160 extra['branch'] = ctx.branch() | |
| 161 utility_commands.rebase(ui, repo, extrafn=extrafn, | |
| 162 sourcerev=needs_transplant, **opts) | |
| 163 repo = hg.repository(ui, hge.path) | |
| 164 for child in repo[replacement.node()].children(): | |
| 165 rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) | |
| 166 if rebasesrc in outgoing: | |
| 167 while rebasesrc in outgoing: | |
| 168 rebsrcindex = outgoing.index(rebasesrc) | |
| 169 outgoing = (outgoing[0:rebsrcindex] + | |
| 170 [child.node(), ] + outgoing[rebsrcindex+1:]) | |
| 171 children = [c for c in child.children() if c.branch() == child.branch()] | |
| 172 if children: | |
| 173 child = children[0] | |
| 174 rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) | |
| 175 hge = hg_delta_editor.HgChangeReceiver(hge.path, ui_=ui) | |
| 176 svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) | |
| 177 util.swap_out_encoding(old_encoding) | |
| 178 return 0 | |
| 179 | |
| 180 | |
| 181 | |
| 182 def clone(orig, ui, source, dest=None, *args, **opts): | |
| 183 '''clone Subversion repository to a local Mercurial repository. | |
| 184 | |
| 185 If no destination directory name is specified, it defaults to the | |
| 186 basename of the source plus "-hg". | |
| 187 | |
| 188 You can specify multiple paths for the location of tags using comma | |
| 189 separated values. | |
| 190 ''' | |
| 191 svnurl = ui.expandpath(source) | |
| 192 if not cmdutil.issvnurl(svnurl): | |
| 193 orig(ui, source=source, dest=dest, *args, **opts) | |
| 194 | |
| 195 if not dest: | |
| 196 dest = hg.defaultdest(source) + '-hg' | |
| 197 ui.status("Assuming destination %s\n" % dest) | |
| 198 | |
| 199 if os.path.exists(dest): | |
| 200 raise hgutil.Abort("destination '%s' already exists" % dest) | |
| 201 url = util.normalize_url(svnurl) | |
| 202 res = -1 | |
| 203 try: | |
| 204 try: | |
| 205 res = pull(None, ui, None, True, opts.pop('svn_stupid', False), | |
| 206 source=url, create_new_dest=dest, **opts) | |
| 207 except core.SubversionException, e: | |
| 208 if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED: | |
| 209 raise hgutil.Abort('It appears svn does not trust the ssl cert for this site.\n' | |
| 210 'Please try running svn ls on that url first.') | |
| 211 raise | |
| 212 finally: | |
| 213 if os.path.exists(dest): | |
| 214 repo = hg.repository(ui, dest) | |
| 215 fp = repo.opener("hgrc", "w", text=True) | |
| 216 fp.write("[paths]\n") | |
| 217 # percent needs to be escaped for ConfigParser | |
| 218 fp.write("default = %(url)s\nsvn = %(url)s\n" % {'url': svnurl.replace('%', '%%')}) | |
| 219 fp.close() | |
| 220 if (res is None or res == 0) and not opts.get('noupdate', False): | |
| 221 commands.update(ui, repo, repo['tip'].node()) | |
| 222 | |
| 223 return res | |
| 224 | |
| 225 | |
| 226 def pull(orig, ui, repo, svn=None, svn_stupid=False, source="default", create_new_dest=False, | |
| 227 *args, **opts): | |
| 228 """pull new revisions from Subversion | |
| 229 """ | |
| 230 url = ((repo and repo.ui) or ui).expandpath(source) | |
| 231 if not (cmdutil.issvnurl(url) or svn or create_new_dest): | |
| 232 orig(ui, repo, source=source, *args, **opts) | |
| 233 svn_url = url | |
| 234 svn_url = util.normalize_url(svn_url) | |
| 235 old_encoding = util.swap_out_encoding() | |
| 236 # TODO implement skipto support | |
| 237 skipto_rev = 0 | |
| 238 have_replay = not svn_stupid | |
| 239 if have_replay and not callable( | |
| 240 delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover | |
| 241 ui.status('You are using old Subversion SWIG bindings. Replay will not' | |
| 242 ' work until you upgrade to 1.5.0 or newer. Falling back to' | |
| 243 ' a slower method that may be buggier. Please upgrade, or' | |
| 244 ' contribute a patch to use the ctypes bindings instead' | |
| 245 ' of SWIG.\n') | |
| 246 have_replay = False | |
| 247 initializing_repo = False | |
| 248 user = opts.get('username', hgutil.getuser()) | |
| 249 passwd = opts.get('password', '') | |
| 250 svn = svnwrap.SubversionRepo(svn_url, user, passwd) | |
| 251 author_host = "@%s" % svn.uuid | |
| 252 # TODO these should be configurable again, but I'm torn on how. | |
| 253 # Maybe this should be configured in .hg/hgrc for each repo? Seems vaguely reasonable. | |
| 254 tag_locations = ['tags', ] | |
| 255 authors = opts.pop('svn_authors', None) | |
| 256 filemap = opts.pop('svn_filemap', None) | |
| 257 if repo: | |
| 258 hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo, | |
| 259 subdir=svn.subdir, | |
| 260 author_host=author_host, | |
| 261 tag_locations=tag_locations, | |
| 262 authors=authors, | |
| 263 filemap=filemap) | |
| 264 else: | |
| 265 hg_editor = hg_delta_editor.HgChangeReceiver(ui_=ui, | |
| 266 path=create_new_dest, | |
| 267 subdir=svn.subdir, | |
| 268 author_host=author_host, | |
| 269 tag_locations=tag_locations, | |
| 270 authors=authors, | |
| 271 filemap=filemap) | |
| 272 if os.path.exists(hg_editor.uuid_file): | |
| 273 uuid = open(hg_editor.uuid_file).read() | |
| 274 assert uuid == svn.uuid | |
| 275 start = hg_editor.last_known_revision() | |
| 276 else: | |
| 277 open(hg_editor.uuid_file, 'w').write(svn.uuid) | |
| 278 open(hg_editor.svn_url_file, 'w').write(svn_url) | |
| 279 initializing_repo = True | |
| 280 start = skipto_rev | |
| 281 | |
| 282 if initializing_repo and start > 0: | |
| 283 raise hgutil.Abort('Revision skipping at repository initialization ' | |
| 284 'remains unimplemented.') | |
| 285 | |
| 286 # start converting revisions | |
| 287 for r in svn.revisions(start=start): | |
| 288 valid = True | |
| 289 hg_editor.update_branch_tag_map_for_rev(r) | |
| 290 for p in r.paths: | |
| 291 if hg_editor._is_path_valid(p): | |
| 292 valid = True | |
| 293 break | |
| 294 if valid: | |
| 295 # got a 502? Try more than once! | |
| 296 tries = 0 | |
| 297 converted = False | |
| 298 while not converted: | |
| 299 try: | |
| 300 util.describe_revision(ui, r) | |
| 301 if have_replay: | |
| 302 try: | |
| 303 cmdutil.replay_convert_rev(hg_editor, svn, r) | |
| 304 except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover | |
| 305 ui.status('%s\n' % e.message) | |
| 306 stupidmod.print_your_svn_is_old_message(ui) | |
| 307 have_replay = False | |
| 308 stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) | |
| 309 else: | |
| 310 stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) | |
| 311 converted = True | |
| 312 except core.SubversionException, e: #pragma: no cover | |
| 313 if (e.apr_err == core.SVN_ERR_RA_DAV_REQUEST_FAILED | |
| 314 and '502' in str(e) | |
| 315 and tries < 3): | |
| 316 tries += 1 | |
| 317 ui.status('Got a 502, retrying (%s)\n' % tries) | |
| 318 else: | |
| 319 raise hgutil.Abort(*e.args) | |
| 320 util.swap_out_encoding(old_encoding) |
