Mercurial > hgsubversion
comparison fetch_command.py @ 127:7e45bcf52b64
fetch_command: patch files in memory in stupid mode
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Wed, 10 Dec 2008 11:03:20 -0600 |
parents | ea65fe2b0856 |
children | bab5bcbbb3dc |
comparison
equal
deleted
inserted
replaced
126:24a64fb0e74b | 127:7e45bcf52b64 |
---|---|
167 property_special_removed_re = re.compile(r'''Property changes on: ([^\n]*) | 167 property_special_removed_re = re.compile(r'''Property changes on: ([^\n]*) |
168 _* | 168 _* |
169 (?:Deleted|Name): svn:special | 169 (?:Deleted|Name): svn:special |
170 \-''') | 170 \-''') |
171 | 171 |
172 def stupid_diff_branchrev(ui, svn, hg_editor, branch, r, parentctx, tempdir): | 172 def mempatchproxy(parentctx, files): |
173 # Avoid circular references patch.patchfile -> mempatch | |
174 patchfile = patch.patchfile | |
175 | |
176 class mempatch(patchfile): | |
177 def __init__(self, ui, fname, opener, missing=False): | |
178 patchfile.__init__(self, ui, fname, None, False) | |
179 | |
180 def readlines(self, fname): | |
181 if fname not in parentctx: | |
182 raise IOError('Cannot find %r to patch' % fname) | |
183 return cStringIO.StringIO(parentctx[fname].data()).readlines() | |
184 | |
185 def writelines(self, fname, lines): | |
186 files[fname] = ''.join(lines) | |
187 | |
188 def unlink(self, fname): | |
189 files[fname] = None | |
190 | |
191 return mempatch | |
192 | |
193 def stupid_diff_branchrev(ui, svn, hg_editor, branch, r, parentctx): | |
173 """Extract all 'branch' content at a given revision. | 194 """Extract all 'branch' content at a given revision. |
174 | 195 |
175 Return a tuple (files, filectxfn) where 'files' is the list of all files | 196 Return a tuple (files, filectxfn) where 'files' is the list of all files |
176 in the branch at the given revision, and 'filectxfn' is a memctx compatible | 197 in the branch at the given revision, and 'filectxfn' is a memctx compatible |
177 callable to retrieve individual file information. Raise BadPatchApply upon | 198 callable to retrieve individual file information. Raise BadPatchApply upon |
182 return 'trunk' | 203 return 'trunk' |
183 return 'branches/' + b | 204 return 'branches/' + b |
184 | 205 |
185 parent_rev, br_p = hg_editor.get_parent_svn_branch_and_rev(r.revnum, branch) | 206 parent_rev, br_p = hg_editor.get_parent_svn_branch_and_rev(r.revnum, branch) |
186 diff_path = make_diff_path(branch) | 207 diff_path = make_diff_path(branch) |
187 files_touched = set() | |
188 try: | 208 try: |
189 if br_p == branch: | 209 if br_p == branch: |
190 # letting patch handle binaries sounded | 210 # letting patch handle binaries sounded |
191 # cool, but it breaks patch in sad ways | 211 # cool, but it breaks patch in sad ways |
192 d = svn.get_unified_diff(diff_path, r.revnum, deleted=False, | 212 d = svn.get_unified_diff(diff_path, r.revnum, deleted=False, |
202 raise BadPatchApply('subversion diffing code is not supported') | 222 raise BadPatchApply('subversion diffing code is not supported') |
203 except core.SubversionException, e: | 223 except core.SubversionException, e: |
204 if (hasattr(e, 'apr_err') and e.apr_err != 160013): | 224 if (hasattr(e, 'apr_err') and e.apr_err != 160013): |
205 raise | 225 raise |
206 raise BadPatchApply('previous revision does not exist') | 226 raise BadPatchApply('previous revision does not exist') |
207 opener = merc_util.opener(tempdir) | 227 files_data = {} |
208 for m in binary_file_re.findall(d): | 228 for m in binary_file_re.findall(d): |
209 # we have to pull each binary file by hand as a fulltext, | 229 # we have to pull each binary file by hand as a fulltext, |
210 # which sucks but we've got no choice | 230 # which sucks but we've got no choice |
211 files_touched.add(m) | |
212 try: | 231 try: |
213 f = opener(m, 'w') | 232 files_data[m] = svn.get_file(diff_path+'/'+m, r.revnum)[0] |
214 f.write(svn.get_file(diff_path+'/'+m, r.revnum)[0]) | |
215 f.close() | |
216 except IOError: | 233 except IOError: |
217 pass | 234 files_data[m] = None |
218 d2 = empty_file_patch_wont_make_re.sub('', d) | 235 d2 = empty_file_patch_wont_make_re.sub('', d) |
219 d2 = property_exec_set_re.sub('', d2) | 236 d2 = property_exec_set_re.sub('', d2) |
220 d2 = property_exec_removed_re.sub('', d2) | 237 d2 = property_exec_removed_re.sub('', d2) |
221 for f in any_file_re.findall(d): | 238 for f in any_file_re.findall(d): |
222 if f in files_touched: | 239 if f in files_data: |
223 # this check is here because modified binary files will get | 240 continue |
224 # created before here. | 241 # Here we ensure that all files, including the new empty ones |
225 continue | 242 # will be marked with proper data. |
226 files_touched.add(f) | 243 # TODO: load file data when necessary |
227 data = '' | 244 files_data[f] = '' |
228 if f in parentctx: | 245 if f in parentctx: |
229 data = parentctx[f].data() | 246 files_data[f] = parentctx[f].data() |
230 fp = opener(f, 'w') | |
231 fp.write(data) | |
232 fp.close() | |
233 if d2.strip() and len(re.findall('\n[-+]', d2.strip())) > 0: | 247 if d2.strip() and len(re.findall('\n[-+]', d2.strip())) > 0: |
234 old_cwd = os.getcwd() | |
235 os.chdir(tempdir) | |
236 changed = {} | |
237 try: | 248 try: |
238 patch_st = patch.applydiff(ui, cStringIO.StringIO(d2), | 249 oldpatchfile = patch.patchfile |
239 changed, strip=0) | 250 patch.patchfile = mempatchproxy(parentctx, files_data) |
251 try: | |
252 # We can safely ignore the changed list since we are | |
253 # handling non-git patches. Touched files are known | |
254 # by our memory patcher. | |
255 patch_st = patch.applydiff(ui, cStringIO.StringIO(d2), | |
256 {}, strip=0) | |
257 finally: | |
258 patch.patchfile = oldpatchfile | |
240 except patch.PatchError: | 259 except patch.PatchError: |
241 # TODO: this happens if the svn server has the wrong mime | 260 # TODO: this happens if the svn server has the wrong mime |
242 # type stored and doesn't know a file is binary. It would | 261 # type stored and doesn't know a file is binary. It would |
243 # be better to do one file at a time and only do a | 262 # be better to do one file at a time and only do a |
244 # full fetch on files that had problems. | 263 # full fetch on files that had problems. |
245 os.chdir(old_cwd) | |
246 raise BadPatchApply('patching failed') | 264 raise BadPatchApply('patching failed') |
247 for x in changed.iterkeys(): | 265 for x in files_data.iterkeys(): |
248 ui.status('M %s\n' % x) | 266 ui.status('M %s\n' % x) |
249 files_touched.add(x) | |
250 os.chdir(old_cwd) | |
251 # if this patch didn't apply right, fall back to exporting the | 267 # if this patch didn't apply right, fall back to exporting the |
252 # entire rev. | 268 # entire rev. |
253 if patch_st == -1: | 269 if patch_st == -1: |
254 for fn in files_touched: | 270 for fn in files_data: |
255 if 'l' in parentctx.flags(fn): | 271 if 'l' in parentctx.flags(fn): |
256 # I think this might be an underlying bug in svn - | 272 # I think this might be an underlying bug in svn - |
257 # I get diffs of deleted symlinks even though I | 273 # I get diffs of deleted symlinks even though I |
258 # specifically said no deletes above. | 274 # specifically said no deletes above. |
259 raise BadPatchApply('deleted symlinked prevent patching') | 275 raise BadPatchApply('deleted symlinked prevent patching') |
266 raise BadPatchApply('patching succeeded with fuzz') | 282 raise BadPatchApply('patching succeeded with fuzz') |
267 else: | 283 else: |
268 ui.status('Not using patch for %s, diff had no hunks.\n' % | 284 ui.status('Not using patch for %s, diff had no hunks.\n' % |
269 r.revnum) | 285 r.revnum) |
270 | 286 |
271 # we create the files if they don't exist here because we know | |
272 # that we'll never have diff info for a deleted file, so if the | |
273 # property is set, we should force the file to exist no matter what. | |
274 exec_files = {} | 287 exec_files = {} |
275 for m in property_exec_removed_re.findall(d): | 288 for m in property_exec_removed_re.findall(d): |
276 exec_files[m] = False | 289 exec_files[m] = False |
277 for m in property_exec_set_re.findall(d): | 290 for m in property_exec_set_re.findall(d): |
278 exec_files[m] = True | 291 exec_files[m] = True |
279 for m in exec_files: | 292 for m in exec_files: |
280 files_touched.add(m) | 293 if m in files_data: |
281 f = os.path.join(tempdir, m) | 294 continue |
282 if not os.path.exists(f): | 295 data = '' |
283 data = '' | 296 if m in parentctx: |
284 if m in parentctx: | 297 data = parentctx[m].data() |
285 data = parentctx[m].data() | 298 files_data[m] = data |
286 fp = opener(m, 'w') | |
287 fp.write(data) | |
288 fp.close() | |
289 link_files = {} | 299 link_files = {} |
290 for m in property_special_set_re.findall(d): | 300 for m in property_special_set_re.findall(d): |
291 # TODO(augie) when a symlink is removed, patching will fail. | 301 # TODO(augie) when a symlink is removed, patching will fail. |
292 # We're seeing that above - there's gotta be a better | 302 # We're seeing that above - there's gotta be a better |
293 # workaround than just bailing like that. | 303 # workaround than just bailing like that. |
294 path = os.path.join(tempdir, m) | 304 assert m in files_data |
295 assert os.path.exists(path) | 305 files_data[m] = files_data[m][len('link '):] |
296 link_path = open(path).read() | 306 link_files[m] = True |
297 link_path = link_path[len('link '):] | 307 |
298 os.remove(path) | |
299 link_files[m] = link_path | |
300 files_touched.add(m) | |
301 | |
302 deleted_files = set() | |
303 for p in r.paths: | 308 for p in r.paths: |
304 if p.startswith(diff_path) and r.paths[p].action == 'D': | 309 if p.startswith(diff_path) and r.paths[p].action == 'D': |
305 p2 = p[len(diff_path)+1:].strip('/') | 310 p2 = p[len(diff_path)+1:].strip('/') |
306 if p2 in parentctx: | 311 if p2 in parentctx: |
307 deleted_files.add(p2) | 312 files_data[p2] = None |
308 continue | 313 continue |
309 # If this isn't in the parent ctx, it must've been a dir | 314 # If this isn't in the parent ctx, it must've been a dir |
310 deleted_files.update([f for f in parentctx if f.startswith(p2 + '/')]) | 315 files_data.update([(f, None) for f in parentctx if f.startswith(p2 + '/')]) |
311 files_touched.update(deleted_files) | 316 |
312 | 317 copies = getcopies(svn, hg_editor, branch, diff_path, r, files_data, |
313 copies = getcopies(svn, hg_editor, branch, diff_path, r, files_touched, | |
314 parentctx) | 318 parentctx) |
315 | 319 |
316 def filectxfn(repo, memctx, path): | 320 def filectxfn(repo, memctx, path): |
317 if path in deleted_files: | 321 data = files_data[path] |
322 if data is None: | |
318 raise IOError() | 323 raise IOError() |
319 if path in link_files: | 324 if path in link_files: |
320 return context.memfilectx(path=path, data=link_files[path], | 325 return context.memfilectx(path=path, data=data, |
321 islink=True, isexec=False, | 326 islink=True, isexec=False, |
322 copied=False) | 327 copied=False) |
323 data = opener(path).read() | |
324 exe = exec_files.get(path, 'x' in parentctx.flags(path)) | 328 exe = exec_files.get(path, 'x' in parentctx.flags(path)) |
325 copied = copies.get(path) | 329 copied = copies.get(path) |
326 return context.memfilectx(path=path, data=data, islink=False, | 330 return context.memfilectx(path=path, data=data, islink=False, |
327 isexec=exe, copied=copied) | 331 isexec=exe, copied=copied) |
328 | 332 |
329 return list(files_touched), filectxfn | 333 return list(files_data), filectxfn |
330 | 334 |
331 def makecopyfinder(r, branchpath, rootdir): | 335 def makecopyfinder(r, branchpath, rootdir): |
332 """Return a function detecting copies. | 336 """Return a function detecting copies. |
333 | 337 |
334 Returned copyfinder(path) returns None if no copy information can | 338 Returned copyfinder(path) returns None if no copy information can |
468 return files, filectxfn | 472 return files, filectxfn |
469 | 473 |
470 def stupid_svn_server_pull_rev(ui, svn, hg_editor, r): | 474 def stupid_svn_server_pull_rev(ui, svn, hg_editor, r): |
471 # this server fails at replay | 475 # this server fails at replay |
472 branches = hg_editor.branches_in_paths(r.paths) | 476 branches = hg_editor.branches_in_paths(r.paths) |
473 temp_location = os.path.join(hg_editor.path, '.hg', 'svn', 'temp') | |
474 if not os.path.exists(temp_location): | |
475 os.makedirs(temp_location) | |
476 for b in branches: | 477 for b in branches: |
477 our_tempdir = tempfile.mkdtemp('svn_fetch_temp', dir=temp_location) | |
478 parentctx = hg_editor.repo[hg_editor.get_parent_revision(r.revnum, b)] | 478 parentctx = hg_editor.repo[hg_editor.get_parent_revision(r.revnum, b)] |
479 kind = svn.checkpath(branches[b], r.revnum) | 479 kind = svn.checkpath(branches[b], r.revnum) |
480 if kind != 'd': | 480 if kind != 'd': |
481 # Branch does not exist at this revision. Get parent revision and | 481 # Branch does not exist at this revision. Get parent revision and |
482 # remove everything. | 482 # remove everything. |
484 def filectxfn(repo, memctx, path): | 484 def filectxfn(repo, memctx, path): |
485 raise IOError() | 485 raise IOError() |
486 else: | 486 else: |
487 try: | 487 try: |
488 files_touched, filectxfn = stupid_diff_branchrev( | 488 files_touched, filectxfn = stupid_diff_branchrev( |
489 ui, svn, hg_editor, b, r, parentctx, our_tempdir) | 489 ui, svn, hg_editor, b, r, parentctx) |
490 except BadPatchApply, e: | 490 except BadPatchApply, e: |
491 # Either this revision or the previous one does not exist. | 491 # Either this revision or the previous one does not exist. |
492 ui.status("fetching entire rev: %s.\n" % e.message) | 492 ui.status("fetching entire rev: %s.\n" % e.message) |
493 files_touched, filectxfn = stupid_fetch_branchrev( | 493 files_touched, filectxfn = stupid_fetch_branchrev( |
494 svn, hg_editor, b, branches[b], r, parentctx) | 494 svn, hg_editor, b, branches[b], r, parentctx) |
517 ha = hg_editor.repo.commitctx(current_ctx) | 517 ha = hg_editor.repo.commitctx(current_ctx) |
518 hg_editor.add_to_revmap(r.revnum, b, ha) | 518 hg_editor.add_to_revmap(r.revnum, b, ha) |
519 hg_editor._save_metadata() | 519 hg_editor._save_metadata() |
520 ui.status('committed as %s on branch %s\n' % | 520 ui.status('committed as %s on branch %s\n' % |
521 (node.hex(ha), b or 'default')) | 521 (node.hex(ha), b or 'default')) |
522 if our_tempdir is not None: | |
523 shutil.rmtree(our_tempdir) | |
524 | |
525 | 522 |
526 class BadPatchApply(Exception): | 523 class BadPatchApply(Exception): |
527 pass | 524 pass |