in eden/scm/edenscm/mercurial/cmdutil.py [0:0]
def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts):
from . import merge as mergemod
if not ui.interactive():
if cmdsuggest:
msg = _("running non-interactively, use %s instead") % cmdsuggest
else:
msg = _("running non-interactively")
raise error.Abort(msg)
# 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)
wctx = repo[None]
merge = len(wctx.parents()) > 1
if merge:
raise error.Abort(
_("cannot partially commit a merge " '(use "hg commit" instead)')
)
def fail(f, msg):
raise error.Abort("%s: %s" % (f, msg))
force = opts.get("force")
if not force:
match.bad = fail
status = repo.status(match=match)
if not force:
repo.checkcommitpatterns(wctx, match, status, fail)
diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
diffopts.nodates = True
diffopts.git = True
diffopts.showfunc = True
originaldiff = patch.diff(
repo, repo[repo.dirstate.p1()], repo[None], changes=status, opts=diffopts
)
originalchunks = patch.parsepatch(originaldiff)
# 1. filter patch, since we are intending to apply subset of it
try:
chunks, newopts = filterfn(ui, originalchunks)
except error.PatchError as err:
raise error.Abort(_("error parsing patch: %s") % err)
opts.update(newopts)
# We need to keep a backup of files that have been newly added and
# modified during the recording process because there is a previous
# version without the edit in the workdir
newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
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
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 newlyaddedandmodifiedfiles
]
copied = extractcopies(chunks)
tobackup += sorted(copied.keys()) # backup "copyto" - delete by step 3a
tobackup += sorted(copied.values()) # backup "copyfrom" - rewrite by step 3a
backups = {}
if tobackup:
backupdir = repo.localvfs.join("record-backups")
try:
os.mkdir(backupdir)
except OSError as err:
if err.errno != errno.EEXIST:
raise
try:
# backup continues
for f in tobackup:
if not repo.wvfs.exists(f):
continue
fd, tmpname = tempfile.mkstemp(dir=backupdir)
os.close(fd)
ui.debug("backup %r as %r\n" % (f, tmpname))
util.copyfile(repo.wjoin(f), tmpname, copystat=True)
backups[f] = tmpname
fp = stringio()
for c in chunks:
if c.filename() in backups:
c.write(fp)
dopatch = fp.tell()
fp.seek(0)
# 2.5 optionally review / modify patch in text editor
if opts.get("review", False):
patchtext = (
crecordmod.diffhelptext
+ crecordmod.patchhelptext
+ pycompat.decodeutf8(fp.read())
)
reviewedpatch = ui.edit(
patchtext, "", action="diff", repopath=repo.path
)
fp.truncate(0)
fp.write(pycompat.encodeutf8(reviewedpatch))
fp.seek(0)
[os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
# 3a. Prepare "copyfrom" -> "copyto" files. Write "copyfrom"
# and remove "copyto".
# This is used by patch._applydiff. If _applydiff reads directly
# from repo["."], not repo.wvfs, then this could be unnecessary.
for copyto, copyfrom in copied.items():
content = repo["."][copyfrom].data()
repo.wvfs.write(copyfrom, content)
repo.wvfs.tryunlink(copyto)
# 3b. apply filtered patch to clean repo (clean)
if backups:
# Equivalent to hg.revert
m = scmutil.matchfiles(repo, backups.keys())
mergemod.update(repo, repo.dirstate.p1(), False, True, matcher=m)
# 3c. (apply)
if dopatch:
try:
ui.debug("applying patch\n")
patch.internalpatch(ui, repo, fp, 1, eolmode=None)
except error.PatchError as err:
raise error.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]
return commitfunc(ui, repo, *newfiles, **opts)
finally:
# 5. finally restore backed-up files
try:
dirstate = repo.dirstate
for realname, tmpname in pycompat.iteritems(backups):
ui.debug("restoring %r to %r\n" % (tmpname, realname))
if dirstate[realname] == "n":
# without normallookup, restoring timestamp
# may cause partially committed files
# to be treated as unmodified
dirstate.normallookup(realname)
# copystat=True 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.
util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
os.unlink(tmpname)
if tobackup:
os.rmdir(backupdir)
except OSError:
pass
def recordinwlock(ui, repo, message, match, opts):
with repo.wlock():
return recordfunc(ui, repo, message, match, opts)
return commit(ui, repo, recordinwlock, pats, opts)