comparison hgext/record.py @ 5827:0c29977bd7db

record: refactor record into generic record driver rationale --------- I'd like to make MQ version of record -- qrecord. >From the first glance it seemed to be easy -- the task in essence would be to change call to cmdutil.commit() to something like mq.qrefresh(). As it turned out queue.refresh() and cmdutil.commit() have different semantics -- cmdutil.commit() first scans for changes and then delegate the actual commit to lowlevel func. On the other hand queue.refresh() do it all in once, and I am a bit scary to change it. Maybe the right way would be to first refactor queue.refresh() to use cmdutil.commit() machinery, and then trivially adjust record, but I feel I'm not competent for the task right now. Instead, I propose we refactor record to be some sort of high-level driver, or like a high-level decorator one can say, which will first interactively filter changes, and then delegate commit job to high-level commiter, e.g. 'commit' or 'qrefresh' So, this patch does just that -- refactor record to be generic driver, and update 'hg record' code to use the driver. 'hg qrecord' will follow.
author Kirill Smelkov <kirr@mns.spb.ru>
date Thu, 10 Jan 2008 12:07:13 +0300
parents cc43d9f36ff2
children c32d41affb68
comparison
equal deleted inserted replaced
5826:cc43d9f36ff2 5827:0c29977bd7db
356 a - record all changes to all remaining files 356 a - record all changes to all remaining files
357 q - quit, recording no changes 357 q - quit, recording no changes
358 358
359 ? - display help''' 359 ? - display help'''
360 360
361 def record_commiter(ui, repo, pats, opts):
362 commands.commit(ui, repo, *pats, **opts)
363
364 dorecord(ui, repo, record_commiter, *pats, **opts)
365
366
367 def dorecord(ui, repo, committer, *pats, **opts):
361 if not ui.interactive: 368 if not ui.interactive:
362 raise util.Abort(_('running non-interactively, use commit instead')) 369 raise util.Abort(_('running non-interactively, use commit instead'))
363 370
364 def recordfunc(ui, repo, files, message, match, opts): 371 def recordfunc(ui, repo, files, message, match, opts):
372 """This is generic record driver.
373
374 It's job is to interactively filter local changes, and accordingly
375 prepare working dir into a state, where the job can be delegated to
376 non-interactive commit command such as 'commit' or 'qrefresh'.
377
378 After the actual job is done by non-interactive command, working dir
379 state is restored to original.
380
381 In the end we'll record intresting changes, and everything else will be
382 left in place, so the user can continue his work.
383 """
365 if files: 384 if files:
366 changes = None 385 changes = None
367 else: 386 else:
368 changes = repo.status(files=files, match=match)[:5] 387 changes = repo.status(files=files, match=match)[:5]
369 modified, added, removed = changes[:3] 388 modified, added, removed = changes[:3]
372 fp = cStringIO.StringIO() 391 fp = cStringIO.StringIO()
373 patch.diff(repo, repo.dirstate.parents()[0], files=files, 392 patch.diff(repo, repo.dirstate.parents()[0], files=files,
374 match=match, changes=changes, opts=diffopts, fp=fp) 393 match=match, changes=changes, opts=diffopts, fp=fp)
375 fp.seek(0) 394 fp.seek(0)
376 395
396 # 1. filter patch, so we have intending-to apply subset of it
377 chunks = filterpatch(ui, parsepatch(fp)) 397 chunks = filterpatch(ui, parsepatch(fp))
378 del fp 398 del fp
379 399
380 contenders = {} 400 contenders = {}
381 for h in chunks: 401 for h in chunks:
390 410
391 if changes is None: 411 if changes is None:
392 changes = repo.status(files=newfiles, match=match)[:5] 412 changes = repo.status(files=newfiles, match=match)[:5]
393 modified = dict.fromkeys(changes[0]) 413 modified = dict.fromkeys(changes[0])
394 414
415 # 2. backup changed files, so we can restore them in the end
395 backups = {} 416 backups = {}
396 backupdir = repo.join('record-backups') 417 backupdir = repo.join('record-backups')
397 try: 418 try:
398 os.mkdir(backupdir) 419 os.mkdir(backupdir)
399 except OSError, err: 420 except OSError, err:
400 if err.errno != errno.EEXIST: 421 if err.errno != errno.EEXIST:
401 raise 422 raise
402 try: 423 try:
424 # backup continues
403 for f in newfiles: 425 for f in newfiles:
404 if f not in modified: 426 if f not in modified:
405 continue 427 continue
406 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.', 428 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
407 dir=backupdir) 429 dir=backupdir)
415 if c.filename() in backups: 437 if c.filename() in backups:
416 c.write(fp) 438 c.write(fp)
417 dopatch = fp.tell() 439 dopatch = fp.tell()
418 fp.seek(0) 440 fp.seek(0)
419 441
442 # 3a. apply filtered patch to clean repo (clean)
420 if backups: 443 if backups:
421 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) 444 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
422 445
446 # 3b. (apply)
423 if dopatch: 447 if dopatch:
424 ui.debug('applying patch\n') 448 ui.debug('applying patch\n')
425 ui.debug(fp.getvalue()) 449 ui.debug(fp.getvalue())
426 patch.internalpatch(fp, ui, 1, repo.root) 450 patch.internalpatch(fp, ui, 1, repo.root)
427 del fp 451 del fp
428 452
429 repo.commit(newfiles, message, opts['user'], opts['date'], match, 453 # 4. We prepared working directory according to filtered patch.
430 force_editor=opts.get('force_editor')) 454 # Now is the time to delegate the job to commit/qrefresh or the like!
455
456 # it is important to first chdir to repo root -- we'll call a
457 # highlevel command with list of pathnames relative to repo root
458 cwd = os.getcwd()
459 os.chdir(repo.root)
460 try:
461 committer(ui, repo, newfiles, opts)
462 finally:
463 os.chdir(cwd)
464
431 return 0 465 return 0
432 finally: 466 finally:
467 # 5. finally restore backed-up files
433 try: 468 try:
434 for realname, tmpname in backups.iteritems(): 469 for realname, tmpname in backups.iteritems():
435 ui.debug('restoring %r to %r\n' % (tmpname, realname)) 470 ui.debug('restoring %r to %r\n' % (tmpname, realname))
436 util.copyfile(tmpname, repo.wjoin(realname)) 471 util.copyfile(tmpname, repo.wjoin(realname))
437 os.unlink(tmpname) 472 os.unlink(tmpname)