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 |
