2006-08-19 08:18:01 +04:00
|
|
|
# cmdutil.py - help for command processing in mercurial
|
2006-08-13 03:13:27 +04:00
|
|
|
#
|
2007-06-19 10:51:34 +04:00
|
|
|
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
|
2006-08-13 03:13:27 +04:00
|
|
|
#
|
2009-04-26 03:08:54 +04:00
|
|
|
# This software may be used and distributed according to the terms of the
|
2010-01-20 07:20:08 +03:00
|
|
|
# GNU General Public License version 2 or any later version.
|
2006-08-13 03:13:27 +04:00
|
|
|
|
2016-02-28 10:57:07 +03:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
import errno
|
2016-04-22 15:46:33 +03:00
|
|
|
import itertools
|
2016-02-28 10:57:07 +03:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
from .i18n import _
|
|
|
|
from .node import (
|
|
|
|
hex,
|
|
|
|
nullid,
|
|
|
|
nullrev,
|
|
|
|
short,
|
|
|
|
)
|
|
|
|
|
|
|
|
from . import (
|
|
|
|
bookmarks,
|
|
|
|
changelog,
|
|
|
|
copies,
|
|
|
|
crecord as crecordmod,
|
log: add -L/--line-range option to follow file history by line range
We add an experimental -L/--line-range option to 'hg log' taking file patterns
along with a line range using the (new) FILE,FROMLINE-TOLINE syntax where FILE
may be a pattern (matching exactly one file). The resulting history is similar
to what the "followlines" revset except that, if --patch is specified,
only diff hunks within specified line range are shown.
Basically, this brings the CLI on par with what currently only exists in hgweb
through line selection in "file" and "annotate" views resulting in a file log
with filtered patch to only display followed line range.
The option may be specified multiple times and can be combined with --rev and
regular file patterns to further restrict revisions. Usage of this option
requires --follow; revisions are shown in descending order and renames are
followed. Only the --graph option is currently not supported.
The UI is the result of a consensus from review feedback at:
https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-October/106749.html
The implementation spreads between commands.log() and cmdutil module.
In commands.log(), the main loop may now use a "hunksfilter" factory (similar
to "filematcher") that, for a given "rev", produces a filtering function
for diff hunks for a given file context object.
The logic to build revisions from -L/--line-range options lives in
cmdutil.getloglinerangerevs() which produces "revs", "filematcher" and
"hunksfilter" information. Revisions obtained by following files' line range
are filtered if they do not match the revset specified by --rev option. If
regular FILE arguments are passed along with -L options, both filematchers are
combined into a new matcher.
.. feature::
Add an experimental -L/--line-range FILE,FROMLINE-TOLINE option to 'hg log'
command to follow the history of files by line range. In combination with
-p/--patch option, only diff hunks within specified line range will be
displayed. Feedback, especially on UX aspects, is welcome.
2017-10-17 22:15:31 +03:00
|
|
|
dagop,
|
2017-08-01 00:54:57 +03:00
|
|
|
dirstateguard,
|
2016-02-28 10:57:07 +03:00
|
|
|
encoding,
|
|
|
|
error,
|
|
|
|
formatter,
|
|
|
|
graphmod,
|
|
|
|
match as matchmod,
|
log: add -L/--line-range option to follow file history by line range
We add an experimental -L/--line-range option to 'hg log' taking file patterns
along with a line range using the (new) FILE,FROMLINE-TOLINE syntax where FILE
may be a pattern (matching exactly one file). The resulting history is similar
to what the "followlines" revset except that, if --patch is specified,
only diff hunks within specified line range are shown.
Basically, this brings the CLI on par with what currently only exists in hgweb
through line selection in "file" and "annotate" views resulting in a file log
with filtered patch to only display followed line range.
The option may be specified multiple times and can be combined with --rev and
regular file patterns to further restrict revisions. Usage of this option
requires --follow; revisions are shown in descending order and renames are
followed. Only the --graph option is currently not supported.
The UI is the result of a consensus from review feedback at:
https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-October/106749.html
The implementation spreads between commands.log() and cmdutil module.
In commands.log(), the main loop may now use a "hunksfilter" factory (similar
to "filematcher") that, for a given "rev", produces a filtering function
for diff hunks for a given file context object.
The logic to build revisions from -L/--line-range options lives in
cmdutil.getloglinerangerevs() which produces "revs", "filematcher" and
"hunksfilter" information. Revisions obtained by following files' line range
are filtered if they do not match the revset specified by --rev option. If
regular FILE arguments are passed along with -L options, both filematchers are
combined into a new matcher.
.. feature::
Add an experimental -L/--line-range FILE,FROMLINE-TOLINE option to 'hg log'
command to follow the history of files by line range. In combination with
-p/--patch option, only diff hunks within specified line range will be
displayed. Feedback, especially on UX aspects, is welcome.
2017-10-17 22:15:31 +03:00
|
|
|
mdiff,
|
2016-02-28 10:57:07 +03:00
|
|
|
obsolete,
|
|
|
|
patch,
|
|
|
|
pathutil,
|
2016-11-22 21:33:11 +03:00
|
|
|
pycompat,
|
2016-01-09 17:07:20 +03:00
|
|
|
registrar,
|
2016-02-28 10:57:07 +03:00
|
|
|
revlog,
|
|
|
|
revset,
|
|
|
|
scmutil,
|
2017-02-19 12:16:09 +03:00
|
|
|
smartset,
|
2016-02-28 10:57:07 +03:00
|
|
|
templatekw,
|
|
|
|
templater,
|
|
|
|
util,
|
2017-03-02 15:30:47 +03:00
|
|
|
vfs as vfsmod,
|
2016-02-28 10:57:07 +03:00
|
|
|
)
|
2016-04-10 23:55:37 +03:00
|
|
|
stringio = util.stringio
|
2006-08-13 03:13:27 +04:00
|
|
|
|
2017-05-14 10:19:47 +03:00
|
|
|
# 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')),
|
2017-08-30 04:20:50 +03:00
|
|
|
('Z', 'ignore-space-at-eol', None,
|
|
|
|
_('ignore changes in whitespace at EOL')),
|
2017-05-14 10:19:47 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
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')),
|
|
|
|
]
|
|
|
|
|
2017-01-01 00:36:36 +03:00
|
|
|
# special string such that everything below this line will be ingored in the
|
|
|
|
# editor text
|
|
|
|
_linebelow = "^HG: ------------------------ >8 ------------------------$"
|
|
|
|
|
2015-05-22 00:28:02 +03:00
|
|
|
def ishunk(x):
|
|
|
|
hunkclasses = (crecordmod.uihunk, patch.recordhunk)
|
|
|
|
return isinstance(x, hunkclasses)
|
|
|
|
|
2015-05-22 00:32:14 +03:00
|
|
|
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
|
|
|
|
|
2010-02-08 12:23:44 +03:00
|
|
|
def parsealiases(cmd):
|
|
|
|
return cmd.lstrip("^").split("|")
|
|
|
|
|
2015-03-17 01:30:33 +03:00
|
|
|
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
|
|
|
|
|
2015-05-28 01:49:24 +03:00
|
|
|
def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
|
2015-03-13 00:24:48 +03:00
|
|
|
if usecurses:
|
|
|
|
if testfile:
|
|
|
|
recordfn = crecordmod.testdecorator(testfile,
|
|
|
|
crecordmod.testchunkselector)
|
|
|
|
else:
|
|
|
|
recordfn = crecordmod.chunkselector
|
|
|
|
|
2016-11-23 22:03:24 +03:00
|
|
|
return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
|
2015-03-13 00:24:48 +03:00
|
|
|
|
|
|
|
else:
|
2015-05-28 01:49:24 +03:00
|
|
|
return patch.filterpatch(ui, originalhunks, operation)
|
2015-03-12 02:39:38 +03:00
|
|
|
|
2015-05-28 01:49:24 +03:00
|
|
|
def recordfilter(ui, originalhunks, operation=None):
|
|
|
|
""" Prompts the user to filter the originalhunks and return a list of
|
|
|
|
selected hunks.
|
2016-06-07 11:37:19 +03:00
|
|
|
*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).
|
2015-05-28 01:49:24 +03:00
|
|
|
"""
|
2015-12-16 03:01:45 +03:00
|
|
|
usecurses = crecordmod.checkcurses(ui)
|
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
2017-07-15 00:22:40 +03:00
|
|
|
testfile = ui.config('experimental', 'crecordtest')
|
2015-03-17 01:37:00 +03:00
|
|
|
oldwrite = setupwrapcolorwrite(ui)
|
|
|
|
try:
|
2015-12-01 03:35:21 +03:00
|
|
|
newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
|
|
|
|
testfile, operation)
|
2015-03-17 01:37:00 +03:00
|
|
|
finally:
|
|
|
|
ui.write = oldwrite
|
2015-12-01 03:35:21 +03:00
|
|
|
return newchunks, newopts
|
2015-03-17 01:35:50 +03:00
|
|
|
|
2015-03-12 02:39:38 +03:00
|
|
|
def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
|
|
|
|
filterfn, *pats, **opts):
|
2016-02-28 10:57:07 +03:00
|
|
|
from . import merge as mergemod
|
2017-04-20 23:50:46 +03:00
|
|
|
opts = pycompat.byteskwargs(opts)
|
2015-03-11 03:14:33 +03:00
|
|
|
if not ui.interactive():
|
2015-07-14 21:43:16 +03:00
|
|
|
if cmdsuggest:
|
|
|
|
msg = _('running non-interactively, use %s instead') % cmdsuggest
|
|
|
|
else:
|
|
|
|
msg = _('running non-interactively')
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(msg)
|
2015-03-11 03:14:33 +03:00
|
|
|
|
|
|
|
# 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)
|
2016-04-06 21:19:36 +03:00
|
|
|
wctx = repo[None]
|
|
|
|
merge = len(wctx.parents()) > 1
|
2015-03-11 03:14:33 +03:00
|
|
|
if merge:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('cannot partially commit a merge '
|
2015-03-11 03:14:33 +03:00
|
|
|
'(use "hg commit" instead)'))
|
|
|
|
|
2016-04-06 21:19:36 +03:00
|
|
|
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
|
|
|
|
|
2015-03-11 03:14:33 +03:00
|
|
|
status = repo.status(match=match)
|
2016-04-06 21:19:36 +03:00
|
|
|
if not force:
|
|
|
|
repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
|
2015-03-11 03:14:33 +03:00
|
|
|
diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
|
|
|
|
diffopts.nodates = True
|
|
|
|
diffopts.git = True
|
2015-12-17 17:38:22 +03:00
|
|
|
diffopts.showfunc = True
|
2015-12-31 11:16:59 +03:00
|
|
|
originaldiff = patch.diff(repo, changes=status, opts=diffopts)
|
2015-03-13 03:51:37 +03:00
|
|
|
originalchunks = patch.parsepatch(originaldiff)
|
2015-03-11 03:14:33 +03:00
|
|
|
|
2016-03-18 23:14:57 +03:00
|
|
|
# 1. filter patch, since we are intending to apply subset of it
|
2015-03-11 03:14:33 +03:00
|
|
|
try:
|
2015-12-01 03:35:21 +03:00
|
|
|
chunks, newopts = filterfn(ui, originalchunks)
|
2017-09-03 10:45:33 +03:00
|
|
|
except error.PatchError as err:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('error parsing patch: %s') % err)
|
2015-12-01 03:35:21 +03:00
|
|
|
opts.update(newopts)
|
2015-03-11 03:14:33 +03:00
|
|
|
|
2015-04-24 00:27:26 +03:00
|
|
|
# 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
|
2015-05-22 00:32:14 +03:00
|
|
|
newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
|
2015-03-11 03:14:33 +03:00
|
|
|
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:
|
2015-04-24 00:27:26 +03:00
|
|
|
tobackup = [f for f in newfiles if f in modified or f in \
|
|
|
|
newlyaddedandmodifiedfiles]
|
2015-03-11 03:14:33 +03:00
|
|
|
backups = {}
|
|
|
|
if tobackup:
|
2017-03-09 03:53:09 +03:00
|
|
|
backupdir = repo.vfs.join('record-backups')
|
2015-03-11 03:14:33 +03:00
|
|
|
try:
|
|
|
|
os.mkdir(backupdir)
|
2015-06-24 08:20:08 +03:00
|
|
|
except OSError as err:
|
2015-03-11 03:14:33 +03:00
|
|
|
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))
|
2015-12-12 21:58:05 +03:00
|
|
|
util.copyfile(repo.wjoin(f), tmpname, copystat=True)
|
2015-03-11 03:14:33 +03:00
|
|
|
backups[f] = tmpname
|
|
|
|
|
2016-04-10 23:55:37 +03:00
|
|
|
fp = stringio()
|
2015-03-11 03:14:33 +03:00
|
|
|
for c in chunks:
|
|
|
|
fname = c.filename()
|
2015-04-22 23:56:30 +03:00
|
|
|
if fname in backups:
|
2015-03-11 03:14:33 +03:00
|
|
|
c.write(fp)
|
|
|
|
dopatch = fp.tell()
|
|
|
|
fp.seek(0)
|
|
|
|
|
2016-03-21 04:08:17 +03:00
|
|
|
# 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, "",
|
2017-08-30 23:25:56 +03:00
|
|
|
action="diff",
|
2017-01-19 05:25:51 +03:00
|
|
|
repopath=repo.path)
|
2016-03-21 04:08:17 +03:00
|
|
|
fp.truncate(0)
|
|
|
|
fp.write(reviewedpatch)
|
|
|
|
fp.seek(0)
|
|
|
|
|
2015-04-28 00:02:49 +03:00
|
|
|
[os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
|
2015-03-11 03:14:33 +03:00
|
|
|
# 3a. apply filtered patch to clean repo (clean)
|
|
|
|
if backups:
|
|
|
|
# Equivalent to hg.revert
|
2015-12-15 02:54:03 +03:00
|
|
|
m = scmutil.matchfiles(repo, backups.keys())
|
2015-03-11 03:14:33 +03:00
|
|
|
mergemod.update(repo, repo.dirstate.p1(),
|
2015-12-15 02:54:03 +03:00
|
|
|
False, True, matcher=m)
|
2015-03-11 03:14:33 +03:00
|
|
|
|
|
|
|
# 3b. (apply)
|
|
|
|
if dopatch:
|
|
|
|
try:
|
|
|
|
ui.debug('applying patch\n')
|
|
|
|
ui.debug(fp.getvalue())
|
|
|
|
patch.internalpatch(ui, repo, fp, 1, eolmode=None)
|
2017-09-03 10:45:33 +03:00
|
|
|
except error.PatchError as err:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(str(err))
|
2015-03-11 03:14:33 +03:00
|
|
|
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]
|
2015-03-26 01:51:57 +03:00
|
|
|
return commitfunc(ui, repo, *newfiles, **opts)
|
2015-03-11 03:14:33 +03:00
|
|
|
finally:
|
|
|
|
# 5. finally restore backed-up files
|
|
|
|
try:
|
cmdutil: apply dirstate.normallookup on (maybe partially) committed files
To detect change of a file without redundant comparison of file
content, dirstate recognizes a file as certainly clean, if:
(1) it is already known as "normal",
(2) dirstate entry for it has valid (= not "-1") timestamp, and
(3) mode, size and timestamp of it on the filesystem are as same as
ones expected in dirstate
This works as expected in many cases, but doesn't in the corner case
that changing a file keeps mode, size and timestamp of it on the
filesystem.
The timetable below shows steps in one of typical such situations:
---- ----------------------------------- ----------------
timestamp of "f"
----------------
dirstate file-
time action mem file system
---- ----------------------------------- ---- ----- -----
N *** ***
- change "f" N
- execute 'hg commit -i'
- backup "f" with timestamp N
- revert "f" by 'merge.update()' N
with 'partially'
- apply selected hunks N
by 'patch.patch()'
- 'repo.commit()'
- 'dirstate.normal("f")' N
N+1
- 'dirstate.write()' N N
- restore "f" N+1
- restore timestamp of "f" N
- 'hg status' shows "f" as "clean" N N N
---- ----------------------------------- ---- ----- -----
The most important point is that 'dirstate.write()' is executed at N+1
or later. This causes writing dirstate timestamp N of "f" out
successfully. If it is executed at N, 'parsers.pack_dirstate()'
replaces timestamp N with "-1" before actual writing dirstate out.
This issue can occur when 'hg commit -i' satisfies conditions below:
- the file is committed partially, and
- mode and size of the file aren't changed before and after committing
The root cause of this issue is that (maybe partially changed) files
are restored with original timestamp but dirstate isn't updated for
them.
To detect changes of files correctly, this patch applies
'dirstate.normallookup()' on restored files. Status check is needed
before 'dirstate.normallookup()', because status other than "n(ormal)"
should be kept at failure of committing.
This patch doesn't examine whether each files are committed fully or
partially, because interactive hunk selection makes it difficult.
After this change, timetable is changed as below:
---- ----------------------------------- ----------------
timestamp of "f"
----------------
dirstate file-
time action mem file system
---- ----------------------------------- ---- ----- -----
N *** ***
- change "f" N
- execute 'hg commit -i'
- backup "f" with timestamp N
- revert "f" by 'merge.update()' N
with 'partially'
- apply selected hunks N
by 'patch.internalpatch()'
- 'repo.commit()'
- 'dirstate.normal("f")' N
N+1
- 'dirstate.write()' N N
- restore "f" N+1
- restore timestamp of "f" N
----------------------------------- ---- ----- -----
- normallookup("f") -1
- release wlock
- 'dirstate.write()' -1 -1 N
----------------------------------- ---- ----- -----
- 'hg status' shows "f" as "clean" -1 -1 N
---- ----------------------------------- ---- ----- -----
To reproduce this issue in tests certainly, this patch emulates some
timing critical actions as below:
- change "f" at N
'touch -t 200001010000' before command invocation changes mtime of
"f" to "2000-01-01 00:00" (= N).
- apply selected hunks at N
'patch.internalpatch()' with 'fakepatchtime.py' explicitly changes
mtime of patched files to "2000-01-01 00:00" (= N).
- 'dirstate.write()' at N+1 (or "not at N")
'pack_dirstate()' uses actual timestamp at runtime as "now", and
it should be different from the "2000-01-01 00:00" of "f".
BTW, in 'test-commit-interactive.t', files are sometimes treated as
modified , even though they are just committed fully via 'hg commit
-i' and 'hg diff' shows nothing for them.
Enabling win32text causes EOL style mismatching below:
- files are changed in LF style EOL
=> files restored after committing uses LF style EOL (1)
- 'merge.update()' reverts files in CRLF style EOL
- 'patch.internalpatch()' changes files in CRLF style EOL
=> 'dirstate.normal()' via 'repo.commit()' uses the size of files
in CRLF style EOL (2)
Therefore, fully committed files are treated as "modified", because
'lstat()' returns size of (1) restored files in LF style EOL, but
dirstate expects size of (2) committed files in CRLF style EOL.
After this patch, 'dirstate.normallookup()' on committed files forces
subsequent 'hg status' to examine changes exactly, and fully committed
files are treated as clean as expected.
This is reason why this patch also does:
- add some 'hg status' checking status of fully committed files
- clear win32text configuration before size/timestamp sensitive examination
2015-07-08 11:07:45 +03:00
|
|
|
dirstate = repo.dirstate
|
2015-03-11 03:14:33 +03:00
|
|
|
for realname, tmpname in backups.iteritems():
|
|
|
|
ui.debug('restoring %r to %r\n' % (tmpname, realname))
|
cmdutil: apply dirstate.normallookup on (maybe partially) committed files
To detect change of a file without redundant comparison of file
content, dirstate recognizes a file as certainly clean, if:
(1) it is already known as "normal",
(2) dirstate entry for it has valid (= not "-1") timestamp, and
(3) mode, size and timestamp of it on the filesystem are as same as
ones expected in dirstate
This works as expected in many cases, but doesn't in the corner case
that changing a file keeps mode, size and timestamp of it on the
filesystem.
The timetable below shows steps in one of typical such situations:
---- ----------------------------------- ----------------
timestamp of "f"
----------------
dirstate file-
time action mem file system
---- ----------------------------------- ---- ----- -----
N *** ***
- change "f" N
- execute 'hg commit -i'
- backup "f" with timestamp N
- revert "f" by 'merge.update()' N
with 'partially'
- apply selected hunks N
by 'patch.patch()'
- 'repo.commit()'
- 'dirstate.normal("f")' N
N+1
- 'dirstate.write()' N N
- restore "f" N+1
- restore timestamp of "f" N
- 'hg status' shows "f" as "clean" N N N
---- ----------------------------------- ---- ----- -----
The most important point is that 'dirstate.write()' is executed at N+1
or later. This causes writing dirstate timestamp N of "f" out
successfully. If it is executed at N, 'parsers.pack_dirstate()'
replaces timestamp N with "-1" before actual writing dirstate out.
This issue can occur when 'hg commit -i' satisfies conditions below:
- the file is committed partially, and
- mode and size of the file aren't changed before and after committing
The root cause of this issue is that (maybe partially changed) files
are restored with original timestamp but dirstate isn't updated for
them.
To detect changes of files correctly, this patch applies
'dirstate.normallookup()' on restored files. Status check is needed
before 'dirstate.normallookup()', because status other than "n(ormal)"
should be kept at failure of committing.
This patch doesn't examine whether each files are committed fully or
partially, because interactive hunk selection makes it difficult.
After this change, timetable is changed as below:
---- ----------------------------------- ----------------
timestamp of "f"
----------------
dirstate file-
time action mem file system
---- ----------------------------------- ---- ----- -----
N *** ***
- change "f" N
- execute 'hg commit -i'
- backup "f" with timestamp N
- revert "f" by 'merge.update()' N
with 'partially'
- apply selected hunks N
by 'patch.internalpatch()'
- 'repo.commit()'
- 'dirstate.normal("f")' N
N+1
- 'dirstate.write()' N N
- restore "f" N+1
- restore timestamp of "f" N
----------------------------------- ---- ----- -----
- normallookup("f") -1
- release wlock
- 'dirstate.write()' -1 -1 N
----------------------------------- ---- ----- -----
- 'hg status' shows "f" as "clean" -1 -1 N
---- ----------------------------------- ---- ----- -----
To reproduce this issue in tests certainly, this patch emulates some
timing critical actions as below:
- change "f" at N
'touch -t 200001010000' before command invocation changes mtime of
"f" to "2000-01-01 00:00" (= N).
- apply selected hunks at N
'patch.internalpatch()' with 'fakepatchtime.py' explicitly changes
mtime of patched files to "2000-01-01 00:00" (= N).
- 'dirstate.write()' at N+1 (or "not at N")
'pack_dirstate()' uses actual timestamp at runtime as "now", and
it should be different from the "2000-01-01 00:00" of "f".
BTW, in 'test-commit-interactive.t', files are sometimes treated as
modified , even though they are just committed fully via 'hg commit
-i' and 'hg diff' shows nothing for them.
Enabling win32text causes EOL style mismatching below:
- files are changed in LF style EOL
=> files restored after committing uses LF style EOL (1)
- 'merge.update()' reverts files in CRLF style EOL
- 'patch.internalpatch()' changes files in CRLF style EOL
=> 'dirstate.normal()' via 'repo.commit()' uses the size of files
in CRLF style EOL (2)
Therefore, fully committed files are treated as "modified", because
'lstat()' returns size of (1) restored files in LF style EOL, but
dirstate expects size of (2) committed files in CRLF style EOL.
After this patch, 'dirstate.normallookup()' on committed files forces
subsequent 'hg status' to examine changes exactly, and fully committed
files are treated as clean as expected.
This is reason why this patch also does:
- add some 'hg status' checking status of fully committed files
- clear win32text configuration before size/timestamp sensitive examination
2015-07-08 11:07:45 +03:00
|
|
|
|
|
|
|
if dirstate[realname] == 'n':
|
|
|
|
# without normallookup, restoring timestamp
|
|
|
|
# may cause partially committed files
|
|
|
|
# to be treated as unmodified
|
|
|
|
dirstate.normallookup(realname)
|
|
|
|
|
2015-12-12 21:58:05 +03:00
|
|
|
# copystat=True here and above are a hack to trick any
|
|
|
|
# editors that have f open that we haven't modified them.
|
2015-03-11 03:14:33 +03:00
|
|
|
#
|
2015-12-12 21:58:05 +03:00
|
|
|
# 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)
|
2015-03-11 03:14:33 +03:00
|
|
|
os.unlink(tmpname)
|
|
|
|
if tobackup:
|
|
|
|
os.rmdir(backupdir)
|
|
|
|
except OSError:
|
|
|
|
pass
|
|
|
|
|
2015-07-08 11:01:09 +03:00
|
|
|
def recordinwlock(ui, repo, message, match, opts):
|
2016-01-16 00:14:45 +03:00
|
|
|
with repo.wlock():
|
2015-07-08 11:01:09 +03:00
|
|
|
return recordfunc(ui, repo, message, match, opts)
|
|
|
|
|
|
|
|
return commit(ui, repo, recordinwlock, pats, opts)
|
2015-03-11 03:14:33 +03:00
|
|
|
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
# extracted at module level as it's required each time a file will be added
|
|
|
|
# to dirnode class object below
|
|
|
|
pathsep = pycompat.ossep
|
|
|
|
|
|
|
|
class dirnode(object):
|
2017-06-17 17:40:22 +03:00
|
|
|
"""
|
2017-10-14 14:33:37 +03:00
|
|
|
Represent a directory in user working copy with information required for
|
|
|
|
the purpose of tersing its status.
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
path is the path to the directory
|
|
|
|
|
|
|
|
statuses is a set of statuses of all files in this directory (this includes
|
|
|
|
all the files in all the subdirectories too)
|
|
|
|
|
|
|
|
files is a list of files which are direct child of this directory
|
|
|
|
|
|
|
|
subdirs is a dictionary of sub-directory name as the key and it's own
|
|
|
|
dirnode object as the value
|
2017-06-17 17:40:22 +03:00
|
|
|
"""
|
|
|
|
|
2017-10-06 18:24:23 +03:00
|
|
|
def __init__(self, dirpath):
|
|
|
|
self.path = dirpath
|
|
|
|
self.statuses = set([])
|
|
|
|
self.files = []
|
|
|
|
self.subdirs = {}
|
|
|
|
|
|
|
|
def _addfileindir(self, filename, status):
|
2017-10-14 14:33:37 +03:00
|
|
|
"""Add a file in this directory as a direct child."""
|
2017-10-06 18:24:23 +03:00
|
|
|
self.files.append((filename, status))
|
|
|
|
|
|
|
|
def addfile(self, filename, status):
|
2017-06-17 17:40:22 +03:00
|
|
|
"""
|
2017-10-14 14:33:37 +03:00
|
|
|
Add a file to this directory or to its direct parent directory.
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-14 14:33:37 +03:00
|
|
|
If the file is not direct child of this directory, we traverse to the
|
|
|
|
directory of which this file is a direct child of and add the file
|
|
|
|
there.
|
2017-06-17 17:40:22 +03:00
|
|
|
"""
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
# the filename contains a path separator, it means it's not the direct
|
|
|
|
# child of this directory
|
|
|
|
if pathsep in filename:
|
|
|
|
subdir, filep = filename.split(pathsep, 1)
|
|
|
|
|
|
|
|
# does the dirnode object for subdir exists
|
|
|
|
if subdir not in self.subdirs:
|
|
|
|
subdirpath = os.path.join(self.path, subdir)
|
|
|
|
self.subdirs[subdir] = dirnode(subdirpath)
|
|
|
|
|
|
|
|
# try adding the file in subdir
|
|
|
|
self.subdirs[subdir].addfile(filep, status)
|
|
|
|
|
2017-06-17 17:40:22 +03:00
|
|
|
else:
|
2017-10-06 18:24:23 +03:00
|
|
|
self._addfileindir(filename, status)
|
2017-06-17 17:40:22 +03:00
|
|
|
|
2017-10-06 18:24:23 +03:00
|
|
|
if status not in self.statuses:
|
|
|
|
self.statuses.add(status)
|
|
|
|
|
2017-10-13 01:32:06 +03:00
|
|
|
def iterfilepaths(self):
|
2017-10-14 14:33:37 +03:00
|
|
|
"""Yield (status, path) for files directly under this directory."""
|
2017-10-11 22:19:19 +03:00
|
|
|
for f, st in self.files:
|
2017-10-13 01:32:06 +03:00
|
|
|
yield st, os.path.join(self.path, f)
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-13 01:32:06 +03:00
|
|
|
def tersewalk(self, terseargs):
|
2017-10-11 22:19:19 +03:00
|
|
|
"""
|
2017-10-14 14:33:37 +03:00
|
|
|
Yield (status, path) obtained by processing the status of this
|
|
|
|
dirnode.
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
terseargs is the string of arguments passed by the user with `--terse`
|
|
|
|
flag.
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
Following are the cases which can happen:
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
1) All the files in the directory (including all the files in its
|
|
|
|
subdirectories) share the same status and the user has asked us to terse
|
2017-10-13 01:32:06 +03:00
|
|
|
that status. -> yield (status, dirpath)
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-14 14:33:37 +03:00
|
|
|
2) Otherwise, we do following:
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-13 01:32:06 +03:00
|
|
|
a) Yield (status, filepath) for all the files which are in this
|
|
|
|
directory (only the ones in this directory, not the subdirs)
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
b) Recurse the function on all the subdirectories of this
|
|
|
|
directory
|
|
|
|
"""
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
if len(self.statuses) == 1:
|
|
|
|
onlyst = self.statuses.pop()
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
# Making sure we terse only when the status abbreviation is
|
|
|
|
# passed as terse argument
|
|
|
|
if onlyst in terseargs:
|
2017-10-13 01:32:06 +03:00
|
|
|
yield onlyst, self.path + pycompat.ossep
|
2017-10-11 22:19:19 +03:00
|
|
|
return
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
# add the files to status list
|
2017-10-13 01:32:06 +03:00
|
|
|
for st, fpath in self.iterfilepaths():
|
|
|
|
yield st, fpath
|
2017-10-06 18:24:23 +03:00
|
|
|
|
2017-10-11 22:19:19 +03:00
|
|
|
#recurse on the subdirs
|
|
|
|
for dirobj in self.subdirs.values():
|
2017-10-13 01:32:06 +03:00
|
|
|
for st, fpath in dirobj.tersewalk(terseargs):
|
|
|
|
yield st, fpath
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
def tersedir(statuslist, terseargs):
|
|
|
|
"""
|
2017-10-14 14:33:37 +03:00
|
|
|
Terse the status if all the files in a directory shares the same status.
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
statuslist is scmutil.status() object which contains a list of files for
|
|
|
|
each status.
|
|
|
|
terseargs is string which is passed by the user as the argument to `--terse`
|
|
|
|
flag.
|
|
|
|
|
|
|
|
The function makes a tree of objects of dirnode class, and at each node it
|
|
|
|
stores the information required to know whether we can terse a certain
|
|
|
|
directory or not.
|
|
|
|
"""
|
|
|
|
# the order matters here as that is used to produce final list
|
|
|
|
allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
|
|
|
|
|
|
|
|
# checking the argument validity
|
2017-10-19 01:30:02 +03:00
|
|
|
for s in pycompat.bytestr(terseargs):
|
2017-10-06 18:24:23 +03:00
|
|
|
if s not in allst:
|
|
|
|
raise error.Abort(_("'%s' not recognized") % s)
|
|
|
|
|
|
|
|
# creating a dirnode object for the root of the repo
|
|
|
|
rootobj = dirnode('')
|
|
|
|
pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
|
|
|
|
'ignored', 'removed')
|
|
|
|
|
|
|
|
tersedict = {}
|
|
|
|
for attrname in pstatus:
|
2017-10-19 01:30:02 +03:00
|
|
|
statuschar = attrname[0:1]
|
2017-10-06 18:24:23 +03:00
|
|
|
for f in getattr(statuslist, attrname):
|
2017-10-19 01:30:02 +03:00
|
|
|
rootobj.addfile(f, statuschar)
|
|
|
|
tersedict[statuschar] = []
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
# we won't be tersing the root dir, so add files in it
|
2017-10-13 01:32:06 +03:00
|
|
|
for st, fpath in rootobj.iterfilepaths():
|
|
|
|
tersedict[st].append(fpath)
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
# process each sub-directory and build tersedict
|
|
|
|
for subdir in rootobj.subdirs.values():
|
2017-10-13 01:32:06 +03:00
|
|
|
for st, f in subdir.tersewalk(terseargs):
|
|
|
|
tersedict[st].append(f)
|
2017-10-06 18:24:23 +03:00
|
|
|
|
|
|
|
tersedlist = []
|
|
|
|
for st in allst:
|
|
|
|
tersedict[st].sort()
|
|
|
|
tersedlist.append(tersedict[st])
|
|
|
|
|
|
|
|
return tersedlist
|
2017-06-17 17:40:22 +03:00
|
|
|
|
2017-08-03 02:42:35 +03:00
|
|
|
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])
|
2017-08-29 00:47:18 +03:00
|
|
|
unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
|
2017-08-03 02:42:35 +03:00
|
|
|
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)
|
2017-08-21 14:13:37 +03:00
|
|
|
if conmsg:
|
|
|
|
fm.write('conflictsmsg', '%s\n', conmsg, label=label)
|
2017-08-03 02:42:35 +03:00
|
|
|
if helpfulmsg:
|
|
|
|
helpmsg = helpfulmsg()
|
|
|
|
fm.write('helpmsg', '%s\n', helpmsg, label=label)
|
|
|
|
|
2008-10-23 02:34:08 +04:00
|
|
|
def findpossible(cmd, table, strict=False):
|
2007-06-12 06:09:24 +04:00
|
|
|
"""
|
|
|
|
Return cmd -> (aliases, command table entry)
|
|
|
|
for each matching command.
|
|
|
|
Return debug commands (or their aliases) only if no normal command matches.
|
|
|
|
"""
|
|
|
|
choice = {}
|
|
|
|
debugchoice = {}
|
2011-12-02 01:51:36 +04:00
|
|
|
|
|
|
|
if cmd in table:
|
|
|
|
# short-circuit exact matches, "log" alias beats "^log|history"
|
|
|
|
keys = [cmd]
|
|
|
|
else:
|
|
|
|
keys = table.keys()
|
|
|
|
|
2015-02-10 23:59:12 +03:00
|
|
|
allcmds = []
|
2011-12-02 01:51:36 +04:00
|
|
|
for e in keys:
|
2010-02-08 12:23:44 +03:00
|
|
|
aliases = parsealiases(e)
|
2015-02-10 23:59:12 +03:00
|
|
|
allcmds.extend(aliases)
|
2007-06-12 06:09:24 +04:00
|
|
|
found = None
|
|
|
|
if cmd in aliases:
|
|
|
|
found = cmd
|
2008-10-23 02:34:08 +04:00
|
|
|
elif not strict:
|
2007-06-12 06:09:24 +04:00
|
|
|
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"):
|
2007-08-16 01:55:13 +04:00
|
|
|
debugchoice[found] = (aliases, table[e])
|
2007-06-12 06:09:24 +04:00
|
|
|
else:
|
2007-08-16 01:55:13 +04:00
|
|
|
choice[found] = (aliases, table[e])
|
2007-06-12 06:09:24 +04:00
|
|
|
|
|
|
|
if not choice and debugchoice:
|
|
|
|
choice = debugchoice
|
|
|
|
|
2015-02-10 23:59:12 +03:00
|
|
|
return choice, allcmds
|
2007-06-12 06:09:24 +04:00
|
|
|
|
2008-10-23 02:34:08 +04:00
|
|
|
def findcmd(cmd, table, strict=True):
|
2007-06-12 06:09:24 +04:00
|
|
|
"""Return (aliases, command table entry) for command string."""
|
2015-02-10 23:59:12 +03:00
|
|
|
choice, allcmds = findpossible(cmd, table, strict)
|
2007-06-12 06:09:24 +04:00
|
|
|
|
2008-01-20 16:39:25 +03:00
|
|
|
if cmd in choice:
|
2007-06-12 06:09:24 +04:00
|
|
|
return choice[cmd]
|
|
|
|
|
|
|
|
if len(choice) > 1:
|
2017-05-28 21:02:14 +03:00
|
|
|
clist = sorted(choice)
|
2009-01-12 20:39:38 +03:00
|
|
|
raise error.AmbiguousCommand(cmd, clist)
|
2007-06-12 06:09:24 +04:00
|
|
|
|
|
|
|
if choice:
|
2017-06-15 22:58:23 +03:00
|
|
|
return list(choice.values())[0]
|
2007-06-12 06:09:24 +04:00
|
|
|
|
2015-02-10 23:59:12 +03:00
|
|
|
raise error.UnknownCommand(cmd, allcmds)
|
2007-06-12 06:09:24 +04:00
|
|
|
|
2010-02-08 12:50:00 +03:00
|
|
|
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
|
|
|
|
|
2017-01-10 11:32:27 +03:00
|
|
|
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.
|
|
|
|
"""
|
|
|
|
|
2015-03-25 07:55:35 +03:00
|
|
|
if merge and repo.dirstate.p2() != nullid:
|
2017-01-10 11:32:27 +03:00
|
|
|
raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
|
2007-06-12 06:09:24 +04:00
|
|
|
modified, added, removed, deleted = repo.status()[:4]
|
|
|
|
if modified or added or removed or deleted:
|
2017-01-10 11:32:27 +03:00
|
|
|
raise error.Abort(_('uncommitted changes'), hint=hint)
|
2011-10-12 05:18:15 +04:00
|
|
|
ctx = repo[None]
|
2012-12-12 05:38:14 +04:00
|
|
|
for s in sorted(ctx.substate):
|
2017-01-10 11:32:27 +03:00
|
|
|
ctx.sub(s).bailifchanged(hint=hint)
|
2007-06-12 06:09:24 +04:00
|
|
|
|
2011-06-08 15:54:52 +04:00
|
|
|
def logmessage(ui, opts):
|
2007-06-12 06:09:24 +04:00
|
|
|
""" get the log message according to -m and -l option """
|
2009-01-19 13:38:54 +03:00
|
|
|
message = opts.get('message')
|
|
|
|
logfile = opts.get('logfile')
|
2007-06-12 06:09:24 +04:00
|
|
|
|
|
|
|
if message and logfile:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('options --message and --logfile are mutually '
|
2007-06-12 06:09:24 +04:00
|
|
|
'exclusive'))
|
|
|
|
if not message and logfile:
|
|
|
|
try:
|
2017-06-01 17:08:23 +03:00
|
|
|
if isstdiofilename(logfile):
|
2011-06-08 15:54:52 +04:00
|
|
|
message = ui.fin.read()
|
2007-06-12 06:09:24 +04:00
|
|
|
else:
|
2011-05-07 23:12:33 +04:00
|
|
|
message = '\n'.join(util.readfile(logfile).splitlines())
|
2015-06-24 08:20:08 +03:00
|
|
|
except IOError as inst:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("can't read commit message '%s': %s") %
|
2017-08-23 03:03:07 +03:00
|
|
|
(logfile, encoding.strtolocal(inst.strerror)))
|
2007-06-12 06:09:24 +04:00
|
|
|
return message
|
|
|
|
|
2015-01-18 04:38:57 +03:00
|
|
|
def mergeeditform(ctxorbool, baseformname):
|
|
|
|
"""return appropriate editform name (referencing a committemplate)
|
2014-08-16 05:43:59 +04:00
|
|
|
|
2015-01-18 04:38:57 +03:00
|
|
|
'ctxorbool' is either a ctx to be committed, or a bool indicating whether
|
2014-08-16 05:43:59 +04:00
|
|
|
merging is committed.
|
|
|
|
|
2015-01-18 04:38:57 +03:00
|
|
|
This returns baseformname with '.merge' appended if it is a merge,
|
|
|
|
otherwise '.normal' is appended.
|
2014-08-16 05:43:59 +04:00
|
|
|
"""
|
|
|
|
if isinstance(ctxorbool, bool):
|
|
|
|
if ctxorbool:
|
2015-01-18 04:38:57 +03:00
|
|
|
return baseformname + ".merge"
|
2014-08-16 05:43:59 +04:00
|
|
|
elif 1 < len(ctxorbool.parents()):
|
2015-01-18 04:38:57 +03:00
|
|
|
return baseformname + ".merge"
|
2014-08-16 05:43:59 +04:00
|
|
|
|
2015-01-18 04:38:57 +03:00
|
|
|
return baseformname + ".normal"
|
2014-08-16 05:43:59 +04:00
|
|
|
|
2014-08-02 16:46:26 +04:00
|
|
|
def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
|
|
|
|
editform='', **opts):
|
2014-05-10 19:49:36 +04:00
|
|
|
"""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.
|
|
|
|
|
2014-08-02 16:46:26 +04:00
|
|
|
'editform' is a dot-separated list of names, to distinguish
|
|
|
|
the purpose of commit text editing.
|
|
|
|
|
2014-05-10 19:49:36 +04:00
|
|
|
'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,
|
2014-08-02 16:46:26 +04:00
|
|
|
extramsg=extramsg,
|
|
|
|
editform=editform)
|
|
|
|
elif editform:
|
|
|
|
return lambda r, c, s: commiteditor(r, c, s, editform=editform)
|
2014-05-10 19:49:35 +04:00
|
|
|
else:
|
|
|
|
return commiteditor
|
|
|
|
|
2008-02-29 03:51:23 +03:00
|
|
|
def loglimit(opts):
|
|
|
|
"""get the log limit according to option -l/--limit"""
|
|
|
|
limit = opts.get('limit')
|
|
|
|
if limit:
|
|
|
|
try:
|
|
|
|
limit = int(limit)
|
|
|
|
except ValueError:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('limit must be a positive integer'))
|
2010-01-25 09:05:27 +03:00
|
|
|
if limit <= 0:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('limit must be positive'))
|
2008-02-29 03:51:23 +03:00
|
|
|
else:
|
2009-12-13 18:32:29 +03:00
|
|
|
limit = None
|
2008-02-29 03:51:23 +03:00
|
|
|
return limit
|
|
|
|
|
2011-07-30 14:08:45 +04:00
|
|
|
def makefilename(repo, pat, node, desc=None,
|
2006-08-13 03:13:27 +04:00
|
|
|
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),
|
2011-07-30 14:08:45 +04:00
|
|
|
'm': lambda: re.sub('[^\w]', '_', str(desc))
|
2006-08-13 03:13:27 +04:00
|
|
|
}
|
|
|
|
expander = {
|
|
|
|
'%': lambda: '%',
|
|
|
|
'b': lambda: os.path.basename(repo.root),
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
if node:
|
|
|
|
expander.update(node_expander)
|
2007-07-12 02:56:16 +04:00
|
|
|
if node:
|
2006-08-13 03:13:27 +04:00
|
|
|
expander['r'] = (lambda:
|
2007-07-12 02:56:16 +04:00
|
|
|
str(repo.changelog.rev(node)).zfill(revwidth or 0))
|
2006-08-13 03:13:27 +04:00
|
|
|
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:
|
2006-11-17 10:06:54 +03:00
|
|
|
expander['n'] = lambda: str(seqno).zfill(len(str(total)))
|
2006-08-13 03:13:27 +04:00
|
|
|
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:
|
2017-05-04 22:56:13 +03:00
|
|
|
c = pat[i:i + 1]
|
2006-08-13 03:13:27 +04:00
|
|
|
if c == '%':
|
|
|
|
i += 1
|
2017-05-04 22:56:13 +03:00
|
|
|
c = pat[i:i + 1]
|
2006-08-13 03:13:27 +04:00
|
|
|
c = expander[c]()
|
|
|
|
newname.append(c)
|
|
|
|
i += 1
|
|
|
|
return ''.join(newname)
|
2015-06-24 08:20:08 +03:00
|
|
|
except KeyError as inst:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("invalid format spec '%%%s' in output filename") %
|
2006-09-08 11:36:18 +04:00
|
|
|
inst.args[0])
|
2006-08-13 03:13:27 +04:00
|
|
|
|
2017-05-25 15:28:08 +03:00
|
|
|
def isstdiofilename(pat):
|
|
|
|
"""True if the given pat looks like a filename denoting stdin/stdout"""
|
|
|
|
return not pat or pat == '-'
|
|
|
|
|
2015-12-13 14:01:11 +03:00
|
|
|
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)
|
|
|
|
|
2016-10-08 01:59:41 +03:00
|
|
|
def __enter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
|
|
pass
|
|
|
|
|
2011-07-30 14:08:45 +04:00
|
|
|
def makefileobj(repo, pat, node=None, desc=None, total=None,
|
2013-10-22 18:38:58 +04:00
|
|
|
seqno=None, revwidth=None, mode='wb', modemap=None,
|
2013-02-10 01:38:57 +04:00
|
|
|
pathname=None):
|
2008-11-06 13:17:38 +03:00
|
|
|
|
2011-02-24 01:30:48 +03:00
|
|
|
writable = mode not in ('r', 'rb')
|
2008-11-06 13:17:38 +03:00
|
|
|
|
2017-05-25 15:28:08 +03:00
|
|
|
if isstdiofilename(pat):
|
2015-03-14 00:00:06 +03:00
|
|
|
if writable:
|
|
|
|
fp = repo.ui.fout
|
|
|
|
else:
|
|
|
|
fp = repo.ui.fin
|
2015-12-13 14:07:19 +03:00
|
|
|
return _unclosablefile(fp)
|
2013-02-10 01:38:57 +04:00
|
|
|
fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
|
2013-10-22 18:38:58 +04:00
|
|
|
if modemap is not None:
|
|
|
|
mode = modemap.get(fn, mode)
|
|
|
|
if mode == 'wb':
|
|
|
|
modemap[fn] = 'ab'
|
2013-02-10 01:38:57 +04:00
|
|
|
return open(fn, mode)
|
2006-08-14 03:11:22 +04:00
|
|
|
|
2011-05-14 02:30:32 +04:00
|
|
|
def openrevlog(repo, cmd, file_, opts):
|
|
|
|
"""opens the changelog, manifest, a filelog or a given revlog"""
|
|
|
|
cl = opts['changelog']
|
|
|
|
mf = opts['manifest']
|
2015-04-13 09:51:06 +03:00
|
|
|
dir = opts['dir']
|
2011-05-14 02:30:32 +04:00
|
|
|
msg = None
|
|
|
|
if cl and mf:
|
|
|
|
msg = _('cannot specify --changelog and --manifest at the same time')
|
2015-04-13 09:51:06 +03:00
|
|
|
elif cl and dir:
|
|
|
|
msg = _('cannot specify --changelog and --dir at the same time')
|
2016-06-24 21:12:41 +03:00
|
|
|
elif cl or mf or dir:
|
2011-05-14 02:30:32 +04:00
|
|
|
if file_:
|
|
|
|
msg = _('cannot specify filename with --changelog or --manifest')
|
|
|
|
elif not repo:
|
2015-04-13 09:51:06 +03:00
|
|
|
msg = _('cannot specify --changelog or --manifest or --dir '
|
2011-05-14 02:30:32 +04:00
|
|
|
'without a repository')
|
|
|
|
if msg:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(msg)
|
2011-05-14 02:30:32 +04:00
|
|
|
|
|
|
|
r = None
|
|
|
|
if repo:
|
|
|
|
if cl:
|
2014-04-15 07:27:31 +04:00
|
|
|
r = repo.unfiltered().changelog
|
2015-04-13 09:51:06 +03:00
|
|
|
elif dir:
|
|
|
|
if 'treemanifest' not in repo.requirements:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("--dir can only be used on repos with "
|
2015-04-13 09:51:06 +03:00
|
|
|
"treemanifest enabled"))
|
2016-11-10 13:13:19 +03:00
|
|
|
dirlog = repo.manifestlog._revlog.dirlog(dir)
|
2015-04-13 09:51:06 +03:00
|
|
|
if len(dirlog):
|
|
|
|
r = dirlog
|
2011-05-14 02:30:32 +04:00
|
|
|
elif mf:
|
2016-11-10 13:13:19 +03:00
|
|
|
r = repo.manifestlog._revlog
|
2011-05-14 02:30:32 +04:00
|
|
|
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_):
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("revlog '%s' not found") % file_)
|
2017-03-02 15:30:47 +03:00
|
|
|
r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
|
2011-05-14 02:30:32 +04:00
|
|
|
file_[:-2] + ".i")
|
|
|
|
return r
|
|
|
|
|
2007-12-07 11:29:55 +03:00
|
|
|
def copy(ui, repo, pats, opts, rename=False):
|
2007-12-03 03:11:59 +03:00
|
|
|
# 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 = {}
|
2007-12-07 11:01:10 +03:00
|
|
|
after = opts.get("after")
|
|
|
|
dryrun = opts.get("dry_run")
|
2010-06-07 22:03:32 +04:00
|
|
|
wctx = repo[None]
|
2007-12-03 03:11:59 +03:00
|
|
|
|
2007-12-06 23:58:57 +03:00
|
|
|
def walkpat(pat):
|
|
|
|
srcs = []
|
2015-03-14 00:00:06 +03:00
|
|
|
if after:
|
|
|
|
badstates = '?'
|
|
|
|
else:
|
|
|
|
badstates = '?r'
|
2017-05-21 08:27:52 +03:00
|
|
|
m = scmutil.match(wctx, [pat], opts, globbed=True)
|
|
|
|
for abs in wctx.walk(m):
|
2007-12-06 23:58:57 +03:00
|
|
|
state = repo.dirstate[abs]
|
2008-05-12 20:37:08 +04:00
|
|
|
rel = m.rel(abs)
|
|
|
|
exact = m.exact(abs)
|
2010-05-26 18:16:47 +04:00
|
|
|
if state in badstates:
|
2007-12-06 23:58:57 +03:00
|
|
|
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
|
2007-12-03 03:11:59 +03:00
|
|
|
|
|
|
|
# abssrc: hgsep
|
|
|
|
# relsrc: ossep
|
|
|
|
# otarget: ossep
|
2007-12-06 23:58:57 +03:00
|
|
|
def copyfile(abssrc, relsrc, otarget, exact):
|
2013-11-07 03:19:04 +04:00
|
|
|
abstarget = pathutil.canonpath(repo.root, cwd, otarget)
|
2012-04-28 22:29:21 +04:00
|
|
|
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
|
2007-12-03 03:11:59 +03:00
|
|
|
reltarget = repo.pathto(abstarget, cwd)
|
|
|
|
target = repo.wjoin(abstarget)
|
2007-12-07 11:01:10 +03:00
|
|
|
src = repo.wjoin(abssrc)
|
2007-12-07 11:01:13 +03:00
|
|
|
state = repo.dirstate[abstarget]
|
2007-12-07 11:01:10 +03:00
|
|
|
|
2011-04-19 14:42:53 +04:00
|
|
|
scmutil.checkportable(ui, abstarget)
|
2011-04-15 18:15:32 +04:00
|
|
|
|
2007-12-07 11:01:10 +03:00
|
|
|
# check for collisions
|
|
|
|
prevsrc = targets.get(abstarget)
|
2007-12-03 03:11:59 +03:00
|
|
|
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
|
2007-12-07 11:01:10 +03:00
|
|
|
|
|
|
|
# check for overwrites
|
2010-09-20 23:46:39 +04:00
|
|
|
exists = os.path.lexists(target)
|
2012-03-23 20:47:27 +04:00
|
|
|
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
|
|
|
|
|
2009-04-22 03:39:47 +04:00
|
|
|
if not after and exists or after and state in 'mn':
|
2007-12-03 03:11:59 +03:00
|
|
|
if not opts['force']:
|
2016-09-20 00:15:39 +03:00
|
|
|
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)
|
2007-12-03 03:11:59 +03:00
|
|
|
return
|
2007-12-07 11:01:10 +03:00
|
|
|
|
|
|
|
if after:
|
2007-12-07 11:01:13 +03:00
|
|
|
if not exists:
|
2010-05-02 02:39:40 +04:00
|
|
|
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))
|
2007-12-03 03:11:59 +03:00
|
|
|
return
|
2007-12-07 11:01:13 +03:00
|
|
|
elif not dryrun:
|
2007-12-03 03:11:59 +03:00
|
|
|
try:
|
2007-12-07 11:01:13 +03:00
|
|
|
if exists:
|
|
|
|
os.unlink(target)
|
|
|
|
targetdir = os.path.dirname(target) or '.'
|
|
|
|
if not os.path.isdir(targetdir):
|
|
|
|
os.makedirs(targetdir)
|
2012-03-23 20:47:27 +04:00
|
|
|
if samefile:
|
|
|
|
tmp = target + "~hgrename"
|
|
|
|
os.rename(src, tmp)
|
|
|
|
os.rename(tmp, target)
|
|
|
|
else:
|
|
|
|
util.copyfile(src, target)
|
2011-06-02 02:33:33 +04:00
|
|
|
srcexists = True
|
2015-06-24 08:20:08 +03:00
|
|
|
except IOError as inst:
|
2007-12-03 03:11:59 +03:00
|
|
|
if inst.errno == errno.ENOENT:
|
2015-03-17 16:47:08 +03:00
|
|
|
ui.warn(_('%s: deleted in working directory\n') % relsrc)
|
2011-06-02 02:33:33 +04:00
|
|
|
srcexists = False
|
2007-12-03 03:11:59 +03:00
|
|
|
else:
|
|
|
|
ui.warn(_('%s: cannot copy - %s\n') %
|
2017-08-23 03:03:07 +03:00
|
|
|
(relsrc, encoding.strtolocal(inst.strerror)))
|
2007-12-07 00:15:47 +03:00
|
|
|
return True # report a failure
|
2007-12-07 11:01:10 +03:00
|
|
|
|
2007-12-03 03:11:59 +03:00
|
|
|
if ui.verbose or not exact:
|
2009-03-27 19:35:00 +03:00
|
|
|
if rename:
|
|
|
|
ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
|
|
|
|
else:
|
|
|
|
ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
|
2007-12-07 11:01:13 +03:00
|
|
|
|
2007-12-03 03:11:59 +03:00
|
|
|
targets[abstarget] = abssrc
|
2007-12-07 11:01:10 +03:00
|
|
|
|
|
|
|
# fix up dirstate
|
2011-05-13 23:48:48 +04:00
|
|
|
scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
|
|
|
|
dryrun=dryrun, cwd=cwd)
|
2007-12-07 11:29:55 +03:00
|
|
|
if rename and not dryrun:
|
2012-03-23 20:47:27 +04:00
|
|
|
if not after and srcexists and not samefile:
|
2015-01-14 03:15:26 +03:00
|
|
|
repo.wvfs.unlinkpath(abssrc)
|
2011-06-02 02:33:33 +04:00
|
|
|
wctx.forget([abssrc])
|
2007-12-03 03:11:59 +03:00
|
|
|
|
|
|
|
# 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):
|
2013-11-07 03:19:04 +04:00
|
|
|
abspfx = pathutil.canonpath(repo.root, cwd, pat)
|
2007-12-03 03:11:59 +03:00
|
|
|
abspfx = util.localpath(abspfx)
|
|
|
|
if destdirexists:
|
|
|
|
striplen = len(os.path.split(abspfx)[0])
|
|
|
|
else:
|
|
|
|
striplen = len(abspfx)
|
|
|
|
if striplen:
|
2016-12-17 17:44:24 +03:00
|
|
|
striplen += len(pycompat.ossep)
|
2007-12-03 03:11:59 +03:00
|
|
|
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):
|
2010-08-30 16:38:24 +04:00
|
|
|
if matchmod.patkind(pat):
|
2007-12-03 03:11:59 +03:00
|
|
|
# a mercurial pattern
|
|
|
|
res = lambda p: os.path.join(dest,
|
|
|
|
os.path.basename(util.localpath(p)))
|
|
|
|
else:
|
2013-11-07 03:19:04 +04:00
|
|
|
abspfx = pathutil.canonpath(repo.root, cwd, pat)
|
2007-12-03 03:11:59 +03:00
|
|
|
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:])
|
2010-09-21 00:41:10 +04:00
|
|
|
if os.path.lexists(t):
|
2007-12-03 03:11:59 +03:00
|
|
|
score += 1
|
|
|
|
return score
|
|
|
|
|
|
|
|
abspfx = util.localpath(abspfx)
|
|
|
|
striplen = len(abspfx)
|
|
|
|
if striplen:
|
2016-12-17 17:44:24 +03:00
|
|
|
striplen += len(pycompat.ossep)
|
2007-12-03 03:11:59 +03:00
|
|
|
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:
|
2016-12-17 17:44:24 +03:00
|
|
|
striplen1 += len(pycompat.ossep)
|
2007-12-03 03:11:59 +03:00
|
|
|
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
|
|
|
|
|
2011-05-13 23:48:48 +04:00
|
|
|
pats = scmutil.expandpats(pats)
|
2007-12-03 03:11:59 +03:00
|
|
|
if not pats:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('no source or destination specified'))
|
2007-12-03 03:11:59 +03:00
|
|
|
if len(pats) == 1:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('no destination specified'))
|
2007-12-03 03:11:59 +03:00
|
|
|
dest = pats.pop()
|
2008-03-14 16:32:19 +03:00
|
|
|
destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
|
2007-12-03 03:11:59 +03:00
|
|
|
if not destdirexists:
|
2010-08-30 16:38:24 +04:00
|
|
|
if len(pats) > 1 or matchmod.patkind(pats[0]):
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('with multiple sources, destination must be an '
|
2007-12-03 03:11:59 +03:00
|
|
|
'existing directory'))
|
2008-01-09 15:30:35 +03:00
|
|
|
if util.endswithsep(dest):
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('destination %s is not a directory') % dest)
|
2007-12-07 11:01:10 +03:00
|
|
|
|
|
|
|
tfn = targetpathfn
|
|
|
|
if after:
|
2007-12-03 03:11:59 +03:00
|
|
|
tfn = targetpathafterfn
|
|
|
|
copylist = []
|
|
|
|
for pat in pats:
|
2007-12-06 23:58:57 +03:00
|
|
|
srcs = walkpat(pat)
|
2007-12-03 03:11:59 +03:00
|
|
|
if not srcs:
|
|
|
|
continue
|
|
|
|
copylist.append((tfn(pat, dest, srcs), srcs))
|
|
|
|
if not copylist:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('no files to copy'))
|
2007-12-03 03:11:59 +03:00
|
|
|
|
2007-12-07 00:15:47 +03:00
|
|
|
errors = 0
|
2007-12-03 03:11:59 +03:00
|
|
|
for targetpath, srcs in copylist:
|
2007-12-06 23:58:57 +03:00
|
|
|
for abssrc, relsrc, exact in srcs:
|
2007-12-07 00:15:47 +03:00
|
|
|
if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
|
|
|
|
errors += 1
|
2007-12-03 03:11:59 +03:00
|
|
|
|
|
|
|
if errors:
|
|
|
|
ui.warn(_('(consider using --after)\n'))
|
2007-12-07 11:03:42 +03:00
|
|
|
|
2010-05-16 02:48:49 +04:00
|
|
|
return errors != 0
|
2007-12-03 03:11:59 +03:00
|
|
|
|
2015-10-06 19:51:24 +03:00
|
|
|
## facility to let extension process additional data into an import patch
|
|
|
|
# list of identifier to be executed in order
|
|
|
|
extrapreimport = [] # run before commit
|
2015-10-07 23:05:25 +03:00
|
|
|
extrapostimport = [] # run after commit
|
2015-10-06 19:51:24 +03:00
|
|
|
# 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),
|
2015-10-17 01:58:46 +03:00
|
|
|
# - extra: the future extra dictionary of the changeset, please mutate it,
|
2015-10-06 19:51:24 +03:00
|
|
|
# - 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 = {}
|
2015-10-07 23:05:25 +03:00
|
|
|
# 'postimport' are run after the commit is made and are provided the following
|
|
|
|
# argument:
|
|
|
|
# - ctx: the changectx created by import.
|
|
|
|
extrapostimportmap = {}
|
2015-10-06 19:51:24 +03:00
|
|
|
|
2014-02-12 04:52:36 +04:00
|
|
|
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>)
|
|
|
|
"""
|
2015-08-08 10:47:19 +03:00
|
|
|
# avoid cycle context -> subrepo -> cmdutil
|
2016-02-28 10:57:07 +03:00
|
|
|
from . import context
|
2015-10-06 12:01:53 +03:00
|
|
|
extractdata = patch.extract(ui, hunk)
|
|
|
|
tmpname = extractdata.get('filename')
|
|
|
|
message = extractdata.get('message')
|
2015-12-31 00:06:31 +03:00
|
|
|
user = opts.get('user') or extractdata.get('user')
|
|
|
|
date = opts.get('date') or extractdata.get('date')
|
2015-10-06 12:01:53 +03:00
|
|
|
branch = extractdata.get('branch')
|
|
|
|
nodeid = extractdata.get('nodeid')
|
|
|
|
p1 = extractdata.get('p1')
|
|
|
|
p2 = extractdata.get('p2')
|
2014-02-12 04:52:36 +04:00
|
|
|
|
2015-12-31 00:07:51 +03:00
|
|
|
nocommit = opts.get('no_commit')
|
|
|
|
importbranch = opts.get('import_branch')
|
2014-02-12 04:52:36 +04:00
|
|
|
update = not opts.get('bypass')
|
|
|
|
strip = opts["strip"]
|
2015-03-10 04:22:24 +03:00
|
|
|
prefix = opts["prefix"]
|
2014-02-12 04:52:36 +04:00
|
|
|
sim = float(opts.get('similarity') or 0)
|
|
|
|
if not tmpname:
|
2014-05-09 04:08:17 +04:00
|
|
|
return (None, None, False)
|
2014-02-12 04:52:36 +04:00
|
|
|
|
2014-05-09 04:08:17 +04:00
|
|
|
rejects = False
|
|
|
|
|
2014-02-12 04:52:36 +04:00
|
|
|
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:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('not a Mercurial patch'))
|
2014-02-12 04:52:36 +04:00
|
|
|
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
|
2014-08-27 18:10:06 +04:00
|
|
|
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"))
|
2014-02-12 04:52:36 +04:00
|
|
|
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())
|
|
|
|
|
2015-12-31 00:07:51 +03:00
|
|
|
if opts.get('exact') or importbranch:
|
2014-02-12 04:52:36 +04:00
|
|
|
repo.dirstate.setbranch(branch or 'default')
|
|
|
|
|
2014-05-09 04:08:17 +04:00
|
|
|
partial = opts.get('partial', False)
|
2014-02-12 04:52:36 +04:00
|
|
|
files = set()
|
2014-05-09 04:08:17 +04:00
|
|
|
try:
|
2015-03-10 04:22:24 +03:00
|
|
|
patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
|
|
|
|
files=files, eolmode=None, similarity=sim / 100.0)
|
2017-09-03 10:45:33 +03:00
|
|
|
except error.PatchError as e:
|
2014-05-09 04:08:17 +04:00
|
|
|
if not partial:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(str(e))
|
2014-05-09 04:08:17 +04:00
|
|
|
if partial:
|
|
|
|
rejects = True
|
|
|
|
|
2014-02-12 04:52:36 +04:00
|
|
|
files = list(files)
|
2015-12-31 00:07:51 +03:00
|
|
|
if nocommit:
|
2014-02-12 04:52:36 +04:00
|
|
|
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 [])
|
2014-08-16 05:43:59 +04:00
|
|
|
editform = mergeeditform(repo[None], 'import.normal')
|
2014-08-23 18:03:50 +04:00
|
|
|
if opts.get('exact'):
|
|
|
|
editor = None
|
|
|
|
else:
|
|
|
|
editor = getcommiteditor(editform=editform, **opts)
|
2015-10-06 19:51:24 +03:00
|
|
|
extra = {}
|
|
|
|
for idfunc in extrapreimport:
|
|
|
|
extrapreimportmap[idfunc](repo, extractdata, extra, opts)
|
2017-03-17 00:23:49 +03:00
|
|
|
overrides = {}
|
|
|
|
if partial:
|
|
|
|
overrides[('ui', 'allowemptycommit')] = True
|
|
|
|
with repo.ui.configoverride(overrides, 'import'):
|
2015-12-31 00:06:31 +03:00
|
|
|
n = repo.commit(message, user,
|
|
|
|
date, match=m,
|
2015-10-06 19:51:24 +03:00
|
|
|
editor=editor, extra=extra)
|
2015-10-07 23:05:25 +03:00
|
|
|
for idfunc in extrapostimport:
|
|
|
|
extrapostimportmap[idfunc](repo[n])
|
2014-02-12 04:52:36 +04:00
|
|
|
else:
|
2015-12-31 00:07:51 +03:00
|
|
|
if opts.get('exact') or importbranch:
|
2014-02-12 04:52:36 +04:00
|
|
|
branch = branch or 'default'
|
|
|
|
else:
|
|
|
|
branch = p1.branch()
|
|
|
|
store = patch.filestore()
|
|
|
|
try:
|
|
|
|
files = set()
|
|
|
|
try:
|
2015-03-10 23:06:38 +03:00
|
|
|
patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
|
2014-02-12 04:52:36 +04:00
|
|
|
files, eolmode=None)
|
2017-09-03 10:45:33 +03:00
|
|
|
except error.PatchError as e:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(str(e))
|
2014-08-23 18:03:50 +04:00
|
|
|
if opts.get('exact'):
|
|
|
|
editor = None
|
|
|
|
else:
|
|
|
|
editor = getcommiteditor(editform='import.bypass')
|
2017-06-10 17:24:33 +03:00
|
|
|
memctx = context.memctx(repo, (p1.node(), p2.node()),
|
2014-02-12 04:52:36 +04:00
|
|
|
message,
|
2017-06-10 17:24:33 +03:00
|
|
|
files=files,
|
|
|
|
filectxfn=store,
|
|
|
|
user=user,
|
|
|
|
date=date,
|
|
|
|
branch=branch,
|
2014-08-02 16:46:27 +04:00
|
|
|
editor=editor)
|
2014-02-12 04:52:36 +04:00
|
|
|
n = memctx.commit()
|
|
|
|
finally:
|
|
|
|
store.close()
|
2015-12-31 00:07:51 +03:00
|
|
|
if opts.get('exact') and nocommit:
|
2014-09-19 23:51:58 +04:00
|
|
|
# --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:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('patch is damaged or loses information'))
|
2015-12-31 00:05:42 +03:00
|
|
|
msg = _('applied to working directory')
|
2014-02-12 04:52:36 +04:00
|
|
|
if n:
|
|
|
|
# i18n: refers to a short changeset id
|
|
|
|
msg = _('created %s') % short(n)
|
2014-05-09 04:08:17 +04:00
|
|
|
return (msg, n, rejects)
|
2014-02-12 04:52:36 +04:00
|
|
|
finally:
|
|
|
|
os.unlink(tmpname)
|
|
|
|
|
2015-10-06 09:17:01 +03:00
|
|
|
# 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 = {}
|
|
|
|
|
2017-05-22 20:34:03 +03:00
|
|
|
def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
|
2017-06-03 13:17:19 +03:00
|
|
|
node = scmutil.binnode(ctx)
|
2017-05-22 20:34:03 +03:00
|
|
|
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)
|
|
|
|
|
2017-05-21 03:15:05 +03:00
|
|
|
def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
|
2015-09-10 05:27:48 +03:00
|
|
|
opts=None, match=None):
|
2017-05-21 00:58:04 +03:00
|
|
|
'''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.
|
2017-05-21 03:15:05 +03:00
|
|
|
fntemplate: An optional string to use for generating patch file names.
|
2017-05-21 00:58:04 +03:00
|
|
|
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.
|
2017-05-21 03:15:05 +03:00
|
|
|
fntemplate specified: Each rev is written to a unique file named using
|
2017-05-21 00:58:04 +03:00
|
|
|
the given template.
|
|
|
|
Neither fp nor template specified: All revs written to repo.ui.write()
|
|
|
|
'''
|
2010-03-08 21:43:24 +03:00
|
|
|
|
|
|
|
total = len(revs)
|
2017-05-22 20:35:22 +03:00
|
|
|
revwidth = max(len(str(rev)) for rev in revs)
|
2013-02-10 01:38:57 +04:00
|
|
|
filemode = {}
|
2010-03-08 21:43:24 +03:00
|
|
|
|
2017-05-22 22:05:18 +03:00
|
|
|
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
|
|
|
|
|
2017-05-22 20:34:03 +03:00
|
|
|
for seqno, rev in enumerate(revs, 1):
|
2010-03-08 21:43:24 +03:00
|
|
|
ctx = repo[rev]
|
2017-05-22 20:34:03 +03:00
|
|
|
fo = None
|
2017-05-22 22:05:18 +03:00
|
|
|
if not fp and fntemplate:
|
2011-07-30 14:08:45 +04:00
|
|
|
desc_lines = ctx.description().rstrip().split('\n')
|
|
|
|
desc = desc_lines[0] #Commit always has a first line.
|
2017-05-22 20:34:03 +03:00
|
|
|
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()
|
2010-03-08 21:43:24 +03:00
|
|
|
|
2010-04-06 19:45:20 +04:00
|
|
|
def diffordiffstat(ui, repo, diffopts, node1, node2, match,
|
2010-09-03 14:58:51 +04:00
|
|
|
changes=None, stat=False, fp=None, prefix='',
|
2017-10-06 15:45:17 +03:00
|
|
|
root='', listsubrepos=False, hunksfilterfn=None):
|
2010-04-06 19:45:20 +04:00
|
|
|
'''show diff or diffstat.'''
|
|
|
|
if fp is None:
|
|
|
|
write = ui.write
|
|
|
|
else:
|
|
|
|
def write(s, **kw):
|
|
|
|
fp.write(s)
|
|
|
|
|
2015-03-25 21:55:15 +03:00
|
|
|
if root:
|
|
|
|
relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
|
2015-03-18 01:06:40 +03:00
|
|
|
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))
|
|
|
|
|
2010-04-06 19:45:20 +04:00
|
|
|
if stat:
|
2010-08-13 10:29:30 +04:00
|
|
|
diffopts = diffopts.copy(context=0)
|
2010-04-06 19:45:20 +04:00
|
|
|
width = 80
|
|
|
|
if not ui.plain():
|
2010-10-10 19:06:36 +04:00
|
|
|
width = ui.termwidth()
|
2010-09-03 14:58:51 +04:00
|
|
|
chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
|
2017-10-06 15:45:17 +03:00
|
|
|
prefix=prefix, relroot=relroot,
|
|
|
|
hunksfilterfn=hunksfilterfn)
|
2010-04-06 19:45:20 +04:00
|
|
|
for chunk, label in patch.diffstatui(util.iterlines(chunks),
|
2016-11-12 15:36:17 +03:00
|
|
|
width=width):
|
2010-04-06 19:45:20 +04:00
|
|
|
write(chunk, label=label)
|
|
|
|
else:
|
|
|
|
for chunk, label in patch.diffui(repo, node1, node2, match,
|
2015-03-18 01:06:40 +03:00
|
|
|
changes, diffopts, prefix=prefix,
|
2017-10-06 15:45:17 +03:00
|
|
|
relroot=relroot,
|
|
|
|
hunksfilterfn=hunksfilterfn):
|
2010-04-06 19:45:20 +04:00
|
|
|
write(chunk, label=label)
|
|
|
|
|
2010-09-03 14:58:51 +04:00
|
|
|
if listsubrepos:
|
|
|
|
ctx1 = repo[node1]
|
2010-09-07 18:23:55 +04:00
|
|
|
ctx2 = repo[node2]
|
2014-02-04 03:36:00 +04:00
|
|
|
for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
|
2011-12-16 01:41:03 +04:00
|
|
|
tempnode2 = node2
|
2011-12-14 18:28:00 +04:00
|
|
|
try:
|
|
|
|
if node2 is not None:
|
2011-12-16 01:41:03 +04:00
|
|
|
tempnode2 = ctx2.substate[subpath][1]
|
2011-12-14 18:28:00 +04:00
|
|
|
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.
|
2011-12-16 01:41:03 +04:00
|
|
|
tempnode2 = None
|
2016-02-06 08:09:32 +03:00
|
|
|
submatch = matchmod.subdirmatcher(subpath, match)
|
2012-11-29 19:43:55 +04:00
|
|
|
sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
|
2010-09-03 14:58:51 +04:00
|
|
|
stat=stat, fp=fp, prefix=prefix)
|
|
|
|
|
2017-01-03 12:56:41 +03:00
|
|
|
def _changesetlabels(ctx):
|
|
|
|
labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
|
2017-03-25 11:39:07 +03:00
|
|
|
if ctx.obsolete():
|
|
|
|
labels.append('changeset.obsolete')
|
2017-08-02 20:13:56 +03:00
|
|
|
if ctx.isunstable():
|
2017-08-03 15:32:50 +03:00
|
|
|
labels.append('changeset.unstable')
|
2017-08-02 19:34:39 +03:00
|
|
|
for instability in ctx.instabilities():
|
2017-08-03 16:30:41 +03:00
|
|
|
labels.append('instability.%s' % instability)
|
2017-01-03 12:56:41 +03:00
|
|
|
return ' '.join(labels)
|
|
|
|
|
2006-12-01 10:28:19 +03:00
|
|
|
class changeset_printer(object):
|
|
|
|
'''show changeset information when templating not requested.'''
|
|
|
|
|
2014-08-31 18:45:50 +04:00
|
|
|
def __init__(self, ui, repo, matchfn, diffopts, buffered):
|
2006-11-13 22:26:57 +03:00
|
|
|
self.ui = ui
|
2006-12-01 10:28:19 +03:00
|
|
|
self.repo = repo
|
|
|
|
self.buffered = buffered
|
2014-08-31 18:45:50 +04:00
|
|
|
self.matchfn = matchfn
|
2009-02-15 00:40:39 +03:00
|
|
|
self.diffopts = diffopts
|
2006-11-13 22:26:57 +03:00
|
|
|
self.header = {}
|
2006-12-01 10:28:19 +03:00
|
|
|
self.hunk = {}
|
2006-11-13 22:26:57 +03:00
|
|
|
self.lastheader = None
|
2009-12-28 19:55:52 +03:00
|
|
|
self.footer = None
|
2006-12-01 10:28:19 +03:00
|
|
|
|
2015-04-12 15:52:02 +03:00
|
|
|
def flush(self, ctx):
|
|
|
|
rev = ctx.rev()
|
2006-11-13 22:26:57 +03:00
|
|
|
if rev in self.header:
|
2006-12-01 10:28:19 +03:00
|
|
|
h = self.header[rev]
|
2006-11-13 22:26:57 +03:00
|
|
|
if h != self.lastheader:
|
|
|
|
self.lastheader = h
|
|
|
|
self.ui.write(h)
|
|
|
|
del self.header[rev]
|
|
|
|
if rev in self.hunk:
|
2006-12-01 10:28:19 +03:00
|
|
|
self.ui.write(self.hunk[rev])
|
2006-11-13 22:26:57 +03:00
|
|
|
del self.hunk[rev]
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
2009-12-28 19:55:52 +03:00
|
|
|
def close(self):
|
|
|
|
if self.footer:
|
|
|
|
self.ui.write(self.footer)
|
|
|
|
|
2017-10-06 15:45:17 +03:00
|
|
|
def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
|
|
|
|
**props):
|
2017-06-26 21:50:55 +03:00
|
|
|
props = pycompat.byteskwargs(props)
|
2006-12-01 10:28:19 +03:00
|
|
|
if self.buffered:
|
2015-11-23 01:13:25 +03:00
|
|
|
self.ui.pushbuffer(labeled=True)
|
2017-10-06 15:45:17 +03:00
|
|
|
self._show(ctx, copies, matchfn, hunksfilterfn, props)
|
2015-11-24 22:23:10 +03:00
|
|
|
self.hunk[ctx.rev()] = self.ui.popbuffer()
|
2006-12-01 10:28:19 +03:00
|
|
|
else:
|
2017-10-06 15:45:17 +03:00
|
|
|
self._show(ctx, copies, matchfn, hunksfilterfn, props)
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2017-10-06 15:45:17 +03:00
|
|
|
def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
|
2006-11-13 22:26:57 +03:00
|
|
|
'''show a single changeset or file revision'''
|
2008-11-14 15:59:25 +03:00
|
|
|
changenode = ctx.node()
|
|
|
|
rev = ctx.rev()
|
2006-11-13 22:26:57 +03:00
|
|
|
|
|
|
|
if self.ui.quiet:
|
2017-09-24 06:43:57 +03:00
|
|
|
self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
|
|
|
|
label='log.node')
|
2006-11-13 22:26:57 +03:00
|
|
|
return
|
|
|
|
|
2009-10-06 02:17:13 +04:00
|
|
|
date = util.datestr(ctx.date())
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2017-09-24 06:43:57 +03:00
|
|
|
self.ui.write(_("changeset: %s\n") % scmutil.formatchangeid(ctx),
|
2017-01-03 12:56:41 +03:00
|
|
|
label=_changesetlabels(ctx))
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2014-10-17 20:26:37 +04:00
|
|
|
# branches are shown first before any other names due to backwards
|
|
|
|
# compatibility
|
2009-10-24 21:01:40 +04:00
|
|
|
branch = ctx.branch()
|
2007-03-13 21:02:33 +03:00
|
|
|
# don't show the default branch name
|
|
|
|
if branch != 'default':
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2010-04-03 00:22:07 +04:00
|
|
|
self.ui.write(_("branch: %s\n") % branch,
|
|
|
|
label='log.branch')
|
2014-10-17 20:26:37 +04:00
|
|
|
|
2016-04-14 22:26:18 +03:00
|
|
|
for nsname, ns in self.repo.names.iteritems():
|
2014-10-17 20:26:37 +04:00
|
|
|
# branches has special logic already handled above, so here we just
|
|
|
|
# skip it
|
2016-04-14 22:26:18 +03:00
|
|
|
if nsname == 'branches':
|
2014-10-17 20:26:37 +04:00
|
|
|
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):
|
2015-01-28 16:22:59 +03:00
|
|
|
self.ui.write(ns.logfmt % name,
|
|
|
|
label='log.%s' % ns.colorname)
|
2014-10-05 00:28:28 +04:00
|
|
|
if self.ui.debugflag:
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2015-04-25 17:44:53 +03:00
|
|
|
self.ui.write(_("phase: %s\n") % ctx.phasestr(),
|
2012-01-17 23:23:23 +04:00
|
|
|
label='log.phase')
|
2015-09-26 06:29:09 +03:00
|
|
|
for pctx in scmutil.meaningfulparents(self.repo, ctx):
|
2015-03-14 11:19:04 +03:00
|
|
|
label = 'log.parent changeset.%s' % pctx.phasestr()
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2017-09-24 06:43:57 +03:00
|
|
|
self.ui.write(_("parent: %s\n") % scmutil.formatchangeid(pctx),
|
2014-08-24 02:03:08 +04:00
|
|
|
label=label)
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2015-03-14 11:33:22 +03:00
|
|
|
if self.ui.debugflag and rev is not None:
|
2009-10-06 02:17:13 +04:00
|
|
|
mnode = ctx.manifestnode()
|
2017-09-24 06:43:57 +03:00
|
|
|
mrev = self.repo.manifestlog._revlog.rev(mnode)
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2017-09-24 06:43:57 +03:00
|
|
|
self.ui.write(_("manifest: %s\n")
|
|
|
|
% scmutil.formatrevnode(self.ui, mrev, mnode),
|
2010-04-03 00:22:07 +04:00
|
|
|
label='ui.debug log.manifest')
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2010-04-03 00:22:07 +04:00
|
|
|
self.ui.write(_("user: %s\n") % ctx.user(),
|
|
|
|
label='log.user')
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2010-04-03 00:22:07 +04:00
|
|
|
self.ui.write(_("date: %s\n") % date,
|
|
|
|
label='log.date')
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2017-08-02 20:13:56 +03:00
|
|
|
if ctx.isunstable():
|
2017-01-06 14:36:21 +03:00
|
|
|
# i18n: column positioning for "hg log"
|
2017-08-02 19:34:39 +03:00
|
|
|
instabilities = ctx.instabilities()
|
|
|
|
self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
|
2017-08-03 16:31:54 +03:00
|
|
|
label='log.instability')
|
2016-10-10 13:06:58 +03:00
|
|
|
|
2017-10-05 16:25:18 +03:00
|
|
|
elif ctx.obsolete():
|
|
|
|
self._showobsfate(ctx)
|
|
|
|
|
2017-06-26 16:46:24 +03:00
|
|
|
self._exthook(ctx)
|
|
|
|
|
2006-11-13 22:26:57 +03:00
|
|
|
if self.ui.debugflag:
|
2015-03-14 11:40:47 +03:00
|
|
|
files = ctx.p1().status(ctx)[:3]
|
2012-10-30 22:59:27 +04:00
|
|
|
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):
|
2006-11-13 22:26:57 +03:00
|
|
|
if value:
|
2010-04-03 00:22:07 +04:00
|
|
|
self.ui.write("%-12s %s\n" % (key, " ".join(value)),
|
|
|
|
label='ui.debug log.files')
|
2009-10-06 02:17:13 +04:00
|
|
|
elif ctx.files() and self.ui.verbose:
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2010-04-03 00:22:07 +04:00
|
|
|
self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
|
|
|
|
label='ui.note log.files')
|
2006-11-13 22:26:57 +03:00
|
|
|
if copies and self.ui.verbose:
|
|
|
|
copies = ['%s (%s)' % c for c in copies]
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2010-04-03 00:22:07 +04:00
|
|
|
self.ui.write(_("copies: %s\n") % ' '.join(copies),
|
|
|
|
label='ui.note log.copies')
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2009-10-24 21:01:40 +04:00
|
|
|
extra = ctx.extra()
|
2006-11-13 22:26:57 +03:00
|
|
|
if extra and self.ui.debugflag:
|
2009-04-27 01:50:44 +04:00
|
|
|
for key, value in sorted(extra.items()):
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2006-11-13 22:26:57 +03:00
|
|
|
self.ui.write(_("extra: %s=%s\n")
|
2017-03-15 17:06:50 +03:00
|
|
|
% (key, util.escapestr(value)),
|
2010-04-03 00:22:07 +04:00
|
|
|
label='ui.debug log.extra')
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2009-10-06 02:17:13 +04:00
|
|
|
description = ctx.description().strip()
|
2006-11-13 22:26:57 +03:00
|
|
|
if description:
|
|
|
|
if self.ui.verbose:
|
2010-04-03 00:22:07 +04:00
|
|
|
self.ui.write(_("description:\n"),
|
|
|
|
label='ui.note log.description')
|
|
|
|
self.ui.write(description,
|
|
|
|
label='ui.note log.description')
|
2006-11-13 22:26:57 +03:00
|
|
|
self.ui.write("\n\n")
|
|
|
|
else:
|
2012-10-30 22:59:27 +04:00
|
|
|
# i18n: column positioning for "hg log"
|
2006-11-13 22:26:57 +03:00
|
|
|
self.ui.write(_("summary: %s\n") %
|
2010-04-03 00:22:07 +04:00
|
|
|
description.splitlines()[0],
|
|
|
|
label='log.summary')
|
2006-11-13 22:26:57 +03:00
|
|
|
self.ui.write("\n")
|
|
|
|
|
2017-10-06 15:45:17 +03:00
|
|
|
self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2017-10-05 16:25:18 +03:00
|
|
|
def _showobsfate(self, ctx):
|
|
|
|
obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
|
|
|
|
|
|
|
|
if obsfate:
|
|
|
|
for obsfateline in obsfate:
|
|
|
|
# i18n: column positioning for "hg log"
|
2017-10-19 13:32:42 +03:00
|
|
|
self.ui.write(_("obsolete: %s\n") % obsfateline,
|
2017-10-05 16:25:18 +03:00
|
|
|
label='log.obsfate')
|
|
|
|
|
2017-06-26 16:46:24 +03:00
|
|
|
def _exthook(self, ctx):
|
|
|
|
'''empty method used by extension as a hook point
|
|
|
|
'''
|
|
|
|
|
2017-10-06 15:45:17 +03:00
|
|
|
def showpatch(self, ctx, matchfn, hunksfilterfn=None):
|
2010-06-29 14:12:34 +04:00
|
|
|
if not matchfn:
|
2014-08-31 18:45:50 +04:00
|
|
|
matchfn = self.matchfn
|
2010-06-29 14:12:34 +04:00
|
|
|
if matchfn:
|
2010-03-31 19:35:12 +04:00
|
|
|
stat = self.diffopts.get('stat')
|
2010-08-13 10:29:30 +04:00
|
|
|
diff = self.diffopts.get('patch')
|
2014-11-22 03:01:55 +03:00
|
|
|
diffopts = patch.diffallopts(self.ui, self.diffopts)
|
2015-11-15 04:44:01 +03:00
|
|
|
node = ctx.node()
|
2016-01-05 03:14:04 +03:00
|
|
|
prev = ctx.p1().node()
|
2010-08-13 10:29:30 +04:00
|
|
|
if stat:
|
|
|
|
diffordiffstat(self.ui, self.repo, diffopts, prev, node,
|
2017-10-06 15:45:17 +03:00
|
|
|
match=matchfn, stat=True,
|
|
|
|
hunksfilterfn=hunksfilterfn)
|
2010-08-13 10:29:30 +04:00
|
|
|
if diff:
|
|
|
|
if stat:
|
|
|
|
self.ui.write("\n")
|
|
|
|
diffordiffstat(self.ui, self.repo, diffopts, prev, node,
|
2017-10-06 15:45:17 +03:00
|
|
|
match=matchfn, stat=False,
|
|
|
|
hunksfilterfn=hunksfilterfn)
|
2006-11-13 22:26:57 +03:00
|
|
|
self.ui.write("\n")
|
|
|
|
|
2014-09-15 22:11:13 +04:00
|
|
|
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")
|
|
|
|
|
2017-10-06 15:45:17 +03:00
|
|
|
def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
|
2014-09-15 22:11:13 +04:00
|
|
|
'''show a single changeset or file revision'''
|
|
|
|
rev = ctx.rev()
|
2015-03-14 14:15:40 +03:00
|
|
|
if rev is None:
|
|
|
|
jrev = jnode = 'null'
|
|
|
|
else:
|
2017-05-04 23:11:54 +03:00
|
|
|
jrev = '%d' % rev
|
2015-03-14 14:15:40 +03:00
|
|
|
jnode = '"%s"' % hex(ctx.node())
|
2014-09-15 22:11:13 +04:00
|
|
|
j = encoding.jsonescape
|
|
|
|
|
|
|
|
if self._first:
|
|
|
|
self.ui.write("[\n {")
|
|
|
|
self._first = False
|
|
|
|
else:
|
|
|
|
self.ui.write(",\n {")
|
|
|
|
|
|
|
|
if self.ui.quiet:
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write(('\n "rev": %s') % jrev)
|
|
|
|
self.ui.write((',\n "node": %s') % jnode)
|
2014-09-15 22:11:13 +04:00
|
|
|
self.ui.write('\n }')
|
|
|
|
return
|
|
|
|
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
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()))
|
2014-09-15 22:11:13 +04:00
|
|
|
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "bookmarks": [%s]') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "tags": [%s]') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s"' % j(t) for t in ctx.tags()))
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "parents": [%s]') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s"' % c.hex() for c in ctx.parents()))
|
|
|
|
|
|
|
|
if self.ui.debugflag:
|
2015-03-14 14:16:35 +03:00
|
|
|
if rev is None:
|
|
|
|
jmanifestnode = 'null'
|
|
|
|
else:
|
|
|
|
jmanifestnode = '"%s"' % hex(ctx.manifestnode())
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "manifest": %s') % jmanifestnode)
|
2014-09-15 22:11:13 +04:00
|
|
|
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "extra": {%s}') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s": "%s"' % (j(k), j(v))
|
|
|
|
for k, v in ctx.extra().items()))
|
|
|
|
|
2015-01-06 09:18:55 +03:00
|
|
|
files = ctx.p1().status(ctx)
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "modified": [%s]') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s"' % j(f) for f in files[0]))
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "added": [%s]') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s"' % j(f) for f in files[1]))
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "removed": [%s]') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s"' % j(f) for f in files[2]))
|
|
|
|
|
|
|
|
elif self.ui.verbose:
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "files": [%s]') %
|
2014-09-15 22:11:13 +04:00
|
|
|
", ".join('"%s"' % j(f) for f in ctx.files()))
|
|
|
|
|
|
|
|
if copies:
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "copies": {%s}') %
|
2015-02-02 22:26:47 +03:00
|
|
|
", ".join('"%s": "%s"' % (j(k), j(v))
|
|
|
|
for k, v in copies))
|
2014-09-15 22:11:13 +04:00
|
|
|
|
|
|
|
matchfn = self.matchfn
|
|
|
|
if matchfn:
|
|
|
|
stat = self.diffopts.get('stat')
|
|
|
|
diff = self.diffopts.get('patch')
|
2014-11-19 09:13:05 +03:00
|
|
|
diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
|
2014-09-15 22:11:13 +04:00
|
|
|
node, prev = ctx.node(), ctx.p1().node()
|
|
|
|
if stat:
|
|
|
|
self.ui.pushbuffer()
|
|
|
|
diffordiffstat(self.ui, self.repo, diffopts, prev, node,
|
|
|
|
match=matchfn, stat=True)
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "diffstat": "%s"')
|
|
|
|
% j(self.ui.popbuffer()))
|
2014-09-15 22:11:13 +04:00
|
|
|
if diff:
|
|
|
|
self.ui.pushbuffer()
|
|
|
|
diffordiffstat(self.ui, self.repo, diffopts, prev, node,
|
|
|
|
match=matchfn, stat=False)
|
check-code: detect "missing _() in ui message" more exactly
Before this patch, "missing _() in ui message" rule overlooks
translatable message, which starts with other than alphabet.
To detect "missing _() in ui message" more exactly, this patch
improves the regexp with assumptions below.
- sequence consisting of below might precede "translatable message"
in same string token
- formatting string, which starts with '%'
- escaped character, which starts with 'b' (as replacement of '\\'), or
- characters other than '%', 'b' and 'x' (as replacement of alphabet)
- any string tokens might precede a string token, which contains
"translatable message"
This patch builds an input file, which is used to examine "missing _()
in ui message" detection, before '"$check_code" stringjoin.py' in
test-contrib-check-code.t, because this reduces amount of change churn
in subsequent patch.
This patch also applies "()" instead of "_()" on messages below to
hide false-positives:
- messages for ui.debug() or debug commands/tools
- contrib/debugshell.py
- hgext/win32mbcs.py (ui.write() is used, though)
- mercurial/commands.py
- _debugchangegroup
- debugindex
- debuglocks
- debugrevlog
- debugrevspec
- debugtemplate
- untranslatable messages
- doc/gendoc.py (ReST specific text)
- hgext/hgk.py (permission string)
- hgext/keyword.py (text written into configuration file)
- mercurial/cmdutil.py (formatting strings for JSON)
2016-06-20 18:50:39 +03:00
|
|
|
self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
|
2014-09-15 22:11:13 +04:00
|
|
|
|
|
|
|
self.ui.write("\n }")
|
2007-07-08 14:52:08 +04:00
|
|
|
|
2006-11-13 22:26:57 +03:00
|
|
|
class changeset_templater(changeset_printer):
|
2006-11-13 22:26:57 +03:00
|
|
|
'''format changeset information.'''
|
|
|
|
|
2017-06-24 21:47:26 +03:00
|
|
|
# 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 {}
|
|
|
|
|
2014-08-31 18:45:50 +04:00
|
|
|
changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
|
2017-04-22 13:07:00 +03:00
|
|
|
self.t = formatter.loadtemplater(ui, tmplspec,
|
2017-04-22 09:06:06 +03:00
|
|
|
cache=templatekw.defaulttempl)
|
2016-04-22 15:46:33 +03:00
|
|
|
self._counter = itertools.count()
|
2014-03-09 02:14:08 +04:00
|
|
|
self.cache = {}
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2017-04-11 15:38:24 +03:00
|
|
|
self._tref = tmplspec.ref
|
|
|
|
self._parts = {'header': '', 'footer': '',
|
|
|
|
tmplspec.ref: tmplspec.ref,
|
2017-06-17 10:32:20 +03:00
|
|
|
'docheader': '', 'docfooter': '',
|
|
|
|
'separator': ''}
|
2017-04-22 15:09:07 +03:00
|
|
|
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
|
2017-04-22 15:16:08 +03:00
|
|
|
else:
|
|
|
|
partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
|
|
|
|
m = formatter.templatepartsmap(tmplspec, self.t, partnames)
|
|
|
|
self._parts.update(m)
|
2015-08-25 21:38:20 +03:00
|
|
|
|
2015-08-27 00:27:14 +03:00
|
|
|
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()
|
|
|
|
|
2017-10-06 15:45:17 +03:00
|
|
|
def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
|
2006-11-13 22:26:57 +03:00
|
|
|
'''show a single changeset or file revision'''
|
|
|
|
props = props.copy()
|
2009-12-13 20:06:23 +03:00
|
|
|
props.update(templatekw.keywords)
|
2009-12-13 20:06:23 +03:00
|
|
|
props['templ'] = self.t
|
2009-12-13 20:06:23 +03:00
|
|
|
props['ctx'] = ctx
|
2009-12-13 20:06:23 +03:00
|
|
|
props['repo'] = self.repo
|
2016-03-08 15:08:33 +03:00
|
|
|
props['ui'] = self.repo.ui
|
2017-06-17 10:32:20 +03:00
|
|
|
props['index'] = index = next(self._counter)
|
2009-12-13 20:06:24 +03:00
|
|
|
props['revcache'] = {'copies': copies}
|
2009-12-13 20:06:24 +03:00
|
|
|
props['cache'] = self.cache
|
2017-05-03 21:54:21 +03:00
|
|
|
props = pycompat.strkwargs(props)
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2017-06-17 10:32:20 +03:00
|
|
|
# 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'])))
|
|
|
|
|
2016-04-03 05:23:31 +03:00
|
|
|
# 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
|
2017-04-11 15:38:24 +03:00
|
|
|
key = self._parts[self._tref]
|
2016-04-03 05:23:31 +03:00
|
|
|
self.ui.write(templater.stringify(self.t(key, **props)))
|
2017-10-06 15:45:17 +03:00
|
|
|
self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
|
2016-04-03 05:23:31 +03:00
|
|
|
|
|
|
|
if self._parts['footer']:
|
|
|
|
if not self.footer:
|
|
|
|
self.footer = templater.stringify(
|
|
|
|
self.t(self._parts['footer'], **props))
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2017-04-22 13:07:00 +03:00
|
|
|
def logtemplatespec(tmpl, mapfile):
|
2017-04-22 14:14:55 +03:00
|
|
|
if mapfile:
|
|
|
|
return formatter.templatespec('changeset', tmpl, mapfile)
|
|
|
|
else:
|
|
|
|
return formatter.templatespec('', tmpl, None)
|
2017-04-22 12:48:38 +03:00
|
|
|
|
2017-04-22 09:30:27 +03:00
|
|
|
def _lookuplogtemplate(ui, tmpl, style):
|
2017-05-06 10:24:21 +03:00
|
|
|
"""Find the template matching the given template spec or style
|
|
|
|
|
|
|
|
See formatter.lookuptemplate() for details.
|
2006-11-13 22:26:57 +03:00
|
|
|
"""
|
2009-04-04 19:55:52 +04:00
|
|
|
|
|
|
|
# ui settings
|
2014-09-30 18:15:56 +04:00
|
|
|
if not tmpl and not style: # template are stronger than style
|
2009-04-04 19:55:52 +04:00
|
|
|
tmpl = ui.config('ui', 'logtemplate')
|
|
|
|
if tmpl:
|
2017-04-22 12:48:38 +03:00
|
|
|
return logtemplatespec(templater.unquotestring(tmpl), None)
|
2009-04-04 19:55:52 +04:00
|
|
|
else:
|
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
2017-07-15 00:22:40 +03:00
|
|
|
style = util.expandpath(ui.config('ui', 'style'))
|
2009-04-04 19:55:52 +04:00
|
|
|
|
2014-09-30 18:15:56 +04:00
|
|
|
if not tmpl and style:
|
2009-04-04 19:55:52 +04:00
|
|
|
mapfile = style
|
|
|
|
if not os.path.split(mapfile)[0]:
|
|
|
|
mapname = (templater.templatepath('map-cmdline.' + mapfile)
|
|
|
|
or templater.templatepath(mapfile))
|
2010-01-25 09:05:27 +03:00
|
|
|
if mapname:
|
|
|
|
mapfile = mapname
|
2017-04-22 12:48:38 +03:00
|
|
|
return logtemplatespec(None, mapfile)
|
2014-03-09 02:01:58 +04:00
|
|
|
|
2014-03-09 03:38:50 +04:00
|
|
|
if not tmpl:
|
2017-04-22 12:48:38 +03:00
|
|
|
return logtemplatespec(None, None)
|
2014-03-09 03:38:50 +04:00
|
|
|
|
2015-06-10 22:29:13 +03:00
|
|
|
return formatter.lookuptemplate(ui, 'changeset', tmpl)
|
2014-03-09 02:01:58 +04:00
|
|
|
|
2017-04-22 12:42:03 +03:00
|
|
|
def makelogtemplater(ui, repo, tmpl, buffered=False):
|
|
|
|
"""Create a changeset_templater from a literal template 'tmpl'"""
|
2017-04-22 13:02:47 +03:00
|
|
|
spec = logtemplatespec(tmpl, None)
|
2017-06-24 21:47:26 +03:00
|
|
|
return changeset_templater(ui, repo, spec, buffered=buffered)
|
2017-04-22 12:42:03 +03:00
|
|
|
|
2014-03-09 02:01:58 +04:00
|
|
|
def show_changeset(ui, repo, opts, buffered=False):
|
|
|
|
"""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
|
2017-09-06 01:06:45 +03:00
|
|
|
match = None
|
2014-03-09 02:01:58 +04:00
|
|
|
if opts.get('patch') or opts.get('stat'):
|
2017-09-06 01:06:45 +03:00
|
|
|
match = scmutil.matchall(repo)
|
2014-03-09 02:01:58 +04:00
|
|
|
|
2014-09-15 22:11:13 +04:00
|
|
|
if opts.get('template') == 'json':
|
2017-09-06 01:06:45 +03:00
|
|
|
return jsonchangeset(ui, repo, match, opts, buffered)
|
2014-09-15 22:11:13 +04:00
|
|
|
|
2017-04-22 09:30:27 +03:00
|
|
|
spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
|
2014-03-09 02:01:58 +04:00
|
|
|
|
2017-04-22 14:14:55 +03:00
|
|
|
if not spec.ref and not spec.tmpl and not spec.mapfile:
|
2017-09-06 01:06:45 +03:00
|
|
|
return changeset_printer(ui, repo, match, opts, buffered)
|
2009-04-04 19:55:52 +04:00
|
|
|
|
2017-09-06 01:06:45 +03:00
|
|
|
return changeset_templater(ui, repo, spec, match, opts, buffered)
|
2006-11-13 22:26:57 +03:00
|
|
|
|
2016-08-15 10:07:55 +03:00
|
|
|
def showmarker(fm, marker, index=None):
|
2014-02-12 04:30:23 +04:00
|
|
|
"""utility function to display obsolescence marker in a readable way
|
|
|
|
|
|
|
|
To be used by debug function."""
|
2016-03-23 20:50:24 +03:00
|
|
|
if index is not None:
|
2016-08-15 10:07:55 +03:00
|
|
|
fm.write('index', '%i ', index)
|
2017-08-16 11:26:26 +03:00
|
|
|
fm.write('prednode', '%s ', hex(marker.prednode()))
|
2016-08-15 10:07:55 +03:00
|
|
|
succs = marker.succnodes()
|
|
|
|
fm.condwrite(succs, 'succnodes', '%s ',
|
|
|
|
fm.formatlist(map(hex, succs), name='node'))
|
|
|
|
fm.write('flag', '%X ', marker.flags())
|
2014-08-19 04:17:23 +04:00
|
|
|
parents = marker.parentnodes()
|
|
|
|
if parents is not None:
|
2016-08-15 10:07:55 +03:00
|
|
|
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')
|
2014-02-12 04:30:23 +04:00
|
|
|
|
2006-12-07 02:58:09 +03:00
|
|
|
def finddate(ui, repo, date):
|
|
|
|
"""Find the tipmost changeset that matches the given date spec"""
|
2009-10-30 11:53:39 +03:00
|
|
|
|
2008-01-11 04:36:20 +03:00
|
|
|
df = util.matchdate(date)
|
2011-05-13 23:58:24 +04:00
|
|
|
m = scmutil.matchall(repo)
|
2006-12-07 02:58:09 +03:00
|
|
|
results = {}
|
2009-10-30 01:07:51 +03:00
|
|
|
|
|
|
|
def prep(ctx, fns):
|
|
|
|
d = ctx.date()
|
|
|
|
if df(d[0]):
|
2009-10-30 11:54:39 +03:00
|
|
|
results[ctx.rev()] = d
|
2009-10-30 01:07:51 +03:00
|
|
|
|
2009-10-30 11:53:39 +03:00
|
|
|
for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
|
2009-10-29 06:47:46 +03:00
|
|
|
rev = ctx.rev()
|
2009-10-30 01:07:51 +03:00
|
|
|
if rev in results:
|
2012-06-12 16:18:18 +04:00
|
|
|
ui.status(_("found revision %s from %s\n") %
|
2009-10-30 01:07:51 +03:00
|
|
|
(rev, util.datestr(results[rev])))
|
2017-05-04 23:11:54 +03:00
|
|
|
return '%d' % rev
|
2006-12-07 02:58:09 +03:00
|
|
|
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("revision matching date not found"))
|
2006-12-07 02:58:09 +03:00
|
|
|
|
2014-02-14 02:27:12 +04:00
|
|
|
def increasingwindows(windowsize=8, sizelimit=512):
|
|
|
|
while True:
|
|
|
|
yield windowsize
|
|
|
|
if windowsize < sizelimit:
|
|
|
|
windowsize *= 2
|
2012-05-09 00:43:44 +04:00
|
|
|
|
2013-05-31 06:25:55 +04:00
|
|
|
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:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('cannot follow file not in parent '
|
2013-05-31 06:25:55 +04:00
|
|
|
'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:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(
|
2013-05-31 06:25:55 +04:00
|
|
|
_('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
|
2017-02-11 03:56:29 +03:00
|
|
|
ancestors = {filelog.linkrev(last)}
|
2013-05-31 06:25:55 +04:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2015-03-19 19:12:21 +03:00
|
|
|
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
|
|
|
|
|
2009-10-30 03:03:16 +03:00
|
|
|
def walkchangerevs(repo, match, opts, prepare):
|
2009-02-28 14:38:45 +03:00
|
|
|
'''Iterate over files and the revs in which they changed.
|
2006-11-16 00:51:58 +03:00
|
|
|
|
|
|
|
Callers most commonly need to iterate backwards over the history
|
2009-02-28 14:38:45 +03:00
|
|
|
in which they are interested. Doing so has awful (quadratic-looking)
|
2006-11-16 00:51:58 +03:00
|
|
|
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.
|
|
|
|
|
2009-10-30 01:07:51 +03:00
|
|
|
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.'''
|
2006-11-16 00:51:58 +03:00
|
|
|
|
|
|
|
follow = opts.get('follow') or opts.get('follow_first')
|
2015-02-05 18:22:20 +03:00
|
|
|
revs = _logrevs(repo, opts)
|
2010-06-04 03:00:15 +04:00
|
|
|
if not revs:
|
|
|
|
return []
|
2009-04-22 02:57:28 +04:00
|
|
|
wanted = set()
|
2015-05-19 21:34:50 +03:00
|
|
|
slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
|
|
|
|
opts.get('removed'))
|
2006-11-16 00:51:58 +03:00
|
|
|
fncache = {}
|
2012-02-11 02:52:32 +04:00
|
|
|
change = repo.changectx
|
2006-11-16 00:51:58 +03:00
|
|
|
|
2010-07-20 09:32:33 +04:00
|
|
|
# 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.
|
|
|
|
|
2014-10-29 08:32:18 +03:00
|
|
|
if match.always():
|
2006-11-16 00:51:58 +03:00
|
|
|
# No files, no patterns. Display all revs.
|
2014-02-14 02:27:12 +04:00
|
|
|
wanted = revs
|
2015-05-22 00:20:24 +03:00
|
|
|
elif not slowpath:
|
2010-07-20 09:32:33 +04:00
|
|
|
# We only have to read through the filelog to find wanted revisions
|
|
|
|
|
2013-05-31 06:25:55 +04:00
|
|
|
try:
|
|
|
|
wanted = walkfilerevs(repo, match, follow, revs, fncache)
|
|
|
|
except FileWalkError:
|
|
|
|
slowpath = True
|
2010-08-15 18:17:53 +04:00
|
|
|
|
2013-05-31 06:25:55 +04:00
|
|
|
# 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
|
2012-09-14 10:50:45 +04:00
|
|
|
for path in match.files():
|
|
|
|
if path == '.' or path in repo.store:
|
|
|
|
break
|
2013-01-15 04:05:12 +04:00
|
|
|
else:
|
|
|
|
return []
|
2012-09-14 10:50:45 +04:00
|
|
|
|
2006-11-16 00:51:58 +03:00
|
|
|
if slowpath:
|
2010-07-20 09:32:33 +04:00
|
|
|
# We have to read the changelog to match filenames against
|
|
|
|
# changed files
|
|
|
|
|
2006-11-16 00:51:58 +03:00
|
|
|
if follow:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('can only follow copies/renames for explicit '
|
2009-06-09 17:25:17 +04:00
|
|
|
'filenames'))
|
2006-11-16 00:51:58 +03:00
|
|
|
|
|
|
|
# The slow path checks files modified in every changeset.
|
2013-09-11 06:49:34 +04:00
|
|
|
# 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()
|
2006-11-16 00:51:58 +03:00
|
|
|
|
|
|
|
# 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', ()):
|
2012-04-08 21:38:24 +04:00
|
|
|
rev = repo[rev].rev()
|
2015-03-19 19:12:21 +03:00
|
|
|
ff = _followfilter(repo)
|
2006-11-16 00:51:58 +03:00
|
|
|
stop = min(revs[0], revs[-1])
|
2010-01-25 09:05:27 +03:00
|
|
|
for x in xrange(rev, stop - 1, -1):
|
2009-04-22 02:57:28 +04:00
|
|
|
if ff.match(x):
|
2014-02-14 02:27:12 +04:00
|
|
|
wanted = wanted - [x]
|
2013-02-20 23:31:38 +04:00
|
|
|
|
2010-07-20 09:32:33 +04:00
|
|
|
# Now that wanted is correctly initialized, we can iterate over the
|
|
|
|
# revision range, yielding only revisions in wanted.
|
2006-11-16 00:51:58 +03:00
|
|
|
def iterate():
|
2015-05-19 21:34:50 +03:00
|
|
|
if follow and match.always():
|
2015-03-19 19:12:21 +03:00
|
|
|
ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
|
2006-11-16 00:51:58 +03:00
|
|
|
def want(rev):
|
2009-04-22 19:15:59 +04:00
|
|
|
return ff.match(rev) and rev in wanted
|
2006-11-16 00:51:58 +03:00
|
|
|
else:
|
|
|
|
def want(rev):
|
|
|
|
return rev in wanted
|
|
|
|
|
2014-02-14 02:27:12 +04:00
|
|
|
it = iter(revs)
|
|
|
|
stopiteration = False
|
|
|
|
for windowsize in increasingwindows():
|
|
|
|
nrevs = []
|
|
|
|
for i in xrange(windowsize):
|
2015-05-18 04:11:02 +03:00
|
|
|
rev = next(it, None)
|
|
|
|
if rev is None:
|
2014-02-14 02:27:12 +04:00
|
|
|
stopiteration = True
|
|
|
|
break
|
2015-05-18 04:11:02 +03:00
|
|
|
elif want(rev):
|
|
|
|
nrevs.append(rev)
|
2009-04-27 01:50:44 +04:00
|
|
|
for rev in sorted(nrevs):
|
2006-11-16 00:51:58 +03:00
|
|
|
fns = fncache.get(rev)
|
2009-10-26 02:43:59 +03:00
|
|
|
ctx = change(rev)
|
2006-11-16 00:51:58 +03:00
|
|
|
if not fns:
|
|
|
|
def fns_generator():
|
2009-10-26 02:43:59 +03:00
|
|
|
for f in ctx.files():
|
2009-10-26 02:43:56 +03:00
|
|
|
if match(f):
|
2006-11-16 00:51:58 +03:00
|
|
|
yield f
|
|
|
|
fns = fns_generator()
|
2009-10-30 01:07:51 +03:00
|
|
|
prepare(ctx, fns)
|
2006-11-16 00:51:58 +03:00
|
|
|
for rev in nrevs:
|
2009-10-30 01:07:51 +03:00
|
|
|
yield change(rev)
|
2014-02-14 02:27:12 +04:00
|
|
|
|
|
|
|
if stopiteration:
|
|
|
|
break
|
|
|
|
|
2009-10-26 02:43:56 +03:00
|
|
|
return iterate()
|
2007-08-01 03:28:05 +04:00
|
|
|
|
2014-08-14 02:15:13 +04:00
|
|
|
def _makefollowlogfilematcher(repo, files, followfirst):
|
2012-07-14 20:55:21 +04:00
|
|
|
# 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
|
2016-09-24 13:58:23 +03:00
|
|
|
# and relating revs to file names (which is not "correct" but
|
2012-07-14 20:55:21 +04:00
|
|
|
# good enough).
|
|
|
|
fcache = {}
|
|
|
|
fcacheready = [False]
|
|
|
|
pctx = repo['.']
|
|
|
|
|
|
|
|
def populate():
|
2014-07-12 13:23:17 +04:00
|
|
|
for fn in files:
|
2016-09-24 13:52:02 +03:00
|
|
|
fctx = pctx[fn]
|
2016-09-24 13:58:23 +03:00
|
|
|
fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
|
2016-09-24 13:52:02 +03:00
|
|
|
for c in fctx.ancestors(followfirst=followfirst):
|
2016-09-24 13:58:23 +03:00
|
|
|
fcache.setdefault(c.rev(), set()).add(c.path())
|
2012-07-14 20:55:21 +04:00
|
|
|
|
|
|
|
def filematcher(rev):
|
|
|
|
if not fcacheready[0]:
|
|
|
|
# Lazy initialization
|
|
|
|
fcacheready[0] = True
|
|
|
|
populate()
|
2014-07-13 05:31:18 +04:00
|
|
|
return scmutil.matchfiles(repo, fcache.get(rev, []))
|
2012-07-14 20:55:21 +04:00
|
|
|
|
|
|
|
return filematcher
|
|
|
|
|
2014-08-14 02:17:03 +04:00
|
|
|
def _makenofollowlogfilematcher(repo, pats, opts):
|
|
|
|
'''hook for extensions to override the filematcher for non-follow cases'''
|
|
|
|
return None
|
|
|
|
|
2014-02-25 22:40:04 +04:00
|
|
|
def _makelogrevset(repo, pats, opts, revs):
|
2012-07-14 20:55:21 +04:00
|
|
|
"""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')
|
2015-03-14 00:00:06 +03:00
|
|
|
if opts.get('follow_first'):
|
|
|
|
followfirst = 1
|
|
|
|
else:
|
|
|
|
followfirst = 0
|
2015-08-28 17:53:55 +03:00
|
|
|
# --follow with FILE behavior depends on revs...
|
2014-03-14 22:35:17 +04:00
|
|
|
it = iter(revs)
|
2016-05-17 00:30:53 +03:00
|
|
|
startrev = next(it)
|
2015-05-18 20:17:08 +03:00
|
|
|
followdescendants = startrev < next(it, startrev)
|
2012-07-14 20:55:21 +04:00
|
|
|
|
|
|
|
# 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
|
2012-08-16 00:38:42 +04:00
|
|
|
# _matchfiles() revset but walkchangerevs() builds its matcher with
|
2012-07-14 20:55:21 +04:00
|
|
|
# scmutil.match(). The difference is input pats are globbed on
|
|
|
|
# platforms without shell expansion (windows).
|
2015-03-19 07:44:25 +03:00
|
|
|
wctx = repo[None]
|
|
|
|
match, pats = scmutil.matchandpats(wctx, pats, opts)
|
2015-05-19 21:35:43 +03:00
|
|
|
slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
|
|
|
|
opts.get('removed'))
|
2012-07-14 20:55:21 +04:00
|
|
|
if not slowpath:
|
|
|
|
for f in match.files():
|
2015-03-19 07:44:25 +03:00
|
|
|
if follow and f not in wctx:
|
2014-07-23 09:40:16 +04:00
|
|
|
# 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:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('cannot follow file not in parent '
|
2014-07-23 09:40:16 +04:00
|
|
|
'revision: "%s"') % f)
|
2012-07-14 20:55:21 +04:00
|
|
|
filelog = repo.file(f)
|
2013-05-31 05:47:16 +04:00
|
|
|
if not filelog:
|
2012-07-14 20:55:21 +04:00
|
|
|
# A zero count may be a directory or deleted file, so
|
|
|
|
# try to find matching entries on the slow path.
|
|
|
|
if follow:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(
|
2012-07-14 20:55:21 +04:00
|
|
|
_('cannot follow nonexistent file: "%s"') % f)
|
|
|
|
slowpath = True
|
2012-09-14 10:50:45 +04:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2014-12-06 01:27:32 +03:00
|
|
|
fpats = ('_patsfollow', '_patsfollowfirst')
|
|
|
|
fnopats = (('_ancestors', '_fancestors'),
|
|
|
|
('_descendants', '_fdescendants'))
|
2012-07-14 20:55:21 +04:00
|
|
|
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
|
2014-12-06 01:27:32 +03:00
|
|
|
if follow:
|
|
|
|
opts[fnopats[0][followfirst]] = '.'
|
2012-07-14 20:55:21 +04:00
|
|
|
else:
|
|
|
|
if follow:
|
|
|
|
if pats:
|
2012-08-16 00:38:42 +04:00
|
|
|
# follow() revset interprets its file argument as a
|
2012-07-14 20:55:21 +04:00
|
|
|
# manifest entry, so use match.files(), not pats.
|
|
|
|
opts[fpats[followfirst]] = list(match.files())
|
|
|
|
else:
|
2015-01-10 07:14:00 +03:00
|
|
|
op = fnopats[followdescendants][followfirst]
|
|
|
|
opts[op] = 'rev(%d)' % startrev
|
2012-07-14 20:55:21 +04:00
|
|
|
else:
|
|
|
|
opts['_patslog'] = list(pats)
|
|
|
|
|
|
|
|
filematcher = None
|
|
|
|
if opts.get('patch') or opts.get('stat'):
|
2014-07-23 09:40:16 +04:00
|
|
|
# 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:
|
2014-04-18 00:47:38 +04:00
|
|
|
# _makefollowlogfilematcher expects its files argument to be
|
|
|
|
# relative to the repo root, so use match.files(), not pats.
|
2014-08-14 02:15:13 +04:00
|
|
|
filematcher = _makefollowlogfilematcher(repo, match.files(),
|
|
|
|
followfirst)
|
2012-07-14 20:55:21 +04:00
|
|
|
else:
|
2014-08-14 02:17:03 +04:00
|
|
|
filematcher = _makenofollowlogfilematcher(repo, pats, opts)
|
|
|
|
if filematcher is None:
|
|
|
|
filematcher = lambda rev: match
|
2012-07-14 20:55:21 +04:00
|
|
|
|
|
|
|
expr = []
|
2014-12-09 02:41:54 +03:00
|
|
|
for op, val in sorted(opts.iteritems()):
|
2012-07-14 20:55:21 +04:00
|
|
|
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
|
|
|
|
|
2015-02-05 18:15:35 +03:00
|
|
|
def _logrevs(repo, opts):
|
2015-08-28 17:53:55 +03:00
|
|
|
# Default --rev value depends on --follow but --follow behavior
|
2015-02-05 18:15:35 +03:00
|
|
|
# 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'])
|
2015-02-06 15:53:39 +03:00
|
|
|
elif follow and repo.dirstate.p1() == nullid:
|
2017-02-19 12:16:09 +03:00
|
|
|
revs = smartset.baseset()
|
2015-02-05 18:15:35 +03:00
|
|
|
elif follow:
|
|
|
|
revs = repo.revs('reverse(:.)')
|
|
|
|
else:
|
2017-02-19 12:16:09 +03:00
|
|
|
revs = smartset.spanset(repo)
|
2015-02-05 18:15:35 +03:00
|
|
|
revs.reverse()
|
|
|
|
return revs
|
|
|
|
|
2012-07-14 20:55:21 +04:00
|
|
|
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.
|
|
|
|
"""
|
2012-12-29 04:25:12 +04:00
|
|
|
limit = loglimit(opts)
|
2015-02-05 18:15:35 +03:00
|
|
|
revs = _logrevs(repo, opts)
|
2012-07-14 20:55:21 +04:00
|
|
|
if not revs:
|
2017-02-19 12:16:09 +03:00
|
|
|
return smartset.baseset(), None, None
|
2014-02-25 22:40:04 +04:00
|
|
|
expr, filematcher = _makelogrevset(repo, pats, opts, revs)
|
2015-02-05 17:49:18 +03:00
|
|
|
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
|
2016-06-14 13:05:36 +03:00
|
|
|
if not (revs.isdescending() or revs.istopo()):
|
2016-06-08 18:18:43 +03:00
|
|
|
revs.sort(reverse=True)
|
2012-07-14 20:55:21 +04:00
|
|
|
if expr:
|
2017-08-30 16:41:36 +03:00
|
|
|
matcher = revset.match(repo.ui, expr)
|
2017-08-30 16:51:28 +03:00
|
|
|
revs = matcher(repo, revs)
|
2013-01-07 22:24:36 +04:00
|
|
|
if limit is not None:
|
2014-10-07 11:20:00 +04:00
|
|
|
limitedrevs = []
|
2014-03-14 19:43:52 +04:00
|
|
|
for idx, rev in enumerate(revs):
|
|
|
|
if idx >= limit:
|
|
|
|
break
|
|
|
|
limitedrevs.append(rev)
|
2017-02-19 12:16:09 +03:00
|
|
|
revs = smartset.baseset(limitedrevs)
|
2012-12-29 04:25:12 +04:00
|
|
|
|
2012-07-14 20:55:21 +04:00
|
|
|
return revs, expr, filematcher
|
|
|
|
|
2014-03-01 03:10:56 +04:00
|
|
|
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)
|
2015-02-05 18:15:35 +03:00
|
|
|
revs = _logrevs(repo, opts)
|
2014-03-01 03:10:56 +04:00
|
|
|
if not revs:
|
2017-02-19 12:16:09 +03:00
|
|
|
return smartset.baseset([]), None, None
|
2014-03-01 03:10:56 +04:00
|
|
|
expr, filematcher = _makelogrevset(repo, pats, opts, revs)
|
|
|
|
if expr:
|
2017-08-30 16:41:36 +03:00
|
|
|
matcher = revset.match(repo.ui, expr)
|
2017-08-30 16:51:28 +03:00
|
|
|
revs = matcher(repo, revs)
|
2014-03-01 03:10:56 +04:00
|
|
|
if limit is not None:
|
2014-10-07 11:04:16 +04:00
|
|
|
limitedrevs = []
|
2015-05-18 20:18:00 +03:00
|
|
|
for idx, r in enumerate(revs):
|
|
|
|
if limit <= idx:
|
2014-03-01 03:10:56 +04:00
|
|
|
break
|
2015-05-18 20:18:00 +03:00
|
|
|
limitedrevs.append(r)
|
2017-02-19 12:16:09 +03:00
|
|
|
revs = smartset.baseset(limitedrevs)
|
2014-03-01 03:10:56 +04:00
|
|
|
|
|
|
|
return revs, expr, filematcher
|
|
|
|
|
log: add -L/--line-range option to follow file history by line range
We add an experimental -L/--line-range option to 'hg log' taking file patterns
along with a line range using the (new) FILE,FROMLINE-TOLINE syntax where FILE
may be a pattern (matching exactly one file). The resulting history is similar
to what the "followlines" revset except that, if --patch is specified,
only diff hunks within specified line range are shown.
Basically, this brings the CLI on par with what currently only exists in hgweb
through line selection in "file" and "annotate" views resulting in a file log
with filtered patch to only display followed line range.
The option may be specified multiple times and can be combined with --rev and
regular file patterns to further restrict revisions. Usage of this option
requires --follow; revisions are shown in descending order and renames are
followed. Only the --graph option is currently not supported.
The UI is the result of a consensus from review feedback at:
https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-October/106749.html
The implementation spreads between commands.log() and cmdutil module.
In commands.log(), the main loop may now use a "hunksfilter" factory (similar
to "filematcher") that, for a given "rev", produces a filtering function
for diff hunks for a given file context object.
The logic to build revisions from -L/--line-range options lives in
cmdutil.getloglinerangerevs() which produces "revs", "filematcher" and
"hunksfilter" information. Revisions obtained by following files' line range
are filtered if they do not match the revset specified by --rev option. If
regular FILE arguments are passed along with -L options, both filematchers are
combined into a new matcher.
.. feature::
Add an experimental -L/--line-range FILE,FROMLINE-TOLINE option to 'hg log'
command to follow the history of files by line range. In combination with
-p/--patch option, only diff hunks within specified line range will be
displayed. Feedback, especially on UX aspects, is welcome.
2017-10-17 22:15:31 +03:00
|
|
|
def _parselinerangelogopt(repo, opts):
|
|
|
|
"""Parse --line-range log option and return a list of tuples (filename,
|
|
|
|
(fromline, toline)).
|
|
|
|
"""
|
|
|
|
linerangebyfname = []
|
|
|
|
for pat in opts.get('line_range', []):
|
|
|
|
try:
|
|
|
|
pat, linerange = pat.rsplit(',', 1)
|
|
|
|
except ValueError:
|
|
|
|
raise error.Abort(_('malformatted line-range pattern %s') % pat)
|
|
|
|
try:
|
2017-10-19 13:37:26 +03:00
|
|
|
fromline, toline = map(int, linerange.split(':'))
|
log: add -L/--line-range option to follow file history by line range
We add an experimental -L/--line-range option to 'hg log' taking file patterns
along with a line range using the (new) FILE,FROMLINE-TOLINE syntax where FILE
may be a pattern (matching exactly one file). The resulting history is similar
to what the "followlines" revset except that, if --patch is specified,
only diff hunks within specified line range are shown.
Basically, this brings the CLI on par with what currently only exists in hgweb
through line selection in "file" and "annotate" views resulting in a file log
with filtered patch to only display followed line range.
The option may be specified multiple times and can be combined with --rev and
regular file patterns to further restrict revisions. Usage of this option
requires --follow; revisions are shown in descending order and renames are
followed. Only the --graph option is currently not supported.
The UI is the result of a consensus from review feedback at:
https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-October/106749.html
The implementation spreads between commands.log() and cmdutil module.
In commands.log(), the main loop may now use a "hunksfilter" factory (similar
to "filematcher") that, for a given "rev", produces a filtering function
for diff hunks for a given file context object.
The logic to build revisions from -L/--line-range options lives in
cmdutil.getloglinerangerevs() which produces "revs", "filematcher" and
"hunksfilter" information. Revisions obtained by following files' line range
are filtered if they do not match the revset specified by --rev option. If
regular FILE arguments are passed along with -L options, both filematchers are
combined into a new matcher.
.. feature::
Add an experimental -L/--line-range FILE,FROMLINE-TOLINE option to 'hg log'
command to follow the history of files by line range. In combination with
-p/--patch option, only diff hunks within specified line range will be
displayed. Feedback, especially on UX aspects, is welcome.
2017-10-17 22:15:31 +03:00
|
|
|
except ValueError:
|
|
|
|
raise error.Abort(_("invalid line range for %s") % pat)
|
|
|
|
msg = _("line range pattern '%s' must match exactly one file") % pat
|
|
|
|
fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
|
|
|
|
linerangebyfname.append(
|
|
|
|
(fname, util.processlinerange(fromline, toline)))
|
|
|
|
return linerangebyfname
|
|
|
|
|
|
|
|
def getloglinerangerevs(repo, userrevs, opts):
|
|
|
|
"""Return (revs, filematcher, hunksfilter).
|
|
|
|
|
|
|
|
"revs" are revisions obtained by processing "line-range" log options and
|
|
|
|
walking block ancestors of each specified file/line-range.
|
|
|
|
|
|
|
|
"filematcher(rev) -> match" is a factory function returning a match object
|
|
|
|
for a given revision for file patterns specified in --line-range option.
|
|
|
|
If neither --stat nor --patch options are passed, "filematcher" is None.
|
|
|
|
|
|
|
|
"hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
|
|
|
|
returning a hunks filtering function.
|
|
|
|
If neither --stat nor --patch options are passed, "filterhunks" is None.
|
|
|
|
"""
|
|
|
|
wctx = repo[None]
|
|
|
|
|
|
|
|
# Two-levels map of "rev -> file ctx -> [line range]".
|
|
|
|
linerangesbyrev = {}
|
|
|
|
for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
|
|
|
|
fctx = wctx.filectx(fname)
|
|
|
|
for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
|
|
|
|
rev = fctx.introrev()
|
|
|
|
if rev not in userrevs:
|
|
|
|
continue
|
|
|
|
linerangesbyrev.setdefault(
|
|
|
|
rev, {}).setdefault(
|
|
|
|
fctx.path(), []).append(linerange)
|
|
|
|
|
|
|
|
filematcher = None
|
|
|
|
hunksfilter = None
|
|
|
|
if opts.get('patch') or opts.get('stat'):
|
|
|
|
|
|
|
|
def nofilterhunksfn(fctx, hunks):
|
|
|
|
return hunks
|
|
|
|
|
|
|
|
def hunksfilter(rev):
|
|
|
|
fctxlineranges = linerangesbyrev.get(rev)
|
|
|
|
if fctxlineranges is None:
|
|
|
|
return nofilterhunksfn
|
|
|
|
|
|
|
|
def filterfn(fctx, hunks):
|
|
|
|
lineranges = fctxlineranges.get(fctx.path())
|
|
|
|
if lineranges is not None:
|
|
|
|
for hr, lines in hunks:
|
2017-10-18 20:00:33 +03:00
|
|
|
if hr is None: # binary
|
|
|
|
yield hr, lines
|
|
|
|
continue
|
log: add -L/--line-range option to follow file history by line range
We add an experimental -L/--line-range option to 'hg log' taking file patterns
along with a line range using the (new) FILE,FROMLINE-TOLINE syntax where FILE
may be a pattern (matching exactly one file). The resulting history is similar
to what the "followlines" revset except that, if --patch is specified,
only diff hunks within specified line range are shown.
Basically, this brings the CLI on par with what currently only exists in hgweb
through line selection in "file" and "annotate" views resulting in a file log
with filtered patch to only display followed line range.
The option may be specified multiple times and can be combined with --rev and
regular file patterns to further restrict revisions. Usage of this option
requires --follow; revisions are shown in descending order and renames are
followed. Only the --graph option is currently not supported.
The UI is the result of a consensus from review feedback at:
https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-October/106749.html
The implementation spreads between commands.log() and cmdutil module.
In commands.log(), the main loop may now use a "hunksfilter" factory (similar
to "filematcher") that, for a given "rev", produces a filtering function
for diff hunks for a given file context object.
The logic to build revisions from -L/--line-range options lives in
cmdutil.getloglinerangerevs() which produces "revs", "filematcher" and
"hunksfilter" information. Revisions obtained by following files' line range
are filtered if they do not match the revset specified by --rev option. If
regular FILE arguments are passed along with -L options, both filematchers are
combined into a new matcher.
.. feature::
Add an experimental -L/--line-range FILE,FROMLINE-TOLINE option to 'hg log'
command to follow the history of files by line range. In combination with
-p/--patch option, only diff hunks within specified line range will be
displayed. Feedback, especially on UX aspects, is welcome.
2017-10-17 22:15:31 +03:00
|
|
|
if any(mdiff.hunkinrange(hr[2:], lr)
|
|
|
|
for lr in lineranges):
|
|
|
|
yield hr, lines
|
|
|
|
else:
|
|
|
|
for hunk in hunks:
|
|
|
|
yield hunk
|
|
|
|
|
|
|
|
return filterfn
|
|
|
|
|
|
|
|
def filematcher(rev):
|
|
|
|
files = list(linerangesbyrev.get(rev, []))
|
|
|
|
return scmutil.matchfiles(repo, files)
|
|
|
|
|
|
|
|
revs = sorted(linerangesbyrev, reverse=True)
|
|
|
|
|
|
|
|
return revs, filematcher, hunksfilter
|
|
|
|
|
2015-11-14 11:25:43 +03:00
|
|
|
def _graphnodeformatter(ui, displayer):
|
|
|
|
spec = ui.config('ui', 'graphnodetemplate')
|
|
|
|
if not spec:
|
|
|
|
return templatekw.showgraphnode # fast path for "{graphnode}"
|
|
|
|
|
2017-02-25 13:28:16 +03:00
|
|
|
spec = templater.unquotestring(spec)
|
2017-04-22 13:56:47 +03:00
|
|
|
templ = formatter.maketemplater(ui, spec)
|
2015-11-14 11:25:43 +03:00
|
|
|
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
|
2016-03-09 17:57:15 +03:00
|
|
|
props['ui'] = repo.ui
|
2015-11-14 11:25:43 +03:00
|
|
|
props['revcache'] = {}
|
2017-04-22 13:56:47 +03:00
|
|
|
return templ.render(props)
|
2015-11-14 11:25:43 +03:00
|
|
|
return formatnode
|
|
|
|
|
2015-11-14 10:45:15 +03:00
|
|
|
def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
|
2017-08-04 07:13:27 +03:00
|
|
|
filematcher=None, props=None):
|
|
|
|
props = props or {}
|
2015-11-14 11:25:43 +03:00
|
|
|
formatnode = _graphnodeformatter(ui, displayer)
|
2016-03-04 17:44:32 +03:00
|
|
|
state = graphmod.asciistate()
|
2016-03-20 02:46:15 +03:00
|
|
|
styles = state['styles']
|
2016-04-20 18:33:13 +03:00
|
|
|
|
|
|
|
# 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')
|
2016-04-08 17:42:43 +03:00
|
|
|
|
2012-07-14 20:55:21 +04:00
|
|
|
for rev, type, ctx, parents in dag:
|
2015-11-14 11:25:43 +03:00
|
|
|
char = formatnode(repo, ctx)
|
2012-07-14 20:55:21 +04:00
|
|
|
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())
|
2017-08-15 20:15:31 +03:00
|
|
|
edges = edgefn(type, char, state, rev, parents)
|
|
|
|
firstedge = next(edges)
|
|
|
|
width = firstedge[2]
|
|
|
|
displayer.show(ctx, copies=copies, matchfn=revmatchfn,
|
2017-08-04 07:13:27 +03:00
|
|
|
_graphwidth=width, **props)
|
2012-07-14 20:55:21 +04:00
|
|
|
lines = displayer.hunk.pop(rev).split('\n')
|
|
|
|
if not lines[-1]:
|
|
|
|
del lines[-1]
|
2015-04-12 15:52:02 +03:00
|
|
|
displayer.flush(ctx)
|
2017-08-15 20:15:31 +03:00
|
|
|
for type, char, width, coldata in itertools.chain([firstedge], edges):
|
2012-07-14 20:55:21 +04:00
|
|
|
graphmod.ascii(ui, state, type, char, lines, coldata)
|
2017-08-15 20:15:31 +03:00
|
|
|
lines = []
|
2012-07-14 20:55:21 +04:00
|
|
|
displayer.close()
|
|
|
|
|
2017-03-14 12:16:13 +03:00
|
|
|
def graphlog(ui, repo, pats, opts):
|
2012-07-14 21:09:22 +04:00
|
|
|
# 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'):
|
2014-03-15 00:27:12 +04:00
|
|
|
endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
|
2012-07-14 21:09:22 +04:00
|
|
|
getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
|
2017-02-23 15:20:26 +03:00
|
|
|
|
|
|
|
ui.pager('log')
|
2012-07-14 21:09:22 +04:00
|
|
|
displayer = show_changeset(ui, repo, opts, buffered=True)
|
2015-11-14 10:45:15 +03:00
|
|
|
displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
|
|
|
|
filematcher)
|
2012-07-14 21:09:22 +04:00
|
|
|
|
2012-07-11 20:22:07 +04:00
|
|
|
def checkunsupportedgraphflags(pats, opts):
|
|
|
|
for op in ["newest_first"]:
|
|
|
|
if op in opts and opts[op]:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("-G/--graph option is incompatible with --%s")
|
2012-07-11 20:22:07 +04:00
|
|
|
% 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)
|
|
|
|
|
2015-01-13 04:59:17 +03:00
|
|
|
def add(ui, repo, match, prefix, explicitonly, **opts):
|
2010-09-13 15:09:20 +04:00
|
|
|
join = lambda f: os.path.join(prefix, f)
|
2010-09-13 15:09:11 +04:00
|
|
|
bad = []
|
2015-06-05 04:49:50 +03:00
|
|
|
|
|
|
|
badfn = lambda x, y: bad.append(x) or match.bad(x, y)
|
2010-09-13 15:09:11 +04:00
|
|
|
names = []
|
2010-09-13 15:09:20 +04:00
|
|
|
wctx = repo[None]
|
2011-05-01 01:27:00 +04:00
|
|
|
cca = None
|
|
|
|
abort, warn = scmutil.checkportabilityalert(ui)
|
|
|
|
if abort or warn:
|
2012-07-07 00:56:40 +04:00
|
|
|
cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
|
2015-06-05 04:49:50 +03:00
|
|
|
|
2015-09-09 19:07:27 +03:00
|
|
|
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.
|
2017-09-30 00:19:36 +03:00
|
|
|
for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
|
|
|
|
unknown=True, ignored=False, full=False)):
|
2010-09-13 15:09:11 +04:00
|
|
|
exact = match.exact(f)
|
2014-12-04 01:33:29 +03:00
|
|
|
if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
|
2011-05-01 01:27:00 +04:00
|
|
|
if cca:
|
|
|
|
cca(f)
|
2010-09-13 15:09:11 +04:00
|
|
|
names.append(f)
|
|
|
|
if ui.verbose or not exact:
|
2014-11-27 18:16:56 +03:00
|
|
|
ui.status(_('adding %s\n') % match.rel(f))
|
2010-09-13 15:09:20 +04:00
|
|
|
|
2012-12-12 05:38:14 +04:00
|
|
|
for subpath in sorted(wctx.substate):
|
2011-11-02 09:17:11 +04:00
|
|
|
sub = wctx.sub(subpath)
|
|
|
|
try:
|
2016-02-06 08:09:32 +03:00
|
|
|
submatch = matchmod.subdirmatcher(subpath, match)
|
2017-04-24 23:22:30 +03:00
|
|
|
if opts.get(r'subrepos'):
|
2015-01-13 04:59:17 +03:00
|
|
|
bad.extend(sub.add(ui, submatch, prefix, False, **opts))
|
2011-11-02 09:17:11 +04:00
|
|
|
else:
|
2015-01-13 04:59:17 +03:00
|
|
|
bad.extend(sub.add(ui, submatch, prefix, True, **opts))
|
2011-11-02 09:17:11 +04:00
|
|
|
except error.LookupError:
|
|
|
|
ui.status(_("skipping missing subrepository: %s\n")
|
|
|
|
% join(subpath))
|
2010-09-13 15:09:20 +04:00
|
|
|
|
2017-04-24 23:22:30 +03:00
|
|
|
if not opts.get(r'dry_run'):
|
2010-09-13 15:09:20 +04:00
|
|
|
rejected = wctx.add(names, prefix)
|
2010-09-13 15:09:11 +04:00
|
|
|
bad.extend(f for f in rejected if f in match.files())
|
|
|
|
return bad
|
|
|
|
|
serve: add support for Mercurial subrepositories
I've been using `hg serve --web-conf ...` with a simple '/=projects/**' [paths]
configuration for awhile without issue. Let's ditch the need for the manual
configuration in this case, and limit the repos served to the actual subrepos.
This doesn't attempt to handle the case where a new subrepo appears while the
server is running. That could probably be handled with a hook if somebody wants
it. But it's such a rare case, it probably doesn't matter for the temporary
serves.
The main repo is served at '/', just like a repository without subrepos. I'm
not sure why the duplicate 'adding ...' lines appear on Linux. They don't
appear on Windows (see 3f4ff1bdf101), so they are optional.
Subrepositories that are configured with '../path' or absolute paths are not
cloneable from the server. (They aren't cloneable locally either, unless they
also exist at their configured source, perhaps via the share extension.) They
are still served, so that they can be browsed, or cloned individually. If we
care about that cloning someday, we can probably just add the extra entries to
the webconf dictionary. Even if the entries use '../' to escape the root, only
the related subrepositories would end up in the dictionary.
2017-04-16 01:05:40 +03:00
|
|
|
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)
|
|
|
|
|
2012-01-18 04:10:59 +04:00
|
|
|
def forget(ui, repo, match, prefix, explicitonly):
|
|
|
|
join = lambda f: os.path.join(prefix, f)
|
|
|
|
bad = []
|
2015-06-05 04:53:16 +03:00
|
|
|
badfn = lambda x, y: bad.append(x) or match.bad(x, y)
|
2012-01-18 04:10:59 +04:00
|
|
|
wctx = repo[None]
|
|
|
|
forgot = []
|
2015-06-05 04:53:16 +03:00
|
|
|
|
|
|
|
s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
|
2017-05-05 07:11:40 +03:00
|
|
|
forget = sorted(s.modified + s.added + s.deleted + s.clean)
|
2012-01-18 04:10:59 +04:00
|
|
|
if explicitonly:
|
|
|
|
forget = [f for f in forget if match.exact(f)]
|
|
|
|
|
2012-12-12 05:38:14 +04:00
|
|
|
for subpath in sorted(wctx.substate):
|
2012-01-18 04:10:59 +04:00
|
|
|
sub = wctx.sub(subpath)
|
|
|
|
try:
|
2016-02-06 08:09:32 +03:00
|
|
|
submatch = matchmod.subdirmatcher(subpath, match)
|
2014-12-14 03:23:30 +03:00
|
|
|
subbad, subforgot = sub.forget(submatch, prefix)
|
2012-01-18 04:10:59 +04:00
|
|
|
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))
|
|
|
|
|
2012-02-06 09:37:49 +04:00
|
|
|
if not explicitonly:
|
|
|
|
for f in match.files():
|
2014-12-26 05:43:45 +03:00
|
|
|
if f not in repo.dirstate and not repo.wvfs.isdir(f):
|
2012-01-18 04:10:59 +04:00
|
|
|
if f not in forgot:
|
2014-12-26 05:43:45 +03:00
|
|
|
if repo.wvfs.exists(f):
|
2015-04-01 00:42:46 +03:00
|
|
|
# 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
|
2012-01-18 04:10:59 +04:00
|
|
|
ui.warn(_('not removing %s: '
|
|
|
|
'file is already untracked\n')
|
2014-11-27 18:16:56 +03:00
|
|
|
% match.rel(f))
|
2012-01-18 04:10:59 +04:00
|
|
|
bad.append(f)
|
|
|
|
|
|
|
|
for f in forget:
|
|
|
|
if ui.verbose or not match.exact(f):
|
2014-11-27 18:16:56 +03:00
|
|
|
ui.status(_('removing %s\n') % match.rel(f))
|
2012-01-18 04:10:59 +04:00
|
|
|
|
|
|
|
rejected = wctx.forget(forget, prefix)
|
|
|
|
bad.extend(f for f in rejected if f in match.files())
|
2015-01-12 07:25:23 +03:00
|
|
|
forgot.extend(f for f in forget if f not in rejected)
|
2012-01-18 04:10:59 +04:00
|
|
|
return bad, forgot
|
|
|
|
|
2015-03-19 06:03:41 +03:00
|
|
|
def files(ui, ctx, m, fm, fmt, subrepos):
|
2015-03-08 23:50:57 +03:00
|
|
|
rev = ctx.rev()
|
|
|
|
ret = 1
|
2015-03-13 05:55:35 +03:00
|
|
|
ds = ctx.repo().dirstate
|
2015-03-08 23:50:57 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2015-05-18 05:42:47 +03:00
|
|
|
for subpath in sorted(ctx.substate):
|
2016-08-16 11:15:12 +03:00
|
|
|
submatch = matchmod.subdirmatcher(subpath, m)
|
|
|
|
if (subrepos or m.exact(subpath) or any(submatch.files())):
|
2015-03-19 06:03:41 +03:00
|
|
|
sub = ctx.sub(subpath)
|
|
|
|
try:
|
2016-03-06 22:30:34 +03:00
|
|
|
recurse = m.exact(subpath) or subrepos
|
|
|
|
if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
|
2015-03-19 06:03:41 +03:00
|
|
|
ret = 0
|
|
|
|
except error.LookupError:
|
|
|
|
ui.status(_("skipping missing subrepository: %s\n")
|
|
|
|
% m.abs(subpath))
|
|
|
|
|
2015-03-08 23:50:57 +03:00
|
|
|
return ret
|
|
|
|
|
2016-03-17 21:19:36 +03:00
|
|
|
def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
|
2014-11-16 05:36:19 +03:00
|
|
|
join = lambda f: os.path.join(prefix, f)
|
2014-11-09 20:31:34 +03:00
|
|
|
ret = 0
|
|
|
|
s = repo.status(match=m, clean=True)
|
|
|
|
modified, added, deleted, clean = s[0], s[1], s[3], s[6]
|
|
|
|
|
|
|
|
wctx = repo[None]
|
2014-11-16 05:36:19 +03:00
|
|
|
|
2016-03-17 21:19:36 +03:00
|
|
|
if warnings is None:
|
|
|
|
warnings = []
|
|
|
|
warn = True
|
|
|
|
else:
|
|
|
|
warn = False
|
|
|
|
|
2016-03-18 00:03:22 +03:00
|
|
|
subs = sorted(wctx.substate)
|
|
|
|
total = len(subs)
|
|
|
|
count = 0
|
|
|
|
for subpath in subs:
|
|
|
|
count += 1
|
2016-08-16 11:15:12 +03:00
|
|
|
submatch = matchmod.subdirmatcher(subpath, m)
|
|
|
|
if subrepos or m.exact(subpath) or any(submatch.files()):
|
2016-03-18 00:03:22 +03:00
|
|
|
ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
|
2014-11-16 05:36:19 +03:00
|
|
|
sub = wctx.sub(subpath)
|
|
|
|
try:
|
2016-03-17 21:19:36 +03:00
|
|
|
if sub.removefiles(submatch, prefix, after, force, subrepos,
|
|
|
|
warnings):
|
2014-11-16 05:36:19 +03:00
|
|
|
ret = 1
|
|
|
|
except error.LookupError:
|
2016-03-17 21:19:36 +03:00
|
|
|
warnings.append(_("skipping missing subrepository: %s\n")
|
2014-11-16 05:36:19 +03:00
|
|
|
% join(subpath))
|
2016-03-18 00:03:22 +03:00
|
|
|
ui.progress(_('searching'), None)
|
2014-11-16 05:36:19 +03:00
|
|
|
|
|
|
|
# warn about failure to delete explicit files/dirs
|
2015-04-07 00:36:08 +03:00
|
|
|
deleteddirs = util.dirs(deleted)
|
2016-03-18 00:03:22 +03:00
|
|
|
files = m.files()
|
|
|
|
total = len(files)
|
|
|
|
count = 0
|
|
|
|
for f in files:
|
2014-11-16 05:36:19 +03:00
|
|
|
def insubrepo():
|
|
|
|
for subpath in wctx.substate:
|
2016-07-22 14:29:42 +03:00
|
|
|
if f.startswith(subpath + '/'):
|
2014-11-16 05:36:19 +03:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2016-03-18 00:03:22 +03:00
|
|
|
count += 1
|
|
|
|
ui.progress(_('deleting'), count, total=total, unit=_('files'))
|
2015-04-21 19:29:59 +03:00
|
|
|
isdir = f in deleteddirs or wctx.hasdir(f)
|
2016-07-22 14:29:42 +03:00
|
|
|
if (f in repo.dirstate or isdir or f == '.'
|
|
|
|
or insubrepo() or f in subs):
|
2014-11-09 20:31:34 +03:00
|
|
|
continue
|
2014-11-16 05:36:19 +03:00
|
|
|
|
2014-12-26 05:50:35 +03:00
|
|
|
if repo.wvfs.exists(f):
|
|
|
|
if repo.wvfs.isdir(f):
|
2016-03-17 21:19:36 +03:00
|
|
|
warnings.append(_('not removing %s: no tracked files\n')
|
2014-11-27 18:16:56 +03:00
|
|
|
% m.rel(f))
|
2014-11-09 20:31:34 +03:00
|
|
|
else:
|
2016-03-17 21:19:36 +03:00
|
|
|
warnings.append(_('not removing %s: file is untracked\n')
|
2014-11-27 18:16:56 +03:00
|
|
|
% m.rel(f))
|
2014-11-09 20:31:34 +03:00
|
|
|
# missing files will generate a warning elsewhere
|
|
|
|
ret = 1
|
2016-03-18 00:03:22 +03:00
|
|
|
ui.progress(_('deleting'), None)
|
2014-11-09 20:31:34 +03:00
|
|
|
|
|
|
|
if force:
|
|
|
|
list = modified + deleted + clean + added
|
|
|
|
elif after:
|
|
|
|
list = deleted
|
2016-03-18 00:03:22 +03:00
|
|
|
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))
|
2014-11-09 20:31:34 +03:00
|
|
|
ret = 1
|
2016-03-18 00:03:22 +03:00
|
|
|
ui.progress(_('skipping'), None)
|
2014-11-09 20:31:34 +03:00
|
|
|
else:
|
|
|
|
list = deleted + clean
|
2016-03-18 00:03:22 +03:00
|
|
|
total = len(modified) + len(added)
|
|
|
|
count = 0
|
2014-11-09 20:31:34 +03:00
|
|
|
for f in modified:
|
2016-03-18 00:03:22 +03:00
|
|
|
count += 1
|
|
|
|
ui.progress(_('skipping'), count, total=total, unit=_('files'))
|
2016-03-17 21:19:36 +03:00
|
|
|
warnings.append(_('not removing %s: file is modified (use -f'
|
2014-11-27 18:16:56 +03:00
|
|
|
' to force removal)\n') % m.rel(f))
|
2014-11-09 20:31:34 +03:00
|
|
|
ret = 1
|
|
|
|
for f in added:
|
2016-03-18 00:03:22 +03:00
|
|
|
count += 1
|
|
|
|
ui.progress(_('skipping'), count, total=total, unit=_('files'))
|
2016-09-03 00:46:00 +03:00
|
|
|
warnings.append(_("not removing %s: file has been marked for add"
|
|
|
|
" (use 'hg forget' to undo add)\n") % m.rel(f))
|
2014-11-09 20:31:34 +03:00
|
|
|
ret = 1
|
2016-03-18 00:03:22 +03:00
|
|
|
ui.progress(_('skipping'), None)
|
2014-11-09 20:31:34 +03:00
|
|
|
|
2016-03-18 00:03:22 +03:00
|
|
|
list = sorted(list)
|
|
|
|
total = len(list)
|
|
|
|
count = 0
|
|
|
|
for f in list:
|
|
|
|
count += 1
|
2014-11-09 20:31:34 +03:00
|
|
|
if ui.verbose or not m.exact(f):
|
2016-03-18 00:03:22 +03:00
|
|
|
ui.progress(_('deleting'), count, total=total, unit=_('files'))
|
2014-11-27 18:16:56 +03:00
|
|
|
ui.status(_('removing %s\n') % m.rel(f))
|
2016-03-18 00:03:22 +03:00
|
|
|
ui.progress(_('deleting'), None)
|
2014-11-09 20:31:34 +03:00
|
|
|
|
2016-01-16 00:14:45 +03:00
|
|
|
with repo.wlock():
|
2014-11-09 20:31:34 +03:00
|
|
|
if not after:
|
|
|
|
for f in list:
|
|
|
|
if f in added:
|
|
|
|
continue # we never unlink added files on remove
|
2015-01-14 03:15:26 +03:00
|
|
|
repo.wvfs.unlinkpath(f, ignoremissing=True)
|
2014-11-09 20:31:34 +03:00
|
|
|
repo[None].forget(list)
|
|
|
|
|
2016-03-17 21:19:36 +03:00
|
|
|
if warn:
|
|
|
|
for warning in warnings:
|
|
|
|
ui.warn(warning)
|
|
|
|
|
2014-11-09 20:31:34 +03:00
|
|
|
return ret
|
|
|
|
|
2017-05-25 15:53:44 +03:00
|
|
|
def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
|
2014-03-14 07:45:18 +04:00
|
|
|
err = 1
|
|
|
|
|
|
|
|
def write(path):
|
2017-05-25 15:53:44 +03:00
|
|
|
filename = None
|
2017-05-25 15:43:09 +03:00
|
|
|
if fntemplate:
|
|
|
|
filename = makefilename(repo, fntemplate, ctx.node(),
|
|
|
|
pathname=os.path.join(prefix, path))
|
2017-05-25 15:53:44 +03:00
|
|
|
with formatter.maybereopen(basefm, filename, opts) as fm:
|
2017-05-27 11:58:36 +03:00
|
|
|
data = ctx[path].data()
|
|
|
|
if opts.get('decode'):
|
|
|
|
data = repo.wwritedata(path, data)
|
2017-05-25 15:53:44 +03:00
|
|
|
fm.startitem()
|
|
|
|
fm.write('data', '%s', data)
|
|
|
|
fm.data(abspath=path, path=matcher.rel(path))
|
2014-03-14 07:45:18 +04:00
|
|
|
|
|
|
|
# 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]
|
2016-11-08 19:03:43 +03:00
|
|
|
mfl = repo.manifestlog
|
2015-04-11 10:01:42 +03:00
|
|
|
mfnode = ctx.manifestnode()
|
2016-11-08 19:03:43 +03:00
|
|
|
try:
|
|
|
|
if mfnode and mfl[mfnode].find(file)[0]:
|
|
|
|
write(file)
|
|
|
|
return 0
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2014-03-14 07:45:18 +04:00
|
|
|
|
2016-08-07 17:06:20 +03:00
|
|
|
for abs in ctx.walk(matcher):
|
2014-03-14 07:45:18 +04:00
|
|
|
write(abs)
|
|
|
|
err = 0
|
2014-03-15 05:32:05 +04:00
|
|
|
|
|
|
|
for subpath in sorted(ctx.substate):
|
|
|
|
sub = ctx.sub(subpath)
|
|
|
|
try:
|
2016-02-06 08:09:32 +03:00
|
|
|
submatch = matchmod.subdirmatcher(subpath, matcher)
|
2014-03-15 05:32:05 +04:00
|
|
|
|
2017-05-25 15:53:44 +03:00
|
|
|
if not sub.cat(submatch, basefm, fntemplate,
|
2017-05-27 12:50:05 +03:00
|
|
|
os.path.join(prefix, sub._path), **opts):
|
2014-03-15 05:32:05 +04:00
|
|
|
err = 0
|
|
|
|
except error.RepoLookupError:
|
|
|
|
ui.status(_("skipping missing subrepository: %s\n")
|
|
|
|
% os.path.join(prefix, subpath))
|
|
|
|
|
2014-03-14 07:45:18 +04:00
|
|
|
return err
|
|
|
|
|
2007-08-01 03:28:05 +04:00
|
|
|
def commit(ui, repo, commitfunc, pats, opts):
|
|
|
|
'''commit the specified files or all outstanding changes'''
|
2008-02-17 23:34:28 +03:00
|
|
|
date = opts.get('date')
|
|
|
|
if date:
|
|
|
|
opts['date'] = util.parsedate(date)
|
2011-06-08 15:54:52 +04:00
|
|
|
message = logmessage(ui, opts)
|
2014-11-10 03:57:02 +03:00
|
|
|
matcher = scmutil.match(repo[None], pats, opts)
|
2007-08-01 03:28:05 +04:00
|
|
|
|
2017-08-01 00:54:57 +03:00
|
|
|
dsguard = None
|
2008-01-10 12:07:18 +03:00
|
|
|
# extract addremove carefully -- this function can be called from a command
|
|
|
|
# that doesn't support addremove
|
2017-08-15 09:26:51 +03:00
|
|
|
if opts.get('addremove'):
|
|
|
|
dsguard = dirstateguard.dirstateguard(repo, 'commit')
|
2017-08-15 09:26:54 +03:00
|
|
|
with dsguard or util.nullcontextmanager():
|
2017-08-15 09:26:51 +03:00
|
|
|
if dsguard:
|
2017-08-01 00:54:57 +03:00
|
|
|
if scmutil.addremove(repo, matcher, "", opts) != 0:
|
|
|
|
raise error.Abort(
|
|
|
|
_("failed to mark all new/missing files as added/removed"))
|
|
|
|
|
2017-08-15 09:26:54 +03:00
|
|
|
return commitfunc(ui, repo, message, matcher, opts)
|
2009-05-14 22:20:40 +04:00
|
|
|
|
2016-08-21 11:00:18 +03:00
|
|
|
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()
|
|
|
|
|
2017-09-01 22:34:36 +03:00
|
|
|
def amend(ui, repo, old, extra, pats, opts):
|
2015-08-08 10:47:19 +03:00
|
|
|
# avoid cycle context -> subrepo -> cmdutil
|
2016-02-28 10:57:07 +03:00
|
|
|
from . import context
|
2015-08-08 10:47:19 +03:00
|
|
|
|
2014-10-29 06:05:19 +03:00
|
|
|
# amend will reuse the existing user if not specified, but the obsolete
|
|
|
|
# marker creation requires that the current user's name is specified.
|
2015-03-18 00:52:58 +03:00
|
|
|
if obsolete.isenabled(repo, obsolete.createmarkersopt):
|
2014-10-29 06:05:19 +03:00
|
|
|
ui.username() # raise exception if username not set
|
|
|
|
|
2012-04-18 02:20:16 +04:00
|
|
|
ui.note(_('amending changeset %s\n') % old)
|
|
|
|
base = old.p1()
|
|
|
|
|
2017-07-10 18:48:33 +03:00
|
|
|
with repo.wlock(), repo.lock(), repo.transaction('amend'):
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
# Participating changesets:
|
|
|
|
#
|
2017-09-01 22:34:36 +03:00
|
|
|
# wctx o - workingctx that contains changes from working copy
|
|
|
|
# | to go into amending commit
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
# |
|
|
|
|
# old o - changeset to amend
|
|
|
|
# |
|
2017-09-01 04:35:39 +03:00
|
|
|
# base o - first parent of the changeset to amend
|
2017-09-01 22:34:36 +03:00
|
|
|
wctx = repo[None]
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
|
|
|
|
# Update extra dict from amended commit (e.g. to preserve graft
|
|
|
|
# source)
|
|
|
|
extra.update(old.extra())
|
|
|
|
|
2017-09-01 22:34:36 +03:00
|
|
|
# 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()
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
|
2017-09-11 19:07:29 +03:00
|
|
|
# Parse the date to allow comparison between date and old.date()
|
|
|
|
date = util.parsedate(date)
|
|
|
|
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
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())
|
|
|
|
|
2017-09-01 22:34:36 +03:00
|
|
|
# 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))
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
|
2017-09-01 22:34:36 +03:00
|
|
|
changes = (len(filestoamend) > 0)
|
|
|
|
if changes:
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
# Recompute copies (avoid recording a -> b -> a)
|
2017-09-01 22:34:36 +03:00
|
|
|
copied = copies.pathcopies(base, wctx, matcher)
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
if old.p2:
|
2017-09-01 22:34:36 +03:00
|
|
|
copied.update(copies.pathcopies(old.p2(), wctx, matcher))
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
|
|
|
|
# Prune files which were reverted by the updates: if old
|
2017-09-01 22:34:36 +03:00
|
|
|
# introduced file X and the file was renamed in the working
|
|
|
|
# copy, then those two files are the same and
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
# we can discard X from our list of files. Likewise if X
|
|
|
|
# was deleted, it's no longer relevant
|
2017-09-01 22:34:36 +03:00
|
|
|
files.update(filestoamend)
|
|
|
|
files = [f for f in files if not samefile(f, wctx, base)]
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
|
|
|
|
def filectxfn(repo, ctx_, path):
|
|
|
|
try:
|
2017-09-01 22:34:36 +03:00
|
|
|
# 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
|
|
|
|
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
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))
|
2012-09-11 01:44:24 +04:00
|
|
|
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
# Use version of files as in the old cset
|
|
|
|
def filectxfn(repo, ctx_, path):
|
|
|
|
try:
|
|
|
|
return old.filectx(path)
|
|
|
|
except KeyError:
|
|
|
|
return None
|
|
|
|
|
2017-09-01 22:34:36 +03:00
|
|
|
# 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)
|
|
|
|
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
editform = mergeeditform(old, 'commit.amend')
|
|
|
|
editor = getcommiteditor(editform=editform,
|
|
|
|
**pycompat.strkwargs(opts))
|
2017-09-01 22:34:36 +03:00
|
|
|
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
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())
|
2017-09-01 22:34:36 +03:00
|
|
|
if ((not changes)
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
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.
|
2012-09-11 01:44:24 +04:00
|
|
|
#
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
# This not what we expect from amend.
|
|
|
|
return old.node()
|
2012-09-11 01:44:24 +04:00
|
|
|
|
2017-09-06 20:41:13 +03:00
|
|
|
if opts.get('secret'):
|
|
|
|
commitphase = 'secret'
|
|
|
|
else:
|
|
|
|
commitphase = old.phase()
|
|
|
|
overrides = {('phases', 'new-commit'): commitphase}
|
|
|
|
with ui.configoverride(overrides, 'amend'):
|
codemod: simplify nested withs
This is the result of running:
python codemod_nestedwith.py **/*.py
where codemod_nestedwith.py looks like this:
#!/usr/bin/env python
# codemod_nestedwith.py - codemod tool to rewrite nested with
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
def main(argv):
if not argv:
print('Usage: codemod_nestedwith.py FILES')
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
changed = False
red = redbaron.RedBaron(readpath(path))
processed = set()
for node in red.find_all('with'):
if node in processed or node.type != 'with':
continue
top = node
child = top[0]
while True:
if len(top) > 1 or child.type != 'with':
break
# estimate line length after merging two "with"s
new = '%swith %s:' % (top.indentation, top.contexts.dumps())
new += ', %s' % child.contexts.dumps()
# only do the rewrite if the end result is within 80 chars
if len(new) > 80:
break
processed.add(child)
top.contexts.extend(child.contexts)
top.value = child.value
top.value.decrease_indentation(4)
child = child[0]
changed = True
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Differential Revision: https://phab.mercurial-scm.org/D77
2017-07-14 04:31:35 +03:00
|
|
|
newid = repo.commitctx(new)
|
2017-09-02 01:08:54 +03:00
|
|
|
|
|
|
|
# Reroute the working copy parent to the new changeset
|
|
|
|
repo.setparents(newid, nullid)
|
|
|
|
mapping = {old.node(): (newid,)}
|
2017-10-15 13:02:03 +03:00
|
|
|
obsmetadata = None
|
|
|
|
if opts.get('note'):
|
|
|
|
obsmetadata = {'note': opts['note']}
|
|
|
|
scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
|
2017-09-02 01:08:54 +03:00
|
|
|
|
2017-09-01 22:34:36 +03:00
|
|
|
# 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)
|
|
|
|
|
2012-04-18 02:20:16 +04:00
|
|
|
return newid
|
|
|
|
|
2014-08-02 16:46:26 +04:00
|
|
|
def commiteditor(repo, ctx, subs, editform=''):
|
2009-05-14 22:20:40 +04:00
|
|
|
if ctx.description():
|
|
|
|
return ctx.description()
|
2015-10-10 07:44:54 +03:00
|
|
|
return commitforceeditor(repo, ctx, subs, editform=editform,
|
|
|
|
unchangedmessagedetection=True)
|
2009-05-14 22:20:40 +04:00
|
|
|
|
2014-08-02 16:46:26 +04:00
|
|
|
def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
|
2015-10-10 07:44:54 +03:00
|
|
|
editform='', unchangedmessagedetection=False):
|
2014-07-19 08:15:28 +04:00
|
|
|
if not extramsg:
|
|
|
|
extramsg = _("Leave message empty to abort commit.")
|
2014-08-02 16:46:27 +04:00
|
|
|
|
|
|
|
forms = [e for e in editform.split('.') if e]
|
|
|
|
forms.insert(0, 'changeset')
|
2015-10-10 07:44:54 +03:00
|
|
|
templatetext = None
|
2014-08-02 16:46:27 +04:00
|
|
|
while forms:
|
2017-04-22 14:29:45 +03:00
|
|
|
ref = '.'.join(forms)
|
|
|
|
if repo.ui.config('committemplate', ref):
|
2015-10-10 07:44:54 +03:00
|
|
|
templatetext = committext = buildcommittemplate(
|
2017-04-22 14:29:45 +03:00
|
|
|
repo, ctx, subs, extramsg, ref)
|
2014-08-02 16:46:27 +04:00
|
|
|
break
|
|
|
|
forms.pop()
|
2014-07-15 18:34:13 +04:00
|
|
|
else:
|
|
|
|
committext = buildcommittext(repo, ctx, subs, extramsg)
|
2014-07-14 19:59:09 +04:00
|
|
|
|
|
|
|
# run editor in the repository root
|
2016-11-22 21:33:11 +03:00
|
|
|
olddir = pycompat.getcwd()
|
2014-07-14 19:59:09 +04:00
|
|
|
os.chdir(repo.root)
|
2015-10-10 07:44:54 +03:00
|
|
|
|
cmdutil: make in-memory changes visible to external editor (issue4378)
Before this patch, external editor process for the commit log can't
view some in-memory changes (especially, of dirstate), because they
aren't written out until the end of transaction (or wlock).
This causes unexpected output of Mercurial commands spawned from that
editor process.
To make in-memory changes visible to external editor process, this
patch does:
- write (or schedule to write) in-memory dirstate changes, and
- set HG_PENDING environment variable, if:
- a transaction is running, and
- there are in-memory changes to be visible
"hg diff" spawned from external editor process for "hg qrefresh"
shows:
- "changes newly imported into the topmost" before ab68b153ce34(*)
- "all changes recorded in the topmost by refreshing" after this patch
(*) ab68b153ce34 changed steps invoking editor process
Even though backward compatibility may be broken, the latter behavior
looks reasonable, because "hg diff" spawned from the editor process
consistently shows "what changes new revision records" regardless of
invocation context.
In fact, issue4378 itself should be resolved by b46029eb5b29, which
made 'repo.transaction()' write in-memory dirstate changes out
explicitly before starting transaction. It also made "hg qrefresh"
imply 'dirstate.write()' before external editor invocation in call
chain below.
- mq.queue.refresh
- strip.strip
- repair.strip
- localrepository.transaction
- dirstate.write
- localrepository.commit
- invoke external editor
Though, this patch has '(issue4378)' in own summary line to indicate
that issues like issue4378 should be fixed by this.
BTW, this patch adds '-m' option to a 'hg ci --amend' execution in
'test-commit-amend.t', to avoid invoking external editor process.
In this case, "unsure" states may be changed to "clean" according to
timestamp or so on. These changes should be written into pending file,
if external editor invocation is required,
Then, writing dirstate changes out breaks stability of test, because
it shows "transaction abort!/rollback completed" occasionally.
Aborting after editor process invocation while commands below may
cause similar instability of tests, too (AFAIK, there is no more such
one, at this revision)
- commit --amend
- without --message/--logfile
- import
- without --message/--logfile,
- without --no-commit,
- without --bypass,
- one of below, and
- patch has no description text, or
- with --edit
- aborting at the 1st patch, which adds or removes file(s)
- if it only changes existing files, status is checked only for
changed files by 'scmutil.matchfiles()', and transition from
"unsure" to "normal" in dirstate doesn't occur (= dirstate
isn't changed, and written out)
- aborting at the 2nd or later patch implies other pending
changes (e.g. changelog), and always causes showing
"transaction abort!/rollback completed"
2015-10-16 19:15:34 +03:00
|
|
|
# make in-memory changes visible to external process
|
|
|
|
tr = repo.currenttransaction()
|
|
|
|
repo.dirstate.write(tr)
|
|
|
|
pending = tr and tr.writepending() and repo.root
|
|
|
|
|
|
|
|
editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
|
2017-01-17 08:15:21 +03:00
|
|
|
editform=editform, pending=pending,
|
2017-08-30 23:25:56 +03:00
|
|
|
repopath=repo.path, action='commit')
|
2017-01-06 16:50:04 +03:00
|
|
|
text = editortext
|
2017-01-01 00:36:36 +03:00
|
|
|
|
|
|
|
# strip away anything below this special string (used for editors that want
|
|
|
|
# to display the diff)
|
2017-01-06 16:50:04 +03:00
|
|
|
stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
|
2017-01-01 00:36:36 +03:00
|
|
|
if stripbelow:
|
2017-01-06 16:50:04 +03:00
|
|
|
text = text[:stripbelow.start()]
|
2017-01-01 00:36:36 +03:00
|
|
|
|
2017-01-06 16:50:04 +03:00
|
|
|
text = re.sub("(?m)^HG:.*(\n|$)", "", text)
|
2014-07-14 19:59:09 +04:00
|
|
|
os.chdir(olddir)
|
|
|
|
|
|
|
|
if finishdesc:
|
|
|
|
text = finishdesc(text)
|
|
|
|
if not text.strip():
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("empty commit message"))
|
2015-10-10 07:44:54 +03:00
|
|
|
if unchangedmessagedetection and editortext == templatetext:
|
|
|
|
raise error.Abort(_("commit message unchanged"))
|
2014-07-14 19:59:09 +04:00
|
|
|
|
|
|
|
return text
|
|
|
|
|
2017-04-22 14:29:45 +03:00
|
|
|
def buildcommittemplate(repo, ctx, subs, extramsg, ref):
|
2014-07-15 18:34:13 +04:00
|
|
|
ui = repo.ui
|
2017-04-22 14:29:45 +03:00
|
|
|
spec = formatter.templatespec(ref, None, None)
|
2017-04-22 13:02:47 +03:00
|
|
|
t = changeset_templater(ui, repo, spec, None, {}, False)
|
2017-04-22 14:29:45 +03:00
|
|
|
t.t.cache.update((k, templater.unquotestring(v))
|
|
|
|
for k, v in repo.ui.configitems('committemplate'))
|
2014-08-02 16:46:27 +04:00
|
|
|
|
2014-07-15 18:34:13 +04:00
|
|
|
if not extramsg:
|
|
|
|
extramsg = '' # ensure that extramsg is string
|
|
|
|
|
|
|
|
ui.pushbuffer()
|
|
|
|
t.show(ctx, extramsg=extramsg)
|
|
|
|
return ui.popbuffer()
|
|
|
|
|
2015-09-30 19:36:30 +03:00
|
|
|
def hgprefix(msg):
|
|
|
|
return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
|
|
|
|
|
2014-07-14 19:59:09 +04:00
|
|
|
def buildcommittext(repo, ctx, subs, extramsg):
|
2009-05-14 22:20:40 +04:00
|
|
|
edittext = []
|
2009-06-01 23:51:47 +04:00
|
|
|
modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
|
2009-05-14 22:20:40 +04:00
|
|
|
if ctx.description():
|
|
|
|
edittext.append(ctx.description())
|
|
|
|
edittext.append("")
|
|
|
|
edittext.append("") # Empty line between message and comments.
|
2015-09-30 19:36:30 +03:00
|
|
|
edittext.append(hgprefix(_("Enter commit message."
|
|
|
|
" Lines beginning with 'HG:' are removed.")))
|
|
|
|
edittext.append(hgprefix(extramsg))
|
2009-05-14 22:20:40 +04:00
|
|
|
edittext.append("HG: --")
|
2015-09-30 19:36:30 +03:00
|
|
|
edittext.append(hgprefix(_("user: %s") % ctx.user()))
|
2009-05-14 22:20:40 +04:00
|
|
|
if ctx.p2():
|
2015-09-30 19:36:30 +03:00
|
|
|
edittext.append(hgprefix(_("branch merge")))
|
2009-05-14 22:20:40 +04:00
|
|
|
if ctx.branch():
|
2015-09-30 19:36:30 +03:00
|
|
|
edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
|
2015-04-14 22:45:15 +03:00
|
|
|
if bookmarks.isactivewdirparent(repo):
|
2015-09-30 19:36:30 +03:00
|
|
|
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])
|
2009-06-01 23:51:47 +04:00
|
|
|
if not added and not modified and not removed:
|
2015-09-30 19:36:30 +03:00
|
|
|
edittext.append(hgprefix(_("no files changed")))
|
2009-05-14 22:20:40 +04:00
|
|
|
edittext.append("")
|
|
|
|
|
2014-07-14 19:59:09 +04:00
|
|
|
return "\n".join(edittext)
|
2011-05-12 10:14:04 +04:00
|
|
|
|
2015-09-24 11:58:33 +03:00
|
|
|
def commitstatus(repo, node, branch, bheads=None, opts=None):
|
|
|
|
if opts is None:
|
|
|
|
opts = {}
|
2013-02-12 20:32:14 +04:00
|
|
|
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))
|
|
|
|
|
2016-01-24 07:24:30 +03:00
|
|
|
def postcommitstatus(repo, pats, opts):
|
|
|
|
return repo.status(match=scmutil.match(repo[None], pats, opts))
|
|
|
|
|
2012-03-28 13:42:17 +04:00
|
|
|
def revert(ui, repo, ctx, parents, *pats, **opts):
|
|
|
|
parent, p2 = parents
|
|
|
|
node = ctx.node()
|
|
|
|
|
|
|
|
mf = ctx.manifest()
|
2014-05-14 21:38:05 +04:00
|
|
|
if node == p2:
|
|
|
|
parent = p2
|
2012-03-28 13:42:17 +04:00
|
|
|
|
|
|
|
# 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
|
2015-01-17 08:22:21 +03:00
|
|
|
# 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.
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2014-05-14 03:29:42 +04:00
|
|
|
# `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?>)
|
2012-03-28 13:42:17 +04:00
|
|
|
names = {}
|
|
|
|
|
2016-01-16 00:14:46 +03:00
|
|
|
with repo.wlock():
|
2014-05-14 03:29:42 +04:00
|
|
|
## filling of the `names` mapping
|
|
|
|
# walk dirstate to fill `names`
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2015-03-26 00:01:14 +03:00
|
|
|
interactive = opts.get('interactive', False)
|
2015-03-24 23:56:51 +03:00
|
|
|
wctx = repo[None]
|
|
|
|
m = scmutil.match(wctx, pats, opts)
|
2015-03-27 21:48:51 +03:00
|
|
|
|
|
|
|
# we'll need this later
|
|
|
|
targetsubs = sorted(s for s in wctx.substate if m(s))
|
|
|
|
|
2015-03-25 01:47:57 +03:00
|
|
|
if not m.always():
|
2017-05-19 01:00:52 +03:00
|
|
|
matcher = matchmod.badmatch(m, lambda x, y: False)
|
2017-05-21 08:27:52 +03:00
|
|
|
for abs in wctx.walk(matcher):
|
2014-09-20 05:43:53 +04:00
|
|
|
names[abs] = m.rel(abs), m.exact(abs)
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2014-09-20 05:43:53 +04:00
|
|
|
# walk target manifest to fill `names`
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2014-09-20 05:43:53 +04:00
|
|
|
def badfn(path, msg):
|
|
|
|
if path in names:
|
2012-03-28 13:42:17 +04:00
|
|
|
return
|
2014-09-20 05:43:53 +04:00
|
|
|
if path in ctx.substate:
|
|
|
|
return
|
|
|
|
path_ = path + '/'
|
|
|
|
for f in names:
|
|
|
|
if f.startswith(path_):
|
|
|
|
return
|
|
|
|
ui.warn("%s: %s\n" % (m.rel(path), msg))
|
|
|
|
|
2015-06-05 05:02:22 +03:00
|
|
|
for abs in ctx.walk(matchmod.badmatch(m, badfn)):
|
2014-09-20 05:43:53 +04:00
|
|
|
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:
|
2015-03-25 01:47:57 +03:00
|
|
|
changes = repo.status(node1=node, match=m)
|
2014-09-20 05:43:53 +04:00
|
|
|
for kind in changes:
|
|
|
|
for abs in kind:
|
|
|
|
names[abs] = m.rel(abs), m.exact(abs)
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2014-09-20 05:43:53 +04:00
|
|
|
m = scmutil.matchfiles(repo, names)
|
2014-06-24 18:47:12 +04:00
|
|
|
|
2014-11-20 04:07:27 +03:00
|
|
|
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)
|
2014-08-30 04:39:08 +04:00
|
|
|
modadded = set()
|
2014-08-02 05:57:53 +04:00
|
|
|
|
2015-01-18 04:38:57 +03:00
|
|
|
# We need to account for the state of the file in the dirstate,
|
|
|
|
# even when we revert against something else than parent. This will
|
2014-06-24 18:47:12 +04:00
|
|
|
# slightly alter the behavior of revert (doing back up or not, delete
|
2014-04-18 00:47:38 +04:00
|
|
|
# or just forget etc).
|
2014-06-24 18:47:12 +04:00
|
|
|
if parent == node:
|
|
|
|
dsmodified = modified
|
|
|
|
dsadded = added
|
|
|
|
dsremoved = removed
|
2014-11-26 06:40:54 +03:00
|
|
|
# store all local modifications, useful later for rename detection
|
|
|
|
localchanges = dsmodified | dsadded
|
2014-06-24 18:47:12 +04:00
|
|
|
modified, added, removed = set(), set(), set()
|
|
|
|
else:
|
|
|
|
changes = repo.status(node1=parent, match=m)
|
2014-11-20 04:07:27 +03:00
|
|
|
dsmodified = set(changes.modified)
|
|
|
|
dsadded = set(changes.added)
|
|
|
|
dsremoved = set(changes.removed)
|
2014-11-26 06:40:54 +03:00
|
|
|
# store all local modifications, useful later for rename detection
|
|
|
|
localchanges = dsmodified | dsadded
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2014-06-24 19:53:22 +04:00
|
|
|
# only take into account for removes between wc and target
|
|
|
|
clean |= dsremoved - removed
|
|
|
|
dsremoved &= removed
|
|
|
|
# distinct between dirstate remove and other
|
|
|
|
removed -= dsremoved
|
|
|
|
|
2014-08-30 04:39:08 +04:00
|
|
|
modadded = added & dsmodified
|
|
|
|
added -= modadded
|
|
|
|
|
2014-06-24 20:27:18 +04:00
|
|
|
# tell newly modified apart.
|
|
|
|
dsmodified &= modified
|
2016-05-06 21:22:17 +03:00
|
|
|
dsmodified |= modified & dsadded # dirstate added may need backup
|
2014-06-24 20:27:18 +04:00
|
|
|
modified -= dsmodified
|
|
|
|
|
2014-08-30 20:20:29 +04:00
|
|
|
# We need to wait for some post-processing to update this set
|
|
|
|
# before making the distinction. The dirstate will be used for
|
|
|
|
# that purpose.
|
2014-08-02 22:32:24 +04:00
|
|
|
dsadded = added
|
|
|
|
|
2014-06-24 20:28:20 +04:00
|
|
|
# in case of merge, files that are actually added can be reported as
|
|
|
|
# modified, we need to post process the result
|
|
|
|
if p2 != nullid:
|
2017-03-02 06:51:05 +03:00
|
|
|
mergeadd = set(dsmodified)
|
|
|
|
for path in dsmodified:
|
|
|
|
if path in mf:
|
|
|
|
mergeadd.remove(path)
|
2014-06-24 20:28:20 +04:00
|
|
|
dsadded |= mergeadd
|
|
|
|
dsmodified -= mergeadd
|
|
|
|
|
2014-05-14 03:29:42 +04:00
|
|
|
# if f is a rename, update `names` to also revert the source
|
2012-03-28 13:42:17 +04:00
|
|
|
cwd = repo.getcwd()
|
2014-11-26 06:40:54 +03:00
|
|
|
for f in localchanges:
|
2012-03-28 13:42:17 +04:00
|
|
|
src = repo.dirstate.copied(f)
|
2014-06-24 19:59:40 +04:00
|
|
|
# XXX should we check for rename down to target node?
|
2012-03-28 13:42:17 +04:00
|
|
|
if src and src not in names and repo.dirstate[src] == 'r':
|
2014-06-24 18:35:43 +04:00
|
|
|
dsremoved.add(src)
|
2012-03-28 13:42:17 +04:00
|
|
|
names[src] = (repo.pathto(src, cwd), True)
|
|
|
|
|
2016-02-25 01:44:14 +03:00
|
|
|
# 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
|
|
|
|
|
2014-08-30 20:20:29 +04:00
|
|
|
# distinguish between file to forget and the other
|
|
|
|
added = set()
|
|
|
|
for abs in dsadded:
|
|
|
|
if repo.dirstate[abs] != 'a':
|
|
|
|
added.add(abs)
|
|
|
|
dsadded -= added
|
|
|
|
|
2014-08-30 04:23:25 +04:00
|
|
|
for abs in deladded:
|
|
|
|
if repo.dirstate[abs] == 'a':
|
|
|
|
dsadded.add(abs)
|
|
|
|
deladded -= dsadded
|
|
|
|
|
2014-08-30 04:00:20 +04:00
|
|
|
# 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
|
|
|
|
|
2014-05-14 03:29:42 +04:00
|
|
|
# action to be actually performed by revert
|
|
|
|
# (<list of file>, message>) tuple
|
2014-05-14 03:42:31 +04:00
|
|
|
actions = {'revert': ([], _('reverting %s\n')),
|
|
|
|
'add': ([], _('adding %s\n')),
|
2014-09-01 14:36:48 +04:00
|
|
|
'remove': ([], _('removing %s\n')),
|
2014-08-30 04:25:23 +04:00
|
|
|
'drop': ([], _('removing %s\n')),
|
2014-09-01 14:36:48 +04:00
|
|
|
'forget': ([], _('forgetting %s\n')),
|
2014-08-02 23:45:34 +04:00
|
|
|
'undelete': ([], _('undeleting %s\n')),
|
2014-08-03 00:07:01 +04:00
|
|
|
'noop': (None, _('no changes needed to %s\n')),
|
2014-06-25 03:01:30 +04:00
|
|
|
'unknown': (None, _('file not managed: %s\n')),
|
2014-08-02 23:45:34 +04:00
|
|
|
}
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2014-08-30 04:28:27 +04:00
|
|
|
# "constant" that convey the backup strategy.
|
|
|
|
# All set to `discard` if `no-backup` is set do avoid checking
|
|
|
|
# no_backup lower in the code.
|
2014-08-30 04:30:24 +04:00
|
|
|
# These values are ordered for comparison purposes
|
2016-06-30 18:38:19 +03:00
|
|
|
backupinteractive = 3 # do backup if interactively modified
|
2014-08-30 04:28:27 +04:00
|
|
|
backup = 2 # unconditionally do backup
|
2014-08-30 04:30:24 +04:00
|
|
|
check = 1 # check if the existing file differs from target
|
2014-08-30 04:28:27 +04:00
|
|
|
discard = 0 # never do backup
|
|
|
|
if opts.get('no_backup'):
|
2016-06-30 18:38:19 +03:00
|
|
|
backupinteractive = backup = check = discard
|
|
|
|
if interactive:
|
|
|
|
dsmodifiedbackup = backupinteractive
|
|
|
|
else:
|
|
|
|
dsmodifiedbackup = backup
|
|
|
|
tobackup = set()
|
2014-06-24 20:54:33 +04:00
|
|
|
|
2014-08-31 15:01:00 +04:00
|
|
|
backupanddel = actions['remove']
|
|
|
|
if not opts.get('no_backup'):
|
|
|
|
backupanddel = actions['drop']
|
|
|
|
|
2012-03-28 13:42:17 +04:00
|
|
|
disptable = (
|
|
|
|
# dispatch table:
|
|
|
|
# file state
|
2014-06-24 18:28:22 +04:00
|
|
|
# action
|
|
|
|
# make backup
|
2014-08-30 03:48:58 +04:00
|
|
|
|
|
|
|
## Sets that results that will change file on disk
|
|
|
|
# Modified compared to target, no local change
|
2014-08-30 18:06:09 +04:00
|
|
|
(modified, actions['revert'], discard),
|
2014-08-30 04:09:53 +04:00
|
|
|
# Modified compared to target, but local file is deleted
|
|
|
|
(deleted, actions['revert'], discard),
|
2014-08-30 03:48:58 +04:00
|
|
|
# Modified compared to target, local change
|
2016-06-30 18:38:19 +03:00
|
|
|
(dsmodified, actions['revert'], dsmodifiedbackup),
|
2014-08-30 03:48:58 +04:00
|
|
|
# Added since target
|
2014-08-30 20:20:29 +04:00
|
|
|
(added, actions['remove'], discard),
|
|
|
|
# Added in working directory
|
2014-09-01 14:36:48 +04:00
|
|
|
(dsadded, actions['forget'], discard),
|
2014-08-30 04:39:08 +04:00
|
|
|
# Added since target, have local modification
|
2014-08-31 15:01:00 +04:00
|
|
|
(modadded, backupanddel, backup),
|
2014-08-30 04:23:25 +04:00
|
|
|
# Added since target but file is missing in working directory
|
2014-08-30 04:25:23 +04:00
|
|
|
(deladded, actions['drop'], discard),
|
2014-08-30 03:48:58 +04:00
|
|
|
# Removed since target, before working copy parent
|
2014-08-30 04:00:20 +04:00
|
|
|
(removed, actions['add'], discard),
|
|
|
|
# Same as `removed` but an unknown file exists at the same path
|
2014-08-30 04:30:24 +04:00
|
|
|
(removunk, actions['add'], check),
|
2014-08-30 03:48:58 +04:00
|
|
|
# Removed since targe, marked as such in working copy parent
|
2014-08-30 04:00:20 +04:00
|
|
|
(dsremoved, actions['undelete'], discard),
|
|
|
|
# Same as `dsremoved` but an unknown file exists at the same path
|
2014-08-30 04:30:24 +04:00
|
|
|
(dsremovunk, actions['undelete'], check),
|
2014-08-30 03:48:58 +04:00
|
|
|
## the following sets does not result in any file changes
|
|
|
|
# File with no modification
|
2014-08-30 18:06:09 +04:00
|
|
|
(clean, actions['noop'], discard),
|
2014-08-30 03:48:58 +04:00
|
|
|
# Existing file, not tracked anywhere
|
2014-08-30 18:06:09 +04:00
|
|
|
(unknown, actions['unknown'], discard),
|
2012-03-28 13:42:17 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
for abs, (rel, exact) in sorted(names.items()):
|
2014-05-14 03:29:42 +04:00
|
|
|
# target file to be touch on disk (relative to cwd)
|
2012-03-28 13:42:17 +04:00
|
|
|
target = repo.wjoin(abs)
|
2014-06-24 20:39:43 +04:00
|
|
|
# search the entry in the dispatch table.
|
|
|
|
# if the file is in any of these sets, it was touched in the working
|
|
|
|
# directory parent and we are sure it needs to be reverted.
|
2014-06-24 21:04:13 +04:00
|
|
|
for table, (xlist, msg), dobackup in disptable:
|
2014-06-24 20:39:43 +04:00
|
|
|
if abs not in table:
|
|
|
|
continue
|
2014-08-01 22:41:56 +04:00
|
|
|
if xlist is not None:
|
|
|
|
xlist.append(abs)
|
2016-06-30 18:38:19 +03:00
|
|
|
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])):
|
2016-01-02 14:02:57 +03:00
|
|
|
bakname = scmutil.origpath(ui, repo, rel)
|
2014-08-30 04:30:24 +04:00
|
|
|
ui.note(_('saving current version of %s as %s\n') %
|
|
|
|
(rel, bakname))
|
|
|
|
if not opts.get('dry_run'):
|
2015-03-26 00:01:14 +03:00
|
|
|
if interactive:
|
|
|
|
util.copyfile(target, bakname)
|
|
|
|
else:
|
|
|
|
util.rename(target, bakname)
|
2014-08-01 22:41:56 +04:00
|
|
|
if ui.verbose or not exact:
|
|
|
|
if not isinstance(msg, basestring):
|
|
|
|
msg = msg(abs)
|
|
|
|
ui.status(msg % rel)
|
|
|
|
elif exact:
|
2014-08-03 00:07:01 +04:00
|
|
|
ui.warn(msg % rel)
|
2012-03-28 13:42:17 +04:00
|
|
|
break
|
2014-05-14 03:29:42 +04:00
|
|
|
|
2012-03-28 13:42:17 +04:00
|
|
|
if not opts.get('dry_run'):
|
2015-01-28 06:52:26 +03:00
|
|
|
needdata = ('revert', 'add', 'undelete')
|
|
|
|
_revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
|
2016-06-30 18:38:19 +03:00
|
|
|
_performrevert(repo, parents, ctx, actions, interactive, tobackup)
|
2013-05-03 23:40:17 +04:00
|
|
|
|
2015-02-08 05:47:28 +03:00
|
|
|
if targetsubs:
|
|
|
|
# Revert the subrepos on the revert list
|
|
|
|
for sub in targetsubs:
|
2015-03-26 04:54:47 +03:00
|
|
|
try:
|
2015-03-26 05:20:44 +03:00
|
|
|
wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
|
2015-03-26 04:54:47 +03:00
|
|
|
except KeyError:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort("subrepository '%s' does not exist in %s!"
|
2015-03-26 04:54:47 +03:00
|
|
|
% (sub, short(ctx.node())))
|
2012-03-28 13:42:17 +04:00
|
|
|
|
2014-08-30 04:47:59 +04:00
|
|
|
def _revertprefetch(repo, ctx, *files):
|
|
|
|
"""Let extension changing the storage layer prefetch content"""
|
|
|
|
|
2016-06-30 18:38:19 +03:00
|
|
|
def _performrevert(repo, parents, ctx, actions, interactive=False,
|
|
|
|
tobackup=None):
|
2014-05-14 03:42:31 +04:00
|
|
|
"""function that actually perform all the actions computed for revert
|
2014-02-27 06:56:27 +04:00
|
|
|
|
|
|
|
This is an independent function to let extension to plug in and react to
|
|
|
|
the imminent revert.
|
|
|
|
|
2014-04-13 21:01:00 +04:00
|
|
|
Make sure you have the working directory locked when calling this function.
|
2014-02-27 06:56:27 +04:00
|
|
|
"""
|
|
|
|
parent, p2 = parents
|
|
|
|
node = ctx.node()
|
2016-02-05 17:18:40 +03:00
|
|
|
excluded_files = []
|
|
|
|
matcher_opts = {"exclude": excluded_files}
|
|
|
|
|
2014-02-27 06:56:27 +04:00
|
|
|
def checkout(f):
|
|
|
|
fc = ctx[f]
|
2015-07-08 11:01:09 +03:00
|
|
|
repo.wwrite(f, fc.data(), fc.flags())
|
2014-02-27 06:56:27 +04:00
|
|
|
|
2016-11-25 11:10:30 +03:00
|
|
|
def doremove(f):
|
|
|
|
try:
|
2015-01-14 03:15:26 +03:00
|
|
|
repo.wvfs.unlinkpath(f)
|
2016-11-25 11:10:30 +03:00
|
|
|
except OSError:
|
|
|
|
pass
|
|
|
|
repo.dirstate.remove(f)
|
|
|
|
|
2017-07-26 16:10:15 +03:00
|
|
|
audit_path = pathutil.pathauditor(repo.root, cached=True)
|
2014-09-01 14:36:48 +04:00
|
|
|
for f in actions['forget'][0]:
|
2016-02-05 17:18:40 +03:00
|
|
|
if interactive:
|
2016-11-25 11:09:03 +03:00
|
|
|
choice = repo.ui.promptchoice(
|
2016-11-25 11:09:31 +03:00
|
|
|
_("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
|
2016-02-05 17:18:40 +03:00
|
|
|
if choice == 0:
|
|
|
|
repo.dirstate.drop(f)
|
|
|
|
else:
|
|
|
|
excluded_files.append(repo.wjoin(f))
|
|
|
|
else:
|
|
|
|
repo.dirstate.drop(f)
|
2014-05-14 03:42:31 +04:00
|
|
|
for f in actions['remove'][0]:
|
2014-02-27 06:56:27 +04:00
|
|
|
audit_path(f)
|
2016-11-25 11:10:30 +03:00
|
|
|
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)
|
2014-08-30 04:25:23 +04:00
|
|
|
for f in actions['drop'][0]:
|
|
|
|
audit_path(f)
|
2014-02-27 06:56:27 +04:00
|
|
|
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
|
2015-03-17 02:33:59 +03:00
|
|
|
|
2015-05-22 00:34:24 +03:00
|
|
|
newlyaddedandmodifiedfiles = set()
|
2015-03-17 02:33:59 +03:00
|
|
|
if interactive:
|
|
|
|
# Prompt the user for changes to revert
|
|
|
|
torevert = [repo.wjoin(f) for f in actions['revert'][0]]
|
2016-02-05 17:18:40 +03:00
|
|
|
m = scmutil.match(ctx, torevert, matcher_opts)
|
2015-05-22 00:25:57 +03:00
|
|
|
diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
|
|
|
|
diffopts.nodates = True
|
|
|
|
diffopts.git = True
|
2017-03-06 16:11:21 +03:00
|
|
|
operation = 'discard'
|
|
|
|
reversehunks = True
|
|
|
|
if node != parent:
|
|
|
|
operation = 'revert'
|
|
|
|
reversehunks = repo.ui.configbool('experimental',
|
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
2017-07-15 00:22:40 +03:00
|
|
|
'revertalternateinteractivemode')
|
2015-05-29 23:11:52 +03:00
|
|
|
if reversehunks:
|
|
|
|
diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
|
|
|
|
else:
|
|
|
|
diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
|
2015-03-17 02:33:59 +03:00
|
|
|
originalchunks = patch.parsepatch(diff)
|
2015-05-29 23:11:52 +03:00
|
|
|
|
2015-03-17 02:33:59 +03:00
|
|
|
try:
|
2015-05-29 23:11:52 +03:00
|
|
|
|
2016-06-03 16:55:07 +03:00
|
|
|
chunks, opts = recordfilter(repo.ui, originalchunks,
|
|
|
|
operation=operation)
|
2015-05-29 23:11:52 +03:00
|
|
|
if reversehunks:
|
|
|
|
chunks = patch.reversehunks(chunks)
|
|
|
|
|
2017-09-03 10:45:33 +03:00
|
|
|
except error.PatchError as err:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('error parsing patch: %s') % err)
|
2015-03-17 02:33:59 +03:00
|
|
|
|
2015-05-22 00:34:24 +03:00
|
|
|
newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
|
2016-06-30 18:38:19 +03:00
|
|
|
if tobackup is None:
|
|
|
|
tobackup = set()
|
2015-03-17 02:33:59 +03:00
|
|
|
# Apply changes
|
2016-04-10 23:55:37 +03:00
|
|
|
fp = stringio()
|
2015-03-17 02:33:59 +03:00
|
|
|
for c in chunks:
|
2016-06-30 18:38:19 +03:00
|
|
|
# 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)
|
2015-03-17 02:33:59 +03:00
|
|
|
c.write(fp)
|
|
|
|
dopatch = fp.tell()
|
|
|
|
fp.seek(0)
|
|
|
|
if dopatch:
|
|
|
|
try:
|
|
|
|
patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
|
2017-09-03 10:45:33 +03:00
|
|
|
except error.PatchError as err:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(str(err))
|
2015-03-17 02:33:59 +03:00
|
|
|
del fp
|
|
|
|
else:
|
|
|
|
for f in actions['revert'][0]:
|
2015-07-08 11:01:09 +03:00
|
|
|
checkout(f)
|
2015-03-17 02:33:59 +03:00
|
|
|
if normal:
|
|
|
|
normal(f)
|
2014-02-27 06:56:27 +04:00
|
|
|
|
2014-05-14 03:42:31 +04:00
|
|
|
for f in actions['add'][0]:
|
2015-05-22 00:34:24 +03:00
|
|
|
# Don't checkout modified files, they are already created by the diff
|
|
|
|
if f not in newlyaddedandmodifiedfiles:
|
|
|
|
checkout(f)
|
|
|
|
repo.dirstate.add(f)
|
2014-02-27 06:56:27 +04:00
|
|
|
|
|
|
|
normal = repo.dirstate.normallookup
|
|
|
|
if node == parent and p2 == nullid:
|
|
|
|
normal = repo.dirstate.normal
|
2014-05-14 03:42:31 +04:00
|
|
|
for f in actions['undelete'][0]:
|
2014-02-27 06:56:27 +04:00
|
|
|
checkout(f)
|
|
|
|
normal(f)
|
|
|
|
|
|
|
|
copied = copies.pathcopies(repo[parent], ctx)
|
|
|
|
|
2014-05-14 03:42:31 +04:00
|
|
|
for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
|
2014-02-27 06:56:27 +04:00
|
|
|
if f in copied:
|
|
|
|
repo.dirstate.copy(copied[f], f)
|
|
|
|
|
2016-01-09 17:24:52 +03:00
|
|
|
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)
|
2013-05-14 22:23:15 +04:00
|
|
|
|
2014-04-15 19:37:24 +04:00
|
|
|
# a list of (ui, repo, otherpeer, opts, missing) functions called by
|
|
|
|
# commands.outgoing. "missing" is "missing" of the result of
|
|
|
|
# "findcommonoutgoing()"
|
|
|
|
outgoinghooks = util.hooks()
|
|
|
|
|
2013-05-14 22:23:15 +04:00
|
|
|
# a list of (ui, repo) functions called by commands.summary
|
|
|
|
summaryhooks = util.hooks()
|
2013-07-25 08:27:30 +04:00
|
|
|
|
2014-04-15 19:37:24 +04:00
|
|
|
# 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()
|
|
|
|
|
2013-07-25 08:27:30 +04:00
|
|
|
# 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
|
2013-07-25 11:17:52 +04:00
|
|
|
# (state file, clearable, allowcommit, error, hint)
|
2013-07-25 08:27:30 +04:00
|
|
|
unfinishedstates = [
|
2013-07-25 11:17:52 +04:00
|
|
|
('graftstate', True, False, _('graft in progress'),
|
2013-07-25 09:33:28 +04:00
|
|
|
_("use 'hg graft --continue' or 'hg update' to abort")),
|
2013-07-25 11:17:52 +04:00
|
|
|
('updatestate', True, False, _('last update was interrupted'),
|
2013-07-25 09:33:28 +04:00
|
|
|
_("use 'hg update' to get a consistent checkout"))
|
2013-07-25 08:27:30 +04:00
|
|
|
]
|
|
|
|
|
2013-07-25 11:17:52 +04:00
|
|
|
def checkunfinished(repo, commit=False):
|
2013-07-25 08:27:30 +04:00
|
|
|
'''Look for an unfinished multistep operation, like graft, and abort
|
|
|
|
if found. It's probably good to check this right before
|
|
|
|
bailifchanged().
|
|
|
|
'''
|
2013-07-25 11:17:52 +04:00
|
|
|
for f, clearable, allowcommit, msg, hint in unfinishedstates:
|
|
|
|
if commit and allowcommit:
|
|
|
|
continue
|
2013-07-25 08:27:30 +04:00
|
|
|
if repo.vfs.exists(f):
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(msg, hint=hint)
|
2013-07-25 08:27:30 +04:00
|
|
|
|
|
|
|
def clearunfinished(repo):
|
|
|
|
'''Check for unfinished operations (as above), and clear the ones
|
|
|
|
that are clearable.
|
|
|
|
'''
|
2013-07-25 11:17:52 +04:00
|
|
|
for f, clearable, allowcommit, msg, hint in unfinishedstates:
|
2013-07-25 08:27:30 +04:00
|
|
|
if not clearable and repo.vfs.exists(f):
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(msg, hint=hint)
|
2013-07-25 11:17:52 +04:00
|
|
|
for f, clearable, allowcommit, msg, hint in unfinishedstates:
|
2013-07-25 08:27:30 +04:00
|
|
|
if clearable and repo.vfs.exists(f):
|
2017-03-09 03:53:09 +03:00
|
|
|
util.unlink(repo.vfs.join(f))
|
cmdutil: add class to restore dirstate during unexpected failure
Before this patch, after "dirstate.write()" execution, there was no way to
restore dirstate to the original status before "dirstate.write()".
In some code paths, "dirstate.invalidate()" is used as a kind of "restore
.hg/dirstate to the original status", but it just avoids writing changes in
memory out, and doesn't actually restore the ".hg/dirstate" file.
To fix the issue that the recent (in memory) dirstate isn't visible to external
processes (e.g. "precommit" hooks), "dirstate.write()" should be invoked before
invocation of external processes. But at the same time, ".hg/dirstate" should be
restored to its content before "dirstate.write()" during an unexpected failure
in some cases.
This patch adds the class "dirstateguard" to easily restore ".hg/dirstate"
during unexpected failures. Typical usecase of it is:
# (1) build dirstate up
....
# (2) write dirstate out, and backup ".hg/dirstate"
dsguard = dirstateguard(repo, 'scopename')
try:
# (3) execute somethig to do:
# this may imply making some additional changes on dirstate
....
# (4) unlink backed-up dirstate file at the end of dsguard scope
dsguard.close()
finally:
# (5) if execution is aborted before "dsguard.close()",
# ".hg/dirstate" is restored from the backup
dsguard.release()
For this kind of issue, an "extending transaction" approach (in
https://titanpad.com/mercurial32-sprint) seems to not be suitable, because:
- transaction nesting occurs in some cases (e.g. "shelve => rebase"), and
- "dirstate" may be already modified since the beginning of OUTER
transaction scope, then
- dirstate should be backed up into the file other than
"dirstate.journal" at the beginning of INNER transaction scope, but
- such alternative backup files are useless for transaction itself,
and increases complication of its implementation
"transaction" and "dirstateguard" differ from each other also in "what
it should do for .hg/dirstate" in cases other than success.
============== ======= ======== =============
type success fail "hg rollback"
============== ======= ======== =============
transaction keep keep restore
dirstateguard keep restore (not implied)
============== ======= ======== =============
Some collaboration between transaction and dirstate will probably be introduced
in the future. But this layer is needed in all cases.
2015-05-07 06:07:10 +03:00
|
|
|
|
2015-12-24 22:25:44 +03:00
|
|
|
afterresolvedstates = [
|
2015-12-24 23:46:06 +03:00
|
|
|
('graftstate',
|
|
|
|
_('hg graft --continue')),
|
2015-12-24 22:25:44 +03:00
|
|
|
]
|
|
|
|
|
2016-02-14 19:16:17 +03:00
|
|
|
def howtocontinue(repo):
|
|
|
|
'''Check for an unfinished operation and return the command to finish
|
|
|
|
it.
|
|
|
|
|
2016-10-18 00:16:55 +03:00
|
|
|
afterresolvedstates tuples define a .hg/{file} and the corresponding
|
2016-02-14 19:16:17 +03:00
|
|
|
command needed to finish it.
|
|
|
|
|
|
|
|
Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
|
|
|
|
a boolean.
|
|
|
|
'''
|
|
|
|
contmsg = _("continue: %s")
|
2015-12-24 22:25:44 +03:00
|
|
|
for f, msg in afterresolvedstates:
|
|
|
|
if repo.vfs.exists(f):
|
2016-02-14 19:16:17 +03:00
|
|
|
return contmsg % msg, True
|
2017-07-09 07:53:16 +03:00
|
|
|
if repo[None].dirty(missing=True, merge=False, branch=False):
|
2016-02-14 19:16:17 +03:00
|
|
|
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)
|