Mercurial > hg
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) |