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