Commit Graph

537 Commits

Author SHA1 Message Date
Augie Fackler
6de511f29f ui: fix progress debug log format strings to work on Python 3 2017-09-18 13:37:00 -04:00
Yuya Nishihara
81c2aaf747 py3: convert function name to bytes in ui.configwith() 2017-09-03 17:47:21 +09:00
Yuya Nishihara
a71f259bd2 doctest: bulk-replace string literals with b'' for Python 3
Our code transformer can't rewrite string literals in docstrings, and I
don't want to make the transformer more complex.
2017-09-03 14:32:11 +09:00
Michael Bolin
16648b82d6 editor: file created for diff action should have .diff suffix
This is a follow-up to https://phab.mercurial-scm.org/D464 (02b917f3e672) that
introduced the new file extension behavior. It erroneously changed `.diff` to
`.diff.hg.txt`.

Test Plan:
Verified `make tests` passes, particularly `test-editor-filename.t`.

Differential Revision: https://phab.mercurial-scm.org/D607
2017-09-01 20:28:26 +00:00
Michael Bolin
094c271fff editor: use an unambiguous path suffix for editor files
Changes the API of `ui.edit()` to take an optional `action` argument,
which is used when constructing the suffix of the temp file.
Previously, it was possible to set the suffix by specifying a `suffix` to the
optional `extra` dict that was passed to `ui.edit()`, but the goal is to
drop support for `extra.suffix` and make `action` a required argument.
To this end, `ui.edit()` now yields a `develwarn()` if `action` is not set
or if `extra.suffix` is set.

I updated all calls to `ui.edit()` I could find in `hg-crew` to specify the
appropriate `action`. This means that when creating a commit, instead
of the path to the editor file being something like:

`/tmp/hg-editor-XXXXXX.txt`

it is now something like:

`/tmp/hg-editor-XXXXXX.commit.hg.txt`

Some editors (such as Atom) make it possible to statically define a [TextMate]
grammar for files with a particular suffix. For example, because Git reliably
uses `.git/COMMIT_EDITMSG` and `.git/MERGE_MSG` as the paths for commit-type
messages, it is trivial to define a grammar that is applied when files of
either name are opened in Atom:

https://github.com/atom/language-git/blob/v0.19.1/grammars/git%20commit%20message.cson#L4-L5

Because Hg historically used a generic `.txt` suffix, it was much harder to
disambiguate whether a file was an arbitrary text file as opposed to one
created for the specific purpose of authoring an Hg commit message.

This also makes it easier to add special support for `histedit`, as it has its own
suffix that is distinct from a commit:

`/tmp/hg-histedit-XXXXXX.histedit.hg.txt`

Test Plan:
Added an integration test: `test-editor-filename.t`.

Manually tested: ran `hg ci --amend` for this change and saw that it
used `/tmp/hg-editor-ZZjcz0.commit.hg.txt` as the path instead of
`/tmp/hg-editor-ZZjcz0.txt` as the path.

Verified `make tests` passes.

Differential Revision: https://phab.mercurial-scm.org/D464
2017-08-30 20:25:56 +00:00
Jun Wu
bd039f3688 pager: do not start pager if ui has been pushbuffer-ed
The `pushbuffer`, `popbuffer` APIs are intended to capture internal output.
They will prevent `ui.write` from writing to the actual `ui.fout`. So a
pager won't receive the output and do the right thing. In general, it does
not make sense to start a pager if ui is in the "pushbuffer" mode.

Differential Revision: https://phab.mercurial-scm.org/D574
2017-08-30 14:04:55 -07:00
Sean Farley
e18a90ab08 merge with stable 2017-08-21 21:35:06 -07:00
Gregory Szorc
8e7a19b422 ui: restore behavior to ignore some I/O errors (issue5658)
45345e9870c3 and b30126fa95bc refactored ui methods to no longer
silently swallow some IOError instances. This is arguably the
correct thing to do. However, it had the unfortunate side-effect
of causing StdioError to bubble up to sensitive code like
transaction aborts, leading to an uncaught exceptions and failures
to e.g. roll back a transaction. This could occur when a remote
HTTP or SSH client connection dropped. The new behavior is
resulting in semi-frequent "abandonded transaction" errors on
multiple high-volume repositories at Mozilla.

This commit effectively reverts 45345e9870c3 and b30126fa95bc to
restore the old behavior.

I agree with the principle that I/O errors shouldn't be ignored.
That makes this change... unfortunate. However, our hands are tied
for what to do on stable. I think the proper solution is for the
ui's behavior to be configurable (possibly via a context manager).
During critical sections like transaction rollback and abort, it
should be possible to suppress errors. But this feature would not
be appropriate on stable.
2017-08-15 13:04:31 -07:00
Kostia Balytskyi
edfeaa5101 ui: make sure buffer is flushed before waiting for user input (issue5587)
Without this patch on Windows 'hg ci -i' hangs waiting for user input
and "examine changes to 'file'? [Ynesfdaq?]" is never displayed (at least
if the diff is sufficiently small). When Ctrl+C is pressed, this prompt
becomes visible, which suggests that the buffer just wasn't flushed.
I've never seen this happening on Linux, but this looks harmless enough
to not platform-gate it.
2017-08-05 13:19:09 -07:00
FUJIWARA Katsunori
af39ee1c25 ui: enable pager always for explicit --pager=on (issue5580)
Before this patch, explicit --pager=on is unintentionally ignored by
any disabling factor, even if priority of it is less than --pager=on
(e.g. "[ui] paginate = off").
2017-08-01 18:52:52 +09:00
Augie Fackler
4dfc9655ac ui: fix configbytes isinstance check to look for bytes and not str
Fixes configbytes on Python 3.
2017-07-24 13:50:25 -04:00
Augie Fackler
a80f148d0c py3: introduce a wrapper for __builtins__.{raw_,}input()
In order to make this work, we have to wrap the io streams in a
TextIOWrapper so that __builtins__.input() can do unicode IO on Python
3. We can't just restore the original (unicode) sys.std* because we
might be running a cmdserver, and if we blindly restore sys.* to the
original values then we end up breaking the cmdserver. Sadly,
TextIOWrapper tries to close the underlying stream during its __del__,
so we have to make a sublcass to prevent that.

If you see errors like:

TypeError: a bytes-like object is required, not 'str'

On an input() or print() call on Python 3, the substitution of
sys.std* is probably the root cause.

A previous version of this change tried to put the bytesinput() method
in pycompat - it turns out we need to do some encoding handling, so we
have to be in a higher layer that's allowed to use
mercurial.encoding.encoding. As a result, this is in util for now,
with the TextIOWrapper subclass hiding in encoding.py. I'm not sure of
a better place for the time being.

Differential Revision: https://phab.mercurial-scm.org/D299
2017-07-24 14:38:40 -04:00
Jun Wu
e47f7dc2fa 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-14 14:22:40 -07:00
Augie Fackler
9a0febea27 merge with stable 2017-08-10 14:23:41 -04:00
Augie Fackler
dadffe35c4 ui: refactor extractchoices so it doesn't break on Python 3
Differential Revision: https://phab.mercurial-scm.org/D275
2017-07-24 13:48:32 -04:00
Yuya Nishihara
126b1dc317 py3: use bytes IO to write sample hgrc
Unicode sucks. Stop using Text IO and manually convert line endings.
2017-08-03 00:45:02 +09:00
Boris Feld
40b893532b configitems: handle case were the default value is not static
In some case, the default of one value is derived from other value. We add a
way to register them anyway and an associated devel-warning.

The registration is very naive for the moment. We might be able to have a
better way for registering each of these cases but it could be done later.
2017-07-12 23:36:10 +02:00
David Demelier
d324b7a9bf configitems: add alias support in config
Aliases define optional alternatives to existing options. For example the old
option ui.user was deprecated and replaced by ui.username. With this mechanism,
it's even possible to create an alias to an option in a different section.

Add ui.user as alias to ui.username as an example of this concept.

The old alternates principle in ui.config is removed as it was used only for
this option.
2017-07-07 08:33:10 +02:00
Pierre-Yves David
8eb6764bad configitems: support callable as a default value
Yuya pointed out that using mutable value as the default could be problematic.
To work around this we now support callable object as default value. This
allows for creating new mutable objects on demand when needed.
2017-06-28 13:50:20 +02:00
Pulkit Goyal
d7e69a39e2 py3: add b'' to make the regex pattern bytes 2017-06-25 03:11:55 +05:30
Pierre-Yves David
e2366ea12d configitems: register 'ui.interactive'
That item default value is a bit special (None) so this adds a second proof
that everything is still working fine.
2017-06-23 17:19:29 +02:00
Pierre-Yves David
337073fb57 config: use '_config' within 'configbytes'
This will prevent bugs from using None as the sentinel value (eg:
'ui.interactive')
2017-06-25 14:41:12 +02:00
Pierre-Yves David
bcbc568807 config: use '_config' within 'configbool'
This will prevent bugs from using None as the sentinel value (eg:
'ui.interactive')
2017-06-25 14:38:56 +02:00
Pierre-Yves David
634a0d74db config: extract the core config logic into a private method
This will make it easier for the other 'configxxx' function to detect unset
value.
2017-06-25 14:34:34 +02:00
Pierre-Yves David
4579a8de56 configitems: issue a devel warning when overriding default config
If the option is registered, there is already a default value available and
passing a new one is at best redundant. So we issue a deprecation warning in
this case.

(note: there will be case were the default value will not be as simple as what
is currently possible. We'll upgrade the configitems code to handle them in
time.)
2017-06-17 13:08:03 +02:00
Pierre-Yves David
7918debc04 configitems: get default values from the central registry when available
We do not have any registered config yet, but we are now ready to use them.

For now we ignore this feature for config access with "alternates". On the long
run, we expect alternates to be handled as "aliases" by the config item
themself.
2017-06-17 12:15:28 +02:00
Pierre-Yves David
ba2fa929d4 configitems: introduce a central registry for config option
We now have the appropriate infrastructure to register config items. Usage will
added in the next changeset.
2017-06-17 18:43:27 +02:00
Pierre-Yves David
5640bb2ad1 config: use the new '_unset' value for 'configsuboptions'
This should let configsuboptions delegate all special processing of the default
config value to the main 'config' method.
2017-06-17 12:51:37 +02:00
Pierre-Yves David
3828008441 config: use the 'config' method in 'configsuboptions'
There was unnecessary code duplication. It was getting in the way of the
unification of the default value logic.
2017-06-17 18:28:20 +02:00
Pierre-Yves David
30b883a533 config: use the new '_unset' value for 'configpath'
This should let 'configpath' delegate all special processing of the default
config value to the main 'config' method.
2017-06-17 12:52:02 +02:00
Pierre-Yves David
73fc40fe02 config: use the new '_unset' value for 'configdate'
This should let 'configdate' delegate all special processing of the default
config value to the main 'config' method.

The default value for date (None) is still enforced in this method if no other
default were passed.
2017-06-17 12:54:45 +02:00
Pierre-Yves David
3ba5dde647 config: use the new '_unset' value for 'configlist'
This should let 'configlist' delegate all special processing of the default
config value to the main 'config' method.

The default config value ([]) is still handled in this method.
2017-06-17 12:54:04 +02:00
Pierre-Yves David
dd12eb0ca1 config: use the new '_unset' value for 'configbytes'
This should let 'configbytes' delegates all special processing of the default
config value to the main 'config' method.
2017-06-17 12:53:51 +02:00
Pierre-Yves David
a1226e02b2 config: use the new '_unset' value for 'configint'
This should let 'configint' delegates all special processing of the default
config value to the main 'config' method.
2017-06-17 12:53:40 +02:00
Pierre-Yves David
488077019c config: use the new '_unset' value for 'configwith'
This should let 'configwith' delegate all special processing of the default
config value to the main 'config' method.

This changeset introduce a small change in behavior since the default value is
run through the 'convert' function. This does not seems harmful and no actual
test break. This small change make the code simpler so I'm keeping it.
2017-06-17 12:52:31 +02:00
Pierre-Yves David
ae76544acb config: use the new '_unset' value for 'configbool'
This should let 'configbool' delegate all special processing of the default
config value to the main 'config' method.

The default value for bool (False) is still enforced in this method if no other
default were passed.
2017-06-17 12:52:16 +02:00
Pierre-Yves David
b2090538ae config: explicitly track the use of the standard default value
We introduce a small object used to detect that no specific default value has
been passed to 'ui.config'. We need this explicit special value since "None" is
a valid and common default value.

The end goal here is to make progress on a centralised and explicit declaration
of the available config option. A first good usecase for this are "default"
value.  Before starting looking further down this alley we needs to rework the
handling of default value in the 'ui' object to have all configxyz methods going
through the same logic. This is the first changeset on this trek.
2017-06-17 12:51:11 +02:00
Augie Fackler
708044618a ui: add support for a tweakdefaults knob
We've been talking for years about a one-stop config knob to opt in to
better behavior. There have been a lot of ideas thrown around, but
they all seem to be too complicated to get anyone to actually do the
work.. As such, this patch is the stupidest thing that can possibly
work in the name of getting a good feature to users.

Right now it's just three config settings that I think are generally
uncontroversial, but I expect to add more soon. That will likely
include adding new config knobs for the express purpose of adding them
to tweakdefaults.
2017-06-14 20:56:34 -04:00
Yuya Nishihara
7c796bc384 formatter: add option to redirect output to file object
Commands like 'export' have --output=OUTFILESPEC option, so we need a way
to write formatter output optionally to a file.
2015-01-18 18:04:44 +09:00
Boris Feld
f7e21a1d7c util: raise ParseError when parsing dates (BC)
d46a7814be5f refactored util.parsedate in order to raise ValueError instead
of Abort for using with ui.configwith. It causes several problems, putting
arbitrary bytes in ValueError can cause issues with Python 3. Moreover, we
added a function to convert ValueError exceptions back to Abort.

A better approach would be to make parsedate raises ParseError, removing
the convert function and update configwith to also catch ParseError.

The side-effect is that error message when giving an invalid date in CLI
change from:

  abort: invalid date: 'foo bar'

to:

  hg: parse error: invalid date: 'foo bar'

I'm not sure if it's an acceptable change, I found personally the error
message more clear but more verbose too.
2017-05-24 17:50:17 +02:00
Boris Feld
e12d51e5f8 ui: fix ui.configdate for invalid dates
d46a7814be5f introduced util._parsedate with the aim to be used in
ui.configdate but ui.configdate was using util.parsedate instead. It have the
impact of raising an AbortError in case of an invalid date instead of a
ConfigError exception. Fix ui.configdate to use the right function and add a
test for invalid dates.

Thanks to Yuya for the catch!
2017-05-23 15:44:50 +02:00
Boris Feld
04a3cbe923 ui: add the possiblity to get a date config field
Add the method configdate to read a date from configuration. It uses the
util.rawparsedate refactored earlier to support all standard date formats.
2017-05-19 12:07:41 +02:00
Pierre-Yves David
a5a7702af1 pager: drop the support for 'pager.enable=<bool>'
This option was never released except for a release candidate. Dropping
compatibility with this option will free the 'pager.enable' config option for
other usage in the future.
2017-05-02 17:18:13 +02:00
Pierre-Yves David
bf9fa9e05b pager: rename 'pager.enable' to 'ui.paginate'
This aligns with what we do for color (see cea7a760c58d). Pager is a central
enough notion that having the master config in the [ui] section makes senses. It
will helps with consistency, discoverability. It will also help having a simple
and clear example hgrc mentioning pager.

The previous form of the option had never been released in a non-rc version but
we keep it around for convenience. If both are set, 'ui.pager' take priority.
2017-05-01 16:36:50 +02:00
Pierre-Yves David
50e449e083 pager: advertise the config option in the default hgrc
Same as for 'ui.color', this is a critical part of the UI and we want user to
find this config knob easily.
2017-05-01 18:07:23 +02:00
Pierre-Yves David
0600a78b19 config: drop pager from the recommended extension
The extension is deprecated, we should having exposing it to users.
2017-05-01 15:51:57 +02:00
Pierre-Yves David
c674366f10 config: use "churn" as an example extension
"Churn" is not the useful example we have, but this is the one used in
'hg help config.extensions'. As we need something to replace the deprecated
'pager' extension in the example config, we are adding 'churn'.
2017-05-01 15:51:47 +02:00
Pierre-Yves David
39d62892b4 color: point to the global help in the example hgrc
This is probably the best way to have users learn more is they need too.
2017-05-01 15:40:41 +02:00
Pierre-Yves David
d8acfa70c4 color: reflect the new default in the example hgrc
Color is enabled by default so un-commenting the line would not have any effect.
We'll point to the help in the next changeset.
2017-05-01 15:39:50 +02:00
Yuya Nishihara
c11e91f8ff pager: use less as a fallback on Unix
This seems reasonable choice per discussion, and the default-default of Git.
See also the inline-comment for why.

https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-April/097042.html
2017-04-28 20:51:14 +09:00