contrib/check-py3-compat.py
author Martin von Zweigbergk <martinvonz@google.com>
Tue, 18 Jan 2022 13:05:21 -0800
changeset 48981 f3aafd785e65
parent 48875 6000f5b25c9b
child 49217 13dfad0f9f7a
permissions -rwxr-xr-x
filemerge: add support for partial conflict resolution by external tool A common class of merge conflicts is in imports/#includes/etc. It's relatively easy to write a tool that can resolve these conflicts, perhaps by naively just unioning the statements and leaving any cleanup to other tools to do later [1]. Such specialized tools cannot generally resolve all conflicts in a file, of course. Let's therefore call them "partial merge tools". Note that the internal simplemerge algorithm is such a partial merge tool - one that only resolves trivial "conflicts" where one side is unchanged or both sides change in the same way. One can also imagine having smarter language-aware partial tools that merge the AST. It may be useful for such tools to interactively let the user resolve any conflicts it can't resolve itself. However, having the option of implementing it as a partial merge tool means that the developer doesn't *need* to create a UI for it. Instead, the user can resolve any remaining conflicts with their regular merge tool (e.g. `:merge3` or `meld). We don't currently have a way to let the user define such partial merge tools. That's what this patch addresses. It lets the user configure partial merge tools to run. Each tool can be configured to run only on files matching certain patterns (e.g. "*.py"). The tool takes three inputs (local, base, other) and resolves conflicts by updating these in place. For example, let's say the inputs are these: base: ``` import sys def main(): print('Hello') ``` local: ``` import os import sys def main(): print('Hi') ``` other: ``` import re import sys def main(): print('Howdy') ``` A partial merge tool could now resolve the conflicting imports by replacing the import statements in *all* files by the following snippet, while leaving the remainder of the files unchanged. ``` import os import re import sys ``` As a result, simplemerge and any regular merge tool that runs after the partial merge tool(s) will consider the imports to be non-conflicting and will only present the conflict in `main()` to the user. Differential Revision: https://phab.mercurial-scm.org/D12356
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
45830
c102b704edb5 global: use python3 in shebangs
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45217
diff changeset
     1
#!/usr/bin/env python3
27279
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     2
#
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     3
# check-py3-compat - check Python 3 compatibility of Mercurial files
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     4
#
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     5
# Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     6
#
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     7
# This software may be used and distributed according to the terms of the
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     8
# GNU General Public License version 2 or any later version.
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     9
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    10
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    11
import ast
32280
9b81fb217820 py3: remove delayed import of importlib
Gregory Szorc <gregory.szorc@gmail.com>
parents: 32212
diff changeset
    12
import importlib
28584
d69172ddfdca tests: try to import modules with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28583
diff changeset
    13
import os
27279
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    14
import sys
28584
d69172ddfdca tests: try to import modules with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28583
diff changeset
    15
import traceback
41555
ba7eaff26474 check-py3-compat: manually format and print warnings
Gregory Szorc <gregory.szorc@gmail.com>
parents: 41554
diff changeset
    16
import warnings
27279
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    17
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    18
28583
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    19
def check_compat_py3(f):
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    20
    """Check Python 3 compatibility of a file with Python 3."""
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    21
    with open(f, 'rb') as fh:
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    22
        content = fh.read()
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    23
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    24
    try:
41554
01417ca7f2e2 check-py3-compat: provide filename to ast.parse()
Gregory Szorc <gregory.szorc@gmail.com>
parents: 32421
diff changeset
    25
        ast.parse(content, filename=f)
28583
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    26
    except SyntaxError as e:
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    27
        print('%s: invalid syntax: %s' % (f, e))
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    28
        return
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    29
28584
d69172ddfdca tests: try to import modules with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28583
diff changeset
    30
    # Try to import the module.
32421
778dc37ce683 check: check modules in hgdemandimport
Siddharth Agarwal <sid0@fb.com>
parents: 32374
diff changeset
    31
    # For now we only support modules in packages because figuring out module
778dc37ce683 check: check modules in hgdemandimport
Siddharth Agarwal <sid0@fb.com>
parents: 32374
diff changeset
    32
    # paths for things not in a package can be confusing.
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    33
    if f.startswith(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    34
        ('hgdemandimport/', 'hgext/', 'mercurial/')
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    35
    ) and not f.endswith('__init__.py'):
28584
d69172ddfdca tests: try to import modules with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28583
diff changeset
    36
        assert f.endswith('.py')
32212
65cd7e705ff6 policy: eliminate ".pure." from module name only if marked as dual
Yuya Nishihara <yuya@tcha.org>
parents: 30117
diff changeset
    37
        name = f.replace('/', '.')[:-3]
30095
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    38
        try:
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    39
            importlib.import_module(name)
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    40
        except Exception as e:
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    41
            exc_type, exc_value, tb = sys.exc_info()
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    42
            # We walk the stack and ignore frames from our custom importer,
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    43
            # import mechanisms, and stdlib modules. This kinda/sorta
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    44
            # emulates CPython behavior in import.c while also attempting
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    45
            # to pin blame on a Mercurial file.
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    46
            for frame in reversed(traceback.extract_tb(tb)):
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    47
                if frame.name == '_call_with_frames_removed':
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    48
                    continue
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    49
                if 'importlib' in frame.filename:
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    50
                    continue
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    51
                if 'mercurial/__init__.py' in frame.filename:
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    52
                    continue
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    53
                if frame.filename.startswith(sys.prefix):
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    54
                    continue
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    55
                break
28584
d69172ddfdca tests: try to import modules with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28583
diff changeset
    56
30095
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    57
            if frame.filename:
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    58
                filename = os.path.basename(frame.filename)
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    59
                print(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    60
                    '%s: error importing: <%s> %s (error at %s:%d)'
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    61
                    % (f, type(e).__name__, e, filename, frame.lineno)
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    62
                )
30095
e8aeeb28e35e py3: remove superfluous indent from check-py3-compat.py
Yuya Nishihara <yuya@tcha.org>
parents: 30094
diff changeset
    63
            else:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    64
                print(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    65
                    '%s: error importing module: <%s> %s (line %d)'
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    66
                    % (f, type(e).__name__, e, frame.lineno)
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    67
                )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    68
28584
d69172ddfdca tests: try to import modules with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28583
diff changeset
    69
27279
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    70
if __name__ == '__main__':
48872
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    71
    # check_compat_py3 will import every filename we specify as long as it
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    72
    # starts with one of a few prefixes. It does this by converting
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    73
    # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    74
    # importing that. When running standalone (not as part of a test), this
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    75
    # means we actually import the installed versions, not the files we just
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    76
    # specified. When running as test-check-py3-compat.t, we technically
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    77
    # would import the correct paths, but it's cleaner to have both cases
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    78
    # use the same import logic.
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    79
    sys.path.insert(0, '.')
28583
260ce2eed951 tests: perform an ast parse with Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 28475
diff changeset
    80
27279
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    81
    for f in sys.argv[1:]:
41555
ba7eaff26474 check-py3-compat: manually format and print warnings
Gregory Szorc <gregory.szorc@gmail.com>
parents: 41554
diff changeset
    82
        with warnings.catch_warnings(record=True) as warns:
48872
968b29a5a7fc check-py3-compat: drop support for Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents: 45830
diff changeset
    83
            check_compat_py3(f)
41555
ba7eaff26474 check-py3-compat: manually format and print warnings
Gregory Szorc <gregory.szorc@gmail.com>
parents: 41554
diff changeset
    84
ba7eaff26474 check-py3-compat: manually format and print warnings
Gregory Szorc <gregory.szorc@gmail.com>
parents: 41554
diff changeset
    85
        for w in warns:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    86
            print(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    87
                warnings.formatwarning(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    88
                    w.message, w.category, w.filename, w.lineno
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    89
                ).rstrip()
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 41555
diff changeset
    90
            )
27279
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    91
40eb385f798f tests: add test for Python 3 compatibility
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    92
    sys.exit(0)