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