cmdutil.py
3885 lines
| 140.3 KiB
| text/x-python
|
PythonLexer
/ mercurial / cmdutil.py
Vadim Gelfer
|
r2957 | # cmdutil.py - help for command processing in mercurial | ||
Vadim Gelfer
|
r2874 | # | ||
Thomas Arendsen Hein
|
r4635 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||
Vadim Gelfer
|
r2874 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Vadim Gelfer
|
r2874 | |||
Gregory Szorc
|
r28322 | from __future__ import absolute_import | ||
import errno | ||||
Yuya Nishihara
|
r31807 | import itertools | ||
Gregory Szorc
|
r28322 | import os | ||
import re | ||||
import tempfile | ||||
from .i18n import _ | ||||
from .node import ( | ||||
hex, | ||||
nullid, | ||||
nullrev, | ||||
short, | ||||
) | ||||
from . import ( | ||||
bookmarks, | ||||
changelog, | ||||
copies, | ||||
crecord as crecordmod, | ||||
Martin von Zweigbergk
|
r33619 | dirstateguard, | ||
Gregory Szorc
|
r28322 | encoding, | ||
error, | ||||
formatter, | ||||
graphmod, | ||||
match as matchmod, | ||||
obsolete, | ||||
patch, | ||||
pathutil, | ||||
Pulkit Goyal
|
r30519 | pycompat, | ||
Yuya Nishihara
|
r32337 | registrar, | ||
Gregory Szorc
|
r28322 | revlog, | ||
revset, | ||||
scmutil, | ||||
Yuya Nishihara
|
r31023 | smartset, | ||
Gregory Szorc
|
r28322 | templatekw, | ||
templater, | ||||
util, | ||||
Pierre-Yves David
|
r31237 | vfs as vfsmod, | ||
Gregory Szorc
|
r28322 | ) | ||
timeless
|
r28861 | stringio = util.stringio | ||
Vadim Gelfer
|
r2874 | |||
Yuya Nishihara
|
r32375 | # templates of common command options | ||
dryrunopts = [ | ||||
('n', 'dry-run', None, | ||||
_('do not perform actions, just print output')), | ||||
] | ||||
remoteopts = [ | ||||
('e', 'ssh', '', | ||||
_('specify ssh command to use'), _('CMD')), | ||||
('', 'remotecmd', '', | ||||
_('specify hg command to run on the remote side'), _('CMD')), | ||||
('', 'insecure', None, | ||||
_('do not verify server certificate (ignoring web.cacerts config)')), | ||||
] | ||||
walkopts = [ | ||||
('I', 'include', [], | ||||
_('include names matching the given patterns'), _('PATTERN')), | ||||
('X', 'exclude', [], | ||||
_('exclude names matching the given patterns'), _('PATTERN')), | ||||
] | ||||
commitopts = [ | ||||
('m', 'message', '', | ||||
_('use text as commit message'), _('TEXT')), | ||||
('l', 'logfile', '', | ||||
_('read commit message from file'), _('FILE')), | ||||
] | ||||
commitopts2 = [ | ||||
('d', 'date', '', | ||||
_('record the specified date as commit date'), _('DATE')), | ||||
('u', 'user', '', | ||||
_('record the specified user as committer'), _('USER')), | ||||
] | ||||
# hidden for now | ||||
formatteropts = [ | ||||
('T', 'template', '', | ||||
_('display with template (EXPERIMENTAL)'), _('TEMPLATE')), | ||||
] | ||||
templateopts = [ | ||||
('', 'style', '', | ||||
_('display using template map file (DEPRECATED)'), _('STYLE')), | ||||
('T', 'template', '', | ||||
_('display with template'), _('TEMPLATE')), | ||||
] | ||||
logopts = [ | ||||
('p', 'patch', None, _('show patch')), | ||||
('g', 'git', None, _('use git extended diff format')), | ||||
('l', 'limit', '', | ||||
_('limit number of changes displayed'), _('NUM')), | ||||
('M', 'no-merges', None, _('do not show merges')), | ||||
('', 'stat', None, _('output diffstat-style summary of changes')), | ||||
('G', 'graph', None, _("show the revision DAG")), | ||||
] + templateopts | ||||
diffopts = [ | ||||
('a', 'text', None, _('treat all files as text')), | ||||
('g', 'git', None, _('use git extended diff format')), | ||||
('', 'binary', None, _('generate binary diffs in git mode (default)')), | ||||
('', 'nodates', None, _('omit dates from diff headers')) | ||||
] | ||||
diffwsopts = [ | ||||
('w', 'ignore-all-space', None, | ||||
_('ignore white space when comparing lines')), | ||||
('b', 'ignore-space-change', None, | ||||
_('ignore changes in the amount of white space')), | ||||
('B', 'ignore-blank-lines', None, | ||||
_('ignore changes whose lines are all blank')), | ||||
David Soria Parra
|
r34015 | ('Z', 'ignore-space-at-eol', None, | ||
_('ignore changes in whitespace at EOL')), | ||||
Yuya Nishihara
|
r32375 | ] | ||
diffopts2 = [ | ||||
('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')), | ||||
('p', 'show-function', None, _('show which function each change is in')), | ||||
('', 'reverse', None, _('produce a diff that undoes the changes')), | ||||
] + diffwsopts + [ | ||||
('U', 'unified', '', | ||||
_('number of lines of context to show'), _('NUM')), | ||||
('', 'stat', None, _('output diffstat-style summary of changes')), | ||||
('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')), | ||||
] | ||||
mergetoolopts = [ | ||||
('t', 'tool', '', _('specify merge tool')), | ||||
] | ||||
similarityopts = [ | ||||
('s', 'similarity', '', | ||||
_('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY')) | ||||
] | ||||
subrepoopts = [ | ||||
('S', 'subrepos', None, | ||||
_('recurse into subrepositories')) | ||||
] | ||||
debugrevlogopts = [ | ||||
('c', 'changelog', False, _('open changelog')), | ||||
('m', 'manifest', False, _('open manifest')), | ||||
('', 'dir', '', _('open directory manifest')), | ||||
] | ||||
Sean Farley
|
r30703 | # special string such that everything below this line will be ingored in the | ||
# editor text | ||||
_linebelow = "^HG: ------------------------ >8 ------------------------$" | ||||
Laurent Charignon
|
r25256 | def ishunk(x): | ||
hunkclasses = (crecordmod.uihunk, patch.recordhunk) | ||||
return isinstance(x, hunkclasses) | ||||
Laurent Charignon
|
r25257 | def newandmodified(chunks, originalchunks): | ||
newlyaddedandmodifiedfiles = set() | ||||
for chunk in chunks: | ||||
if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \ | ||||
originalchunks: | ||||
newlyaddedandmodifiedfiles.add(chunk.header.filename()) | ||||
return newlyaddedandmodifiedfiles | ||||
Brendan Cully
|
r10401 | def parsealiases(cmd): | ||
return cmd.lstrip("^").split("|") | ||||
Laurent Charignon
|
r24356 | def setupwrapcolorwrite(ui): | ||
# 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) | ||||
return oldwrite | ||||
Laurent Charignon
|
r25310 | def filterchunks(ui, originalhunks, usecurses, testfile, operation=None): | ||
Laurent Charignon
|
r24343 | if usecurses: | ||
if testfile: | ||||
recordfn = crecordmod.testdecorator(testfile, | ||||
crecordmod.testchunkselector) | ||||
else: | ||||
recordfn = crecordmod.chunkselector | ||||
Jun Wu
|
r30534 | return crecordmod.filterpatch(ui, originalhunks, recordfn, operation) | ||
Laurent Charignon
|
r24343 | |||
else: | ||||
Laurent Charignon
|
r25310 | return patch.filterpatch(ui, originalhunks, operation) | ||
def recordfilter(ui, originalhunks, operation=None): | ||||
""" Prompts the user to filter the originalhunks and return a list of | ||||
selected hunks. | ||||
Denis Laxalde
|
r29326 | *operation* is used for to build ui messages to indicate the user what | ||
kind of filtering they are doing: reverting, committing, shelving, etc. | ||||
(see patch.filterpatch). | ||||
Laurent Charignon
|
r25310 | """ | ||
Sean Farley
|
r27531 | usecurses = crecordmod.checkcurses(ui) | ||
Jun Wu
|
r33499 | testfile = ui.config('experimental', 'crecordtest') | ||
Laurent Charignon
|
r24358 | oldwrite = setupwrapcolorwrite(ui) | ||
try: | ||||
Laurent Charignon
|
r27155 | newchunks, newopts = filterchunks(ui, originalhunks, usecurses, | ||
testfile, operation) | ||||
Laurent Charignon
|
r24358 | finally: | ||
ui.write = oldwrite | ||||
Laurent Charignon
|
r27155 | return newchunks, newopts | ||
Laurent Charignon
|
r24357 | |||
Laurent Charignon
|
r24309 | def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, | ||
filterfn, *pats, **opts): | ||||
Gregory Szorc
|
r28322 | from . import merge as mergemod | ||
Pulkit Goyal
|
r32144 | opts = pycompat.byteskwargs(opts) | ||
Laurent Charignon
|
r24272 | if not ui.interactive(): | ||
FUJIWARA Katsunori
|
r25795 | if cmdsuggest: | ||
msg = _('running non-interactively, use %s instead') % cmdsuggest | ||||
else: | ||||
msg = _('running non-interactively') | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(msg) | ||
Laurent Charignon
|
r24272 | |||
# 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) | ||||
timeless
|
r28815 | wctx = repo[None] | ||
merge = len(wctx.parents()) > 1 | ||||
Laurent Charignon
|
r24272 | if merge: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('cannot partially commit a merge ' | ||
Laurent Charignon
|
r24272 | '(use "hg commit" instead)')) | ||
timeless
|
r28815 | def fail(f, msg): | ||
raise error.Abort('%s: %s' % (f, msg)) | ||||
force = opts.get('force') | ||||
if not force: | ||||
vdirs = [] | ||||
match.explicitdir = vdirs.append | ||||
match.bad = fail | ||||
Laurent Charignon
|
r24272 | status = repo.status(match=match) | ||
timeless
|
r28815 | if not force: | ||
repo.checkcommitpatterns(wctx, vdirs, match, status, fail) | ||||
Laurent Charignon
|
r24272 | diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True) | ||
diffopts.nodates = True | ||||
diffopts.git = True | ||||
timeless
|
r27411 | diffopts.showfunc = True | ||
timeless
|
r27637 | originaldiff = patch.diff(repo, changes=status, opts=diffopts) | ||
Laurent Charignon
|
r24341 | originalchunks = patch.parsepatch(originaldiff) | ||
Laurent Charignon
|
r24272 | |||
Jordi Gutiérrez Hermoso
|
r28570 | # 1. filter patch, since we are intending to apply subset of it | ||
Laurent Charignon
|
r24272 | try: | ||
Laurent Charignon
|
r27155 | chunks, newopts = filterfn(ui, originalchunks) | ||
Gregory Szorc
|
r25660 | except patch.PatchError as err: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('error parsing patch: %s') % err) | ||
Laurent Charignon
|
r27155 | opts.update(newopts) | ||
Laurent Charignon
|
r24272 | |||
Laurent Charignon
|
r24845 | # 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 | ||||
Laurent Charignon
|
r25257 | newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks) | ||
Laurent Charignon
|
r24272 | 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: | ||||
Laurent Charignon
|
r24845 | tobackup = [f for f in newfiles if f in modified or f in \ | ||
newlyaddedandmodifiedfiles] | ||||
Laurent Charignon
|
r24272 | backups = {} | ||
if tobackup: | ||||
Pierre-Yves David
|
r31320 | backupdir = repo.vfs.join('record-backups') | ||
Laurent Charignon
|
r24272 | try: | ||
os.mkdir(backupdir) | ||||
Gregory Szorc
|
r25660 | except OSError as err: | ||
Laurent Charignon
|
r24272 | 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)) | ||||
Siddharth Agarwal
|
r27370 | util.copyfile(repo.wjoin(f), tmpname, copystat=True) | ||
Laurent Charignon
|
r24272 | backups[f] = tmpname | ||
timeless
|
r28861 | fp = stringio() | ||
Laurent Charignon
|
r24272 | for c in chunks: | ||
fname = c.filename() | ||||
Laurent Charignon
|
r24837 | if fname in backups: | ||
Laurent Charignon
|
r24272 | c.write(fp) | ||
dopatch = fp.tell() | ||||
fp.seek(0) | ||||
Jordi Gutiérrez Hermoso
|
r28638 | # 2.5 optionally review / modify patch in text editor | ||
if opts.get('review', False): | ||||
patchtext = (crecordmod.diffhelptext | ||||
+ crecordmod.patchhelptext | ||||
+ fp.read()) | ||||
reviewedpatch = ui.edit(patchtext, "", | ||||
Michael Bolin
|
r34030 | action="diff", | ||
Sean Farley
|
r30848 | repopath=repo.path) | ||
Jordi Gutiérrez Hermoso
|
r28638 | fp.truncate(0) | ||
fp.write(reviewedpatch) | ||||
fp.seek(0) | ||||
Laurent Charignon
|
r24866 | [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles] | ||
Laurent Charignon
|
r24272 | # 3a. apply filtered patch to clean repo (clean) | ||
if backups: | ||||
# Equivalent to hg.revert | ||||
Augie Fackler
|
r27344 | m = scmutil.matchfiles(repo, backups.keys()) | ||
Laurent Charignon
|
r24272 | mergemod.update(repo, repo.dirstate.p1(), | ||
Augie Fackler
|
r27344 | False, True, matcher=m) | ||
Laurent Charignon
|
r24272 | |||
# 3b. (apply) | ||||
if dopatch: | ||||
try: | ||||
ui.debug('applying patch\n') | ||||
ui.debug(fp.getvalue()) | ||||
patch.internalpatch(ui, repo, fp, 1, eolmode=None) | ||||
Gregory Szorc
|
r25660 | except patch.PatchError as err: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(str(err)) | ||
Laurent Charignon
|
r24272 | 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] | ||||
Laurent Charignon
|
r24476 | return commitfunc(ui, repo, *newfiles, **opts) | ||
Laurent Charignon
|
r24272 | finally: | ||
# 5. finally restore backed-up files | ||||
try: | ||||
FUJIWARA Katsunori
|
r25759 | dirstate = repo.dirstate | ||
Laurent Charignon
|
r24272 | for realname, tmpname in backups.iteritems(): | ||
ui.debug('restoring %r to %r\n' % (tmpname, realname)) | ||||
FUJIWARA Katsunori
|
r25759 | |||
if dirstate[realname] == 'n': | ||||
# without normallookup, restoring timestamp | ||||
# may cause partially committed files | ||||
# to be treated as unmodified | ||||
dirstate.normallookup(realname) | ||||
Siddharth Agarwal
|
r27370 | # copystat=True here and above are a hack to trick any | ||
# editors that have f open that we haven't modified them. | ||||
Laurent Charignon
|
r24272 | # | ||
Siddharth Agarwal
|
r27370 | # 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) | ||||
Laurent Charignon
|
r24272 | os.unlink(tmpname) | ||
if tobackup: | ||||
os.rmdir(backupdir) | ||||
except OSError: | ||||
pass | ||||
FUJIWARA Katsunori
|
r25758 | def recordinwlock(ui, repo, message, match, opts): | ||
Bryan O'Sullivan
|
r27801 | with repo.wlock(): | ||
FUJIWARA Katsunori
|
r25758 | return recordfunc(ui, repo, message, match, opts) | ||
return commit(ui, repo, recordinwlock, pats, opts) | ||||
Laurent Charignon
|
r24272 | |||
Pulkit Goyal
|
r33548 | def tersestatus(root, statlist, status, ignorefn, ignore): | ||
""" | ||||
Returns a list of statuses with directory collapsed if all the files in the | ||||
directory has the same status. | ||||
""" | ||||
def numfiles(dirname): | ||||
""" | ||||
Calculates the number of tracked files in a given directory which also | ||||
includes files which were removed or deleted. Considers ignored files | ||||
if ignore argument is True or 'i' is present in status argument. | ||||
""" | ||||
if lencache.get(dirname): | ||||
return lencache[dirname] | ||||
if 'i' in status or ignore: | ||||
def match(localpath): | ||||
absolutepath = os.path.join(root, localpath) | ||||
if os.path.isdir(absolutepath) and isemptydir(absolutepath): | ||||
return True | ||||
return False | ||||
else: | ||||
def match(localpath): | ||||
# there can be directory whose all the files are ignored and | ||||
# hence the drectory should also be ignored while counting | ||||
# number of files or subdirs in it's parent directory. This | ||||
# checks the same. | ||||
# XXX: We need a better logic here. | ||||
if os.path.isdir(os.path.join(root, localpath)): | ||||
return isignoreddir(localpath) | ||||
else: | ||||
# XXX: there can be files which have the ignored pattern but | ||||
# are not ignored. That leads to bug in counting number of | ||||
# tracked files in the directory. | ||||
return ignorefn(localpath) | ||||
lendir = 0 | ||||
abspath = os.path.join(root, dirname) | ||||
# There might be cases when a directory does not exists as the whole | ||||
# directory can be removed and/or deleted. | ||||
try: | ||||
for f in os.listdir(abspath): | ||||
localpath = os.path.join(dirname, f) | ||||
if not match(localpath): | ||||
lendir += 1 | ||||
except OSError: | ||||
pass | ||||
lendir += len(absentdir.get(dirname, [])) | ||||
lencache[dirname] = lendir | ||||
return lendir | ||||
def isemptydir(abspath): | ||||
""" | ||||
Check whether a directory is empty or not, i.e. there is no files in the | ||||
directory and all its subdirectories. | ||||
""" | ||||
for f in os.listdir(abspath): | ||||
fullpath = os.path.join(abspath, f) | ||||
if os.path.isdir(fullpath): | ||||
# recursion here | ||||
ret = isemptydir(fullpath) | ||||
if not ret: | ||||
return False | ||||
else: | ||||
return False | ||||
return True | ||||
def isignoreddir(localpath): | ||||
Denis Laxalde
|
r33657 | """Return True if `localpath` directory is ignored or contains only | ||
ignored files and should hence be considered ignored. | ||||
Pulkit Goyal
|
r33548 | """ | ||
dirpath = os.path.join(root, localpath) | ||||
Denis Laxalde
|
r33657 | if ignorefn(dirpath): | ||
return True | ||||
Pulkit Goyal
|
r33548 | for f in os.listdir(dirpath): | ||
filepath = os.path.join(dirpath, f) | ||||
if os.path.isdir(filepath): | ||||
# recursion here | ||||
ret = isignoreddir(os.path.join(localpath, f)) | ||||
if not ret: | ||||
return False | ||||
else: | ||||
if not ignorefn(os.path.join(localpath, f)): | ||||
return False | ||||
return True | ||||
def absentones(removedfiles, missingfiles): | ||||
""" | ||||
Returns a dictionary of directories with files in it which are either | ||||
removed or missing (deleted) in them. | ||||
""" | ||||
absentdir = {} | ||||
absentfiles = removedfiles + missingfiles | ||||
while absentfiles: | ||||
f = absentfiles.pop() | ||||
par = os.path.dirname(f) | ||||
if par == '': | ||||
continue | ||||
# we need to store files rather than number of files as some files | ||||
# or subdirectories in a directory can be counted twice. This is | ||||
# also we have used sets here. | ||||
try: | ||||
absentdir[par].add(f) | ||||
except KeyError: | ||||
absentdir[par] = set([f]) | ||||
absentfiles.append(par) | ||||
return absentdir | ||||
indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6} | ||||
# get a dictonary of directories and files which are missing as os.listdir() | ||||
# won't be able to list them. | ||||
absentdir = absentones(statlist[2], statlist[3]) | ||||
finalrs = [[]] * len(indexes) | ||||
didsomethingchanged = False | ||||
# dictionary to store number of files and subdir in a directory so that we | ||||
# don't compute that again. | ||||
lencache = {} | ||||
for st in pycompat.bytestr(status): | ||||
try: | ||||
ind = indexes[st] | ||||
except KeyError: | ||||
# TODO: Need a better error message here | ||||
raise error.Abort("'%s' not recognized" % st) | ||||
sfiles = statlist[ind] | ||||
if not sfiles: | ||||
continue | ||||
pardict = {} | ||||
for a in sfiles: | ||||
par = os.path.dirname(a) | ||||
pardict.setdefault(par, []).append(a) | ||||
rs = [] | ||||
newls = [] | ||||
for par, files in pardict.iteritems(): | ||||
lenpar = numfiles(par) | ||||
if lenpar == len(files): | ||||
newls.append(par) | ||||
if not newls: | ||||
continue | ||||
while newls: | ||||
newel = newls.pop() | ||||
if newel == '': | ||||
continue | ||||
parn = os.path.dirname(newel) | ||||
pardict[newel] = [] | ||||
# Adding pycompat.ossep as newel is a directory. | ||||
pardict.setdefault(parn, []).append(newel + pycompat.ossep) | ||||
lenpar = numfiles(parn) | ||||
if lenpar == len(pardict[parn]): | ||||
newls.append(parn) | ||||
# dict.values() for Py3 compatibility | ||||
for files in pardict.values(): | ||||
rs.extend(files) | ||||
rs.sort() | ||||
finalrs[ind] = rs | ||||
didsomethingchanged = True | ||||
# If nothing is changed, make sure the order of files is preserved. | ||||
if not didsomethingchanged: | ||||
return statlist | ||||
for x in xrange(len(indexes)): | ||||
if not finalrs[x]: | ||||
finalrs[x] = statlist[x] | ||||
return finalrs | ||||
Pulkit Goyal
|
r33766 | def _commentlines(raw): | ||
'''Surround lineswith a comment char and a new line''' | ||||
lines = raw.splitlines() | ||||
commentedlines = ['# %s' % line for line in lines] | ||||
return '\n'.join(commentedlines) + '\n' | ||||
def _conflictsmsg(repo): | ||||
# avoid merge cycle | ||||
from . import merge as mergemod | ||||
mergestate = mergemod.mergestate.read(repo) | ||||
if not mergestate.active(): | ||||
return | ||||
m = scmutil.match(repo[None]) | ||||
Martin von Zweigbergk
|
r34003 | unresolvedlist = [f for f in mergestate.unresolved() if m(f)] | ||
Pulkit Goyal
|
r33766 | if unresolvedlist: | ||
mergeliststr = '\n'.join( | ||||
[' %s' % os.path.relpath( | ||||
os.path.join(repo.root, path), | ||||
pycompat.getcwd()) for path in unresolvedlist]) | ||||
msg = _('''Unresolved merge conflicts: | ||||
%s | ||||
To mark files as resolved: hg resolve --mark FILE''') % mergeliststr | ||||
else: | ||||
msg = _('No unresolved merge conflicts.') | ||||
return _commentlines(msg) | ||||
def _helpmessage(continuecmd, abortcmd): | ||||
msg = _('To continue: %s\n' | ||||
'To abort: %s') % (continuecmd, abortcmd) | ||||
return _commentlines(msg) | ||||
def _rebasemsg(): | ||||
return _helpmessage('hg rebase --continue', 'hg rebase --abort') | ||||
def _histeditmsg(): | ||||
return _helpmessage('hg histedit --continue', 'hg histedit --abort') | ||||
def _unshelvemsg(): | ||||
return _helpmessage('hg unshelve --continue', 'hg unshelve --abort') | ||||
def _updatecleanmsg(dest=None): | ||||
warning = _('warning: this will discard uncommitted changes') | ||||
return 'hg update --clean %s (%s)' % (dest or '.', warning) | ||||
def _graftmsg(): | ||||
# tweakdefaults requires `update` to have a rev hence the `.` | ||||
return _helpmessage('hg graft --continue', _updatecleanmsg()) | ||||
def _mergemsg(): | ||||
# tweakdefaults requires `update` to have a rev hence the `.` | ||||
return _helpmessage('hg commit', _updatecleanmsg()) | ||||
def _bisectmsg(): | ||||
msg = _('To mark the changeset good: hg bisect --good\n' | ||||
'To mark the changeset bad: hg bisect --bad\n' | ||||
'To abort: hg bisect --reset\n') | ||||
return _commentlines(msg) | ||||
def fileexistspredicate(filename): | ||||
return lambda repo: repo.vfs.exists(filename) | ||||
def _mergepredicate(repo): | ||||
return len(repo[None].parents()) > 1 | ||||
STATES = ( | ||||
# (state, predicate to detect states, helpful message function) | ||||
('histedit', fileexistspredicate('histedit-state'), _histeditmsg), | ||||
('bisect', fileexistspredicate('bisect.state'), _bisectmsg), | ||||
('graft', fileexistspredicate('graftstate'), _graftmsg), | ||||
('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg), | ||||
('rebase', fileexistspredicate('rebasestate'), _rebasemsg), | ||||
# The merge state is part of a list that will be iterated over. | ||||
# They need to be last because some of the other unfinished states may also | ||||
# be in a merge or update state (eg. rebase, histedit, graft, etc). | ||||
# We want those to have priority. | ||||
('merge', _mergepredicate, _mergemsg), | ||||
) | ||||
def _getrepostate(repo): | ||||
# experimental config: commands.status.skipstates | ||||
skip = set(repo.ui.configlist('commands', 'status.skipstates')) | ||||
for state, statedetectionpredicate, msgfn in STATES: | ||||
if state in skip: | ||||
continue | ||||
if statedetectionpredicate(repo): | ||||
return (state, statedetectionpredicate, msgfn) | ||||
def morestatus(repo, fm): | ||||
statetuple = _getrepostate(repo) | ||||
label = 'status.morestatus' | ||||
if statetuple: | ||||
fm.startitem() | ||||
state, statedetectionpredicate, helpfulmsg = statetuple | ||||
statemsg = _('The repository is in an unfinished *%s* state.') % state | ||||
fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label) | ||||
conmsg = _conflictsmsg(repo) | ||||
Pulkit Goyal
|
r33885 | if conmsg: | ||
fm.write('conflictsmsg', '%s\n', conmsg, label=label) | ||||
Pulkit Goyal
|
r33766 | if helpfulmsg: | ||
helpmsg = helpfulmsg() | ||||
fm.write('helpmsg', '%s\n', helpmsg, label=label) | ||||
Matt Mackall
|
r7213 | def findpossible(cmd, table, strict=False): | ||
Matt Mackall
|
r4549 | """ | ||
Return cmd -> (aliases, command table entry) | ||||
for each matching command. | ||||
Return debug commands (or their aliases) only if no normal command matches. | ||||
""" | ||||
choice = {} | ||||
debugchoice = {} | ||||
Matt Mackall
|
r15600 | |||
if cmd in table: | ||||
# short-circuit exact matches, "log" alias beats "^log|history" | ||||
keys = [cmd] | ||||
else: | ||||
keys = table.keys() | ||||
Augie Fackler
|
r24222 | allcmds = [] | ||
Matt Mackall
|
r15600 | for e in keys: | ||
Brendan Cully
|
r10401 | aliases = parsealiases(e) | ||
Augie Fackler
|
r24222 | allcmds.extend(aliases) | ||
Matt Mackall
|
r4549 | found = None | ||
if cmd in aliases: | ||||
found = cmd | ||||
Matt Mackall
|
r7213 | elif not strict: | ||
Matt Mackall
|
r4549 | for a in aliases: | ||
if a.startswith(cmd): | ||||
found = a | ||||
break | ||||
if found is not None: | ||||
if aliases[0].startswith("debug") or found.startswith("debug"): | ||||
Matt Mackall
|
r5178 | debugchoice[found] = (aliases, table[e]) | ||
Matt Mackall
|
r4549 | else: | ||
Matt Mackall
|
r5178 | choice[found] = (aliases, table[e]) | ||
Matt Mackall
|
r4549 | |||
if not choice and debugchoice: | ||||
choice = debugchoice | ||||
Augie Fackler
|
r24222 | return choice, allcmds | ||
Matt Mackall
|
r4549 | |||
Matt Mackall
|
r7213 | def findcmd(cmd, table, strict=True): | ||
Matt Mackall
|
r4549 | """Return (aliases, command table entry) for command string.""" | ||
Augie Fackler
|
r24222 | choice, allcmds = findpossible(cmd, table, strict) | ||
Matt Mackall
|
r4549 | |||
Christian Ebert
|
r5915 | if cmd in choice: | ||
Matt Mackall
|
r4549 | return choice[cmd] | ||
if len(choice) > 1: | ||||
Augie Fackler
|
r32528 | clist = sorted(choice) | ||
Matt Mackall
|
r7643 | raise error.AmbiguousCommand(cmd, clist) | ||
Matt Mackall
|
r4549 | |||
if choice: | ||||
Pulkit Goyal
|
r32862 | return list(choice.values())[0] | ||
Matt Mackall
|
r4549 | |||
Augie Fackler
|
r24222 | raise error.UnknownCommand(cmd, allcmds) | ||
Matt Mackall
|
r4549 | |||
Brendan Cully
|
r10402 | def findrepo(p): | ||
while not os.path.isdir(os.path.join(p, ".hg")): | ||||
oldp, p = p, os.path.dirname(p) | ||||
if p == oldp: | ||||
return None | ||||
return p | ||||
Valters Vingolds
|
r30755 | def bailifchanged(repo, merge=True, hint=None): | ||
""" enforce the precondition that working directory must be clean. | ||||
'merge' can be set to false if a pending uncommitted merge should be | ||||
ignored (such as when 'update --check' runs). | ||||
'hint' is the usual hint given to Abort exception. | ||||
""" | ||||
FUJIWARA Katsunori
|
r24472 | if merge and repo.dirstate.p2() != nullid: | ||
Valters Vingolds
|
r30755 | raise error.Abort(_('outstanding uncommitted merge'), hint=hint) | ||
Matt Mackall
|
r4549 | modified, added, removed, deleted = repo.status()[:4] | ||
if modified or added or removed or deleted: | ||||
Valters Vingolds
|
r30755 | raise error.Abort(_('uncommitted changes'), hint=hint) | ||
Eric Roshan Eisner
|
r15231 | ctx = repo[None] | ||
Mads Kiilerich
|
r18364 | for s in sorted(ctx.substate): | ||
Valters Vingolds
|
r30755 | ctx.sub(s).bailifchanged(hint=hint) | ||
Matt Mackall
|
r4549 | |||
Idan Kamara
|
r14635 | def logmessage(ui, opts): | ||
Matt Mackall
|
r4549 | """ get the log message according to -m and -l option """ | ||
Alexander Solovyov
|
r7667 | message = opts.get('message') | ||
logfile = opts.get('logfile') | ||||
Matt Mackall
|
r4549 | |||
if message and logfile: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('options --message and --logfile are mutually ' | ||
Matt Mackall
|
r4549 | 'exclusive')) | ||
if not message and logfile: | ||||
try: | ||||
Yuya Nishihara
|
r32618 | if isstdiofilename(logfile): | ||
Idan Kamara
|
r14635 | message = ui.fin.read() | ||
Matt Mackall
|
r4549 | else: | ||
Patrick Mezard
|
r14249 | message = '\n'.join(util.readfile(logfile).splitlines()) | ||
Gregory Szorc
|
r25660 | except IOError as inst: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_("can't read commit message '%s': %s") % | ||
Augie Fackler
|
r34024 | (logfile, encoding.strtolocal(inst.strerror))) | ||
Matt Mackall
|
r4549 | return message | ||
Mads Kiilerich
|
r24180 | def mergeeditform(ctxorbool, baseformname): | ||
"""return appropriate editform name (referencing a committemplate) | ||||
'ctxorbool' is either a ctx to be committed, or a bool indicating whether | ||||
FUJIWARA Katsunori
|
r22248 | merging is committed. | ||
Mads Kiilerich
|
r24180 | This returns baseformname with '.merge' appended if it is a merge, | ||
otherwise '.normal' is appended. | ||||
FUJIWARA Katsunori
|
r22248 | """ | ||
if isinstance(ctxorbool, bool): | ||||
if ctxorbool: | ||||
Mads Kiilerich
|
r24180 | return baseformname + ".merge" | ||
FUJIWARA Katsunori
|
r22248 | elif 1 < len(ctxorbool.parents()): | ||
Mads Kiilerich
|
r24180 | return baseformname + ".merge" | ||
return baseformname + ".normal" | ||||
FUJIWARA Katsunori
|
r22248 | |||
FUJIWARA Katsunori
|
r21999 | def getcommiteditor(edit=False, finishdesc=None, extramsg=None, | ||
editform='', **opts): | ||||
FUJIWARA Katsunori
|
r21419 | """get appropriate commit message editor according to '--edit' option | ||
'finishdesc' is a function to be called with edited commit message | ||||
(= 'description' of the new changeset) just after editing, but | ||||
before checking empty-ness. It should return actual text to be | ||||
stored into history. This allows to change description before | ||||
storing. | ||||
'extramsg' is a extra message to be shown in the editor instead of | ||||
'Leave message empty to abort commit' line. 'HG: ' prefix and EOL | ||||
is automatically added. | ||||
FUJIWARA Katsunori
|
r21999 | 'editform' is a dot-separated list of names, to distinguish | ||
the purpose of commit text editing. | ||||
FUJIWARA Katsunori
|
r21419 | 'getcommiteditor' returns 'commitforceeditor' regardless of | ||
'edit', if one of 'finishdesc' or 'extramsg' is specified, because | ||||
they are specific for usage in MQ. | ||||
""" | ||||
if edit or finishdesc or extramsg: | ||||
return lambda r, c, s: commitforceeditor(r, c, s, | ||||
finishdesc=finishdesc, | ||||
FUJIWARA Katsunori
|
r21999 | extramsg=extramsg, | ||
editform=editform) | ||||
elif editform: | ||||
return lambda r, c, s: commiteditor(r, c, s, editform=editform) | ||||
FUJIWARA Katsunori
|
r21405 | else: | ||
return commiteditor | ||||
Thomas Arendsen Hein
|
r6190 | def loglimit(opts): | ||
"""get the log limit according to option -l/--limit""" | ||||
limit = opts.get('limit') | ||||
if limit: | ||||
try: | ||||
limit = int(limit) | ||||
except ValueError: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('limit must be a positive integer')) | ||
Matt Mackall
|
r10282 | if limit <= 0: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('limit must be positive')) | ||
Thomas Arendsen Hein
|
r6190 | else: | ||
Nicolas Dumazet
|
r10111 | limit = None | ||
Thomas Arendsen Hein
|
r6190 | return limit | ||
Andrzej Bieniek
|
r14986 | def makefilename(repo, pat, node, desc=None, | ||
Vadim Gelfer
|
r2874 | total=None, seqno=None, revwidth=None, pathname=None): | ||
node_expander = { | ||||
'H': lambda: hex(node), | ||||
'R': lambda: str(repo.changelog.rev(node)), | ||||
'h': lambda: short(node), | ||||
Andrzej Bieniek
|
r14986 | 'm': lambda: re.sub('[^\w]', '_', str(desc)) | ||
Vadim Gelfer
|
r2874 | } | ||
expander = { | ||||
'%': lambda: '%', | ||||
'b': lambda: os.path.basename(repo.root), | ||||
} | ||||
try: | ||||
if node: | ||||
expander.update(node_expander) | ||||
Alexis S. L. Carvalho
|
r4836 | if node: | ||
Vadim Gelfer
|
r2874 | expander['r'] = (lambda: | ||
Alexis S. L. Carvalho
|
r4836 | str(repo.changelog.rev(node)).zfill(revwidth or 0)) | ||
Vadim Gelfer
|
r2874 | if total is not None: | ||
expander['N'] = lambda: str(total) | ||||
if seqno is not None: | ||||
expander['n'] = lambda: str(seqno) | ||||
if total is not None and seqno is not None: | ||||
Thomas Arendsen Hein
|
r3673 | expander['n'] = lambda: str(seqno).zfill(len(str(total))) | ||
Vadim Gelfer
|
r2874 | if pathname is not None: | ||
expander['s'] = lambda: os.path.basename(pathname) | ||||
expander['d'] = lambda: os.path.dirname(pathname) or '.' | ||||
expander['p'] = lambda: pathname | ||||
newname = [] | ||||
patlen = len(pat) | ||||
i = 0 | ||||
while i < patlen: | ||||
Pulkit Goyal
|
r32153 | c = pat[i:i + 1] | ||
Vadim Gelfer
|
r2874 | if c == '%': | ||
i += 1 | ||||
Pulkit Goyal
|
r32153 | c = pat[i:i + 1] | ||
Vadim Gelfer
|
r2874 | c = expander[c]() | ||
newname.append(c) | ||||
i += 1 | ||||
return ''.join(newname) | ||||
Gregory Szorc
|
r25660 | except KeyError as inst: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_("invalid format spec '%%%s' in output filename") % | ||
Thomas Arendsen Hein
|
r3072 | inst.args[0]) | ||
Vadim Gelfer
|
r2874 | |||
Yuya Nishihara
|
r32539 | def isstdiofilename(pat): | ||
"""True if the given pat looks like a filename denoting stdin/stdout""" | ||||
return not pat or pat == '-' | ||||
Yuya Nishihara
|
r27418 | class _unclosablefile(object): | ||
def __init__(self, fp): | ||||
self._fp = fp | ||||
def close(self): | ||||
pass | ||||
def __iter__(self): | ||||
return iter(self._fp) | ||||
def __getattr__(self, attr): | ||||
return getattr(self._fp, attr) | ||||
Mads Kiilerich
|
r30142 | def __enter__(self): | ||
return self | ||||
def __exit__(self, exc_type, exc_value, exc_tb): | ||||
pass | ||||
Andrzej Bieniek
|
r14986 | def makefileobj(repo, pat, node=None, desc=None, total=None, | ||
Yuya Nishihara
|
r19944 | seqno=None, revwidth=None, mode='wb', modemap=None, | ||
Augie Fackler
|
r18613 | pathname=None): | ||
Ronny Pfannschmidt
|
r7319 | |||
Adrian Buehlmann
|
r13769 | writable = mode not in ('r', 'rb') | ||
Ronny Pfannschmidt
|
r7319 | |||
Yuya Nishihara
|
r32539 | if isstdiofilename(pat): | ||
Jordi Gutiérrez Hermoso
|
r24306 | if writable: | ||
fp = repo.ui.fout | ||||
else: | ||||
fp = repo.ui.fin | ||||
Yuya Nishihara
|
r27419 | return _unclosablefile(fp) | ||
Augie Fackler
|
r18613 | fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname) | ||
Yuya Nishihara
|
r19944 | if modemap is not None: | ||
mode = modemap.get(fn, mode) | ||||
if mode == 'wb': | ||||
modemap[fn] = 'ab' | ||||
Augie Fackler
|
r18613 | return open(fn, mode) | ||
Vadim Gelfer
|
r2882 | |||
Sune Foldager
|
r14323 | def openrevlog(repo, cmd, file_, opts): | ||
"""opens the changelog, manifest, a filelog or a given revlog""" | ||||
cl = opts['changelog'] | ||||
mf = opts['manifest'] | ||||
Martin von Zweigbergk
|
r25119 | dir = opts['dir'] | ||
Sune Foldager
|
r14323 | msg = None | ||
if cl and mf: | ||||
msg = _('cannot specify --changelog and --manifest at the same time') | ||||
Martin von Zweigbergk
|
r25119 | elif cl and dir: | ||
msg = _('cannot specify --changelog and --dir at the same time') | ||||
Martin von Zweigbergk
|
r29427 | elif cl or mf or dir: | ||
Sune Foldager
|
r14323 | if file_: | ||
msg = _('cannot specify filename with --changelog or --manifest') | ||||
elif not repo: | ||||
Martin von Zweigbergk
|
r25119 | msg = _('cannot specify --changelog or --manifest or --dir ' | ||
Sune Foldager
|
r14323 | 'without a repository') | ||
if msg: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(msg) | ||
Sune Foldager
|
r14323 | |||
r = None | ||||
if repo: | ||||
if cl: | ||||
Matt Mackall
|
r21033 | r = repo.unfiltered().changelog | ||
Martin von Zweigbergk
|
r25119 | elif dir: | ||
if 'treemanifest' not in repo.requirements: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("--dir can only be used on repos with " | ||
Martin von Zweigbergk
|
r25119 | "treemanifest enabled")) | ||
Durham Goode
|
r30371 | dirlog = repo.manifestlog._revlog.dirlog(dir) | ||
Martin von Zweigbergk
|
r25119 | if len(dirlog): | ||
r = dirlog | ||||
Sune Foldager
|
r14323 | elif mf: | ||
Durham Goode
|
r30375 | r = repo.manifestlog._revlog | ||
Sune Foldager
|
r14323 | elif file_: | ||
filelog = repo.file(file_) | ||||
if len(filelog): | ||||
r = filelog | ||||
if not r: | ||||
if not file_: | ||||
raise error.CommandError(cmd, _('invalid arguments')) | ||||
if not os.path.isfile(file_): | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("revlog '%s' not found") % file_) | ||
Pierre-Yves David
|
r31237 | r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), | ||
Sune Foldager
|
r14323 | file_[:-2] + ".i") | ||
return r | ||||
Matt Mackall
|
r5610 | def copy(ui, repo, pats, opts, rename=False): | ||
Matt Mackall
|
r5589 | # called with the repo lock held | ||
# | ||||
# hgsep => pathname that uses "/" to separate directories | ||||
# ossep => pathname that uses os.sep to separate directories | ||||
cwd = repo.getcwd() | ||||
targets = {} | ||||
Matt Mackall
|
r5607 | after = opts.get("after") | ||
dryrun = opts.get("dry_run") | ||||
Dirkjan Ochtman
|
r11303 | wctx = repo[None] | ||
Matt Mackall
|
r5589 | |||
Matt Mackall
|
r5605 | def walkpat(pat): | ||
srcs = [] | ||||
Jordi Gutiérrez Hermoso
|
r24306 | if after: | ||
badstates = '?' | ||||
else: | ||||
badstates = '?r' | ||||
Martin von Zweigbergk
|
r32382 | m = scmutil.match(wctx, [pat], opts, globbed=True) | ||
for abs in wctx.walk(m): | ||||
Matt Mackall
|
r5605 | state = repo.dirstate[abs] | ||
Matt Mackall
|
r6584 | rel = m.rel(abs) | ||
exact = m.exact(abs) | ||||
Peter Arrenbrecht
|
r11223 | if state in badstates: | ||
Matt Mackall
|
r5605 | if exact and state == '?': | ||
ui.warn(_('%s: not copying - file is not managed\n') % rel) | ||||
if exact and state == 'r': | ||||
ui.warn(_('%s: not copying - file has been marked for' | ||||
' remove\n') % rel) | ||||
continue | ||||
# abs: hgsep | ||||
# rel: ossep | ||||
srcs.append((abs, rel, exact)) | ||||
return srcs | ||||
Matt Mackall
|
r5589 | |||
# abssrc: hgsep | ||||
# relsrc: ossep | ||||
# otarget: ossep | ||||
Matt Mackall
|
r5605 | def copyfile(abssrc, relsrc, otarget, exact): | ||
Augie Fackler
|
r20033 | abstarget = pathutil.canonpath(repo.root, cwd, otarget) | ||
Patrick Mezard
|
r16542 | if '/' in abstarget: | ||
# We cannot normalize abstarget itself, this would prevent | ||||
# case only renames, like a => A. | ||||
abspath, absname = abstarget.rsplit('/', 1) | ||||
abstarget = repo.dirstate.normalize(abspath) + '/' + absname | ||||
Matt Mackall
|
r5589 | reltarget = repo.pathto(abstarget, cwd) | ||
Matt Mackall
|
r5607 | target = repo.wjoin(abstarget) | ||
Matt Mackall
|
r5589 | src = repo.wjoin(abssrc) | ||
Matt Mackall
|
r5608 | state = repo.dirstate[abstarget] | ||
Matt Mackall
|
r5607 | |||
Adrian Buehlmann
|
r13962 | scmutil.checkportable(ui, abstarget) | ||
Adrian Buehlmann
|
r13945 | |||
Matt Mackall
|
r5607 | # check for collisions | ||
prevsrc = targets.get(abstarget) | ||||
Matt Mackall
|
r5589 | if prevsrc is not None: | ||
ui.warn(_('%s: not overwriting - %s collides with %s\n') % | ||||
(reltarget, repo.pathto(abssrc, cwd), | ||||
repo.pathto(prevsrc, cwd))) | ||||
return | ||||
Matt Mackall
|
r5607 | |||
# check for overwrites | ||||
Patrick Mezard
|
r12342 | exists = os.path.lexists(target) | ||
Matt Mackall
|
r16283 | samefile = False | ||
if exists and abssrc != abstarget: | ||||
if (repo.dirstate.normalize(abssrc) == | ||||
repo.dirstate.normalize(abstarget)): | ||||
if not rename: | ||||
ui.warn(_("%s: can't copy - same file\n") % reltarget) | ||||
return | ||||
exists = False | ||||
samefile = True | ||||
Martin Geisler
|
r8117 | if not after and exists or after and state in 'mn': | ||
Matt Mackall
|
r5589 | if not opts['force']: | ||
Augie Fackler
|
r30151 | if state in 'mn': | ||
msg = _('%s: not overwriting - file already committed\n') | ||||
if after: | ||||
flags = '--after --force' | ||||
else: | ||||
flags = '--force' | ||||
if rename: | ||||
hint = _('(hg rename %s to replace the file by ' | ||||
'recording a rename)\n') % flags | ||||
else: | ||||
hint = _('(hg copy %s to replace the file by ' | ||||
'recording a copy)\n') % flags | ||||
else: | ||||
msg = _('%s: not overwriting - file exists\n') | ||||
if rename: | ||||
hint = _('(hg rename --after to record the rename)\n') | ||||
else: | ||||
hint = _('(hg copy --after to record the copy)\n') | ||||
ui.warn(msg % reltarget) | ||||
ui.warn(hint) | ||||
Matt Mackall
|
r5589 | return | ||
Matt Mackall
|
r5607 | |||
if after: | ||||
Matt Mackall
|
r5608 | if not exists: | ||
Steve Losh
|
r11152 | if rename: | ||
ui.warn(_('%s: not recording move - %s does not exist\n') % | ||||
(relsrc, reltarget)) | ||||
else: | ||||
ui.warn(_('%s: not recording copy - %s does not exist\n') % | ||||
(relsrc, reltarget)) | ||||
Matt Mackall
|
r5589 | return | ||
Matt Mackall
|
r5608 | elif not dryrun: | ||
Matt Mackall
|
r5589 | try: | ||
Matt Mackall
|
r5608 | if exists: | ||
os.unlink(target) | ||||
targetdir = os.path.dirname(target) or '.' | ||||
if not os.path.isdir(targetdir): | ||||
os.makedirs(targetdir) | ||||
Matt Mackall
|
r16283 | if samefile: | ||
tmp = target + "~hgrename" | ||||
os.rename(src, tmp) | ||||
os.rename(tmp, target) | ||||
else: | ||||
util.copyfile(src, target) | ||||
Adrian Buehlmann
|
r14518 | srcexists = True | ||
Gregory Szorc
|
r25660 | except IOError as inst: | ||
Matt Mackall
|
r5589 | if inst.errno == errno.ENOENT: | ||
Yuya Nishihara
|
r24364 | ui.warn(_('%s: deleted in working directory\n') % relsrc) | ||
Adrian Buehlmann
|
r14518 | srcexists = False | ||
Matt Mackall
|
r5589 | else: | ||
ui.warn(_('%s: cannot copy - %s\n') % | ||||
Augie Fackler
|
r34024 | (relsrc, encoding.strtolocal(inst.strerror))) | ||
Matt Mackall
|
r5606 | return True # report a failure | ||
Matt Mackall
|
r5607 | |||
Matt Mackall
|
r5589 | if ui.verbose or not exact: | ||
Martin Geisler
|
r7894 | if rename: | ||
ui.status(_('moving %s to %s\n') % (relsrc, reltarget)) | ||||
else: | ||||
ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) | ||||
Matt Mackall
|
r5608 | |||
Matt Mackall
|
r5589 | targets[abstarget] = abssrc | ||
Matt Mackall
|
r5607 | |||
# fix up dirstate | ||||
Matt Mackall
|
r14321 | scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget, | ||
dryrun=dryrun, cwd=cwd) | ||||
Matt Mackall
|
r5610 | if rename and not dryrun: | ||
Matt Mackall
|
r16283 | if not after and srcexists and not samefile: | ||
Mads Kiilerich
|
r31309 | repo.wvfs.unlinkpath(abssrc) | ||
Adrian Buehlmann
|
r14518 | wctx.forget([abssrc]) | ||
Matt Mackall
|
r5589 | |||
# pat: ossep | ||||
# dest ossep | ||||
# srcs: list of (hgsep, hgsep, ossep, bool) | ||||
# return: function that takes hgsep and returns ossep | ||||
def targetpathfn(pat, dest, srcs): | ||||
if os.path.isdir(pat): | ||||
Augie Fackler
|
r20033 | abspfx = pathutil.canonpath(repo.root, cwd, pat) | ||
Matt Mackall
|
r5589 | abspfx = util.localpath(abspfx) | ||
if destdirexists: | ||||
striplen = len(os.path.split(abspfx)[0]) | ||||
else: | ||||
striplen = len(abspfx) | ||||
if striplen: | ||||
Pulkit Goyal
|
r30615 | striplen += len(pycompat.ossep) | ||
Matt Mackall
|
r5589 | res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) | ||
elif destdirexists: | ||||
res = lambda p: os.path.join(dest, | ||||
os.path.basename(util.localpath(p))) | ||||
else: | ||||
res = lambda p: dest | ||||
return res | ||||
# pat: ossep | ||||
# dest ossep | ||||
# srcs: list of (hgsep, hgsep, ossep, bool) | ||||
# return: function that takes hgsep and returns ossep | ||||
def targetpathafterfn(pat, dest, srcs): | ||||
Martin Geisler
|
r12085 | if matchmod.patkind(pat): | ||
Matt Mackall
|
r5589 | # a mercurial pattern | ||
res = lambda p: os.path.join(dest, | ||||
os.path.basename(util.localpath(p))) | ||||
else: | ||||
Augie Fackler
|
r20033 | abspfx = pathutil.canonpath(repo.root, cwd, pat) | ||
Matt Mackall
|
r5589 | if len(abspfx) < len(srcs[0][0]): | ||
# A directory. Either the target path contains the last | ||||
# component of the source path or it does not. | ||||
def evalpath(striplen): | ||||
score = 0 | ||||
for s in srcs: | ||||
t = os.path.join(dest, util.localpath(s[0])[striplen:]) | ||||
Patrick Mezard
|
r12357 | if os.path.lexists(t): | ||
Matt Mackall
|
r5589 | score += 1 | ||
return score | ||||
abspfx = util.localpath(abspfx) | ||||
striplen = len(abspfx) | ||||
if striplen: | ||||
Pulkit Goyal
|
r30615 | striplen += len(pycompat.ossep) | ||
Matt Mackall
|
r5589 | if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])): | ||
score = evalpath(striplen) | ||||
striplen1 = len(os.path.split(abspfx)[0]) | ||||
if striplen1: | ||||
Pulkit Goyal
|
r30615 | striplen1 += len(pycompat.ossep) | ||
Matt Mackall
|
r5589 | if evalpath(striplen1) > score: | ||
striplen = striplen1 | ||||
res = lambda p: os.path.join(dest, | ||||
util.localpath(p)[striplen:]) | ||||
else: | ||||
# a file | ||||
if destdirexists: | ||||
res = lambda p: os.path.join(dest, | ||||
os.path.basename(util.localpath(p))) | ||||
else: | ||||
res = lambda p: dest | ||||
return res | ||||
Matt Mackall
|
r14321 | pats = scmutil.expandpats(pats) | ||
Matt Mackall
|
r5589 | if not pats: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('no source or destination specified')) | ||
Matt Mackall
|
r5589 | if len(pats) == 1: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('no destination specified')) | ||
Matt Mackall
|
r5589 | dest = pats.pop() | ||
Alexis S. L. Carvalho
|
r6258 | destdirexists = os.path.isdir(dest) and not os.path.islink(dest) | ||
Matt Mackall
|
r5589 | if not destdirexists: | ||
Martin Geisler
|
r12085 | if len(pats) > 1 or matchmod.patkind(pats[0]): | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('with multiple sources, destination must be an ' | ||
Matt Mackall
|
r5589 | 'existing directory')) | ||
Shun-ichi GOTO
|
r5843 | if util.endswithsep(dest): | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('destination %s is not a directory') % dest) | ||
Matt Mackall
|
r5607 | |||
tfn = targetpathfn | ||||
if after: | ||||
Matt Mackall
|
r5589 | tfn = targetpathafterfn | ||
copylist = [] | ||||
for pat in pats: | ||||
Matt Mackall
|
r5605 | srcs = walkpat(pat) | ||
Matt Mackall
|
r5589 | if not srcs: | ||
continue | ||||
copylist.append((tfn(pat, dest, srcs), srcs)) | ||||
if not copylist: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('no files to copy')) | ||
Matt Mackall
|
r5589 | |||
Matt Mackall
|
r5606 | errors = 0 | ||
Matt Mackall
|
r5589 | for targetpath, srcs in copylist: | ||
Matt Mackall
|
r5605 | for abssrc, relsrc, exact in srcs: | ||
Matt Mackall
|
r5606 | if copyfile(abssrc, relsrc, targetpath(abssrc), exact): | ||
errors += 1 | ||||
Matt Mackall
|
r5589 | |||
if errors: | ||||
ui.warn(_('(consider using --after)\n')) | ||||
Matt Mackall
|
r5609 | |||
Matt Mackall
|
r11177 | return errors != 0 | ||
Matt Mackall
|
r5589 | |||
Pierre-Yves David
|
r26561 | ## facility to let extension process additional data into an import patch | ||
# list of identifier to be executed in order | ||||
extrapreimport = [] # run before commit | ||||
Pierre-Yves David
|
r26562 | extrapostimport = [] # run after commit | ||
Pierre-Yves David
|
r26561 | # mapping from identifier to actual import function | ||
# | ||||
# 'preimport' are run before the commit is made and are provided the following | ||||
# arguments: | ||||
# - repo: the localrepository instance, | ||||
# - patchdata: data extracted from patch header (cf m.patch.patchheadermap), | ||||
Mads Kiilerich
|
r26781 | # - extra: the future extra dictionary of the changeset, please mutate it, | ||
Pierre-Yves David
|
r26561 | # - opts: the import options. | ||
# XXX ideally, we would just pass an ctx ready to be computed, that would allow | ||||
# mutation of in memory commit and more. Feel free to rework the code to get | ||||
# there. | ||||
extrapreimportmap = {} | ||||
Pierre-Yves David
|
r26562 | # 'postimport' are run after the commit is made and are provided the following | ||
# argument: | ||||
# - ctx: the changectx created by import. | ||||
extrapostimportmap = {} | ||||
Pierre-Yves David
|
r26561 | |||
Pierre-Yves David
|
r20500 | def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc): | ||
"""Utility function used by commands.import to import a single patch | ||||
This function is explicitly defined here to help the evolve extension to | ||||
wrap this part of the import logic. | ||||
The API is currently a bit ugly because it a simple code translation from | ||||
the import command. Feel free to make it better. | ||||
:hunk: a patch (as a binary string) | ||||
:parents: nodes that will be parent of the created commit | ||||
:opts: the full dict of option passed to the import command | ||||
:msgs: list to save commit message to. | ||||
(used in case we need to save it when failing) | ||||
:updatefunc: a function that update a repo to a given node | ||||
updatefunc(<repo>, <node>) | ||||
""" | ||||
Gregory Szorc
|
r25930 | # avoid cycle context -> subrepo -> cmdutil | ||
Gregory Szorc
|
r28322 | from . import context | ||
Pierre-Yves David
|
r26547 | extractdata = patch.extract(ui, hunk) | ||
tmpname = extractdata.get('filename') | ||||
message = extractdata.get('message') | ||||
timeless
|
r27612 | user = opts.get('user') or extractdata.get('user') | ||
date = opts.get('date') or extractdata.get('date') | ||||
Pierre-Yves David
|
r26547 | branch = extractdata.get('branch') | ||
nodeid = extractdata.get('nodeid') | ||||
p1 = extractdata.get('p1') | ||||
p2 = extractdata.get('p2') | ||||
Pierre-Yves David
|
r20500 | |||
timeless
|
r27613 | nocommit = opts.get('no_commit') | ||
importbranch = opts.get('import_branch') | ||||
Pierre-Yves David
|
r20500 | update = not opts.get('bypass') | ||
strip = opts["strip"] | ||||
Siddharth Agarwal
|
r24259 | prefix = opts["prefix"] | ||
Pierre-Yves David
|
r20500 | sim = float(opts.get('similarity') or 0) | ||
if not tmpname: | ||||
Pierre-Yves David
|
r21553 | return (None, None, False) | ||
Pierre-Yves David
|
r20500 | |||
Pierre-Yves David
|
r21553 | rejects = False | ||
Pierre-Yves David
|
r20500 | try: | ||
cmdline_message = logmessage(ui, opts) | ||||
if cmdline_message: | ||||
# pickup the cmdline msg | ||||
message = cmdline_message | ||||
elif message: | ||||
# pickup the patch msg | ||||
message = message.strip() | ||||
else: | ||||
# launch the editor | ||||
message = None | ||||
ui.debug('message:\n%s\n' % message) | ||||
if len(parents) == 1: | ||||
parents.append(repo[nullid]) | ||||
if opts.get('exact'): | ||||
if not nodeid or not p1: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('not a Mercurial patch')) | ||
Pierre-Yves David
|
r20500 | p1 = repo[p1] | ||
p2 = repo[p2 or nullid] | ||||
elif p2: | ||||
try: | ||||
p1 = repo[p1] | ||||
p2 = repo[p2] | ||||
# Without any options, consider p2 only if the | ||||
# patch is being applied on top of the recorded | ||||
# first parent. | ||||
if p1 != parents[0]: | ||||
p1 = parents[0] | ||||
p2 = repo[nullid] | ||||
except error.RepoError: | ||||
p1, p2 = parents | ||||
FUJIWARA Katsunori
|
r22303 | if p2.node() == nullid: | ||
ui.warn(_("warning: import the patch as a normal revision\n" | ||||
"(use --exact to import the patch as a merge)\n")) | ||||
Pierre-Yves David
|
r20500 | else: | ||
p1, p2 = parents | ||||
n = None | ||||
if update: | ||||
if p1 != parents[0]: | ||||
updatefunc(repo, p1.node()) | ||||
if p2 != parents[1]: | ||||
repo.setparents(p1.node(), p2.node()) | ||||
timeless
|
r27613 | if opts.get('exact') or importbranch: | ||
Pierre-Yves David
|
r20500 | repo.dirstate.setbranch(branch or 'default') | ||
Pierre-Yves David
|
r21553 | partial = opts.get('partial', False) | ||
Pierre-Yves David
|
r20500 | files = set() | ||
Pierre-Yves David
|
r21553 | try: | ||
Siddharth Agarwal
|
r24259 | patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix, | ||
files=files, eolmode=None, similarity=sim / 100.0) | ||||
Gregory Szorc
|
r25660 | except patch.PatchError as e: | ||
Pierre-Yves David
|
r21553 | if not partial: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(str(e)) | ||
Pierre-Yves David
|
r21553 | if partial: | ||
rejects = True | ||||
Pierre-Yves David
|
r20500 | files = list(files) | ||
timeless
|
r27613 | if nocommit: | ||
Pierre-Yves David
|
r20500 | if message: | ||
msgs.append(message) | ||||
else: | ||||
if opts.get('exact') or p2: | ||||
# If you got here, you either use --force and know what | ||||
# you are doing or used --exact or a merge patch while | ||||
# being updated to its first parent. | ||||
m = None | ||||
else: | ||||
m = scmutil.matchfiles(repo, files or []) | ||||
FUJIWARA Katsunori
|
r22250 | editform = mergeeditform(repo[None], 'import.normal') | ||
FUJIWARA Katsunori
|
r22278 | if opts.get('exact'): | ||
editor = None | ||||
else: | ||||
editor = getcommiteditor(editform=editform, **opts) | ||||
Pierre-Yves David
|
r26561 | extra = {} | ||
for idfunc in extrapreimport: | ||||
extrapreimportmap[idfunc](repo, extractdata, extra, opts) | ||||
Jun Wu
|
r31457 | overrides = {} | ||
if partial: | ||||
overrides[('ui', 'allowemptycommit')] = True | ||||
with repo.ui.configoverride(overrides, 'import'): | ||||
timeless
|
r27612 | n = repo.commit(message, user, | ||
date, match=m, | ||||
Pierre-Yves David
|
r26561 | editor=editor, extra=extra) | ||
Pierre-Yves David
|
r26562 | for idfunc in extrapostimport: | ||
extrapostimportmap[idfunc](repo[n]) | ||||
Pierre-Yves David
|
r20500 | else: | ||
timeless
|
r27613 | if opts.get('exact') or importbranch: | ||
Pierre-Yves David
|
r20500 | branch = branch or 'default' | ||
else: | ||||
branch = p1.branch() | ||||
store = patch.filestore() | ||||
try: | ||||
files = set() | ||||
try: | ||||
Siddharth Agarwal
|
r24260 | patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix, | ||
Pierre-Yves David
|
r20500 | files, eolmode=None) | ||
Gregory Szorc
|
r25660 | except patch.PatchError as e: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(str(e)) | ||
FUJIWARA Katsunori
|
r22278 | if opts.get('exact'): | ||
editor = None | ||||
else: | ||||
editor = getcommiteditor(editform='import.bypass') | ||||
Sean Farley
|
r32765 | memctx = context.memctx(repo, (p1.node(), p2.node()), | ||
Pierre-Yves David
|
r20500 | message, | ||
Sean Farley
|
r32765 | files=files, | ||
filectxfn=store, | ||||
user=user, | ||||
date=date, | ||||
branch=branch, | ||||
FUJIWARA Katsunori
|
r22011 | editor=editor) | ||
Pierre-Yves David
|
r20500 | n = memctx.commit() | ||
finally: | ||||
store.close() | ||||
timeless
|
r27613 | if opts.get('exact') and nocommit: | ||
Matt Mackall
|
r22485 | # --exact with --no-commit is still useful in that it does merge | ||
# and branch bits | ||||
ui.warn(_("warning: can't check exact import with --no-commit\n")) | ||||
elif opts.get('exact') and hex(n) != nodeid: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('patch is damaged or loses information')) | ||
timeless
|
r27611 | msg = _('applied to working directory') | ||
Pierre-Yves David
|
r20500 | if n: | ||
# i18n: refers to a short changeset id | ||||
msg = _('created %s') % short(n) | ||||
Pierre-Yves David
|
r21553 | return (msg, n, rejects) | ||
Pierre-Yves David
|
r20500 | finally: | ||
os.unlink(tmpname) | ||||
Pierre-Yves David
|
r26545 | # facility to let extensions include additional data in an exported patch | ||
# list of identifiers to be executed in order | ||||
extraexport = [] | ||||
# mapping from identifier to actual export function | ||||
# function as to return a string to be added to the header or None | ||||
# it is given two arguments (sequencenumber, changectx) | ||||
extraexportmap = {} | ||||
Augie Fackler
|
r32433 | def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts): | ||
Yuya Nishihara
|
r32660 | node = scmutil.binnode(ctx) | ||
Augie Fackler
|
r32433 | parents = [p.node() for p in ctx.parents() if p] | ||
branch = ctx.branch() | ||||
if switch_parent: | ||||
parents.reverse() | ||||
if parents: | ||||
prev = parents[0] | ||||
else: | ||||
prev = nullid | ||||
write("# HG changeset patch\n") | ||||
write("# User %s\n" % ctx.user()) | ||||
write("# Date %d %d\n" % ctx.date()) | ||||
write("# %s\n" % util.datestr(ctx.date())) | ||||
if branch and branch != 'default': | ||||
write("# Branch %s\n" % branch) | ||||
write("# Node ID %s\n" % hex(node)) | ||||
write("# Parent %s\n" % hex(prev)) | ||||
if len(parents) > 1: | ||||
write("# Parent %s\n" % hex(parents[1])) | ||||
for headerid in extraexport: | ||||
header = extraexportmap[headerid](seqno, ctx) | ||||
if header is not None: | ||||
write('# %s\n' % header) | ||||
write(ctx.description().rstrip()) | ||||
write("\n\n") | ||||
for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts): | ||||
write(chunk, label=label) | ||||
Augie Fackler
|
r32431 | def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False, | ||
Matt Harbison
|
r26229 | opts=None, match=None): | ||
Augie Fackler
|
r32430 | '''export changesets as hg patches | ||
Args: | ||||
repo: The repository from which we're exporting revisions. | ||||
revs: A list of revisions to export as revision numbers. | ||||
Augie Fackler
|
r32431 | fntemplate: An optional string to use for generating patch file names. | ||
Augie Fackler
|
r32430 | fp: An optional file-like object to which patches should be written. | ||
switch_parent: If True, show diffs against second parent when not nullid. | ||||
Default is false, which always shows diff against p1. | ||||
opts: diff options to use for generating the patch. | ||||
match: If specified, only export changes to files matching this matcher. | ||||
Returns: | ||||
Nothing. | ||||
Side Effect: | ||||
"HG Changeset Patch" data is emitted to one of the following | ||||
destinations: | ||||
fp is specified: All revs are written to the specified | ||||
file-like object. | ||||
Augie Fackler
|
r32431 | fntemplate specified: Each rev is written to a unique file named using | ||
Augie Fackler
|
r32430 | the given template. | ||
Neither fp nor template specified: All revs written to repo.ui.write() | ||||
''' | ||||
Benoit Boissinot
|
r10611 | |||
total = len(revs) | ||||
Augie Fackler
|
r32432 | revwidth = max(len(str(rev)) for rev in revs) | ||
Augie Fackler
|
r18613 | filemode = {} | ||
Benoit Boissinot
|
r10611 | |||
Augie Fackler
|
r32434 | write = None | ||
dest = '<unnamed>' | ||||
if fp: | ||||
dest = getattr(fp, 'name', dest) | ||||
def write(s, **kw): | ||||
fp.write(s) | ||||
elif not fntemplate: | ||||
write = repo.ui.write | ||||
Augie Fackler
|
r32433 | for seqno, rev in enumerate(revs, 1): | ||
Benoit Boissinot
|
r10611 | ctx = repo[rev] | ||
Augie Fackler
|
r32433 | fo = None | ||
Augie Fackler
|
r32434 | if not fp and fntemplate: | ||
Andrzej Bieniek
|
r14986 | desc_lines = ctx.description().rstrip().split('\n') | ||
desc = desc_lines[0] #Commit always has a first line. | ||||
Augie Fackler
|
r32433 | fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc, | ||
total=total, seqno=seqno, revwidth=revwidth, | ||||
mode='wb', modemap=filemode) | ||||
dest = fo.name | ||||
def write(s, **kw): | ||||
fo.write(s) | ||||
if not dest.startswith('<'): | ||||
repo.ui.note("%s\n" % dest) | ||||
_exportsingle( | ||||
repo, ctx, match, switch_parent, rev, seqno, write, opts) | ||||
if fo is not None: | ||||
fo.close() | ||||
Benoit Boissinot
|
r10611 | |||
Yuya Nishihara
|
r11050 | def diffordiffstat(ui, repo, diffopts, node1, node2, match, | ||
Martin Geisler
|
r12167 | changes=None, stat=False, fp=None, prefix='', | ||
Sean Farley
|
r24455 | root='', listsubrepos=False): | ||
Yuya Nishihara
|
r11050 | '''show diff or diffstat.''' | ||
if fp is None: | ||||
write = ui.write | ||||
else: | ||||
def write(s, **kw): | ||||
fp.write(s) | ||||
Sean Farley
|
r24455 | if root: | ||
relroot = pathutil.canonpath(repo.root, repo.getcwd(), root) | ||||
Siddharth Agarwal
|
r24431 | else: | ||
relroot = '' | ||||
if relroot != '': | ||||
# XXX relative roots currently don't work if the root is within a | ||||
# subrepo | ||||
uirelroot = match.uipath(relroot) | ||||
relroot += '/' | ||||
for matchroot in match.files(): | ||||
if not matchroot.startswith(relroot): | ||||
ui.warn(_('warning: %s not inside relative root %s\n') % ( | ||||
match.uipath(matchroot), uirelroot)) | ||||
Yuya Nishihara
|
r11050 | if stat: | ||
Alecs King
|
r11950 | diffopts = diffopts.copy(context=0) | ||
Yuya Nishihara
|
r11050 | width = 80 | ||
if not ui.plain(): | ||||
Augie Fackler
|
r12689 | width = ui.termwidth() | ||
Martin Geisler
|
r12167 | chunks = patch.diff(repo, node1, node2, match, changes, diffopts, | ||
Siddharth Agarwal
|
r24431 | prefix=prefix, relroot=relroot) | ||
Yuya Nishihara
|
r11050 | for chunk, label in patch.diffstatui(util.iterlines(chunks), | ||
Henning Schild
|
r30407 | width=width): | ||
Yuya Nishihara
|
r11050 | write(chunk, label=label) | ||
else: | ||||
for chunk, label in patch.diffui(repo, node1, node2, match, | ||||
Siddharth Agarwal
|
r24431 | changes, diffopts, prefix=prefix, | ||
relroot=relroot): | ||||
Yuya Nishihara
|
r11050 | write(chunk, label=label) | ||
Martin Geisler
|
r12167 | if listsubrepos: | ||
ctx1 = repo[node1] | ||||
Martin Geisler
|
r12175 | ctx2 = repo[node2] | ||
Augie Fackler
|
r20392 | for subpath, sub in scmutil.itersubrepos(ctx1, ctx2): | ||
Alistair Bell
|
r15698 | tempnode2 = node2 | ||
Renato Cunha
|
r15634 | try: | ||
if node2 is not None: | ||||
Alistair Bell
|
r15698 | tempnode2 = ctx2.substate[subpath][1] | ||
Renato Cunha
|
r15634 | except KeyError: | ||
# A subrepo that existed in node1 was deleted between node1 and | ||||
# node2 (inclusive). Thus, ctx2's substate won't contain that | ||||
# subpath. The best we can do is to ignore it. | ||||
Alistair Bell
|
r15698 | tempnode2 = None | ||
Martin von Zweigbergk
|
r28017 | submatch = matchmod.subdirmatcher(subpath, match) | ||
FUJIWARA Katsunori
|
r18006 | sub.diff(ui, diffopts, tempnode2, submatch, changes=changes, | ||
Martin Geisler
|
r12167 | stat=stat, fp=fp, prefix=prefix) | ||
Denis Laxalde
|
r30694 | def _changesetlabels(ctx): | ||
labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()] | ||||
Denis Laxalde
|
r31698 | if ctx.obsolete(): | ||
labels.append('changeset.obsolete') | ||||
Boris Feld
|
r33696 | if ctx.isunstable(): | ||
Boris Feld
|
r33776 | labels.append('changeset.unstable') | ||
Boris Feld
|
r33692 | for instability in ctx.instabilities(): | ||
Boris Feld
|
r33777 | labels.append('instability.%s' % instability) | ||
Denis Laxalde
|
r30694 | return ' '.join(labels) | ||
Matt Mackall
|
r3643 | class changeset_printer(object): | ||
'''show changeset information when templating not requested.''' | ||||
Martin von Zweigbergk
|
r22386 | def __init__(self, ui, repo, matchfn, diffopts, buffered): | ||
Matt Mackall
|
r3643 | self.ui = ui | ||
self.repo = repo | ||||
Matt Mackall
|
r3645 | self.buffered = buffered | ||
Martin von Zweigbergk
|
r22386 | self.matchfn = matchfn | ||
Jim Correia
|
r7762 | self.diffopts = diffopts | ||
Matt Mackall
|
r3738 | self.header = {} | ||
self.hunk = {} | ||||
self.lastheader = None | ||||
Robert Bachmann
|
r10152 | self.footer = None | ||
Matt Mackall
|
r3645 | |||
Yuya Nishihara
|
r25763 | def flush(self, ctx): | ||
rev = ctx.rev() | ||||
Matt Mackall
|
r3738 | if rev in self.header: | ||
h = self.header[rev] | ||||
if h != self.lastheader: | ||||
self.lastheader = h | ||||
self.ui.write(h) | ||||
del self.header[rev] | ||||
if rev in self.hunk: | ||||
self.ui.write(self.hunk[rev]) | ||||
del self.hunk[rev] | ||||
return 1 | ||||
return 0 | ||||
Matt Mackall
|
r3643 | |||
Robert Bachmann
|
r10152 | def close(self): | ||
if self.footer: | ||||
self.ui.write(self.footer) | ||||
Mads Kiilerich
|
r11488 | def show(self, ctx, copies=None, matchfn=None, **props): | ||
Pulkit Goyal
|
r33100 | props = pycompat.byteskwargs(props) | ||
Matt Mackall
|
r3738 | if self.buffered: | ||
Gregory Szorc
|
r27107 | self.ui.pushbuffer(labeled=True) | ||
Mads Kiilerich
|
r11488 | self._show(ctx, copies, matchfn, props) | ||
Gregory Szorc
|
r27109 | self.hunk[ctx.rev()] = self.ui.popbuffer() | ||
Matt Mackall
|
r3738 | else: | ||
Mads Kiilerich
|
r11488 | self._show(ctx, copies, matchfn, props) | ||
Matt Mackall
|
r3738 | |||
Mads Kiilerich
|
r11488 | def _show(self, ctx, copies, matchfn, props): | ||
Matt Mackall
|
r3643 | '''show a single changeset or file revision''' | ||
Dirkjan Ochtman
|
r7369 | changenode = ctx.node() | ||
rev = ctx.rev() | ||||
Jordi Gutiérrez Hermoso
|
r24306 | if self.ui.debugflag: | ||
hexfunc = hex | ||||
else: | ||||
hexfunc = short | ||||
Yuya Nishihara
|
r25762 | # as of now, wctx.node() and wctx.rev() return None, but we want to | ||
# show the same values as {node} and {rev} templatekw | ||||
Yuya Nishihara
|
r32656 | revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx))) | ||
Yuya Nishihara
|
r24584 | |||
if self.ui.quiet: | ||||
self.ui.write("%d:%s\n" % revnode, label='log.node') | ||||
return | ||||
date = util.datestr(ctx.date()) | ||||
Matt Mackall
|
r3643 | |||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Yuya Nishihara
|
r24584 | self.ui.write(_("changeset: %d:%s\n") % revnode, | ||
Denis Laxalde
|
r30694 | label=_changesetlabels(ctx)) | ||
Matt Mackall
|
r3643 | |||
Sean Farley
|
r23772 | # branches are shown first before any other names due to backwards | ||
# compatibility | ||||
Adrian Buehlmann
|
r9637 | branch = ctx.branch() | ||
Alexis S. L. Carvalho
|
r4176 | # don't show the default branch name | ||
if branch != 'default': | ||||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Brodie Rao
|
r10819 | self.ui.write(_("branch: %s\n") % branch, | ||
label='log.branch') | ||||
Sean Farley
|
r23772 | |||
Nathaniel Manista
|
r28904 | for nsname, ns in self.repo.names.iteritems(): | ||
Sean Farley
|
r23772 | # branches has special logic already handled above, so here we just | ||
# skip it | ||||
Nathaniel Manista
|
r28904 | if nsname == 'branches': | ||
Sean Farley
|
r23772 | continue | ||
# we will use the templatename as the color name since those two | ||||
# should be the same | ||||
for name in ns.names(self.repo, changenode): | ||||
FUJIWARA Katsunori
|
r23967 | self.ui.write(ns.logfmt % name, | ||
label='log.%s' % ns.colorname) | ||||
Jordi Gutiérrez Hermoso
|
r22765 | if self.ui.debugflag: | ||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
FUJIWARA Katsunori
|
r24864 | self.ui.write(_("phase: %s\n") % ctx.phasestr(), | ||
Pierre-Yves David
|
r15907 | label='log.phase') | ||
Yuya Nishihara
|
r26433 | for pctx in scmutil.meaningfulparents(self.repo, ctx): | ||
Yuya Nishihara
|
r24483 | label = 'log.parent changeset.%s' % pctx.phasestr() | ||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Yuya Nishihara
|
r24483 | self.ui.write(_("parent: %d:%s\n") | ||
% (pctx.rev(), hexfunc(pctx.node())), | ||||
Sean Farley
|
r22301 | label=label) | ||
Matt Mackall
|
r3643 | |||
Yuya Nishihara
|
r24585 | if self.ui.debugflag and rev is not None: | ||
Greg Ward
|
r9547 | mnode = ctx.manifestnode() | ||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Matt Mackall
|
r3643 | self.ui.write(_("manifest: %d:%s\n") % | ||
Durham Goode
|
r30375 | (self.repo.manifestlog._revlog.rev(mnode), | ||
hex(mnode)), | ||||
Brodie Rao
|
r10819 | label='ui.debug log.manifest') | ||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Brodie Rao
|
r10819 | self.ui.write(_("user: %s\n") % ctx.user(), | ||
label='log.user') | ||||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Brodie Rao
|
r10819 | self.ui.write(_("date: %s\n") % date, | ||
label='log.date') | ||||
Matt Mackall
|
r3643 | |||
Boris Feld
|
r33696 | if ctx.isunstable(): | ||
Denis Laxalde
|
r30710 | # i18n: column positioning for "hg log" | ||
Boris Feld
|
r33692 | instabilities = ctx.instabilities() | ||
self.ui.write(_("instability: %s\n") % ', '.join(instabilities), | ||||
Boris Feld
|
r33778 | label='log.instability') | ||
Denis Laxalde
|
r30695 | |||
Boris Feld
|
r33155 | self._exthook(ctx) | ||
Matt Mackall
|
r3643 | if self.ui.debugflag: | ||
Yuya Nishihara
|
r24485 | files = ctx.p1().status(ctx)[:3] | ||
FUJIWARA Katsunori
|
r17891 | for key, value in zip([# i18n: column positioning for "hg log" | ||
_("files:"), | ||||
# i18n: column positioning for "hg log" | ||||
_("files+:"), | ||||
# i18n: column positioning for "hg log" | ||||
_("files-:")], files): | ||||
Matt Mackall
|
r3643 | if value: | ||
Brodie Rao
|
r10819 | self.ui.write("%-12s %s\n" % (key, " ".join(value)), | ||
label='ui.debug log.files') | ||||
Greg Ward
|
r9547 | elif ctx.files() and self.ui.verbose: | ||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Brodie Rao
|
r10819 | self.ui.write(_("files: %s\n") % " ".join(ctx.files()), | ||
label='ui.note log.files') | ||||
Matt Mackall
|
r3643 | if copies and self.ui.verbose: | ||
copies = ['%s (%s)' % c for c in copies] | ||||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Brodie Rao
|
r10819 | self.ui.write(_("copies: %s\n") % ' '.join(copies), | ||
label='ui.note log.copies') | ||||
Matt Mackall
|
r3643 | |||
Adrian Buehlmann
|
r9637 | extra = ctx.extra() | ||
Matt Mackall
|
r3643 | if extra and self.ui.debugflag: | ||
Matt Mackall
|
r8209 | for key, value in sorted(extra.items()): | ||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Matt Mackall
|
r3643 | self.ui.write(_("extra: %s=%s\n") | ||
Yuya Nishihara
|
r31451 | % (key, util.escapestr(value)), | ||
Brodie Rao
|
r10819 | label='ui.debug log.extra') | ||
Matt Mackall
|
r3643 | |||
Greg Ward
|
r9547 | description = ctx.description().strip() | ||
Matt Mackall
|
r3643 | if description: | ||
if self.ui.verbose: | ||||
Brodie Rao
|
r10819 | self.ui.write(_("description:\n"), | ||
label='ui.note log.description') | ||||
self.ui.write(description, | ||||
label='ui.note log.description') | ||||
Matt Mackall
|
r3643 | self.ui.write("\n\n") | ||
else: | ||||
FUJIWARA Katsunori
|
r17891 | # i18n: column positioning for "hg log" | ||
Matt Mackall
|
r3643 | self.ui.write(_("summary: %s\n") % | ||
Brodie Rao
|
r10819 | description.splitlines()[0], | ||
label='log.summary') | ||||
Matt Mackall
|
r3643 | self.ui.write("\n") | ||
Gregory Szorc
|
r27065 | self.showpatch(ctx, matchfn) | ||
Boris Feld
|
r33155 | def _exthook(self, ctx): | ||
'''empty method used by extension as a hook point | ||||
''' | ||||
pass | ||||
Gregory Szorc
|
r27065 | def showpatch(self, ctx, matchfn): | ||
Mads Kiilerich
|
r11488 | if not matchfn: | ||
Martin von Zweigbergk
|
r22386 | matchfn = self.matchfn | ||
Mads Kiilerich
|
r11488 | if matchfn: | ||
Yuya Nishihara
|
r11061 | stat = self.diffopts.get('stat') | ||
Alecs King
|
r11950 | diff = self.diffopts.get('patch') | ||
Siddharth Agarwal
|
r23691 | diffopts = patch.diffallopts(self.ui, self.diffopts) | ||
Gregory Szorc
|
r27065 | node = ctx.node() | ||
Durham Goode
|
r27622 | prev = ctx.p1().node() | ||
Alecs King
|
r11950 | if stat: | ||
diffordiffstat(self.ui, self.repo, diffopts, prev, node, | ||||
match=matchfn, stat=True) | ||||
if diff: | ||||
if stat: | ||||
self.ui.write("\n") | ||||
diffordiffstat(self.ui, self.repo, diffopts, prev, node, | ||||
match=matchfn, stat=False) | ||||
Matt Mackall
|
r3645 | self.ui.write("\n") | ||
Matt Mackall
|
r22427 | class jsonchangeset(changeset_printer): | ||
'''format changeset information.''' | ||||
def __init__(self, ui, repo, matchfn, diffopts, buffered): | ||||
changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered) | ||||
self.cache = {} | ||||
self._first = True | ||||
def close(self): | ||||
if not self._first: | ||||
self.ui.write("\n]\n") | ||||
else: | ||||
self.ui.write("[]\n") | ||||
def _show(self, ctx, copies, matchfn, props): | ||||
'''show a single changeset or file revision''' | ||||
rev = ctx.rev() | ||||
Yuya Nishihara
|
r24602 | if rev is None: | ||
jrev = jnode = 'null' | ||||
else: | ||||
Pulkit Goyal
|
r32155 | jrev = '%d' % rev | ||
Yuya Nishihara
|
r24602 | jnode = '"%s"' % hex(ctx.node()) | ||
Matt Mackall
|
r22427 | j = encoding.jsonescape | ||
if self._first: | ||||
self.ui.write("[\n {") | ||||
self._first = False | ||||
else: | ||||
self.ui.write(",\n {") | ||||
if self.ui.quiet: | ||||
FUJIWARA Katsunori
|
r29397 | self.ui.write(('\n "rev": %s') % jrev) | ||
self.ui.write((',\n "node": %s') % jnode) | ||||
Matt Mackall
|
r22427 | self.ui.write('\n }') | ||
return | ||||
FUJIWARA Katsunori
|
r29397 | self.ui.write(('\n "rev": %s') % jrev) | ||
self.ui.write((',\n "node": %s') % jnode) | ||||
self.ui.write((',\n "branch": "%s"') % j(ctx.branch())) | ||||
self.ui.write((',\n "phase": "%s"') % ctx.phasestr()) | ||||
self.ui.write((',\n "user": "%s"') % j(ctx.user())) | ||||
self.ui.write((',\n "date": [%d, %d]') % ctx.date()) | ||||
self.ui.write((',\n "desc": "%s"') % j(ctx.description())) | ||||
self.ui.write((',\n "bookmarks": [%s]') % | ||||
Matt Mackall
|
r22427 | ", ".join('"%s"' % j(b) for b in ctx.bookmarks())) | ||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "tags": [%s]') % | ||
Matt Mackall
|
r22427 | ", ".join('"%s"' % j(t) for t in ctx.tags())) | ||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "parents": [%s]') % | ||
Matt Mackall
|
r22427 | ", ".join('"%s"' % c.hex() for c in ctx.parents())) | ||
if self.ui.debugflag: | ||||
Yuya Nishihara
|
r24603 | if rev is None: | ||
jmanifestnode = 'null' | ||||
else: | ||||
jmanifestnode = '"%s"' % hex(ctx.manifestnode()) | ||||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "manifest": %s') % jmanifestnode) | ||
self.ui.write((',\n "extra": {%s}') % | ||||
Matt Mackall
|
r22427 | ", ".join('"%s": "%s"' % (j(k), j(v)) | ||
for k, v in ctx.extra().items())) | ||||
Gregory Szorc
|
r23734 | files = ctx.p1().status(ctx) | ||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "modified": [%s]') % | ||
Matt Mackall
|
r22427 | ", ".join('"%s"' % j(f) for f in files[0])) | ||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "added": [%s]') % | ||
Matt Mackall
|
r22427 | ", ".join('"%s"' % j(f) for f in files[1])) | ||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "removed": [%s]') % | ||
Matt Mackall
|
r22427 | ", ".join('"%s"' % j(f) for f in files[2])) | ||
elif self.ui.verbose: | ||||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "files": [%s]') % | ||
Matt Mackall
|
r22427 | ", ".join('"%s"' % j(f) for f in ctx.files())) | ||
if copies: | ||||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "copies": {%s}') % | ||
Augie Fackler
|
r24013 | ", ".join('"%s": "%s"' % (j(k), j(v)) | ||
for k, v in copies)) | ||||
Matt Mackall
|
r22427 | |||
matchfn = self.matchfn | ||||
if matchfn: | ||||
stat = self.diffopts.get('stat') | ||||
diff = self.diffopts.get('patch') | ||||
Siddharth Agarwal
|
r23453 | diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True) | ||
Matt Mackall
|
r22427 | node, prev = ctx.node(), ctx.p1().node() | ||
if stat: | ||||
self.ui.pushbuffer() | ||||
diffordiffstat(self.ui, self.repo, diffopts, prev, node, | ||||
match=matchfn, stat=True) | ||||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "diffstat": "%s"') | ||
% j(self.ui.popbuffer())) | ||||
Matt Mackall
|
r22427 | if diff: | ||
self.ui.pushbuffer() | ||||
diffordiffstat(self.ui, self.repo, diffopts, prev, node, | ||||
match=matchfn, stat=False) | ||||
FUJIWARA Katsunori
|
r29397 | self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer())) | ||
Matt Mackall
|
r22427 | |||
self.ui.write("\n }") | ||||
Thomas Arendsen Hein
|
r4825 | |||
Matt Mackall
|
r3645 | class changeset_templater(changeset_printer): | ||
Matt Mackall
|
r3643 | '''format changeset information.''' | ||
Gregory Szorc
|
r33045 | # Arguments before "buffered" used to be positional. Consider not | ||
# adding/removing arguments before "buffered" to not break callers. | ||||
def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None, | ||||
buffered=False): | ||||
diffopts = diffopts or {} | ||||
Martin von Zweigbergk
|
r22386 | changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered) | ||
Yuya Nishihara
|
r32840 | self.t = formatter.loadtemplater(ui, tmplspec, | ||
Yuya Nishihara
|
r32831 | cache=templatekw.defaulttempl) | ||
Yuya Nishihara
|
r31807 | self._counter = itertools.count() | ||
Matt Mackall
|
r20667 | self.cache = {} | ||
Matt Mackall
|
r3643 | |||
Yuya Nishihara
|
r32842 | self._tref = tmplspec.ref | ||
self._parts = {'header': '', 'footer': '', | ||||
tmplspec.ref: tmplspec.ref, | ||||
Yuya Nishihara
|
r32951 | 'docheader': '', 'docfooter': '', | ||
'separator': ''} | ||||
Yuya Nishihara
|
r32947 | if tmplspec.mapfile: | ||
# find correct templates for current mode, for backward | ||||
# compatibility with 'log -v/-q/--debug' using a mapfile | ||||
tmplmodes = [ | ||||
(True, ''), | ||||
(self.ui.verbose, '_verbose'), | ||||
(self.ui.quiet, '_quiet'), | ||||
(self.ui.debugflag, '_debug'), | ||||
] | ||||
for mode, postfix in tmplmodes: | ||||
for t in self._parts: | ||||
cur = t + postfix | ||||
if mode and cur in self.t: | ||||
self._parts[t] = cur | ||||
Yuya Nishihara
|
r32953 | else: | ||
partnames = [p for p in self._parts.keys() if p != tmplspec.ref] | ||||
m = formatter.templatepartsmap(tmplspec, self.t, partnames) | ||||
self._parts.update(m) | ||||
Matt Mackall
|
r26086 | |||
Matt Mackall
|
r26222 | if self._parts['docheader']: | ||
self.ui.write(templater.stringify(self.t(self._parts['docheader']))) | ||||
def close(self): | ||||
if self._parts['docfooter']: | ||||
if not self.footer: | ||||
self.footer = "" | ||||
self.footer += templater.stringify(self.t(self._parts['docfooter'])) | ||||
return super(changeset_templater, self).close() | ||||
Mads Kiilerich
|
r11488 | def _show(self, ctx, copies, matchfn, props): | ||
Matt Mackall
|
r3643 | '''show a single changeset or file revision''' | ||
props = props.copy() | ||||
Patrick Mezard
|
r10054 | props.update(templatekw.keywords) | ||
Patrick Mezard
|
r10053 | props['templ'] = self.t | ||
Patrick Mezard
|
r10054 | props['ctx'] = ctx | ||
Patrick Mezard
|
r10055 | props['repo'] = self.repo | ||
Kostia Balytskyi
|
r28384 | props['ui'] = self.repo.ui | ||
Yuya Nishihara
|
r32951 | props['index'] = index = next(self._counter) | ||
Patrick Mezard
|
r10058 | props['revcache'] = {'copies': copies} | ||
Patrick Mezard
|
r10057 | props['cache'] = self.cache | ||
Pulkit Goyal
|
r32289 | props = pycompat.strkwargs(props) | ||
Matt Mackall
|
r3643 | |||
Yuya Nishihara
|
r32951 | # write separator, which wouldn't work well with the header part below | ||
# since there's inherently a conflict between header (across items) and | ||||
# separator (per item) | ||||
if self._parts['separator'] and index > 0: | ||||
self.ui.write(templater.stringify(self.t(self._parts['separator']))) | ||||
Yuya Nishihara
|
r28837 | # write header | ||
if self._parts['header']: | ||||
h = templater.stringify(self.t(self._parts['header'], **props)) | ||||
if self.buffered: | ||||
self.header[ctx.rev()] = h | ||||
else: | ||||
if self.lastheader != h: | ||||
self.lastheader = h | ||||
self.ui.write(h) | ||||
# write changeset metadata, then patch if requested | ||||
Yuya Nishihara
|
r32842 | key = self._parts[self._tref] | ||
Yuya Nishihara
|
r28837 | self.ui.write(templater.stringify(self.t(key, **props))) | ||
self.showpatch(ctx, matchfn) | ||||
if self._parts['footer']: | ||||
if not self.footer: | ||||
self.footer = templater.stringify( | ||||
self.t(self._parts['footer'], **props)) | ||||
Matt Mackall
|
r3643 | |||
Yuya Nishihara
|
r32840 | def logtemplatespec(tmpl, mapfile): | ||
Yuya Nishihara
|
r32875 | if mapfile: | ||
return formatter.templatespec('changeset', tmpl, mapfile) | ||||
else: | ||||
return formatter.templatespec('', tmpl, None) | ||||
Yuya Nishihara
|
r32838 | |||
Yuya Nishihara
|
r32833 | def _lookuplogtemplate(ui, tmpl, style): | ||
Yuya Nishihara
|
r32834 | """Find the template matching the given template spec or style | ||
See formatter.lookuptemplate() for details. | ||||
Matt Mackall
|
r20666 | """ | ||
# ui settings | ||||
Yuya Nishihara
|
r22582 | if not tmpl and not style: # template are stronger than style | ||
Matt Mackall
|
r20666 | tmpl = ui.config('ui', 'logtemplate') | ||
if tmpl: | ||||
Yuya Nishihara
|
r32838 | return logtemplatespec(templater.unquotestring(tmpl), None) | ||
Matt Mackall
|
r20666 | else: | ||
Jun Wu
|
r33499 | style = util.expandpath(ui.config('ui', 'style')) | ||
Matt Mackall
|
r20666 | |||
Yuya Nishihara
|
r22582 | if not tmpl and style: | ||
Matt Mackall
|
r20666 | mapfile = style | ||
if not os.path.split(mapfile)[0]: | ||||
mapname = (templater.templatepath('map-cmdline.' + mapfile) | ||||
or templater.templatepath(mapfile)) | ||||
if mapname: | ||||
mapfile = mapname | ||||
Yuya Nishihara
|
r32838 | return logtemplatespec(None, mapfile) | ||
Matt Mackall
|
r20666 | |||
Matt Mackall
|
r20668 | if not tmpl: | ||
Yuya Nishihara
|
r32838 | return logtemplatespec(None, None) | ||
Matt Mackall
|
r20668 | |||
Matt Mackall
|
r25511 | return formatter.lookuptemplate(ui, 'changeset', tmpl) | ||
Matt Mackall
|
r20666 | |||
Yuya Nishihara
|
r32837 | def makelogtemplater(ui, repo, tmpl, buffered=False): | ||
"""Create a changeset_templater from a literal template 'tmpl'""" | ||||
Yuya Nishihara
|
r32839 | spec = logtemplatespec(tmpl, None) | ||
Gregory Szorc
|
r33045 | return changeset_templater(ui, repo, spec, buffered=buffered) | ||
Yuya Nishihara
|
r32837 | |||
Mads Kiilerich
|
r11488 | def show_changeset(ui, repo, opts, buffered=False): | ||
Matt Mackall
|
r3643 | """show one changeset using template or regular display. | ||
Display format will be the first non-empty hit of: | ||||
1. option 'template' | ||||
2. option 'style' | ||||
3. [ui] setting 'logtemplate' | ||||
4. [ui] setting 'style' | ||||
If all of these values are either the unset or the empty string, | ||||
regular display via changeset_printer() is done. | ||||
""" | ||||
# options | ||||
Martin von Zweigbergk
|
r34085 | match = None | ||
Yuya Nishihara
|
r11061 | if opts.get('patch') or opts.get('stat'): | ||
Martin von Zweigbergk
|
r34085 | match = scmutil.matchall(repo) | ||
Matt Mackall
|
r3837 | |||
Matt Mackall
|
r22427 | if opts.get('template') == 'json': | ||
Martin von Zweigbergk
|
r34085 | return jsonchangeset(ui, repo, match, opts, buffered) | ||
Matt Mackall
|
r3837 | |||
Yuya Nishihara
|
r32833 | spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style')) | ||
Yuya Nishihara
|
r32839 | |||
Yuya Nishihara
|
r32875 | if not spec.ref and not spec.tmpl and not spec.mapfile: | ||
Martin von Zweigbergk
|
r34085 | return changeset_printer(ui, repo, match, opts, buffered) | ||
return changeset_templater(ui, repo, spec, match, opts, buffered) | ||||
Matt Mackall
|
r3643 | |||
Yuya Nishihara
|
r29795 | def showmarker(fm, marker, index=None): | ||
Pierre-Yves David
|
r20470 | """utility function to display obsolescence marker in a readable way | ||
To be used by debug function.""" | ||||
Kostia Balytskyi
|
r28613 | if index is not None: | ||
Yuya Nishihara
|
r29795 | fm.write('index', '%i ', index) | ||
Boris Feld
|
r33856 | fm.write('prednode', '%s ', hex(marker.prednode())) | ||
Yuya Nishihara
|
r29795 | succs = marker.succnodes() | ||
fm.condwrite(succs, 'succnodes', '%s ', | ||||
fm.formatlist(map(hex, succs), name='node')) | ||||
fm.write('flag', '%X ', marker.flags()) | ||||
Pierre-Yves David
|
r22260 | parents = marker.parentnodes() | ||
if parents is not None: | ||||
Yuya Nishihara
|
r29795 | fm.write('parentnodes', '{%s} ', | ||
fm.formatlist(map(hex, parents), name='node', sep=', ')) | ||||
fm.write('date', '(%s) ', fm.formatdate(marker.date())) | ||||
meta = marker.metadata().copy() | ||||
meta.pop('date', None) | ||||
fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', ')) | ||||
fm.plain('\n') | ||||
Pierre-Yves David
|
r20470 | |||
Matt Mackall
|
r3814 | def finddate(ui, repo, date): | ||
"""Find the tipmost changeset that matches the given date spec""" | ||||
Dirkjan Ochtman
|
r9667 | |||
mark.williamson@cl.cam.ac.uk
|
r5836 | df = util.matchdate(date) | ||
Matt Mackall
|
r14322 | m = scmutil.matchall(repo) | ||
Matt Mackall
|
r3814 | results = {} | ||
Matt Mackall
|
r9662 | |||
def prep(ctx, fns): | ||||
d = ctx.date() | ||||
if df(d[0]): | ||||
Dirkjan Ochtman
|
r9668 | results[ctx.rev()] = d | ||
Matt Mackall
|
r9662 | |||
Dirkjan Ochtman
|
r9667 | for ctx in walkchangerevs(repo, m, {'rev': None}, prep): | ||
Matt Mackall
|
r9662 | rev = ctx.rev() | ||
if rev in results: | ||||
Martin Geisler
|
r16937 | ui.status(_("found revision %s from %s\n") % | ||
Matt Mackall
|
r9662 | (rev, util.datestr(results[rev]))) | ||
Pulkit Goyal
|
r32155 | return '%d' % rev | ||
Matt Mackall
|
r3814 | |||
Pierre-Yves David
|
r26587 | raise error.Abort(_("revision matching date not found")) | ||
Matt Mackall
|
r3814 | |||
Lucas Moscovicz
|
r20553 | def increasingwindows(windowsize=8, sizelimit=512): | ||
while True: | ||||
yield windowsize | ||||
if windowsize < sizelimit: | ||||
windowsize *= 2 | ||||
Patrick Mezard
|
r16776 | |||
Durham Goode
|
r19290 | class FileWalkError(Exception): | ||
pass | ||||
def walkfilerevs(repo, match, follow, revs, fncache): | ||||
'''Walks the file history for the matched files. | ||||
Returns the changeset revs that are involved in the file history. | ||||
Throws FileWalkError if the file history can't be walked using | ||||
filelogs alone. | ||||
''' | ||||
wanted = set() | ||||
copies = [] | ||||
minrev, maxrev = min(revs), max(revs) | ||||
def filerevgen(filelog, last): | ||||
""" | ||||
Only files, no patterns. Check the history of each file. | ||||
Examines filelog entries within minrev, maxrev linkrev range | ||||
Returns an iterator yielding (linkrev, parentlinkrevs, copied) | ||||
tuples in backwards order | ||||
""" | ||||
cl_count = len(repo) | ||||
revs = [] | ||||
for j in xrange(0, last + 1): | ||||
linkrev = filelog.linkrev(j) | ||||
if linkrev < minrev: | ||||
continue | ||||
# only yield rev for which we have the changelog, it can | ||||
# happen while doing "hg log" during a pull or commit | ||||
if linkrev >= cl_count: | ||||
break | ||||
parentlinkrevs = [] | ||||
for p in filelog.parentrevs(j): | ||||
if p != nullrev: | ||||
parentlinkrevs.append(filelog.linkrev(p)) | ||||
n = filelog.node(j) | ||||
revs.append((linkrev, parentlinkrevs, | ||||
follow and filelog.renamed(n))) | ||||
return reversed(revs) | ||||
def iterfiles(): | ||||
pctx = repo['.'] | ||||
for filename in match.files(): | ||||
if follow: | ||||
if filename not in pctx: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('cannot follow file not in parent ' | ||
Durham Goode
|
r19290 | 'revision: "%s"') % filename) | ||
yield filename, pctx[filename].filenode() | ||||
else: | ||||
yield filename, None | ||||
for filename_node in copies: | ||||
yield filename_node | ||||
for file_, node in iterfiles(): | ||||
filelog = repo.file(file_) | ||||
if not len(filelog): | ||||
if node is None: | ||||
# A zero count may be a directory or deleted file, so | ||||
# try to find matching entries on the slow path. | ||||
if follow: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort( | ||
Durham Goode
|
r19290 | _('cannot follow nonexistent file: "%s"') % file_) | ||
raise FileWalkError("Cannot walk via filelog") | ||||
else: | ||||
continue | ||||
if node is None: | ||||
last = len(filelog) - 1 | ||||
else: | ||||
last = filelog.rev(node) | ||||
# keep track of all ancestors of the file | ||||
Martin von Zweigbergk
|
r32291 | ancestors = {filelog.linkrev(last)} | ||
Durham Goode
|
r19290 | |||
# iterate from latest to oldest revision | ||||
for rev, flparentlinkrevs, copied in filerevgen(filelog, last): | ||||
if not follow: | ||||
if rev > maxrev: | ||||
continue | ||||
else: | ||||
# Note that last might not be the first interesting | ||||
# rev to us: | ||||
# if the file has been changed after maxrev, we'll | ||||
# have linkrev(last) > maxrev, and we still need | ||||
# to explore the file graph | ||||
if rev not in ancestors: | ||||
continue | ||||
# XXX insert 1327 fix here | ||||
if flparentlinkrevs: | ||||
ancestors.update(flparentlinkrevs) | ||||
fncache.setdefault(rev, []).append(file_) | ||||
wanted.add(rev) | ||||
if copied: | ||||
copies.append(copied) | ||||
return wanted | ||||
Martin von Zweigbergk
|
r24391 | class _followfilter(object): | ||
def __init__(self, repo, onlyfirst=False): | ||||
self.repo = repo | ||||
self.startrev = nullrev | ||||
self.roots = set() | ||||
self.onlyfirst = onlyfirst | ||||
def match(self, rev): | ||||
def realparents(rev): | ||||
if self.onlyfirst: | ||||
return self.repo.changelog.parentrevs(rev)[0:1] | ||||
else: | ||||
return filter(lambda x: x != nullrev, | ||||
self.repo.changelog.parentrevs(rev)) | ||||
if self.startrev == nullrev: | ||||
self.startrev = rev | ||||
return True | ||||
if rev > self.startrev: | ||||
# forward: all descendants | ||||
if not self.roots: | ||||
self.roots.add(self.startrev) | ||||
for parent in realparents(rev): | ||||
if parent in self.roots: | ||||
self.roots.add(rev) | ||||
return True | ||||
else: | ||||
# backwards: all parents | ||||
if not self.roots: | ||||
self.roots.update(realparents(self.startrev)) | ||||
if rev in self.roots: | ||||
self.roots.remove(rev) | ||||
self.roots.update(realparents(rev)) | ||||
return True | ||||
return False | ||||
Matt Mackall
|
r9665 | def walkchangerevs(repo, match, opts, prepare): | ||
timeless
|
r7807 | '''Iterate over files and the revs in which they changed. | ||
Matt Mackall
|
r3650 | |||
Callers most commonly need to iterate backwards over the history | ||||
timeless
|
r7807 | in which they are interested. Doing so has awful (quadratic-looking) | ||
Matt Mackall
|
r3650 | performance, so we use iterators in a "windowed" way. | ||
We walk a window of revisions in the desired order. Within the | ||||
window, we first walk forwards to gather data, then in the desired | ||||
order (usually backwards) to display it. | ||||
Matt Mackall
|
r9662 | This function returns an iterator yielding contexts. Before | ||
yielding each context, the iterator will first call the prepare | ||||
function on each context in the window in forward order.''' | ||||
Matt Mackall
|
r3650 | |||
follow = opts.get('follow') or opts.get('follow_first') | ||||
Yuya Nishihara
|
r24063 | revs = _logrevs(repo, opts) | ||
Matt Mackall
|
r11281 | if not revs: | ||
return [] | ||||
Martin Geisler
|
r8152 | wanted = set() | ||
Martin von Zweigbergk
|
r25272 | slowpath = match.anypats() or ((match.isexact() or match.prefix()) and | ||
opts.get('removed')) | ||||
Matt Mackall
|
r3650 | fncache = {} | ||
Matt Mackall
|
r16108 | change = repo.changectx | ||
Matt Mackall
|
r3650 | |||
Nicolas Dumazet
|
r11632 | # First step is to fill wanted, the set of revisions that we want to yield. | ||
# When it does not induce extra cost, we also fill fncache for revisions in | ||||
# wanted: a cache of filenames that were changed (ctx.files()) and that | ||||
# match the file filtering conditions. | ||||
Martin von Zweigbergk
|
r24384 | if match.always(): | ||
Matt Mackall
|
r3650 | # No files, no patterns. Display all revs. | ||
Lucas Moscovicz
|
r20553 | wanted = revs | ||
Martin von Zweigbergk
|
r25271 | elif not slowpath: | ||
Nicolas Dumazet
|
r11632 | # We only have to read through the filelog to find wanted revisions | ||
Durham Goode
|
r19290 | try: | ||
wanted = walkfilerevs(repo, match, follow, revs, fncache) | ||||
except FileWalkError: | ||||
slowpath = True | ||||
Nicolas Dumazet
|
r11608 | |||
Durham Goode
|
r19290 | # We decided to fall back to the slowpath because at least one | ||
# of the paths was not a file. Check to see if at least one of them | ||||
# existed in history, otherwise simply return | ||||
smuralid
|
r17746 | for path in match.files(): | ||
if path == '.' or path in repo.store: | ||||
break | ||||
Mads Kiilerich
|
r18340 | else: | ||
return [] | ||||
smuralid
|
r17746 | |||
Matt Mackall
|
r3650 | if slowpath: | ||
Nicolas Dumazet
|
r11632 | # We have to read the changelog to match filenames against | ||
# changed files | ||||
Matt Mackall
|
r3650 | if follow: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('can only follow copies/renames for explicit ' | ||
timeless
|
r8761 | 'filenames')) | ||
Matt Mackall
|
r3650 | |||
# The slow path checks files modified in every changeset. | ||||
Durham Goode
|
r19730 | # This is really slow on large repos, so compute the set lazily. | ||
class lazywantedset(object): | ||||
def __init__(self): | ||||
self.set = set() | ||||
self.revs = set(revs) | ||||
# No need to worry about locality here because it will be accessed | ||||
# in the same order as the increasing window below. | ||||
def __contains__(self, value): | ||||
if value in self.set: | ||||
return True | ||||
elif not value in self.revs: | ||||
return False | ||||
else: | ||||
self.revs.discard(value) | ||||
ctx = change(value) | ||||
matches = filter(match, ctx.files()) | ||||
if matches: | ||||
fncache[value] = matches | ||||
self.set.add(value) | ||||
return True | ||||
return False | ||||
def discard(self, value): | ||||
self.revs.discard(value) | ||||
self.set.discard(value) | ||||
wanted = lazywantedset() | ||||
Matt Mackall
|
r3650 | |||
# it might be worthwhile to do this in the iterator if the rev range | ||||
# is descending and the prune args are all within that range | ||||
for rev in opts.get('prune', ()): | ||||
Matt Mackall
|
r16380 | rev = repo[rev].rev() | ||
Martin von Zweigbergk
|
r24391 | ff = _followfilter(repo) | ||
Matt Mackall
|
r3650 | stop = min(revs[0], revs[-1]) | ||
Matt Mackall
|
r10282 | for x in xrange(rev, stop - 1, -1): | ||
Martin Geisler
|
r8152 | if ff.match(x): | ||
Lucas Moscovicz
|
r20553 | wanted = wanted - [x] | ||
Bryan O'Sullivan
|
r18710 | |||
Nicolas Dumazet
|
r11632 | # Now that wanted is correctly initialized, we can iterate over the | ||
# revision range, yielding only revisions in wanted. | ||||
Matt Mackall
|
r3650 | def iterate(): | ||
Martin von Zweigbergk
|
r25272 | if follow and match.always(): | ||
Martin von Zweigbergk
|
r24391 | ff = _followfilter(repo, onlyfirst=opts.get('follow_first')) | ||
Matt Mackall
|
r3650 | def want(rev): | ||
Martin Geisler
|
r8119 | return ff.match(rev) and rev in wanted | ||
Matt Mackall
|
r3650 | else: | ||
def want(rev): | ||||
return rev in wanted | ||||
Lucas Moscovicz
|
r20553 | it = iter(revs) | ||
stopiteration = False | ||||
for windowsize in increasingwindows(): | ||||
nrevs = [] | ||||
for i in xrange(windowsize): | ||||
Pierre-Yves David
|
r25147 | rev = next(it, None) | ||
if rev is None: | ||||
Lucas Moscovicz
|
r20553 | stopiteration = True | ||
break | ||||
Pierre-Yves David
|
r25147 | elif want(rev): | ||
nrevs.append(rev) | ||||
Matt Mackall
|
r8209 | for rev in sorted(nrevs): | ||
Matt Mackall
|
r3650 | fns = fncache.get(rev) | ||
Matt Mackall
|
r9654 | ctx = change(rev) | ||
Matt Mackall
|
r3650 | if not fns: | ||
def fns_generator(): | ||||
Matt Mackall
|
r9654 | for f in ctx.files(): | ||
Matt Mackall
|
r9652 | if match(f): | ||
Matt Mackall
|
r3650 | yield f | ||
fns = fns_generator() | ||||
Matt Mackall
|
r9662 | prepare(ctx, fns) | ||
Matt Mackall
|
r3650 | for rev in nrevs: | ||
Matt Mackall
|
r9662 | yield change(rev) | ||
Lucas Moscovicz
|
r20553 | |||
if stopiteration: | ||||
break | ||||
Matt Mackall
|
r9652 | return iterate() | ||
Bryan O'Sullivan
|
r5034 | |||
Siddharth Agarwal
|
r22166 | def _makefollowlogfilematcher(repo, files, followfirst): | ||
Patrick Mezard
|
r17180 | # When displaying a revision with --patch --follow FILE, we have | ||
# to know which file of the revision must be diffed. With | ||||
# --follow, we want the names of the ancestors of FILE in the | ||||
# revision, stored in "fcache". "fcache" is populated by | ||||
# reproducing the graph traversal already done by --follow revset | ||||
Yuya Nishihara
|
r30016 | # and relating revs to file names (which is not "correct" but | ||
Patrick Mezard
|
r17180 | # good enough). | ||
fcache = {} | ||||
fcacheready = [False] | ||||
pctx = repo['.'] | ||||
def populate(): | ||||
Siddharth Agarwal
|
r21876 | for fn in files: | ||
Yuya Nishihara
|
r30015 | fctx = pctx[fn] | ||
Yuya Nishihara
|
r30016 | fcache.setdefault(fctx.introrev(), set()).add(fctx.path()) | ||
Yuya Nishihara
|
r30015 | for c in fctx.ancestors(followfirst=followfirst): | ||
Yuya Nishihara
|
r30016 | fcache.setdefault(c.rev(), set()).add(c.path()) | ||
Patrick Mezard
|
r17180 | |||
def filematcher(rev): | ||||
if not fcacheready[0]: | ||||
# Lazy initialization | ||||
fcacheready[0] = True | ||||
populate() | ||||
Siddharth Agarwal
|
r21878 | return scmutil.matchfiles(repo, fcache.get(rev, [])) | ||
Patrick Mezard
|
r17180 | |||
return filematcher | ||||
Siddharth Agarwal
|
r22167 | def _makenofollowlogfilematcher(repo, pats, opts): | ||
'''hook for extensions to override the filematcher for non-follow cases''' | ||||
return None | ||||
Lucas Moscovicz
|
r21108 | def _makelogrevset(repo, pats, opts, revs): | ||
Patrick Mezard
|
r17180 | """Return (expr, filematcher) where expr is a revset string built | ||
from log options and file patterns or None. If --stat or --patch | ||||
are not passed filematcher is None. Otherwise it is a callable | ||||
taking a revision number and returning a match objects filtering | ||||
the files to be detailed when displaying the revision. | ||||
""" | ||||
opt2revset = { | ||||
'no_merges': ('not merge()', None), | ||||
'only_merges': ('merge()', None), | ||||
'_ancestors': ('ancestors(%(val)s)', None), | ||||
'_fancestors': ('_firstancestors(%(val)s)', None), | ||||
'_descendants': ('descendants(%(val)s)', None), | ||||
'_fdescendants': ('_firstdescendants(%(val)s)', None), | ||||
'_matchfiles': ('_matchfiles(%(val)s)', None), | ||||
'date': ('date(%(val)r)', None), | ||||
'branch': ('branch(%(val)r)', ' or '), | ||||
'_patslog': ('filelog(%(val)r)', ' or '), | ||||
'_patsfollow': ('follow(%(val)r)', ' or '), | ||||
'_patsfollowfirst': ('_followfirst(%(val)r)', ' or '), | ||||
'keyword': ('keyword(%(val)r)', ' or '), | ||||
'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '), | ||||
'user': ('user(%(val)r)', ' or '), | ||||
} | ||||
opts = dict(opts) | ||||
# follow or not follow? | ||||
follow = opts.get('follow') or opts.get('follow_first') | ||||
Jordi Gutiérrez Hermoso
|
r24306 | if opts.get('follow_first'): | ||
followfirst = 1 | ||||
else: | ||||
followfirst = 0 | ||||
timeless@mozdev.org
|
r26098 | # --follow with FILE behavior depends on revs... | ||
Lucas Moscovicz
|
r20756 | it = iter(revs) | ||
timeless
|
r29216 | startrev = next(it) | ||
Pierre-Yves David
|
r25168 | followdescendants = startrev < next(it, startrev) | ||
Patrick Mezard
|
r17180 | |||
# branch and only_branch are really aliases and must be handled at | ||||
# the same time | ||||
opts['branch'] = opts.get('branch', []) + opts.get('only_branch', []) | ||||
opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']] | ||||
# pats/include/exclude are passed to match.match() directly in | ||||
Mads Kiilerich
|
r17424 | # _matchfiles() revset but walkchangerevs() builds its matcher with | ||
Patrick Mezard
|
r17180 | # scmutil.match(). The difference is input pats are globbed on | ||
# platforms without shell expansion (windows). | ||||
Martin von Zweigbergk
|
r24534 | wctx = repo[None] | ||
match, pats = scmutil.matchandpats(wctx, pats, opts) | ||||
Martin von Zweigbergk
|
r25273 | slowpath = match.anypats() or ((match.isexact() or match.prefix()) and | ||
opts.get('removed')) | ||||
Patrick Mezard
|
r17180 | if not slowpath: | ||
for f in match.files(): | ||||
Martin von Zweigbergk
|
r24534 | if follow and f not in wctx: | ||
Durham Goode
|
r21998 | # If the file exists, it may be a directory, so let it | ||
# take the slow path. | ||||
if os.path.exists(repo.wjoin(f)): | ||||
slowpath = True | ||||
continue | ||||
else: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('cannot follow file not in parent ' | ||
Durham Goode
|
r21998 | 'revision: "%s"') % f) | ||
Patrick Mezard
|
r17180 | filelog = repo.file(f) | ||
Durham Goode
|
r19293 | if not filelog: | ||
Patrick Mezard
|
r17180 | # A zero count may be a directory or deleted file, so | ||
# try to find matching entries on the slow path. | ||||
if follow: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort( | ||
Patrick Mezard
|
r17180 | _('cannot follow nonexistent file: "%s"') % f) | ||
slowpath = True | ||||
smuralid
|
r17746 | |||
# We decided to fall back to the slowpath because at least one | ||||
# of the paths was not a file. Check to see if at least one of them | ||||
# existed in history - in that case, we'll continue down the | ||||
# slowpath; otherwise, we can turn off the slowpath | ||||
if slowpath: | ||||
for path in match.files(): | ||||
if path == '.' or path in repo.store: | ||||
break | ||||
else: | ||||
slowpath = False | ||||
Durham Goode
|
r23500 | fpats = ('_patsfollow', '_patsfollowfirst') | ||
fnopats = (('_ancestors', '_fancestors'), | ||||
('_descendants', '_fdescendants')) | ||||
Patrick Mezard
|
r17180 | if slowpath: | ||
# See walkchangerevs() slow path. | ||||
# | ||||
# pats/include/exclude cannot be represented as separate | ||||
# revset expressions as their filtering logic applies at file | ||||
# level. For instance "-I a -X a" matches a revision touching | ||||
# "a" and "b" while "file(a) and not file(b)" does | ||||
# not. Besides, filesets are evaluated against the working | ||||
# directory. | ||||
matchargs = ['r:', 'd:relpath'] | ||||
for p in pats: | ||||
matchargs.append('p:' + p) | ||||
for p in opts.get('include', []): | ||||
matchargs.append('i:' + p) | ||||
for p in opts.get('exclude', []): | ||||
matchargs.append('x:' + p) | ||||
matchargs = ','.join(('%r' % p) for p in matchargs) | ||||
opts['_matchfiles'] = matchargs | ||||
Durham Goode
|
r23500 | if follow: | ||
opts[fnopats[0][followfirst]] = '.' | ||||
Patrick Mezard
|
r17180 | else: | ||
if follow: | ||||
if pats: | ||||
Mads Kiilerich
|
r17424 | # follow() revset interprets its file argument as a | ||
Patrick Mezard
|
r17180 | # manifest entry, so use match.files(), not pats. | ||
opts[fpats[followfirst]] = list(match.files()) | ||||
else: | ||||
Yuya Nishihara
|
r23955 | op = fnopats[followdescendants][followfirst] | ||
opts[op] = 'rev(%d)' % startrev | ||||
Patrick Mezard
|
r17180 | else: | ||
opts['_patslog'] = list(pats) | ||||
filematcher = None | ||||
if opts.get('patch') or opts.get('stat'): | ||||
Durham Goode
|
r21998 | # When following files, track renames via a special matcher. | ||
# If we're forced to take the slowpath it means we're following | ||||
# at least one pattern/directory, so don't bother with rename tracking. | ||||
if follow and not match.always() and not slowpath: | ||||
Mads Kiilerich
|
r23139 | # _makefollowlogfilematcher expects its files argument to be | ||
# relative to the repo root, so use match.files(), not pats. | ||||
Siddharth Agarwal
|
r22166 | filematcher = _makefollowlogfilematcher(repo, match.files(), | ||
followfirst) | ||||
Patrick Mezard
|
r17180 | else: | ||
Siddharth Agarwal
|
r22167 | filematcher = _makenofollowlogfilematcher(repo, pats, opts) | ||
if filematcher is None: | ||||
filematcher = lambda rev: match | ||||
Patrick Mezard
|
r17180 | |||
expr = [] | ||||
Durham Goode
|
r23501 | for op, val in sorted(opts.iteritems()): | ||
Patrick Mezard
|
r17180 | if not val: | ||
continue | ||||
if op not in opt2revset: | ||||
continue | ||||
revop, andor = opt2revset[op] | ||||
if '%(val)' not in revop: | ||||
expr.append(revop) | ||||
else: | ||||
if not isinstance(val, list): | ||||
e = revop % {'val': val} | ||||
else: | ||||
e = '(' + andor.join((revop % {'val': v}) for v in val) + ')' | ||||
expr.append(e) | ||||
if expr: | ||||
expr = '(' + ' and '.join(expr) + ')' | ||||
else: | ||||
expr = None | ||||
return expr, filematcher | ||||
Yuya Nishihara
|
r24062 | def _logrevs(repo, opts): | ||
timeless@mozdev.org
|
r26098 | # Default --rev value depends on --follow but --follow behavior | ||
Yuya Nishihara
|
r24062 | # depends on revisions resolved from --rev... | ||
follow = opts.get('follow') or opts.get('follow_first') | ||||
if opts.get('rev'): | ||||
revs = scmutil.revrange(repo, opts['rev']) | ||||
Yuya Nishihara
|
r24064 | elif follow and repo.dirstate.p1() == nullid: | ||
Yuya Nishihara
|
r31023 | revs = smartset.baseset() | ||
Yuya Nishihara
|
r24062 | elif follow: | ||
revs = repo.revs('reverse(:.)') | ||||
else: | ||||
Yuya Nishihara
|
r31023 | revs = smartset.spanset(repo) | ||
Yuya Nishihara
|
r24062 | revs.reverse() | ||
return revs | ||||
Patrick Mezard
|
r17180 | def getgraphlogrevs(repo, pats, opts): | ||
"""Return (revs, expr, filematcher) where revs is an iterable of | ||||
revision numbers, expr is a revset string built from log options | ||||
and file patterns or None, and used to filter 'revs'. If --stat or | ||||
--patch are not passed filematcher is None. Otherwise it is a | ||||
callable taking a revision number and returning a match objects | ||||
filtering the files to be detailed when displaying the revision. | ||||
""" | ||||
Siddharth Agarwal
|
r18172 | limit = loglimit(opts) | ||
Yuya Nishihara
|
r24062 | revs = _logrevs(repo, opts) | ||
Patrick Mezard
|
r17180 | if not revs: | ||
Yuya Nishihara
|
r31023 | return smartset.baseset(), None, None | ||
Lucas Moscovicz
|
r21108 | expr, filematcher = _makelogrevset(repo, pats, opts, revs) | ||
Yuya Nishihara
|
r24060 | if opts.get('rev'): | ||
# User-specified revs might be unsorted, but don't sort before | ||||
# _makelogrevset because it might depend on the order of revs | ||||
Martijn Pieters
|
r29346 | if not (revs.isdescending() or revs.istopo()): | ||
Martijn Pieters
|
r29335 | revs.sort(reverse=True) | ||
Patrick Mezard
|
r17180 | if expr: | ||
Yuya Nishihara
|
r34020 | matcher = revset.match(repo.ui, expr) | ||
Yuya Nishihara
|
r34021 | revs = matcher(repo, revs) | ||
Pierre-Yves David
|
r18243 | if limit is not None: | ||
Pierre-Yves David
|
r22807 | limitedrevs = [] | ||
Lucas Moscovicz
|
r20755 | for idx, rev in enumerate(revs): | ||
if idx >= limit: | ||||
break | ||||
limitedrevs.append(rev) | ||||
Yuya Nishihara
|
r31023 | revs = smartset.baseset(limitedrevs) | ||
Siddharth Agarwal
|
r18172 | |||
Patrick Mezard
|
r17180 | return revs, expr, filematcher | ||
Lucas Moscovicz
|
r21127 | def getlogrevs(repo, pats, opts): | ||
"""Return (revs, expr, filematcher) where revs is an iterable of | ||||
revision numbers, expr is a revset string built from log options | ||||
and file patterns or None, and used to filter 'revs'. If --stat or | ||||
--patch are not passed filematcher is None. Otherwise it is a | ||||
callable taking a revision number and returning a match objects | ||||
filtering the files to be detailed when displaying the revision. | ||||
""" | ||||
limit = loglimit(opts) | ||||
Yuya Nishihara
|
r24062 | revs = _logrevs(repo, opts) | ||
Lucas Moscovicz
|
r21127 | if not revs: | ||
Yuya Nishihara
|
r31023 | return smartset.baseset([]), None, None | ||
Lucas Moscovicz
|
r21127 | expr, filematcher = _makelogrevset(repo, pats, opts, revs) | ||
if expr: | ||||
Yuya Nishihara
|
r34020 | matcher = revset.match(repo.ui, expr) | ||
Yuya Nishihara
|
r34021 | revs = matcher(repo, revs) | ||
Lucas Moscovicz
|
r21127 | if limit is not None: | ||
Pierre-Yves David
|
r22806 | limitedrevs = [] | ||
Pierre-Yves David
|
r25169 | for idx, r in enumerate(revs): | ||
if limit <= idx: | ||||
Lucas Moscovicz
|
r21127 | break | ||
Pierre-Yves David
|
r25169 | limitedrevs.append(r) | ||
Yuya Nishihara
|
r31023 | revs = smartset.baseset(limitedrevs) | ||
Lucas Moscovicz
|
r21127 | |||
return revs, expr, filematcher | ||||
Yuya Nishihara
|
r27216 | def _graphnodeformatter(ui, displayer): | ||
spec = ui.config('ui', 'graphnodetemplate') | ||||
if not spec: | ||||
return templatekw.showgraphnode # fast path for "{graphnode}" | ||||
Yuya Nishihara
|
r32045 | spec = templater.unquotestring(spec) | ||
Yuya Nishihara
|
r32873 | templ = formatter.maketemplater(ui, spec) | ||
Yuya Nishihara
|
r27216 | cache = {} | ||
if isinstance(displayer, changeset_templater): | ||||
cache = displayer.cache # reuse cache of slow templates | ||||
props = templatekw.keywords.copy() | ||||
props['templ'] = templ | ||||
props['cache'] = cache | ||||
def formatnode(repo, ctx): | ||||
props['ctx'] = ctx | ||||
props['repo'] = repo | ||||
Yuya Nishihara
|
r28428 | props['ui'] = repo.ui | ||
Yuya Nishihara
|
r27216 | props['revcache'] = {} | ||
Yuya Nishihara
|
r32873 | return templ.render(props) | ||
Yuya Nishihara
|
r27216 | return formatnode | ||
Yuya Nishihara
|
r27213 | def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None, | ||
Patrick Mezard
|
r17180 | filematcher=None): | ||
Yuya Nishihara
|
r27216 | formatnode = _graphnodeformatter(ui, displayer) | ||
Martijn Pieters
|
r28375 | state = graphmod.asciistate() | ||
Martijn Pieters
|
r28600 | styles = state['styles'] | ||
Martijn Pieters
|
r28999 | |||
# only set graph styling if HGPLAIN is not set. | ||||
if ui.plain('graph'): | ||||
# set all edge styles to |, the default pre-3.8 behaviour | ||||
styles.update(dict.fromkeys(styles, '|')) | ||||
else: | ||||
edgetypes = { | ||||
'parent': graphmod.PARENT, | ||||
'grandparent': graphmod.GRANDPARENT, | ||||
'missing': graphmod.MISSINGPARENT | ||||
} | ||||
for name, key in edgetypes.items(): | ||||
# experimental config: experimental.graphstyle.* | ||||
styles[key] = ui.config('experimental', 'graphstyle.%s' % name, | ||||
styles[key]) | ||||
if not styles[key]: | ||||
styles[key] = None | ||||
# experimental config: experimental.graphshorten | ||||
state['graphshorten'] = ui.configbool('experimental', 'graphshorten') | ||||
santiagopim
|
r28891 | |||
Patrick Mezard
|
r17180 | for rev, type, ctx, parents in dag: | ||
Yuya Nishihara
|
r27216 | char = formatnode(repo, ctx) | ||
Patrick Mezard
|
r17180 | copies = None | ||
if getrenamed and ctx.rev(): | ||||
copies = [] | ||||
for fn in ctx.files(): | ||||
rename = getrenamed(fn, ctx.rev()) | ||||
if rename: | ||||
copies.append((fn, rename[0])) | ||||
revmatchfn = None | ||||
if filematcher is not None: | ||||
revmatchfn = filematcher(ctx.rev()) | ||||
Danny Hooper
|
r33860 | edges = edgefn(type, char, state, rev, parents) | ||
firstedge = next(edges) | ||||
width = firstedge[2] | ||||
displayer.show(ctx, copies=copies, matchfn=revmatchfn, | ||||
_graphwidth=width) | ||||
Patrick Mezard
|
r17180 | lines = displayer.hunk.pop(rev).split('\n') | ||
if not lines[-1]: | ||||
del lines[-1] | ||||
Yuya Nishihara
|
r25763 | displayer.flush(ctx) | ||
Danny Hooper
|
r33860 | for type, char, width, coldata in itertools.chain([firstedge], edges): | ||
Patrick Mezard
|
r17180 | graphmod.ascii(ui, state, type, char, lines, coldata) | ||
Danny Hooper
|
r33860 | lines = [] | ||
Patrick Mezard
|
r17180 | displayer.close() | ||
Yuya Nishihara
|
r31486 | def graphlog(ui, repo, pats, opts): | ||
Patrick Mezard
|
r17181 | # Parameters are identical to log command ones | ||
revs, expr, filematcher = getgraphlogrevs(repo, pats, opts) | ||||
revdag = graphmod.dagwalker(repo, revs) | ||||
getrenamed = None | ||||
if opts.get('copies'): | ||||
endrev = None | ||||
if opts.get('rev'): | ||||
Lucas Moscovicz
|
r20760 | endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1 | ||
Patrick Mezard
|
r17181 | getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) | ||
Yuya Nishihara
|
r31078 | |||
ui.pager('log') | ||||
Patrick Mezard
|
r17181 | displayer = show_changeset(ui, repo, opts, buffered=True) | ||
Yuya Nishihara
|
r27213 | displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed, | ||
filematcher) | ||||
Patrick Mezard
|
r17181 | |||
Patrick Mezard
|
r17182 | def checkunsupportedgraphflags(pats, opts): | ||
for op in ["newest_first"]: | ||||
if op in opts and opts[op]: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("-G/--graph option is incompatible with --%s") | ||
Patrick Mezard
|
r17182 | % op.replace("_", "-")) | ||
def graphrevs(repo, nodes, opts): | ||||
limit = loglimit(opts) | ||||
nodes.reverse() | ||||
if limit is not None: | ||||
nodes = nodes[:limit] | ||||
return graphmod.nodes(repo, nodes) | ||||
Matt Harbison
|
r23885 | def add(ui, repo, match, prefix, explicitonly, **opts): | ||
Martin Geisler
|
r12270 | join = lambda f: os.path.join(prefix, f) | ||
Martin Geisler
|
r12269 | bad = [] | ||
Matt Harbison
|
r25436 | |||
badfn = lambda x, y: bad.append(x) or match.bad(x, y) | ||||
Martin Geisler
|
r12269 | names = [] | ||
Martin Geisler
|
r12270 | wctx = repo[None] | ||
Adrian Buehlmann
|
r14138 | cca = None | ||
abort, warn = scmutil.checkportabilityalert(ui) | ||||
if abort or warn: | ||||
Joshua Redstone
|
r17201 | cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate) | ||
Matt Harbison
|
r25436 | |||
Durham Goode
|
r26206 | badmatch = matchmod.badmatch(match, badfn) | ||
dirstate = repo.dirstate | ||||
# We don't want to just call wctx.walk here, since it would return a lot of | ||||
# clean files, which we aren't interested in and takes time. | ||||
for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate), | ||||
True, False, full=False)): | ||||
Martin Geisler
|
r12269 | exact = match.exact(f) | ||
John Coomes
|
r23462 | if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): | ||
Adrian Buehlmann
|
r14138 | if cca: | ||
cca(f) | ||||
Martin Geisler
|
r12269 | names.append(f) | ||
if ui.verbose or not exact: | ||||
Matt Harbison
|
r23686 | ui.status(_('adding %s\n') % match.rel(f)) | ||
Martin Geisler
|
r12270 | |||
Mads Kiilerich
|
r18364 | for subpath in sorted(wctx.substate): | ||
David M. Carr
|
r15410 | sub = wctx.sub(subpath) | ||
try: | ||||
Martin von Zweigbergk
|
r28017 | submatch = matchmod.subdirmatcher(subpath, match) | ||
Pulkit Goyal
|
r32147 | if opts.get(r'subrepos'): | ||
Matt Harbison
|
r23885 | bad.extend(sub.add(ui, submatch, prefix, False, **opts)) | ||
David M. Carr
|
r15410 | else: | ||
Matt Harbison
|
r23885 | bad.extend(sub.add(ui, submatch, prefix, True, **opts)) | ||
David M. Carr
|
r15410 | except error.LookupError: | ||
ui.status(_("skipping missing subrepository: %s\n") | ||||
% join(subpath)) | ||||
Martin Geisler
|
r12270 | |||
Pulkit Goyal
|
r32147 | if not opts.get(r'dry_run'): | ||
Martin Geisler
|
r12270 | rejected = wctx.add(names, prefix) | ||
Martin Geisler
|
r12269 | bad.extend(f for f in rejected if f in match.files()) | ||
return bad | ||||
Matt Harbison
|
r32005 | def addwebdirpath(repo, serverpath, webconf): | ||
webconf[serverpath] = repo.root | ||||
repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root)) | ||||
for r in repo.revs('filelog("path:.hgsub")'): | ||||
ctx = repo[r] | ||||
for subpath in ctx.substate: | ||||
ctx.sub(subpath).addwebdirpath(serverpath, webconf) | ||||
David M. Carr
|
r15912 | def forget(ui, repo, match, prefix, explicitonly): | ||
join = lambda f: os.path.join(prefix, f) | ||||
bad = [] | ||||
Matt Harbison
|
r25437 | badfn = lambda x, y: bad.append(x) or match.bad(x, y) | ||
David M. Carr
|
r15912 | wctx = repo[None] | ||
forgot = [] | ||||
Matt Harbison
|
r25437 | |||
s = repo.status(match=matchmod.badmatch(match, badfn), clean=True) | ||||
Martin von Zweigbergk
|
r32174 | forget = sorted(s.modified + s.added + s.deleted + s.clean) | ||
David M. Carr
|
r15912 | if explicitonly: | ||
forget = [f for f in forget if match.exact(f)] | ||||
Mads Kiilerich
|
r18364 | for subpath in sorted(wctx.substate): | ||
David M. Carr
|
r15912 | sub = wctx.sub(subpath) | ||
try: | ||||
Martin von Zweigbergk
|
r28017 | submatch = matchmod.subdirmatcher(subpath, match) | ||
Matt Harbison
|
r23577 | subbad, subforgot = sub.forget(submatch, prefix) | ||
David M. Carr
|
r15912 | bad.extend([subpath + '/' + f for f in subbad]) | ||
forgot.extend([subpath + '/' + f for f in subforgot]) | ||||
except error.LookupError: | ||||
ui.status(_("skipping missing subrepository: %s\n") | ||||
% join(subpath)) | ||||
FUJIWARA Katsunori
|
r16070 | if not explicitonly: | ||
for f in match.files(): | ||||
Matt Harbison
|
r23673 | if f not in repo.dirstate and not repo.wvfs.isdir(f): | ||
David M. Carr
|
r15912 | if f not in forgot: | ||
Matt Harbison
|
r23673 | if repo.wvfs.exists(f): | ||
Matt Harbison
|
r24548 | # Don't complain if the exact case match wasn't given. | ||
# But don't do this until after checking 'forgot', so | ||||
# that subrepo files aren't normalized, and this op is | ||||
# purely from data cached by the status walk above. | ||||
if repo.dirstate.normalize(f) in repo.dirstate: | ||||
continue | ||||
David M. Carr
|
r15912 | ui.warn(_('not removing %s: ' | ||
'file is already untracked\n') | ||||
Matt Harbison
|
r23686 | % match.rel(f)) | ||
David M. Carr
|
r15912 | bad.append(f) | ||
for f in forget: | ||||
if ui.verbose or not match.exact(f): | ||||
Matt Harbison
|
r23686 | ui.status(_('removing %s\n') % match.rel(f)) | ||
David M. Carr
|
r15912 | |||
rejected = wctx.forget(forget, prefix) | ||||
bad.extend(f for f in rejected if f in match.files()) | ||||
Matt Harbison
|
r23838 | forgot.extend(f for f in forget if f not in rejected) | ||
David M. Carr
|
r15912 | return bad, forgot | ||
Matt Harbison
|
r24413 | def files(ui, ctx, m, fm, fmt, subrepos): | ||
Matt Harbison
|
r24275 | rev = ctx.rev() | ||
ret = 1 | ||||
Matt Harbison
|
r24301 | ds = ctx.repo().dirstate | ||
Matt Harbison
|
r24275 | |||
for f in ctx.matches(m): | ||||
if rev is None and ds[f] == 'r': | ||||
continue | ||||
fm.startitem() | ||||
if ui.verbose: | ||||
fc = ctx[f] | ||||
fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags()) | ||||
fm.data(abspath=f) | ||||
fm.write('path', fmt, m.rel(f)) | ||||
ret = 0 | ||||
Matt Harbison
|
r25228 | for subpath in sorted(ctx.substate): | ||
Hannes Oldenburg
|
r29802 | submatch = matchmod.subdirmatcher(subpath, m) | ||
if (subrepos or m.exact(subpath) or any(submatch.files())): | ||||
Matt Harbison
|
r24413 | sub = ctx.sub(subpath) | ||
try: | ||||
Matt Harbison
|
r28387 | recurse = m.exact(subpath) or subrepos | ||
if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0: | ||||
Matt Harbison
|
r24413 | ret = 0 | ||
except error.LookupError: | ||||
ui.status(_("skipping missing subrepository: %s\n") | ||||
% m.abs(subpath)) | ||||
Matt Harbison
|
r24275 | return ret | ||
timeless
|
r28607 | def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None): | ||
Matt Harbison
|
r23325 | join = lambda f: os.path.join(prefix, f) | ||
Matt Harbison
|
r23289 | ret = 0 | ||
s = repo.status(match=m, clean=True) | ||||
modified, added, deleted, clean = s[0], s[1], s[3], s[6] | ||||
Matt Harbison
|
r23325 | wctx = repo[None] | ||
timeless
|
r28607 | if warnings is None: | ||
warnings = [] | ||||
warn = True | ||||
else: | ||||
warn = False | ||||
timeless
|
r28608 | subs = sorted(wctx.substate) | ||
total = len(subs) | ||||
count = 0 | ||||
for subpath in subs: | ||||
count += 1 | ||||
Hannes Oldenburg
|
r29802 | submatch = matchmod.subdirmatcher(subpath, m) | ||
if subrepos or m.exact(subpath) or any(submatch.files()): | ||||
timeless
|
r28608 | ui.progress(_('searching'), count, total=total, unit=_('subrepos')) | ||
Matt Harbison
|
r23325 | sub = wctx.sub(subpath) | ||
try: | ||||
timeless
|
r28607 | if sub.removefiles(submatch, prefix, after, force, subrepos, | ||
warnings): | ||||
Matt Harbison
|
r23325 | ret = 1 | ||
except error.LookupError: | ||||
timeless
|
r28607 | warnings.append(_("skipping missing subrepository: %s\n") | ||
Matt Harbison
|
r23325 | % join(subpath)) | ||
timeless
|
r28608 | ui.progress(_('searching'), None) | ||
Matt Harbison
|
r23325 | |||
Matt Harbison
|
r23289 | # warn about failure to delete explicit files/dirs | ||
Drew Gottlieb
|
r24635 | deleteddirs = util.dirs(deleted) | ||
timeless
|
r28608 | files = m.files() | ||
total = len(files) | ||||
count = 0 | ||||
for f in files: | ||||
Matt Harbison
|
r23325 | def insubrepo(): | ||
for subpath in wctx.substate: | ||||
Hannes Oldenburg
|
r29622 | if f.startswith(subpath + '/'): | ||
Matt Harbison
|
r23325 | return True | ||
return False | ||||
timeless
|
r28608 | count += 1 | ||
ui.progress(_('deleting'), count, total=total, unit=_('files')) | ||||
Martin von Zweigbergk
|
r24955 | isdir = f in deleteddirs or wctx.hasdir(f) | ||
Hannes Oldenburg
|
r29622 | if (f in repo.dirstate or isdir or f == '.' | ||
or insubrepo() or f in subs): | ||||
Matt Harbison
|
r23289 | continue | ||
Matt Harbison
|
r23325 | |||
Matt Harbison
|
r23674 | if repo.wvfs.exists(f): | ||
if repo.wvfs.isdir(f): | ||||
timeless
|
r28607 | warnings.append(_('not removing %s: no tracked files\n') | ||
Matt Harbison
|
r23686 | % m.rel(f)) | ||
Matt Harbison
|
r23289 | else: | ||
timeless
|
r28607 | warnings.append(_('not removing %s: file is untracked\n') | ||
Matt Harbison
|
r23686 | % m.rel(f)) | ||
Matt Harbison
|
r23289 | # missing files will generate a warning elsewhere | ||
ret = 1 | ||||
timeless
|
r28608 | ui.progress(_('deleting'), None) | ||
Matt Harbison
|
r23289 | |||
if force: | ||||
list = modified + deleted + clean + added | ||||
elif after: | ||||
list = deleted | ||||
timeless
|
r28608 | remaining = modified + added + clean | ||
total = len(remaining) | ||||
count = 0 | ||||
for f in remaining: | ||||
count += 1 | ||||
ui.progress(_('skipping'), count, total=total, unit=_('files')) | ||||
warnings.append(_('not removing %s: file still exists\n') | ||||
% m.rel(f)) | ||||
Matt Harbison
|
r23289 | ret = 1 | ||
timeless
|
r28608 | ui.progress(_('skipping'), None) | ||
Matt Harbison
|
r23289 | else: | ||
list = deleted + clean | ||||
timeless
|
r28608 | total = len(modified) + len(added) | ||
count = 0 | ||||
Matt Harbison
|
r23289 | for f in modified: | ||
timeless
|
r28608 | count += 1 | ||
ui.progress(_('skipping'), count, total=total, unit=_('files')) | ||||
timeless
|
r28607 | warnings.append(_('not removing %s: file is modified (use -f' | ||
Matt Harbison
|
r23686 | ' to force removal)\n') % m.rel(f)) | ||
Matt Harbison
|
r23289 | ret = 1 | ||
for f in added: | ||||
timeless
|
r28608 | count += 1 | ||
ui.progress(_('skipping'), count, total=total, unit=_('files')) | ||||
timeless
|
r29963 | warnings.append(_("not removing %s: file has been marked for add" | ||
" (use 'hg forget' to undo add)\n") % m.rel(f)) | ||||
Matt Harbison
|
r23289 | ret = 1 | ||
timeless
|
r28608 | ui.progress(_('skipping'), None) | ||
list = sorted(list) | ||||
total = len(list) | ||||
count = 0 | ||||
for f in list: | ||||
count += 1 | ||||
Matt Harbison
|
r23289 | if ui.verbose or not m.exact(f): | ||
timeless
|
r28608 | ui.progress(_('deleting'), count, total=total, unit=_('files')) | ||
Matt Harbison
|
r23686 | ui.status(_('removing %s\n') % m.rel(f)) | ||
timeless
|
r28608 | ui.progress(_('deleting'), None) | ||
Matt Harbison
|
r23289 | |||
Bryan O'Sullivan
|
r27802 | with repo.wlock(): | ||
Matt Harbison
|
r23289 | if not after: | ||
for f in list: | ||||
if f in added: | ||||
continue # we never unlink added files on remove | ||||
Mads Kiilerich
|
r31309 | repo.wvfs.unlinkpath(f, ignoremissing=True) | ||
Matt Harbison
|
r23289 | repo[None].forget(list) | ||
timeless
|
r28607 | if warn: | ||
for warning in warnings: | ||||
ui.warn(warning) | ||||
Matt Harbison
|
r23289 | return ret | ||
Yuya Nishihara
|
r32578 | def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): | ||
Matt Harbison
|
r21040 | err = 1 | ||
def write(path): | ||||
Yuya Nishihara
|
r32578 | filename = None | ||
Yuya Nishihara
|
r32576 | if fntemplate: | ||
filename = makefilename(repo, fntemplate, ctx.node(), | ||||
pathname=os.path.join(prefix, path)) | ||||
Yuya Nishihara
|
r32578 | with formatter.maybereopen(basefm, filename, opts) as fm: | ||
Yuya Nishihara
|
r32577 | data = ctx[path].data() | ||
if opts.get('decode'): | ||||
data = repo.wwritedata(path, data) | ||||
Yuya Nishihara
|
r32578 | fm.startitem() | ||
fm.write('data', '%s', data) | ||||
fm.data(abspath=path, path=matcher.rel(path)) | ||||
Matt Harbison
|
r21040 | |||
# Automation often uses hg cat on single files, so special case it | ||||
# for performance to avoid the cost of parsing the manifest. | ||||
if len(matcher.files()) == 1 and not matcher.anypats(): | ||||
file = matcher.files()[0] | ||||
Durham Goode
|
r30340 | mfl = repo.manifestlog | ||
Yuya Nishihara
|
r24718 | mfnode = ctx.manifestnode() | ||
Durham Goode
|
r30340 | try: | ||
if mfnode and mfl[mfnode].find(file)[0]: | ||||
write(file) | ||||
return 0 | ||||
except KeyError: | ||||
pass | ||||
Matt Harbison
|
r21040 | |||
Hannes Oldenburg
|
r29739 | for abs in ctx.walk(matcher): | ||
Matt Harbison
|
r21040 | write(abs) | ||
err = 0 | ||||
Matt Harbison
|
r21041 | |||
for subpath in sorted(ctx.substate): | ||||
sub = ctx.sub(subpath) | ||||
try: | ||||
Martin von Zweigbergk
|
r28017 | submatch = matchmod.subdirmatcher(subpath, matcher) | ||
Matt Harbison
|
r21041 | |||
Yuya Nishihara
|
r32578 | if not sub.cat(submatch, basefm, fntemplate, | ||
Yuya Nishihara
|
r32540 | os.path.join(prefix, sub._path), **opts): | ||
Matt Harbison
|
r21041 | err = 0 | ||
except error.RepoLookupError: | ||||
ui.status(_("skipping missing subrepository: %s\n") | ||||
% os.path.join(prefix, subpath)) | ||||
Matt Harbison
|
r21040 | return err | ||
Bryan O'Sullivan
|
r5034 | def commit(ui, repo, commitfunc, pats, opts): | ||
'''commit the specified files or all outstanding changes''' | ||||
Thomas Arendsen Hein
|
r6139 | date = opts.get('date') | ||
if date: | ||||
opts['date'] = util.parsedate(date) | ||||
Idan Kamara
|
r14635 | message = logmessage(ui, opts) | ||
Matt Harbison
|
r23533 | matcher = scmutil.match(repo[None], pats, opts) | ||
Bryan O'Sullivan
|
r5034 | |||
Martin von Zweigbergk
|
r33619 | dsguard = None | ||
Kirill Smelkov
|
r5829 | # extract addremove carefully -- this function can be called from a command | ||
# that doesn't support addremove | ||||
Martin von Zweigbergk
|
r33822 | if opts.get('addremove'): | ||
dsguard = dirstateguard.dirstateguard(repo, 'commit') | ||||
Martin von Zweigbergk
|
r33823 | with dsguard or util.nullcontextmanager(): | ||
Martin von Zweigbergk
|
r33822 | if dsguard: | ||
Martin von Zweigbergk
|
r33619 | if scmutil.addremove(repo, matcher, "", opts) != 0: | ||
raise error.Abort( | ||||
_("failed to mark all new/missing files as added/removed")) | ||||
Martin von Zweigbergk
|
r33823 | return commitfunc(ui, repo, message, matcher, opts) | ||
Matt Mackall
|
r8407 | |||
Hannes Oldenburg
|
r29819 | def samefile(f, ctx1, ctx2): | ||
if f in ctx1.manifest(): | ||||
a = ctx1.filectx(f) | ||||
if f in ctx2.manifest(): | ||||
b = ctx2.filectx(f) | ||||
return (not a.cmp(b) | ||||
and a.flags() == b.flags()) | ||||
else: | ||||
return False | ||||
else: | ||||
return f not in ctx2.manifest() | ||||
Saurabh Singh
|
r34088 | def amend(ui, repo, old, extra, pats, opts): | ||
Gregory Szorc
|
r25930 | # avoid cycle context -> subrepo -> cmdutil | ||
Gregory Szorc
|
r28322 | from . import context | ||
Gregory Szorc
|
r25930 | |||
Matt Harbison
|
r23101 | # amend will reuse the existing user if not specified, but the obsolete | ||
# marker creation requires that the current user's name is specified. | ||||
Durham Goode
|
r24379 | if obsolete.isenabled(repo, obsolete.createmarkersopt): | ||
Matt Harbison
|
r23101 | ui.username() # raise exception if username not set | ||
Idan Kamara
|
r16458 | ui.note(_('amending changeset %s\n') % old) | ||
base = old.p1() | ||||
Boris Feld
|
r33509 | with repo.wlock(), repo.lock(), repo.transaction('amend'): | ||
Jun Wu
|
r33438 | # Participating changesets: | ||
# | ||||
Saurabh Singh
|
r34087 | # wctx o - workingctx that contains changes from working copy | ||
# | to go into amending commit | ||||
Jun Wu
|
r33438 | # | | ||
# old o - changeset to amend | ||||
# | | ||||
Saurabh Singh
|
r34058 | # base o - first parent of the changeset to amend | ||
Saurabh Singh
|
r34087 | wctx = repo[None] | ||
Jun Wu
|
r33438 | |||
# Update extra dict from amended commit (e.g. to preserve graft | ||||
# source) | ||||
extra.update(old.extra()) | ||||
Saurabh Singh
|
r34087 | # Also update it from the from the wctx | ||
extra.update(wctx.extra()) | ||||
user = opts.get('user') or old.user() | ||||
date = opts.get('date') or old.date() | ||||
Jun Wu
|
r33438 | |||
if len(old.parents()) > 1: | ||||
# ctx.files() isn't reliable for merges, so fall back to the | ||||
# slower repo.status() method | ||||
files = set([fn for st in repo.status(base, old)[:3] | ||||
for fn in st]) | ||||
else: | ||||
files = set(old.files()) | ||||
Saurabh Singh
|
r34087 | # add/remove the files to the working copy if the "addremove" option | ||
# was specified. | ||||
matcher = scmutil.match(wctx, pats, opts) | ||||
if (opts.get('addremove') | ||||
and scmutil.addremove(repo, matcher, "", opts)): | ||||
raise error.Abort( | ||||
_("failed to mark all new/missing files as added/removed")) | ||||
filestoamend = set(f for f in wctx.files() if matcher(f)) | ||||
changes = (len(filestoamend) > 0) | ||||
if changes: | ||||
Jun Wu
|
r33438 | # Recompute copies (avoid recording a -> b -> a) | ||
Saurabh Singh
|
r34087 | copied = copies.pathcopies(base, wctx, matcher) | ||
Jun Wu
|
r33438 | if old.p2: | ||
Saurabh Singh
|
r34087 | copied.update(copies.pathcopies(old.p2(), wctx, matcher)) | ||
Jun Wu
|
r33438 | |||
# Prune files which were reverted by the updates: if old | ||||
Saurabh Singh
|
r34087 | # introduced file X and the file was renamed in the working | ||
# copy, then those two files are the same and | ||||
Jun Wu
|
r33438 | # we can discard X from our list of files. Likewise if X | ||
# was deleted, it's no longer relevant | ||||
Saurabh Singh
|
r34087 | files.update(filestoamend) | ||
files = [f for f in files if not samefile(f, wctx, base)] | ||||
Jun Wu
|
r33438 | |||
def filectxfn(repo, ctx_, path): | ||||
try: | ||||
Saurabh Singh
|
r34087 | # If the file being considered is not amongst the files | ||
# to be amended, we should return the file context from the | ||||
# old changeset. This avoids issues when only some files in | ||||
# the working copy are being amended but there are also | ||||
# changes to other files from the old changeset. | ||||
if path not in filestoamend: | ||||
return old.filectx(path) | ||||
fctx = wctx[path] | ||||
# Return None for removed files. | ||||
if not fctx.exists(): | ||||
return None | ||||
Jun Wu
|
r33438 | flags = fctx.flags() | ||
mctx = context.memfilectx(repo, | ||||
fctx.path(), fctx.data(), | ||||
islink='l' in flags, | ||||
isexec='x' in flags, | ||||
copied=copied.get(path)) | ||||
return mctx | ||||
except KeyError: | ||||
return None | ||||
else: | ||||
ui.note(_('copying changeset %s to %s\n') % (old, base)) | ||||
# Use version of files as in the old cset | ||||
def filectxfn(repo, ctx_, path): | ||||
try: | ||||
return old.filectx(path) | ||||
except KeyError: | ||||
return None | ||||
Saurabh Singh
|
r34087 | # See if we got a message from -m or -l, if not, open the editor with | ||
# the message of the changeset to amend. | ||||
message = logmessage(ui, opts) | ||||
Jun Wu
|
r33438 | editform = mergeeditform(old, 'commit.amend') | ||
editor = getcommiteditor(editform=editform, | ||||
**pycompat.strkwargs(opts)) | ||||
Saurabh Singh
|
r34087 | |||
Jun Wu
|
r33438 | if not message: | ||
editor = getcommiteditor(edit=True, editform=editform) | ||||
message = old.description() | ||||
pureextra = extra.copy() | ||||
extra['amend_source'] = old.hex() | ||||
new = context.memctx(repo, | ||||
parents=[base.node(), old.p2().node()], | ||||
text=message, | ||||
files=files, | ||||
filectxfn=filectxfn, | ||||
user=user, | ||||
date=date, | ||||
extra=extra, | ||||
editor=editor) | ||||
newdesc = changelog.stripdesc(new.description()) | ||||
Saurabh Singh
|
r34087 | if ((not changes) | ||
Jun Wu
|
r33438 | and newdesc == old.description() | ||
and user == old.user() | ||||
and date == old.date() | ||||
and pureextra == old.extra()): | ||||
# nothing changed. continuing here would create a new node | ||||
# anyway because of the amend_source noise. | ||||
Pierre-Yves David
|
r17472 | # | ||
Jun Wu
|
r33438 | # This not what we expect from amend. | ||
return old.node() | ||||
Martin von Zweigbergk
|
r34083 | if opts.get('secret'): | ||
commitphase = 'secret' | ||||
else: | ||||
commitphase = old.phase() | ||||
overrides = {('phases', 'new-commit'): commitphase} | ||||
with ui.configoverride(overrides, 'amend'): | ||||
Jun Wu
|
r33438 | newid = repo.commitctx(new) | ||
Saurabh Singh
|
r34057 | |||
# Reroute the working copy parent to the new changeset | ||||
repo.setparents(newid, nullid) | ||||
mapping = {old.node(): (newid,)} | ||||
scmutil.cleanupnodes(repo, mapping, 'amend') | ||||
Saurabh Singh
|
r34087 | # Fixing the dirstate because localrepo.commitctx does not update | ||
# it. This is rather convenient because we did not need to update | ||||
# the dirstate for all the files in the new commit which commitctx | ||||
# could have done if it updated the dirstate. Now, we can | ||||
# selectively update the dirstate only for the amended files. | ||||
dirstate = repo.dirstate | ||||
# Update the state of the files which were added and | ||||
# and modified in the amend to "normal" in the dirstate. | ||||
normalfiles = set(wctx.modified() + wctx.added()) & filestoamend | ||||
for f in normalfiles: | ||||
dirstate.normal(f) | ||||
# Update the state of files which were removed in the amend | ||||
# to "removed" in the dirstate. | ||||
removedfiles = set(wctx.removed()) & filestoamend | ||||
for f in removedfiles: | ||||
dirstate.drop(f) | ||||
Idan Kamara
|
r16458 | return newid | ||
FUJIWARA Katsunori
|
r21999 | def commiteditor(repo, ctx, subs, editform=''): | ||
Matt Mackall
|
r8407 | if ctx.description(): | ||
return ctx.description() | ||||
Tony Tung
|
r26742 | return commitforceeditor(repo, ctx, subs, editform=editform, | ||
unchangedmessagedetection=True) | ||||
Matt Mackall
|
r8407 | |||
FUJIWARA Katsunori
|
r21999 | def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None, | ||
Tony Tung
|
r26742 | editform='', unchangedmessagedetection=False): | ||
Matt Mackall
|
r21923 | if not extramsg: | ||
extramsg = _("Leave message empty to abort commit.") | ||||
FUJIWARA Katsunori
|
r22012 | |||
forms = [e for e in editform.split('.') if e] | ||||
forms.insert(0, 'changeset') | ||||
Tony Tung
|
r26742 | templatetext = None | ||
FUJIWARA Katsunori
|
r22012 | while forms: | ||
Yuya Nishihara
|
r32878 | ref = '.'.join(forms) | ||
if repo.ui.config('committemplate', ref): | ||||
Tony Tung
|
r26742 | templatetext = committext = buildcommittemplate( | ||
Yuya Nishihara
|
r32878 | repo, ctx, subs, extramsg, ref) | ||
FUJIWARA Katsunori
|
r22012 | break | ||
forms.pop() | ||||
FUJIWARA Katsunori
|
r21924 | else: | ||
committext = buildcommittext(repo, ctx, subs, extramsg) | ||||
FUJIWARA Katsunori
|
r21869 | |||
# run editor in the repository root | ||||
Pulkit Goyal
|
r30519 | olddir = pycompat.getcwd() | ||
FUJIWARA Katsunori
|
r21869 | os.chdir(repo.root) | ||
FUJIWARA Katsunori
|
r26750 | |||
# make in-memory changes visible to external process | ||||
tr = repo.currenttransaction() | ||||
repo.dirstate.write(tr) | ||||
pending = tr and tr.writepending() and repo.root | ||||
Tony Tung
|
r26742 | editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(), | ||
Sean Farley
|
r30836 | editform=editform, pending=pending, | ||
Michael Bolin
|
r34030 | repopath=repo.path, action='commit') | ||
Yuya Nishihara
|
r30724 | text = editortext | ||
Sean Farley
|
r30703 | |||
# strip away anything below this special string (used for editors that want | ||||
# to display the diff) | ||||
Yuya Nishihara
|
r30724 | stripbelow = re.search(_linebelow, text, flags=re.MULTILINE) | ||
Sean Farley
|
r30703 | if stripbelow: | ||
Yuya Nishihara
|
r30724 | text = text[:stripbelow.start()] | ||
text = re.sub("(?m)^HG:.*(\n|$)", "", text) | ||||
FUJIWARA Katsunori
|
r21869 | os.chdir(olddir) | ||
if finishdesc: | ||||
text = finishdesc(text) | ||||
if not text.strip(): | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("empty commit message")) | ||
Tony Tung
|
r26742 | if unchangedmessagedetection and editortext == templatetext: | ||
raise error.Abort(_("commit message unchanged")) | ||||
FUJIWARA Katsunori
|
r21869 | |||
return text | ||||
Yuya Nishihara
|
r32878 | def buildcommittemplate(repo, ctx, subs, extramsg, ref): | ||
FUJIWARA Katsunori
|
r21924 | ui = repo.ui | ||
Yuya Nishihara
|
r32878 | spec = formatter.templatespec(ref, None, None) | ||
Yuya Nishihara
|
r32839 | t = changeset_templater(ui, repo, spec, None, {}, False) | ||
Yuya Nishihara
|
r32878 | t.t.cache.update((k, templater.unquotestring(v)) | ||
for k, v in repo.ui.configitems('committemplate')) | ||||
FUJIWARA Katsunori
|
r22013 | |||
FUJIWARA Katsunori
|
r21924 | if not extramsg: | ||
extramsg = '' # ensure that extramsg is string | ||||
ui.pushbuffer() | ||||
t.show(ctx, extramsg=extramsg) | ||||
return ui.popbuffer() | ||||
timeless@mozdev.org
|
r26426 | def hgprefix(msg): | ||
return "\n".join(["HG: %s" % a for a in msg.split("\n") if a]) | ||||
FUJIWARA Katsunori
|
r21869 | def buildcommittext(repo, ctx, subs, extramsg): | ||
Matt Mackall
|
r8407 | edittext = [] | ||
Matt Mackall
|
r8707 | modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() | ||
Matt Mackall
|
r8407 | if ctx.description(): | ||
edittext.append(ctx.description()) | ||||
edittext.append("") | ||||
edittext.append("") # Empty line between message and comments. | ||||
timeless@mozdev.org
|
r26426 | edittext.append(hgprefix(_("Enter commit message." | ||
" Lines beginning with 'HG:' are removed."))) | ||||
edittext.append(hgprefix(extramsg)) | ||||
Matt Mackall
|
r8407 | edittext.append("HG: --") | ||
timeless@mozdev.org
|
r26426 | edittext.append(hgprefix(_("user: %s") % ctx.user())) | ||
Matt Mackall
|
r8407 | if ctx.p2(): | ||
timeless@mozdev.org
|
r26426 | edittext.append(hgprefix(_("branch merge"))) | ||
Matt Mackall
|
r8407 | if ctx.branch(): | ||
timeless@mozdev.org
|
r26426 | edittext.append(hgprefix(_("branch '%s'") % ctx.branch())) | ||
Ryan McElroy
|
r24986 | if bookmarks.isactivewdirparent(repo): | ||
timeless@mozdev.org
|
r26426 | edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark)) | ||
edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs]) | ||||
edittext.extend([hgprefix(_("added %s") % f) for f in added]) | ||||
edittext.extend([hgprefix(_("changed %s") % f) for f in modified]) | ||||
edittext.extend([hgprefix(_("removed %s") % f) for f in removed]) | ||||
Matt Mackall
|
r8707 | if not added and not modified and not removed: | ||
timeless@mozdev.org
|
r26426 | edittext.append(hgprefix(_("no files changed"))) | ||
Matt Mackall
|
r8407 | edittext.append("") | ||
FUJIWARA Katsunori
|
r21869 | return "\n".join(edittext) | ||
Adrian Buehlmann
|
r14297 | |||
Pierre-Yves David
|
r26324 | def commitstatus(repo, node, branch, bheads=None, opts=None): | ||
if opts is None: | ||||
opts = {} | ||||
Kevin Bullock
|
r18688 | ctx = repo[node] | ||
parents = ctx.parents() | ||||
if (not opts.get('amend') and bheads and node not in bheads and not | ||||
[x for x in parents if x.node() in bheads and x.branch() == branch]): | ||||
repo.ui.status(_('created new head\n')) | ||||
# The message is not printed for initial roots. For the other | ||||
# changesets, it is printed in the following situations: | ||||
# | ||||
# Par column: for the 2 parents with ... | ||||
# N: null or no parent | ||||
# B: parent is on another named branch | ||||
# C: parent is a regular non head changeset | ||||
# H: parent was a branch head of the current branch | ||||
# Msg column: whether we print "created new head" message | ||||
# In the following, it is assumed that there already exists some | ||||
# initial branch heads of the current branch, otherwise nothing is | ||||
# printed anyway. | ||||
# | ||||
# Par Msg Comment | ||||
# N N y additional topo root | ||||
# | ||||
# B N y additional branch root | ||||
# C N y additional topo head | ||||
# H N n usual case | ||||
# | ||||
# B B y weird additional branch root | ||||
# C B y branch merge | ||||
# H B n merge with named branch | ||||
# | ||||
# C C y additional head from merge | ||||
# C H n merge with a head | ||||
# | ||||
# H H n head merge: head count decreases | ||||
if not opts.get('close_branch'): | ||||
for r in parents: | ||||
if r.closesbranch() and r.branch() == branch: | ||||
repo.ui.status(_('reopening closed branch head %d\n') % r) | ||||
if repo.ui.debugflag: | ||||
repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex())) | ||||
elif repo.ui.verbose: | ||||
repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx)) | ||||
Matt Harbison
|
r27943 | def postcommitstatus(repo, pats, opts): | ||
return repo.status(match=scmutil.match(repo[None], pats, opts)) | ||||
Angel Ezquerra
|
r16304 | def revert(ui, repo, ctx, parents, *pats, **opts): | ||
parent, p2 = parents | ||||
node = ctx.node() | ||||
mf = ctx.manifest() | ||||
Pierre-Yves David
|
r21579 | if node == p2: | ||
parent = p2 | ||||
Angel Ezquerra
|
r16304 | |||
# need all matching names in dirstate and manifest of target rev, | ||||
# so have to walk both. do not print errors if files exist in one | ||||
Yuya Nishihara
|
r24451 | # but not other. in both cases, filesets should be evaluated against | ||
# workingctx to get consistent result (issue4497). this means 'set:**' | ||||
# cannot be used to select missing files from target rev. | ||||
Angel Ezquerra
|
r16304 | |||
Pierre-Yves David
|
r21575 | # `names` is a mapping for all elements in working copy and target revision | ||
# The mapping is in the form: | ||||
# <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>) | ||||
Angel Ezquerra
|
r16304 | names = {} | ||
Bryan O'Sullivan
|
r27803 | with repo.wlock(): | ||
Pierre-Yves David
|
r21575 | ## filling of the `names` mapping | ||
# walk dirstate to fill `names` | ||||
Angel Ezquerra
|
r16304 | |||
Laurent Charignon
|
r24475 | interactive = opts.get('interactive', False) | ||
Martin von Zweigbergk
|
r24449 | wctx = repo[None] | ||
m = scmutil.match(wctx, pats, opts) | ||||
Matt Mackall
|
r24479 | |||
# we'll need this later | ||||
targetsubs = sorted(s for s in wctx.substate if m(s)) | ||||
Martin von Zweigbergk
|
r24450 | if not m.always(): | ||
Augie Fackler
|
r32362 | matcher = matchmod.badmatch(m, lambda x, y: False) | ||
Martin von Zweigbergk
|
r32382 | for abs in wctx.walk(matcher): | ||
Durham Goode
|
r22573 | names[abs] = m.rel(abs), m.exact(abs) | ||
# walk target manifest to fill `names` | ||||
def badfn(path, msg): | ||||
if path in names: | ||||
return | ||||
if path in ctx.substate: | ||||
Angel Ezquerra
|
r16304 | return | ||
Durham Goode
|
r22573 | path_ = path + '/' | ||
for f in names: | ||||
if f.startswith(path_): | ||||
return | ||||
ui.warn("%s: %s\n" % (m.rel(path), msg)) | ||||
Matt Harbison
|
r25439 | for abs in ctx.walk(matchmod.badmatch(m, badfn)): | ||
Durham Goode
|
r22573 | if abs not in names: | ||
names[abs] = m.rel(abs), m.exact(abs) | ||||
# Find status of all file in `names`. | ||||
m = scmutil.matchfiles(repo, names) | ||||
changes = repo.status(node1=node, match=m, | ||||
unknown=True, ignored=True, clean=True) | ||||
else: | ||||
Martin von Zweigbergk
|
r24450 | changes = repo.status(node1=node, match=m) | ||
Durham Goode
|
r22573 | for kind in changes: | ||
for abs in kind: | ||||
names[abs] = m.rel(abs), m.exact(abs) | ||||
m = scmutil.matchfiles(repo, names) | ||||
Martin von Zweigbergk
|
r23374 | modified = set(changes.modified) | ||
added = set(changes.added) | ||||
removed = set(changes.removed) | ||||
_deleted = set(changes.deleted) | ||||
unknown = set(changes.unknown) | ||||
unknown.update(changes.ignored) | ||||
clean = set(changes.clean) | ||||
Pierre-Yves David
|
r22610 | modadded = set() | ||
Pierre-Yves David
|
r22185 | |||
Mads Kiilerich
|
r24180 | # We need to account for the state of the file in the dirstate, | ||
# even when we revert against something else than parent. This will | ||||
Pierre-Yves David
|
r22155 | # slightly alter the behavior of revert (doing back up or not, delete | ||
Mads Kiilerich
|
r23139 | # or just forget etc). | ||
Pierre-Yves David
|
r22155 | if parent == node: | ||
dsmodified = modified | ||||
dsadded = added | ||||
dsremoved = removed | ||||
Pierre-Yves David
|
r23403 | # store all local modifications, useful later for rename detection | ||
localchanges = dsmodified | dsadded | ||||
Pierre-Yves David
|
r22155 | modified, added, removed = set(), set(), set() | ||
else: | ||||
changes = repo.status(node1=parent, match=m) | ||||
Martin von Zweigbergk
|
r23374 | dsmodified = set(changes.modified) | ||
dsadded = set(changes.added) | ||||
dsremoved = set(changes.removed) | ||||
Pierre-Yves David
|
r23403 | # store all local modifications, useful later for rename detection | ||
localchanges = dsmodified | dsadded | ||||
Angel Ezquerra
|
r16304 | |||
Pierre-Yves David
|
r22188 | # only take into account for removes between wc and target | ||
clean |= dsremoved - removed | ||||
dsremoved &= removed | ||||
# distinct between dirstate remove and other | ||||
removed -= dsremoved | ||||
Pierre-Yves David
|
r22610 | modadded = added & dsmodified | ||
added -= modadded | ||||
Pierre-Yves David
|
r22190 | # tell newly modified apart. | ||
dsmodified &= modified | ||||
Augie Fackler
|
r29103 | dsmodified |= modified & dsadded # dirstate added may need backup | ||
Pierre-Yves David
|
r22190 | modified -= dsmodified | ||
Pierre-Yves David
|
r22488 | # We need to wait for some post-processing to update this set | ||
# before making the distinction. The dirstate will be used for | ||||
# that purpose. | ||||
Pierre-Yves David
|
r22208 | dsadded = added | ||
Pierre-Yves David
|
r22209 | # in case of merge, files that are actually added can be reported as | ||
# modified, we need to post process the result | ||||
if p2 != nullid: | ||||
Durham Goode
|
r31134 | mergeadd = set(dsmodified) | ||
for path in dsmodified: | ||||
if path in mf: | ||||
mergeadd.remove(path) | ||||
Pierre-Yves David
|
r22209 | dsadded |= mergeadd | ||
dsmodified -= mergeadd | ||||
Angel Ezquerra
|
r16304 | |||
Pierre-Yves David
|
r21575 | # if f is a rename, update `names` to also revert the source | ||
Angel Ezquerra
|
r16304 | cwd = repo.getcwd() | ||
Pierre-Yves David
|
r23403 | for f in localchanges: | ||
Angel Ezquerra
|
r16304 | src = repo.dirstate.copied(f) | ||
Pierre-Yves David
|
r22213 | # XXX should we check for rename down to target node? | ||
Angel Ezquerra
|
r16304 | if src and src not in names and repo.dirstate[src] == 'r': | ||
Pierre-Yves David
|
r22154 | dsremoved.add(src) | ||
Angel Ezquerra
|
r16304 | names[src] = (repo.pathto(src, cwd), True) | ||
Martin von Zweigbergk
|
r31157 | # determine the exact nature of the deleted changesets | ||
deladded = set(_deleted) | ||||
for path in _deleted: | ||||
if path in mf: | ||||
deladded.remove(path) | ||||
deleted = _deleted - deladded | ||||
Pierre-Yves David
|
r22488 | # distinguish between file to forget and the other | ||
added = set() | ||||
for abs in dsadded: | ||||
if repo.dirstate[abs] != 'a': | ||||
added.add(abs) | ||||
dsadded -= added | ||||
Pierre-Yves David
|
r22490 | for abs in deladded: | ||
Angel Ezquerra
|
r16304 | if repo.dirstate[abs] == 'a': | ||
Pierre-Yves David
|
r22490 | dsadded.add(abs) | ||
deladded -= dsadded | ||||
Pierre-Yves David
|
r22396 | # For files marked as removed, we check if an unknown file is present at | ||
# the same path. If a such file exists it may need to be backed up. | ||||
# Making the distinction at this stage helps have simpler backup | ||||
# logic. | ||||
removunk = set() | ||||
for abs in removed: | ||||
target = repo.wjoin(abs) | ||||
if os.path.lexists(target): | ||||
removunk.add(abs) | ||||
removed -= removunk | ||||
dsremovunk = set() | ||||
for abs in dsremoved: | ||||
target = repo.wjoin(abs) | ||||
if os.path.lexists(target): | ||||
dsremovunk.add(abs) | ||||
dsremoved -= dsremovunk | ||||
Angel Ezquerra
|
r16304 | |||
Pierre-Yves David
|
r21575 | # action to be actually performed by revert | ||
# (<list of file>, message>) tuple | ||||
Pierre-Yves David
|
r21576 | actions = {'revert': ([], _('reverting %s\n')), | ||
'add': ([], _('adding %s\n')), | ||||
Pierre-Yves David
|
r22489 | 'remove': ([], _('removing %s\n')), | ||
Pierre-Yves David
|
r22491 | 'drop': ([], _('removing %s\n')), | ||
Pierre-Yves David
|
r22489 | 'forget': ([], _('forgetting %s\n')), | ||
Pierre-Yves David
|
r22231 | 'undelete': ([], _('undeleting %s\n')), | ||
Pierre-Yves David
|
r22234 | 'noop': (None, _('no changes needed to %s\n')), | ||
Pierre-Yves David
|
r22236 | 'unknown': (None, _('file not managed: %s\n')), | ||
Pierre-Yves David
|
r22231 | } | ||
Angel Ezquerra
|
r16304 | |||
Pierre-Yves David
|
r22608 | # "constant" that convey the backup strategy. | ||
# All set to `discard` if `no-backup` is set do avoid checking | ||||
# no_backup lower in the code. | ||||
Pierre-Yves David
|
r22609 | # These values are ordered for comparison purposes | ||
skarlage
|
r29498 | backupinteractive = 3 # do backup if interactively modified | ||
Pierre-Yves David
|
r22608 | backup = 2 # unconditionally do backup | ||
Pierre-Yves David
|
r22609 | check = 1 # check if the existing file differs from target | ||
Pierre-Yves David
|
r22608 | discard = 0 # never do backup | ||
if opts.get('no_backup'): | ||||
skarlage
|
r29498 | backupinteractive = backup = check = discard | ||
if interactive: | ||||
dsmodifiedbackup = backupinteractive | ||||
else: | ||||
dsmodifiedbackup = backup | ||||
tobackup = set() | ||||
Angel Ezquerra
|
r16304 | |||
Pierre-Yves David
|
r22611 | backupanddel = actions['remove'] | ||
if not opts.get('no_backup'): | ||||
backupanddel = actions['drop'] | ||||
Angel Ezquerra
|
r16304 | disptable = ( | ||
# dispatch table: | ||||
# file state | ||||
Pierre-Yves David
|
r22153 | # action | ||
# make backup | ||||
Pierre-Yves David
|
r22371 | |||
## Sets that results that will change file on disk | ||||
# Modified compared to target, no local change | ||||
Pierre-Yves David
|
r22372 | (modified, actions['revert'], discard), | ||
Pierre-Yves David
|
r22397 | # Modified compared to target, but local file is deleted | ||
(deleted, actions['revert'], discard), | ||||
Pierre-Yves David
|
r22371 | # Modified compared to target, local change | ||
skarlage
|
r29498 | (dsmodified, actions['revert'], dsmodifiedbackup), | ||
Pierre-Yves David
|
r22371 | # Added since target | ||
Pierre-Yves David
|
r22488 | (added, actions['remove'], discard), | ||
# Added in working directory | ||||
Pierre-Yves David
|
r22489 | (dsadded, actions['forget'], discard), | ||
Pierre-Yves David
|
r22610 | # Added since target, have local modification | ||
Pierre-Yves David
|
r22611 | (modadded, backupanddel, backup), | ||
Pierre-Yves David
|
r22490 | # Added since target but file is missing in working directory | ||
Pierre-Yves David
|
r22491 | (deladded, actions['drop'], discard), | ||
Pierre-Yves David
|
r22371 | # Removed since target, before working copy parent | ||
Pierre-Yves David
|
r22396 | (removed, actions['add'], discard), | ||
# Same as `removed` but an unknown file exists at the same path | ||||
Pierre-Yves David
|
r22609 | (removunk, actions['add'], check), | ||
Pierre-Yves David
|
r22371 | # Removed since targe, marked as such in working copy parent | ||
Pierre-Yves David
|
r22396 | (dsremoved, actions['undelete'], discard), | ||
# Same as `dsremoved` but an unknown file exists at the same path | ||||
Pierre-Yves David
|
r22609 | (dsremovunk, actions['undelete'], check), | ||
Pierre-Yves David
|
r22371 | ## the following sets does not result in any file changes | ||
# File with no modification | ||||
Pierre-Yves David
|
r22372 | (clean, actions['noop'], discard), | ||
Pierre-Yves David
|
r22371 | # Existing file, not tracked anywhere | ||
Pierre-Yves David
|
r22372 | (unknown, actions['unknown'], discard), | ||
Angel Ezquerra
|
r16304 | ) | ||
for abs, (rel, exact) in sorted(names.items()): | ||||
Pierre-Yves David
|
r21575 | # target file to be touch on disk (relative to cwd) | ||
Angel Ezquerra
|
r16304 | target = repo.wjoin(abs) | ||
Pierre-Yves David
|
r21575 | # search the entry in the dispatch table. | ||
Pierre-Yves David
|
r22212 | # if the file is in any of these sets, it was touched in the working | ||
Pierre-Yves David
|
r21575 | # directory parent and we are sure it needs to be reverted. | ||
Pierre-Yves David
|
r22232 | for table, (xlist, msg), dobackup in disptable: | ||
Angel Ezquerra
|
r16304 | if abs not in table: | ||
continue | ||||
Pierre-Yves David
|
r22233 | if xlist is not None: | ||
xlist.append(abs) | ||||
skarlage
|
r29498 | if dobackup: | ||
# If in interactive mode, don't automatically create | ||||
# .orig files (issue4793) | ||||
if dobackup == backupinteractive: | ||||
tobackup.add(abs) | ||||
elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])): | ||||
Siddharth Agarwal
|
r27651 | bakname = scmutil.origpath(ui, repo, rel) | ||
Pierre-Yves David
|
r22609 | ui.note(_('saving current version of %s as %s\n') % | ||
(rel, bakname)) | ||||
if not opts.get('dry_run'): | ||||
Laurent Charignon
|
r24475 | if interactive: | ||
util.copyfile(target, bakname) | ||||
else: | ||||
util.rename(target, bakname) | ||||
Pierre-Yves David
|
r22233 | if ui.verbose or not exact: | ||
if not isinstance(msg, basestring): | ||||
msg = msg(abs) | ||||
ui.status(msg % rel) | ||||
elif exact: | ||||
Pierre-Yves David
|
r22234 | ui.warn(msg % rel) | ||
Angel Ezquerra
|
r16304 | break | ||
Pierre-Yves David
|
r21575 | |||
Angel Ezquerra
|
r16304 | if not opts.get('dry_run'): | ||
Durham Goode
|
r23965 | needdata = ('revert', 'add', 'undelete') | ||
_revertprefetch(repo, ctx, *[actions[name][0] for name in needdata]) | ||||
skarlage
|
r29498 | _performrevert(repo, parents, ctx, actions, interactive, tobackup) | ||
Bryan O'Sullivan
|
r19129 | |||
Matt Harbison
|
r24134 | if targetsubs: | ||
# Revert the subrepos on the revert list | ||||
for sub in targetsubs: | ||||
Matt Harbison
|
r24463 | try: | ||
Matt Harbison
|
r24464 | wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts) | ||
Matt Harbison
|
r24463 | except KeyError: | ||
Pierre-Yves David
|
r26587 | raise error.Abort("subrepository '%s' does not exist in %s!" | ||
Matt Harbison
|
r24463 | % (sub, short(ctx.node()))) | ||
Angel Ezquerra
|
r16304 | |||
Pierre-Yves David
|
r22370 | def _revertprefetch(repo, ctx, *files): | ||
"""Let extension changing the storage layer prefetch content""" | ||||
pass | ||||
skarlage
|
r29498 | def _performrevert(repo, parents, ctx, actions, interactive=False, | ||
tobackup=None): | ||||
Pierre-Yves David
|
r21576 | """function that actually perform all the actions computed for revert | ||
Pierre-Yves David
|
r20571 | |||
This is an independent function to let extension to plug in and react to | ||||
the imminent revert. | ||||
Mads Kiilerich
|
r21024 | Make sure you have the working directory locked when calling this function. | ||
Pierre-Yves David
|
r20571 | """ | ||
parent, p2 = parents | ||||
node = ctx.node() | ||||
liscju
|
r27985 | excluded_files = [] | ||
matcher_opts = {"exclude": excluded_files} | ||||
Pierre-Yves David
|
r20571 | def checkout(f): | ||
fc = ctx[f] | ||||
FUJIWARA Katsunori
|
r25755 | repo.wwrite(f, fc.data(), fc.flags()) | ||
Pierre-Yves David
|
r20571 | |||
Denis Laxalde
|
r30532 | def doremove(f): | ||
try: | ||||
Mads Kiilerich
|
r31309 | repo.wvfs.unlinkpath(f) | ||
Denis Laxalde
|
r30532 | except OSError: | ||
pass | ||||
repo.dirstate.remove(f) | ||||
Yuya Nishihara
|
r33722 | audit_path = pathutil.pathauditor(repo.root, cached=True) | ||
Pierre-Yves David
|
r22489 | for f in actions['forget'][0]: | ||
liscju
|
r27985 | if interactive: | ||
Denis Laxalde
|
r30530 | choice = repo.ui.promptchoice( | ||
Denis Laxalde
|
r30531 | _("forget added file %s (Yn)?$$ &Yes $$ &No") % f) | ||
liscju
|
r27985 | if choice == 0: | ||
repo.dirstate.drop(f) | ||||
else: | ||||
excluded_files.append(repo.wjoin(f)) | ||||
else: | ||||
repo.dirstate.drop(f) | ||||
Pierre-Yves David
|
r21576 | for f in actions['remove'][0]: | ||
Pierre-Yves David
|
r20571 | audit_path(f) | ||
Denis Laxalde
|
r30532 | if interactive: | ||
choice = repo.ui.promptchoice( | ||||
_("remove added file %s (Yn)?$$ &Yes $$ &No") % f) | ||||
if choice == 0: | ||||
doremove(f) | ||||
else: | ||||
excluded_files.append(repo.wjoin(f)) | ||||
else: | ||||
doremove(f) | ||||
Pierre-Yves David
|
r22491 | for f in actions['drop'][0]: | ||
audit_path(f) | ||||
Pierre-Yves David
|
r20571 | repo.dirstate.remove(f) | ||
normal = None | ||||
if node == parent: | ||||
# We're reverting to our parent. If possible, we'd like status | ||||
# to report the file as clean. We have to use normallookup for | ||||
# merges to avoid losing information about merged/dirty files. | ||||
if p2 != nullid: | ||||
normal = repo.dirstate.normallookup | ||||
else: | ||||
normal = repo.dirstate.normal | ||||
Laurent Charignon
|
r24359 | |||
Laurent Charignon
|
r25259 | newlyaddedandmodifiedfiles = set() | ||
Laurent Charignon
|
r24359 | if interactive: | ||
# Prompt the user for changes to revert | ||||
torevert = [repo.wjoin(f) for f in actions['revert'][0]] | ||||
liscju
|
r27985 | m = scmutil.match(ctx, torevert, matcher_opts) | ||
Laurent Charignon
|
r25258 | diffopts = patch.difffeatureopts(repo.ui, whitespace=True) | ||
diffopts.nodates = True | ||||
diffopts.git = True | ||||
Denis Laxalde
|
r31196 | operation = 'discard' | ||
reversehunks = True | ||||
if node != parent: | ||||
operation = 'revert' | ||||
reversehunks = repo.ui.configbool('experimental', | ||||
Jun Wu
|
r33499 | 'revertalternateinteractivemode') | ||
Laurent Charignon
|
r25424 | if reversehunks: | ||
diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) | ||||
else: | ||||
diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts) | ||||
Laurent Charignon
|
r24359 | originalchunks = patch.parsepatch(diff) | ||
Laurent Charignon
|
r25424 | |||
Laurent Charignon
|
r24359 | try: | ||
Laurent Charignon
|
r25424 | |||
Denis Laxalde
|
r29283 | chunks, opts = recordfilter(repo.ui, originalchunks, | ||
operation=operation) | ||||
Laurent Charignon
|
r25424 | if reversehunks: | ||
chunks = patch.reversehunks(chunks) | ||||
Gregory Szorc
|
r25660 | except patch.PatchError as err: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('error parsing patch: %s') % err) | ||
Laurent Charignon
|
r24359 | |||
Laurent Charignon
|
r25259 | newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks) | ||
skarlage
|
r29498 | if tobackup is None: | ||
tobackup = set() | ||||
Laurent Charignon
|
r24359 | # Apply changes | ||
timeless
|
r28861 | fp = stringio() | ||
Laurent Charignon
|
r24359 | for c in chunks: | ||
skarlage
|
r29498 | # Create a backup file only if this hunk should be backed up | ||
if ishunk(c) and c.header.filename() in tobackup: | ||||
abs = c.header.filename() | ||||
target = repo.wjoin(abs) | ||||
bakname = scmutil.origpath(repo.ui, repo, m.rel(abs)) | ||||
util.copyfile(target, bakname) | ||||
tobackup.remove(abs) | ||||
Laurent Charignon
|
r24359 | c.write(fp) | ||
dopatch = fp.tell() | ||||
fp.seek(0) | ||||
if dopatch: | ||||
try: | ||||
patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None) | ||||
Gregory Szorc
|
r25660 | except patch.PatchError as err: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(str(err)) | ||
Laurent Charignon
|
r24359 | del fp | ||
else: | ||||
for f in actions['revert'][0]: | ||||
FUJIWARA Katsunori
|
r25755 | checkout(f) | ||
Laurent Charignon
|
r24359 | if normal: | ||
normal(f) | ||||
Pierre-Yves David
|
r20571 | |||
Pierre-Yves David
|
r21576 | for f in actions['add'][0]: | ||
Laurent Charignon
|
r25259 | # Don't checkout modified files, they are already created by the diff | ||
if f not in newlyaddedandmodifiedfiles: | ||||
checkout(f) | ||||
repo.dirstate.add(f) | ||||
Pierre-Yves David
|
r20571 | |||
normal = repo.dirstate.normallookup | ||||
if node == parent and p2 == nullid: | ||||
normal = repo.dirstate.normal | ||||
Pierre-Yves David
|
r21576 | for f in actions['undelete'][0]: | ||
Pierre-Yves David
|
r20571 | checkout(f) | ||
normal(f) | ||||
copied = copies.pathcopies(repo[parent], ctx) | ||||
Pierre-Yves David
|
r21576 | for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]: | ||
Pierre-Yves David
|
r20571 | if f in copied: | ||
repo.dirstate.copy(copied[f], f) | ||||
Yuya Nishihara
|
r32343 | class command(registrar.command): | ||
def _doregister(self, func, name, *args, **kwargs): | ||||
func._deprecatedregistrar = True # flag for deprecwarn in extensions.py | ||||
return super(command, self)._doregister(func, name, *args, **kwargs) | ||||
Bryan O'Sullivan
|
r19211 | |||
FUJIWARA Katsunori
|
r21051 | # a list of (ui, repo, otherpeer, opts, missing) functions called by | ||
# commands.outgoing. "missing" is "missing" of the result of | ||||
# "findcommonoutgoing()" | ||||
outgoinghooks = util.hooks() | ||||
Bryan O'Sullivan
|
r19211 | # a list of (ui, repo) functions called by commands.summary | ||
summaryhooks = util.hooks() | ||||
Matt Mackall
|
r19474 | |||
FUJIWARA Katsunori
|
r21047 | # a list of (ui, repo, opts, changes) functions called by commands.summary. | ||
# | ||||
# functions should return tuple of booleans below, if 'changes' is None: | ||||
# (whether-incomings-are-needed, whether-outgoings-are-needed) | ||||
# | ||||
# otherwise, 'changes' is a tuple of tuples below: | ||||
# - (sourceurl, sourcebranch, sourcepeer, incoming) | ||||
# - (desturl, destbranch, destpeer, outgoing) | ||||
summaryremotehooks = util.hooks() | ||||
Matt Mackall
|
r19474 | # A list of state files kept by multistep operations like graft. | ||
# Since graft cannot be aborted, it is considered 'clearable' by update. | ||||
# note: bisect is intentionally excluded | ||||
Matt Mackall
|
r19496 | # (state file, clearable, allowcommit, error, hint) | ||
Matt Mackall
|
r19474 | unfinishedstates = [ | ||
Matt Mackall
|
r19496 | ('graftstate', True, False, _('graft in progress'), | ||
Matt Mackall
|
r19482 | _("use 'hg graft --continue' or 'hg update' to abort")), | ||
Matt Mackall
|
r19496 | ('updatestate', True, False, _('last update was interrupted'), | ||
Matt Mackall
|
r19482 | _("use 'hg update' to get a consistent checkout")) | ||
Matt Mackall
|
r19474 | ] | ||
Matt Mackall
|
r19496 | def checkunfinished(repo, commit=False): | ||
Matt Mackall
|
r19474 | '''Look for an unfinished multistep operation, like graft, and abort | ||
if found. It's probably good to check this right before | ||||
bailifchanged(). | ||||
''' | ||||
Matt Mackall
|
r19496 | for f, clearable, allowcommit, msg, hint in unfinishedstates: | ||
if commit and allowcommit: | ||||
continue | ||||
Matt Mackall
|
r19474 | if repo.vfs.exists(f): | ||
Pierre-Yves David
|
r26587 | raise error.Abort(msg, hint=hint) | ||
Matt Mackall
|
r19474 | |||
def clearunfinished(repo): | ||||
'''Check for unfinished operations (as above), and clear the ones | ||||
that are clearable. | ||||
''' | ||||
Matt Mackall
|
r19496 | for f, clearable, allowcommit, msg, hint in unfinishedstates: | ||
Matt Mackall
|
r19474 | if not clearable and repo.vfs.exists(f): | ||
Pierre-Yves David
|
r26587 | raise error.Abort(msg, hint=hint) | ||
Matt Mackall
|
r19496 | for f, clearable, allowcommit, msg, hint in unfinishedstates: | ||
Matt Mackall
|
r19474 | if clearable and repo.vfs.exists(f): | ||
Pierre-Yves David
|
r31320 | util.unlink(repo.vfs.join(f)) | ||
FUJIWARA Katsunori
|
r24991 | |||
timeless
|
r27624 | afterresolvedstates = [ | ||
timeless
|
r27625 | ('graftstate', | ||
_('hg graft --continue')), | ||||
timeless
|
r27624 | ] | ||
timeless
|
r28120 | def howtocontinue(repo): | ||
'''Check for an unfinished operation and return the command to finish | ||||
it. | ||||
Mads Kiilerich
|
r30332 | afterresolvedstates tuples define a .hg/{file} and the corresponding | ||
timeless
|
r28120 | command needed to finish it. | ||
Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is | ||||
a boolean. | ||||
''' | ||||
contmsg = _("continue: %s") | ||||
timeless
|
r27624 | for f, msg in afterresolvedstates: | ||
if repo.vfs.exists(f): | ||||
timeless
|
r28120 | return contmsg % msg, True | ||
Matt Harbison
|
r33362 | if repo[None].dirty(missing=True, merge=False, branch=False): | ||
timeless
|
r28120 | return contmsg % _("hg commit"), False | ||
return None, None | ||||
def checkafterresolved(repo): | ||||
'''Inform the user about the next action after completing hg resolve | ||||
If there's a matching afterresolvedstates, howtocontinue will yield | ||||
repo.ui.warn as the reporter. | ||||
Otherwise, it will yield repo.ui.note. | ||||
''' | ||||
msg, warning = howtocontinue(repo) | ||||
if msg is not None: | ||||
if warning: | ||||
repo.ui.warn("%s\n" % msg) | ||||
else: | ||||
repo.ui.note("%s\n" % msg) | ||||
def wrongtooltocontinue(repo, task): | ||||
'''Raise an abort suggesting how to properly continue if there is an | ||||
active task. | ||||
Uses howtocontinue() to find the active task. | ||||
If there's no task (repo.ui.note for 'hg commit'), it does not offer | ||||
a hint. | ||||
''' | ||||
after = howtocontinue(repo) | ||||
hint = None | ||||
if after[1]: | ||||
hint = after[0] | ||||
raise error.Abort(_('no %s in progress') % task, hint=hint) | ||||