/usr/share/pyshared/hgext/crecord/crecord_core.py is in mercurial-crecord 0.20140626-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | # crecord.py
#
# Copyright 2008 Mark Edgington <edgimar@gmail.com>
#
# This software may be used and distributed according to the terms of
# the GNU General Public License, incorporated herein by reference.
#
# Much of this extension is based on Bryan O'Sullivan's record extension.
'''text-gui based change selection during commit or qrefresh'''
from mercurial.i18n import _
from mercurial import cmdutil, hg, mdiff, patch
from mercurial import util
import cStringIO
import errno
import os
import tempfile
import crpatch
import chunk_selector
def dorecord(ui, repo, commitfunc, *pats, **opts):
try:
if not ui.interactive():
raise util.Abort(_('running non-interactively, use commit instead'))
except TypeError: # backwards compatibility with hg 1.1
if not ui.interactive:
raise util.Abort(_('running non-interactively, use commit instead'))
def recordfunc(ui, repo, message, match, opts):
"""This is generic record driver.
Its job is to interactively filter local changes, and accordingly
prepare working dir into a state, where the job can be delegated to
non-interactive commit command such as 'commit' or 'qrefresh'.
After the actual job is done by non-interactive command, working dir
state is restored to original.
In the end we'll record interesting changes, and everything else will be
left in place, so the user can continue his work.
"""
merge = len(repo[None].parents()) > 1
if merge:
raise util.Abort(_('cannot partially commit a merge '
'(use hg commit instead)'))
# status gives back
# modified, added, removed, deleted, unknown, ignored, clean
# we take only the first 3 of these
changes = repo.status(match=match)[:3]
modified, added, removed = changes
diffopts = opts.copy()
diffopts['nodates'] = True
diffopts['git'] = True
diffopts = patch.diffopts(ui, opts=diffopts)
chunks = patch.diff(repo, changes=changes, opts=diffopts)
fp = cStringIO.StringIO()
fp.write(''.join(chunks))
fp.seek(0)
# 1. filter patch, so we have intending-to apply subset of it
chunks = crpatch.filterpatch(opts,
crpatch.parsepatch(changes, fp),
chunk_selector.chunkselector, ui)
del fp
contenders = set()
for h in chunks:
try:
contenders.update(set(h.files()))
except AttributeError:
pass
changed = changes[0] + changes[1] + changes[2]
newfiles = [f for f in changed if f in contenders]
if not newfiles:
ui.status(_('no changes to record\n'))
return 0
# 2. backup changed files, so we can restore them in the end
backups = {}
newly_added_backups = {}
backupdir = repo.join('record-backups')
try:
os.mkdir(backupdir)
except OSError, err:
if err.errno != errno.EEXIST:
raise
try:
# backup continues
for f in newfiles:
if f not in (modified + added):
continue
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)
if f in modified:
backups[f] = tmpname
elif f in added:
newly_added_backups[f] = tmpname
fp = cStringIO.StringIO()
all_backups = {}
all_backups.update(backups)
all_backups.update(newly_added_backups)
for c in chunks:
if c.filename() in all_backups:
c.write(fp)
dopatch = fp.tell()
fp.seek(0)
# 2.5 optionally review / modify patch in text editor
if opts['crecord_reviewpatch']:
patchtext = fp.read()
reviewedpatch = ui.edit(patchtext, "")
fp.truncate(0)
fp.write(reviewedpatch)
fp.seek(0)
# 3a. apply filtered patch to clean repo (clean)
if backups:
hg.revert(repo, repo.dirstate.parents()[0],
lambda key: key in backups)
# remove newly added files from 'clean' repo (so patch can apply)
for f in newly_added_backups:
os.unlink(f)
# 3b. (apply)
if dopatch:
try:
ui.debug('applying patch\n')
ui.debug(fp.getvalue())
if hasattr(patch, 'workingbackend'): # detect 1.9
patch.internalpatch(ui, repo, fp, strip=1, eolmode=None)
else:
pfiles = {}
try:
patch.internalpatch(ui, repo, fp, 1, eolmode=None)
except (TypeError, AttributeError): # pre 17cea10c343e
try:
patch.internalpatch(ui, repo, fp, 1, repo.root,
eolmode=None)
except (TypeError, AttributeError): # pre 00a881581400
try:
patch.internalpatch(fp, ui, 1, repo.root,
files=pfiles, eolmode=None)
except TypeError: # backwards compatible with hg 1.1
patch.internalpatch(fp, ui, 1,
repo.root, files=pfiles)
try:
cmdutil.updatedir(ui, repo, pfiles)
except AttributeError:
try:
patch.updatedir(ui, repo, pfiles)
except AttributeError:
# from 00a881581400 onwards
pass
except patch.PatchError, err:
s = str(err)
if s:
raise util.Abort(s)
else:
raise util.Abort(_('patch failed to apply'))
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!
# it is important to first chdir to repo root -- we'll call a
# highlevel command with list of pathnames relative to repo root
newfiles = [repo.wjoin(n) for n 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))
os.unlink(tmpname)
for realname, tmpname in newly_added_backups.iteritems():
ui.debug('restoring %r to %r\n' % (tmpname, realname))
util.copyfile(tmpname, repo.wjoin(realname))
os.unlink(tmpname)
os.rmdir(backupdir)
except OSError:
pass
return cmdutil.commit(ui, repo, recordfunc, pats, opts)
|