# HG changeset patch # User Laurent Charignon # Date 1426032873 25200 # Node ID 26a1c617e047e1f523090fbe597cd4fa9a97a159 # Parent 18792f2e38bb5fb3c9cd161bb2836aa3794f6746 record: move dorecord from record to cmdutil Part of a serie of patches to move record from hgext to core diff -r 18792f2e38bb -r 26a1c617e047 hgext/keyword.py --- a/hgext/keyword.py Tue Mar 10 17:09:07 2015 -0700 +++ b/hgext/keyword.py Tue Mar 10 17:14:33 2015 -0700 @@ -737,13 +737,7 @@ extensions.wrapfunction(patch, 'diff', kw_diff) extensions.wrapfunction(cmdutil, 'amend', kw_amend) extensions.wrapfunction(cmdutil, 'copy', kw_copy) + extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord) for c in 'annotate changeset rev filediff diff'.split(): extensions.wrapfunction(webcommands, c, kwweb_skip) - for name in recordextensions.split(): - try: - record = extensions.find(name) - extensions.wrapfunction(record, 'dorecord', kw_dorecord) - except KeyError: - pass - repo.__class__ = kwrepo diff -r 18792f2e38bb -r 26a1c617e047 hgext/record.py --- a/hgext/record.py Tue Mar 10 17:09:07 2015 -0700 +++ b/hgext/record.py Tue Mar 10 17:14:33 2015 -0700 @@ -8,10 +8,8 @@ '''commands to interactively select changes for commit/qrefresh''' from mercurial.i18n import _ -from mercurial import cmdutil, commands, extensions, patch +from mercurial import cmdutil, commands, extensions from mercurial import util -from mercurial import merge as mergemod -import cStringIO, errno, os, shutil, tempfile cmdtable = {} command = cmdutil.command(cmdtable) @@ -50,7 +48,7 @@ This command is not available when committing a merge.''' - dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts) + cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts) def qrefresh(origfn, ui, repo, *pats, **opts): if not opts['interactive']: @@ -66,7 +64,7 @@ mq.refresh(ui, repo, **opts) # backup all changed files - dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts) + cmdutil.dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts) # This command registration is replaced during uisetup(). @command('qrecord', @@ -91,180 +89,13 @@ opts['checkname'] = False mq.new(ui, repo, patch, *pats, **opts) - dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts) + cmdutil.dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts) def qnew(origfn, ui, repo, patch, *args, **opts): if opts['interactive']: return qrecord(ui, repo, patch, *args, **opts) return origfn(ui, repo, patch, *args, **opts) -def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts): - if not ui.interactive(): - raise util.Abort(_('running non-interactively, use %s instead') % - cmdsuggest) - - # make sure username is set before going interactive - if not opts.get('user'): - ui.username() # raise exception, username not provided - - def recordfunc(ui, repo, message, match, opts): - """This is generic record driver. - - Its job is to interactively filter local changes, and - accordingly prepare working directory into a state in which the - job can be delegated to a non-interactive commit command such as - 'commit' or 'qrefresh'. - - After the actual job is done by non-interactive command, the - working directory is restored to its original state. - - In the end we'll record interesting changes, and everything else - will be left in place, so the user can continue working. - """ - - cmdutil.checkunfinished(repo, commit=True) - merge = len(repo[None].parents()) > 1 - if merge: - raise util.Abort(_('cannot partially commit a merge ' - '(use "hg commit" instead)')) - - status = repo.status(match=match) - diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True) - diffopts.nodates = True - diffopts.git = True - originalchunks = patch.diff(repo, changes=status, opts=diffopts) - fp = cStringIO.StringIO() - fp.write(''.join(originalchunks)) - fp.seek(0) - - # 1. filter patch, so we have intending-to apply subset of it - try: - chunks = patch.filterpatch(ui, patch.parsepatch(fp)) - except patch.PatchError, err: - raise util.Abort(_('error parsing patch: %s') % err) - - del fp - - contenders = set() - for h in chunks: - try: - contenders.update(set(h.files())) - except AttributeError: - pass - - changed = status.modified + status.added + status.removed - newfiles = [f for f in changed if f in contenders] - if not newfiles: - ui.status(_('no changes to record\n')) - return 0 - - newandmodifiedfiles = set() - for h in chunks: - ishunk = isinstance(h, patch.recordhunk) - isnew = h.filename() in status.added - if ishunk and isnew and not h in originalchunks: - newandmodifiedfiles.add(h.filename()) - - modified = set(status.modified) - - # 2. backup changed files, so we can restore them in the end - - if backupall: - tobackup = changed - else: - tobackup = [f for f in newfiles - if f in modified or f in newandmodifiedfiles] - - backups = {} - if tobackup: - backupdir = repo.join('record-backups') - try: - os.mkdir(backupdir) - except OSError, err: - if err.errno != errno.EEXIST: - raise - try: - # backup continues - for f in tobackup: - fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.', - dir=backupdir) - os.close(fd) - ui.debug('backup %r as %r\n' % (f, tmpname)) - util.copyfile(repo.wjoin(f), tmpname) - shutil.copystat(repo.wjoin(f), tmpname) - backups[f] = tmpname - - fp = cStringIO.StringIO() - for c in chunks: - fname = c.filename() - if fname in backups or fname in newandmodifiedfiles: - c.write(fp) - dopatch = fp.tell() - fp.seek(0) - - [os.unlink(c) for c in newandmodifiedfiles] - - # 3a. apply filtered patch to clean repo (clean) - if backups: - # Equivalent to hg.revert - choices = lambda key: key in backups - mergemod.update(repo, repo.dirstate.p1(), - False, True, choices) - - # 3b. (apply) - if dopatch: - try: - ui.debug('applying patch\n') - ui.debug(fp.getvalue()) - patch.internalpatch(ui, repo, fp, 1, eolmode=None) - except patch.PatchError, err: - raise util.Abort(str(err)) - del fp - - # 4. We prepared working directory according to filtered - # patch. Now is the time to delegate the job to - # commit/qrefresh or the like! - - # Make all of the pathnames absolute. - newfiles = [repo.wjoin(nf) for nf in newfiles] - commitfunc(ui, repo, *newfiles, **opts) - - return 0 - finally: - # 5. finally restore backed-up files - try: - for realname, tmpname in backups.iteritems(): - ui.debug('restoring %r to %r\n' % (tmpname, realname)) - util.copyfile(tmpname, repo.wjoin(realname)) - # Our calls to copystat() here and above are a - # hack to trick any editors that have f open that - # we haven't modified them. - # - # Also note that this racy as an editor could - # notice the file's mtime before we've finished - # writing it. - shutil.copystat(tmpname, repo.wjoin(realname)) - os.unlink(tmpname) - if tobackup: - os.rmdir(backupdir) - except OSError: - pass - - # wrap ui.write so diff output can be labeled/colorized - def wrapwrite(orig, *args, **kw): - label = kw.pop('label', '') - for chunk, l in patch.difflabel(lambda: args): - orig(chunk, label=label + l) - oldwrite = ui.write - - def wrap(*args, **kwargs): - return wrapwrite(oldwrite, *args, **kwargs) - setattr(ui, 'write', wrap) - - try: - return cmdutil.commit(ui, repo, recordfunc, pats, opts) - finally: - ui.write = oldwrite def uisetup(ui): try: diff -r 18792f2e38bb -r 26a1c617e047 mercurial/cmdutil.py --- a/mercurial/cmdutil.py Tue Mar 10 17:09:07 2015 -0700 +++ b/mercurial/cmdutil.py Tue Mar 10 17:14:33 2015 -0700 @@ -7,7 +7,7 @@ from node import hex, nullid, nullrev, short from i18n import _ -import os, sys, errno, re, tempfile +import os, sys, errno, re, tempfile, cStringIO, shutil import util, scmutil, templater, patch, error, templatekw, revlog, copies import match as matchmod import context, repair, graphmod, revset, phases, obsolete, pathutil @@ -19,6 +19,177 @@ def parsealiases(cmd): return cmd.lstrip("^").split("|") +def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts): + import merge as mergemod + if not ui.interactive(): + raise util.Abort(_('running non-interactively, use %s instead') % + cmdsuggest) + + # make sure username is set before going interactive + if not opts.get('user'): + ui.username() # raise exception, username not provided + + def recordfunc(ui, repo, message, match, opts): + """This is generic record driver. + + Its job is to interactively filter local changes, and + accordingly prepare working directory into a state in which the + job can be delegated to a non-interactive commit command such as + 'commit' or 'qrefresh'. + + After the actual job is done by non-interactive command, the + working directory is restored to its original state. + + In the end we'll record interesting changes, and everything else + will be left in place, so the user can continue working. + """ + + checkunfinished(repo, commit=True) + merge = len(repo[None].parents()) > 1 + if merge: + raise util.Abort(_('cannot partially commit a merge ' + '(use "hg commit" instead)')) + + status = repo.status(match=match) + diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True) + diffopts.nodates = True + diffopts.git = True + originalchunks = patch.diff(repo, changes=status, opts=diffopts) + fp = cStringIO.StringIO() + fp.write(''.join(originalchunks)) + fp.seek(0) + + # 1. filter patch, so we have intending-to apply subset of it + try: + chunks = patch.filterpatch(ui, patch.parsepatch(fp)) + except patch.PatchError, err: + raise util.Abort(_('error parsing patch: %s') % err) + + del fp + + contenders = set() + for h in chunks: + try: + contenders.update(set(h.files())) + except AttributeError: + pass + + changed = status.modified + status.added + status.removed + newfiles = [f for f in changed if f in contenders] + if not newfiles: + ui.status(_('no changes to record\n')) + return 0 + + newandmodifiedfiles = set() + for h in chunks: + ishunk = isinstance(h, patch.recordhunk) + isnew = h.filename() in status.added + if ishunk and isnew and not h in originalchunks: + newandmodifiedfiles.add(h.filename()) + + modified = set(status.modified) + + # 2. backup changed files, so we can restore them in the end + + if backupall: + tobackup = changed + else: + tobackup = [f for f in newfiles + if f in modified or f in newandmodifiedfiles] + + backups = {} + if tobackup: + backupdir = repo.join('record-backups') + try: + os.mkdir(backupdir) + except OSError, err: + if err.errno != errno.EEXIST: + raise + try: + # backup continues + for f in tobackup: + fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.', + dir=backupdir) + os.close(fd) + ui.debug('backup %r as %r\n' % (f, tmpname)) + util.copyfile(repo.wjoin(f), tmpname) + shutil.copystat(repo.wjoin(f), tmpname) + backups[f] = tmpname + + fp = cStringIO.StringIO() + for c in chunks: + fname = c.filename() + if fname in backups or fname in newandmodifiedfiles: + c.write(fp) + dopatch = fp.tell() + fp.seek(0) + + [os.unlink(c) for c in newandmodifiedfiles] + + # 3a. apply filtered patch to clean repo (clean) + if backups: + # Equivalent to hg.revert + choices = lambda key: key in backups + mergemod.update(repo, repo.dirstate.p1(), + False, True, choices) + + + # 3b. (apply) + if dopatch: + try: + ui.debug('applying patch\n') + ui.debug(fp.getvalue()) + patch.internalpatch(ui, repo, fp, 1, eolmode=None) + except patch.PatchError, err: + raise util.Abort(str(err)) + del fp + + # 4. We prepared working directory according to filtered + # patch. Now is the time to delegate the job to + # commit/qrefresh or the like! + + # Make all of the pathnames absolute. + newfiles = [repo.wjoin(nf) for nf in newfiles] + commitfunc(ui, repo, *newfiles, **opts) + + return 0 + finally: + # 5. finally restore backed-up files + try: + for realname, tmpname in backups.iteritems(): + ui.debug('restoring %r to %r\n' % (tmpname, realname)) + util.copyfile(tmpname, repo.wjoin(realname)) + # Our calls to copystat() here and above are a + # hack to trick any editors that have f open that + # we haven't modified them. + # + # Also note that this racy as an editor could + # notice the file's mtime before we've finished + # writing it. + shutil.copystat(tmpname, repo.wjoin(realname)) + os.unlink(tmpname) + if tobackup: + os.rmdir(backupdir) + except OSError: + pass + + # wrap ui.write so diff output can be labeled/colorized + def wrapwrite(orig, *args, **kw): + label = kw.pop('label', '') + for chunk, l in patch.difflabel(lambda: args): + orig(chunk, label=label + l) + + oldwrite = ui.write + def wrap(*args, **kwargs): + return wrapwrite(oldwrite, *args, **kwargs) + setattr(ui, 'write', wrap) + + try: + return commit(ui, repo, recordfunc, pats, opts) + finally: + ui.write = oldwrite + + def findpossible(cmd, table, strict=False): """ Return cmd -> (aliases, command table entry)