diff --git a/pdm.lock b/pdm.lock index a9ac3bf..4746646 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:35883457653897e26304508d572de32079d9747255fe14c2e96e20edeea08815" +content_hash = "sha256:0d780f6e941113ad13417c6cbf4ea3fd1e3d4af56ab43827b6d6e8be4344425d" [[metadata.targets]] requires_python = ">=3.10" @@ -24,6 +24,21 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] +[[package]] +name = "asttokens" +version = "2.4.1" +summary = "Annotate AST trees with source code positions" +groups = ["dev"] +marker = "python_version > \"3.6\"" +dependencies = [ + "six>=1.12.0", + "typing; python_version < \"3.5\"", +] +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + [[package]] name = "click" version = "8.1.7" @@ -44,8 +59,8 @@ name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." -groups = ["default"] -marker = "platform_system == \"Windows\"" +groups = ["default", "dev"] +marker = "sys_platform == \"win32\" and python_version > \"3.6\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -53,7 +68,7 @@ files = [ [[package]] name = "cscopy" -version = "0.0.1" +version = "0.0.2" requires_python = ">=3.10" summary = "Python binding for cscopy" groups = ["default"] @@ -61,8 +76,115 @@ dependencies = [ "pydantic>=2.7.3", ] files = [ - {file = "cscopy-0.0.1-py3-none-any.whl", hash = "sha256:ae0a8be51933b7e177a79fbf32425f5aa8800cb64a6b63313e47271dd7038434"}, - {file = "cscopy-0.0.1.tar.gz", hash = "sha256:6d4a9d48fe6d46e31255dd1ad6d755558905f8a699956af45cb3a1cd4bfef436"}, + {file = "cscopy-0.0.2-py3-none-any.whl", hash = "sha256:78debba1d1ac40e187a54f0488f6c97d209e591b95e37d50bab15365b15f42a6"}, + {file = "cscopy-0.0.2.tar.gz", hash = "sha256:77dc543df8aaa1685bca6ccdb76bfeb4c006afd9392340d012e94eeea63140df"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +requires_python = ">=3.5" +summary = "Decorators for Humans" +groups = ["dev"] +marker = "python_version > \"3.6\"" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +groups = ["dev"] +marker = "python_version < \"3.11\" and python_version > \"3.6\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[[package]] +name = "executing" +version = "2.1.0" +requires_python = ">=3.8" +summary = "Get the currently executing AST node of a frame, and other information" +groups = ["dev"] +marker = "python_version > \"3.6\"" +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[[package]] +name = "ipdb" +version = "0.13.13" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "IPython-enabled pdb" +groups = ["dev"] +dependencies = [ + "decorator; python_version == \"3.5\"", + "decorator; python_version == \"3.6\"", + "decorator; python_version > \"3.6\" and python_version < \"3.11\"", + "decorator; python_version >= \"3.11\"", + "decorator<5.0.0; python_version == \"2.7\"", + "decorator<5.0.0; python_version == \"3.4\"", + "ipython<6.0.0,>=5.1.0; python_version == \"2.7\"", + "ipython<7.0.0,>=6.0.0; python_version == \"3.4\"", + "ipython<7.10.0,>=7.0.0; python_version == \"3.5\"", + "ipython<7.17.0,>=7.16.3; python_version == \"3.6\"", + "ipython>=7.31.1; python_version > \"3.6\" and python_version < \"3.11\"", + "ipython>=7.31.1; python_version >= \"3.11\"", + "pathlib; python_version == \"2.7\"", + "toml>=0.10.2; python_version == \"2.7\"", + "toml>=0.10.2; python_version == \"3.4\"", + "toml>=0.10.2; python_version == \"3.5\"", + "tomli; python_version == \"3.6\"", + "tomli; python_version > \"3.6\" and python_version < \"3.11\"", +] +files = [ + {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, + {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, +] + +[[package]] +name = "ipython" +version = "8.28.0" +requires_python = ">=3.10" +summary = "IPython: Productive Interactive Computing" +groups = ["dev"] +marker = "python_version > \"3.6\"" +dependencies = [ + "colorama; sys_platform == \"win32\"", + "decorator", + "exceptiongroup; python_version < \"3.11\"", + "jedi>=0.16", + "matplotlib-inline", + "pexpect>4.3; sys_platform != \"win32\" and sys_platform != \"emscripten\"", + "prompt-toolkit<3.1.0,>=3.0.41", + "pygments>=2.4.0", + "stack-data", + "traitlets>=5.13.0", + "typing-extensions>=4.6; python_version < \"3.12\"", +] +files = [ + {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, + {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, +] + +[[package]] +name = "jedi" +version = "0.19.1" +requires_python = ">=3.6" +summary = "An autocompletion tool for Python that can be used for text editors." +groups = ["dev"] +marker = "python_version > \"3.6\"" +dependencies = [ + "parso<0.9.0,>=0.8.3", +] +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, ] [[package]] @@ -79,6 +201,21 @@ files = [ {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +requires_python = ">=3.8" +summary = "Inline Matplotlib backend for Jupyter" +groups = ["dev"] +marker = "python_version > \"3.6\"" +dependencies = [ + "traitlets", +] +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -101,6 +238,69 @@ files = [ {file = "objprint-0.2.3.tar.gz", hash = "sha256:73d0ad5a7c3151fce634c8892e5c2a050ccae3b1a353bf1316f08b7854da863b"}, ] +[[package]] +name = "parso" +version = "0.8.4" +requires_python = ">=3.6" +summary = "A Python Parser" +groups = ["dev"] +marker = "python_version > \"3.6\"" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +summary = "Pexpect allows easy control of interactive console applications." +groups = ["dev"] +marker = "(sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version > \"3.6\"" +dependencies = [ + "ptyprocess>=0.5", +] +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +requires_python = ">=3.7.0" +summary = "Library for building powerful interactive command lines in Python" +groups = ["dev"] +marker = "python_version > \"3.6\"" +dependencies = [ + "wcwidth", +] +files = [ + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +summary = "Run a subprocess in a pseudo terminal" +groups = ["dev"] +marker = "(sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version > \"3.6\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +summary = "Safely evaluate AST nodes without side effects" +groups = ["dev"] +marker = "python_version > \"3.6\"" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + [[package]] name = "pydantic" version = "2.8.2" @@ -207,7 +407,7 @@ name = "pygments" version = "2.18.0" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -251,6 +451,34 @@ files = [ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" +groups = ["dev"] +marker = "python_version > \"3.6\"" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +summary = "Extract data from python stack frames and tracebacks for informative displays" +groups = ["dev"] +marker = "python_version > \"3.6\"" +dependencies = [ + "asttokens>=2.1.0", + "executing>=1.2.0", + "pure-eval", +] +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + [[package]] name = "structlog" version = "24.4.0" @@ -262,6 +490,30 @@ files = [ {file = "structlog-24.4.0.tar.gz", hash = "sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4"}, ] +[[package]] +name = "tomli" +version = "2.0.2" +requires_python = ">=3.8" +summary = "A lil' TOML parser" +groups = ["dev"] +marker = "python_version > \"3.6\" and python_version < \"3.11\"" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +requires_python = ">=3.8" +summary = "Traitlets Python configuration system" +groups = ["dev"] +marker = "python_version > \"3.6\"" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + [[package]] name = "typer" version = "0.12.3" @@ -299,7 +551,7 @@ name = "typing-extensions" version = "4.12.2" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -339,13 +591,27 @@ files = [ {file = "viztracer-0.16.3.tar.gz", hash = "sha256:943cb874cf92cdc786cd87938ac64ea081e3ae06ef73f577deac5b4a2a9621d5"}, ] +[[package]] +name = "wcwidth" +version = "0.2.13" +summary = "Measures the displayed width of unicode strings in a terminal" +groups = ["dev"] +marker = "python_version > \"3.6\"" +dependencies = [ + "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"", +] +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + [[package]] name = "whatthepatch-pydantic" -version = "1.0.6a1" +version = "1.0.6a2" requires_python = ">=3.7" summary = "A patch parsing and application library." groups = ["default"] files = [ - {file = "whatthepatch_pydantic-1.0.6a1-py3-none-any.whl", hash = "sha256:729215b49682ca52b4f85b8ddfe2d61927b162235f98561796e5fef512c0b796"}, - {file = "whatthepatch_pydantic-1.0.6a1.tar.gz", hash = "sha256:29326a7e8edd54f419b84d9166abf13a94d0537501ed1927369d2f997be89c38"}, + {file = "whatthepatch_pydantic-1.0.6a2-py3-none-any.whl", hash = "sha256:d767a6e149d36ea07785eb56363a4b033c19b944a24dc8bcd3c0af6beb0744b9"}, + {file = "whatthepatch_pydantic-1.0.6a2.tar.gz", hash = "sha256:a929f39579fe45bbeb7a4c599e1bd2ab93b9e947f2cfa02ddd82928469785880"}, ] diff --git a/pyproject.toml b/pyproject.toml index 79850e3..737e956 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ppatch" -version = "0.0.5b3" +version = "0.0.5b5" description = "Universal tool to analysis patches, bulit for Linux Kernel." authors = [ {name = "jingfelix", email = "jingfelix@outlook.com"}, @@ -10,7 +10,7 @@ dependencies = [ "pydantic>=2.5.3", "pydantic-settings>=2.2.1", "cscopy>=0.0.1", - "whatthepatch-pydantic==1.0.6a1", + "whatthepatch-pydantic==1.0.6a2", "structlog>=24.2.0", ] requires-python = ">=3.10" @@ -30,4 +30,5 @@ distribution = true [tool.pdm.dev-dependencies] dev = [ "viztracer>=0.16.3", + "ipdb>=0.13.13", ] diff --git a/requirements.txt b/requirements.txt index 57fcc6b..d0ee3f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,25 @@ # Please do not edit it manually. annotated-types==0.7.0 +asttokens==2.4.1; python_version > "3.6" click==8.1.7 -colorama==0.4.6; platform_system == "Windows" -cscopy==0.0.1 +colorama==0.4.6; sys_platform == "win32" and python_version > "3.6" or platform_system == "Windows" +cscopy==0.0.2 +decorator==5.1.1; python_version > "3.6" +exceptiongroup==1.2.2; python_version < "3.11" and python_version > "3.6" +executing==2.1.0; python_version > "3.6" +ipdb==0.13.13 +ipython==8.28.0; python_version > "3.6" +jedi==0.19.1; python_version > "3.6" markdown-it-py==3.0.0 +matplotlib-inline==0.1.7; python_version > "3.6" mdurl==0.1.2 objprint==0.2.3 +parso==0.8.4; python_version > "3.6" +pexpect==4.9.0; (sys_platform != "win32" and sys_platform != "emscripten") and python_version > "3.6" +prompt-toolkit==3.0.48; python_version > "3.6" +ptyprocess==0.7.0; (sys_platform != "win32" and sys_platform != "emscripten") and python_version > "3.6" +pure-eval==0.2.3; python_version > "3.6" pydantic==2.8.2 pydantic-core==2.20.1 pydantic-settings==2.4.0 @@ -15,9 +28,14 @@ pygments==2.18.0 python-dotenv==1.0.1 rich==13.7.1 shellingham==1.5.4 +six==1.16.0; python_version > "3.6" +stack-data==0.6.3; python_version > "3.6" structlog==24.4.0 +tomli==2.0.2; python_version > "3.6" and python_version < "3.11" +traitlets==5.14.3; python_version > "3.6" typer==0.12.3 typer[all]==0.12.3 typing-extensions==4.12.2 viztracer==0.16.3 -whatthepatch-pydantic==1.0.6a1 +wcwidth==0.2.13; python_version > "3.6" +whatthepatch-pydantic==1.0.6a2 diff --git a/src/ppatch/__init__.py b/src/ppatch/__init__.py index b8b7c98..4e2a885 100644 --- a/src/ppatch/__init__.py +++ b/src/ppatch/__init__.py @@ -1 +1,2 @@ -from .app import __version__, app +from .__version__ import __version__ +from .app import app diff --git a/src/ppatch/__version__.py b/src/ppatch/__version__.py new file mode 100644 index 0000000..7d719a3 --- /dev/null +++ b/src/ppatch/__version__.py @@ -0,0 +1,9 @@ +import sys + +if sys.version_info >= (3, 10): + import importlib.metadata as importlib_metadata +else: + import importlib_metadata + + +__version__ = importlib_metadata.version("ppatch") diff --git a/src/ppatch/app.py b/src/ppatch/app.py index acba985..1e29ea6 100644 --- a/src/ppatch/app.py +++ b/src/ppatch/app.py @@ -9,8 +9,6 @@ from ppatch.utils.common import post_executed -__version__ = "0.0.5b3" - app = typer.Typer(result_callback=post_executed, no_args_is_help=True) @@ -23,6 +21,9 @@ def callback(verbose: bool = False, version: bool = False): logger.setLevel(logging.DEBUG) if version: + + from ppatch.__version__ import __version__ + console = Console() console.print(f"ppatch version {__version__}") raise typer.Exit() @@ -30,8 +31,9 @@ def callback(verbose: bool = False, version: bool = False): from ppatch.commands.apply import apply from ppatch.commands.auto import auto +from ppatch.commands.cache import clear_cache from ppatch.commands.get import getpatches from ppatch.commands.help import show_settings from ppatch.commands.show import show from ppatch.commands.symbol import getsymbol_command -from ppatch.commands.trace import trace +from ppatch.commands.trace import trace_command diff --git a/src/ppatch/commands/apply.py b/src/ppatch/commands/apply.py index 033ed03..04fffcb 100644 --- a/src/ppatch/commands/apply.py +++ b/src/ppatch/commands/apply.py @@ -5,14 +5,14 @@ import whatthepatch from ppatch.app import app, logger -from ppatch.model import Diff, File -from ppatch.utils.parse import wtp_diff_to_diff +from ppatch.model import File +from ppatch.utils.parse import parse_patch from ppatch.utils.resolve import apply_change @app.command() def apply( - filename: str, + # filename: str, patch_path: str, reverse: Annotated[bool, typer.Option("-R", "--reverse")] = False, fuzz: Annotated[int, typer.Option("-F", "--fuzz")] = 0, @@ -20,36 +20,47 @@ def apply( """ Apply a patch to a file. """ - if not os.path.exists(filename): - logger.error(f"Warning: {filename} not found!") - return if not os.path.exists(patch_path): logger.error(f"Warning: {patch_path} not found!") - return + raise typer.Exit(code=1) - logger.info(f"Apply patch {patch_path} to {filename}") + if reverse: + logger.info("Reversing patch...") - origin_file = File(file_path=filename) - new_line_list = origin_file.line_list + has_failed = False with open(patch_path, mode="r", encoding="utf-8") as (f): - diffes = whatthepatch.parse_patch(f.read()) + diffes = parse_patch(f.read()).diff for diff in diffes: - diff = wtp_diff_to_diff(diff) - if diff.header.old_path == filename or diff.header.new_path == filename: + + old_filename = diff.header.old_path + new_filename = diff.header.new_path + + if os.path.exists(old_filename): + + logger.info(f"Applying patch to {old_filename}...") + + new_line_list = File(file_path=old_filename).line_list apply_result = apply_change( diff.hunks, new_line_list, reverse=reverse, fuzz=fuzz ) - # TODO: 检查失败数 new_line_list = apply_result.new_line_list + + # 检查失败数 + for failed_hunk in apply_result.failed_hunk_list: + has_failed = True + logger.error(f"Failed hunk: {failed_hunk.index}") else: - logger.info(f"Do not match with {filename}, skip") - # new_line_list, _ = _apply(patch_path, filename, new_line_list, "default") - - # 写入文件 - with open(filename, mode="w+", encoding="utf-8") as (f): - for line in new_line_list: - if line.status: - f.write(line.content + "\n") + logger.error(f"{old_filename} not found!") + raise typer.Exit(code=1) + + # 写入文件 + if not has_failed: + with open(new_filename, mode="w+", encoding="utf-8") as f: + for line in new_line_list: + if line.status: + f.write(line.content + "\n") + + raise typer.Exit(code=1 if has_failed else 0) diff --git a/src/ppatch/commands/auto.py b/src/ppatch/commands/auto.py index dfc5ae5..7824eb6 100644 --- a/src/ppatch/commands/auto.py +++ b/src/ppatch/commands/auto.py @@ -74,10 +74,10 @@ def auto(filename: str, output: str = typer.Option("", "--output", "-o")): f"{len(hunk_list)} hunk(s) failed in {file_name} with subject {subject}" ) - sha_list = getpatches(file_name, subject, save=True) + hit_list, sha_list = getpatches(file_name, subject, save=True) sha_for_sure = None - for sha in sha_list: + for sha in hit_list: with open( os.path.join( settings.base_dir, @@ -108,7 +108,7 @@ def auto(filename: str, output: str = typer.Option("", "--output", "-o")): logger.info(f"Hunk list: {hunk_list}") conflict_list = trace( - file_name, from_commit=sha_for_sure, flag_hunk_list=hunk_list + sha_list, file_name, from_commit=sha_for_sure, flag_hunk_list=hunk_list ) line_list = File(file_path=file_name).line_list diff --git a/src/ppatch/commands/cache.py b/src/ppatch/commands/cache.py new file mode 100644 index 0000000..5cf11b2 --- /dev/null +++ b/src/ppatch/commands/cache.py @@ -0,0 +1,32 @@ +import os + +import typer +from rich import print as rprint +from rich.prompt import Confirm + +from ppatch.app import app +from ppatch.config import settings + + +def clear_patch_cache(): + # patch_store_dir 和 base_dir 在 config 中定义 + patch_store_path = os.path.join(settings.base_dir, settings.patch_store_dir) + + if os.path.exists(patch_store_path) and os.path.isdir(patch_store_path): + for item in os.listdir(patch_store_path): + if os.path.isfile(item): + os.remove(item) + typer.echo("Cache cleared.") + else: + typer.echo(f"Cache directory {patch_store_path} does not exist.") + + +@app.command(name="clear-cache") +def clear_cache(): + """Clear patch cache""" + rprint("[bold red]Warning: This operation will delete all patch caches![/bold red]") + if Confirm.ask("Are you sure you want to continue?"): + pass + # clear_patch_cache() + else: + rprint("[blue]Operation cancelled.[/blue]") diff --git a/src/ppatch/commands/get.py b/src/ppatch/commands/get.py index a6ad289..877e870 100644 --- a/src/ppatch/commands/get.py +++ b/src/ppatch/commands/get.py @@ -8,7 +8,9 @@ @app.command("get") -def getpatches(filename: str, expression: str = None, save: bool = True) -> list[str]: +def getpatches( + filename: str, expression: str = None, save: bool = True +) -> tuple[list[str], list[str]]: """ Get patches of a file. """ @@ -34,14 +36,16 @@ def getpatches(filename: str, expression: str = None, save: bool = True) -> list pattern = re.compile(expression) if expression is not None else None + hit_list = [] sha_list = [] for patch in patches: sha = patch.splitlines()[0].split(" ")[1] + sha_list.append(sha) if pattern is not None and ( pattern.search(patch) is not None or expression in patch ): - sha_list.append(sha) + hit_list.append(sha) logger.info(f"Patch {sha} found with expression {expression}") patch_path = os.path.join( @@ -55,7 +59,7 @@ def getpatches(filename: str, expression: str = None, save: bool = True) -> list with open(patch_path, mode="w+", encoding="utf-8") as (f): f.write(patch) - if pattern and len(sha_list) == 0: + if pattern and len(hit_list) == 0: logger.error(f"No patch found with expression {expression}") - return sha_list + return hit_list, sha_list diff --git a/src/ppatch/commands/symbol.py b/src/ppatch/commands/symbol.py index a48f434..9a10bd4 100644 --- a/src/ppatch/commands/symbol.py +++ b/src/ppatch/commands/symbol.py @@ -1,32 +1,78 @@ +import os + from cscopy.cli import CscopeCLI +from cscopy.model import SearchResult from cscopy.workspace import CscopeWorkspace from ppatch.app import app, logger - -# 一个新的设计思路:不直接调用作为 command 的函数,将 command 和具体功能区分开 -# 写这个临时的 command 的时候可以简单试一下 +from ppatch.utils.parse import parse_patch @app.command("symbol") def getsymbol_command( - files: list[str] = [], + file: str, symbols: list[str] = [], ): - getsymbol(files, symbols) + getsymbol(file, symbols) -def getsymbol(files: list[str], symbols: list[str]): - logger.debug(f"Getting symbols from {files} with {symbols}") +def getsymbol(file: str, symbols: list[str]) -> dict[str, list[SearchResult]]: + logger.debug(f"Getting symbols from {file} with {symbols}") cli = CscopeCLI("/usr/bin/cscope") - # 之后还是要按 patch/file/hunk 划分的,这里只是一个临时的 command - # 解析的思路:先按文件划分,再按每个 hunk 划分 - # DONE 现在需要做的:增强 ppatch 的补丁解析功能,使其能够解析出 diff-hunk-change 三层结构 + files: list[str] = [] + + # 针对 patch 类型的文件需要进行特殊处理 + if file.endswith(".patch"): + diffes = parse_patch( + os.read(os.open(file, os.O_RDONLY), os.path.getsize(file)).decode( + "utf-8", errors="ignore" + ) + ).diff + + for index, diff in enumerate(diffes): + for hunk in diff.hunks: + # add_path = f"/dev/shm/{index}-{hunk.index}-add" + # del_path = f"/dev/shm/{index}-{hunk.index}-del" + + # with open(add_path, "w") as f: + # for change in hunk.middle: + # if change.new is not None and change.old is None: + # f.write(change.line + "\n") + + # with open(del_path, "w") as f: + # for change in hunk.middle: + # if change.new is None and change.old is not None: + # f.write(change.line + "\n") + # files.append(add_path) + # files.append(del_path) + + hunk_path = f"/dev/shm/{index}-{hunk.index}" + with open(hunk_path, "w") as f: + for change in hunk.middle: + if change.new is not None and change.old is None: + f.write(change.line + "\n") + if change.new is None and change.old is not None: + f.write(change.line + "\n") + + files.append(hunk_path) + + else: + files = [file] + + res = {} with CscopeWorkspace(files, cli) as workspace: for symbol in symbols: result = workspace.search_c_symbol(symbol) + res[symbol] = result for res in result: logger.info(f"{res.file}:{res.line} {res.content}") + + if file.endswith(".patch"): + for f in files: + os.remove(f) + + return res diff --git a/src/ppatch/commands/trace.py b/src/ppatch/commands/trace.py index e47ff3c..2d72b67 100644 --- a/src/ppatch/commands/trace.py +++ b/src/ppatch/commands/trace.py @@ -5,14 +5,16 @@ from ppatch.app import app, logger from ppatch.config import settings -from ppatch.model import ApplyResult, Diff, File +from ppatch.model import ApplyResult, File from ppatch.utils.common import process_title from ppatch.utils.parse import wtp_diff_to_diff from ppatch.utils.resolve import apply_change @app.command() -def trace(filename: str, from_commit: str = "", flag_hunk_list: list[int] = None): +def trace_command( + filename: str, from_commit: str = "", flag_hunk_list: list[int] = None +) -> dict[str, ApplyResult]: flag_hunk_list = [] if flag_hunk_list is None else flag_hunk_list if not os.path.exists(filename): @@ -34,6 +36,15 @@ def trace(filename: str, from_commit: str = "", flag_hunk_list: list[int] = None sha_list = output.splitlines() + return trace(sha_list, filename, from_commit, flag_hunk_list) + + +def trace( + sha_list: list[str], + filename: str, + from_commit: str = "", + flag_hunk_list: list[int] = None, +) -> dict[str, ApplyResult]: # 在 sha_list 中找到 from_commit 和 to_commit 的位置 from_index = sha_list.index(from_commit) if from_commit else -1 if from_index == -1: @@ -104,7 +115,7 @@ def trace(filename: str, from_commit: str = "", flag_hunk_list: list[int] = None if diff.header.old_path == filename or diff.header.new_path == filename: try: apply_result = apply_change( - diff.hunks, new_line_list, trace=True, flag=True + diff.hunks, new_line_list, trace=True, flag=True, fuzz=3 ) new_line_list = apply_result.new_line_list diff --git a/src/ppatch/utils/resolve.py b/src/ppatch/utils/resolve.py index 1d5eeef..e2deaf3 100644 --- a/src/ppatch/utils/resolve.py +++ b/src/ppatch/utils/resolve.py @@ -20,15 +20,6 @@ def apply_change( if fuzz > settings.max_diff_lines or fuzz < 0: raise Exception(f"fuzz value should be less than {settings.max_diff_lines}") - # # 如果反向,则交换所有的 old 和 new - # # reverse 不支持 flag trace - # if reverse: - # if flag: - # raise Exception("flag is not supported with reverse") - - # for change in changes: - # change.old, change.new = change.new, change.old - # TODO: 注意,修改了该函数后,需要将此处修改为对 hunk 内的 change 进行修改 if reverse: if flag: @@ -38,20 +29,37 @@ def apply_change( for change in hunk.context + hunk.middle + hunk.post: change.old, change.new = change.new, change.old - for hunk in hunk_list: - # hunk.context 取后 3- fuzz 个 - hunk.context = hunk.context[fuzz:] - hunk.post = hunk.post[0 : 3 - fuzz] - # 然后对每个hunk进行处理,添加偏移 changes: list[Change] = [] failed_hunk_list: list[Hunk] = [] + last_pos = None for hunk in hunk_list: - changes_to_search = hunk.context + hunk.middle + hunk.post - pos_list = find_list_positions( - [line.content for line in target], - [change.line for change in changes_to_search if change.old is not None], - ) + + current_hunk_fuzz = 0 + + while current_hunk_fuzz <= fuzz: + + # hunk.context = hunk.context[1:] + # hunk.post = hunk.post[: fuzz - current_hunk_fuzz] + + logger.debug( + f"current_fuzz: {current_hunk_fuzz} len(hunk.context): {len(hunk.context)} len(hunk.post): {len(hunk.post)}" + ) + + changes_to_search = hunk.context + hunk.middle + hunk.post + pos_list = find_list_positions( + [line.content for line in target], + [change.line for change in changes_to_search if change.old is not None], + ) + + if len(pos_list) != 0: + break + + current_hunk_fuzz += 1 + + if current_hunk_fuzz <= fuzz: + hunk.context = hunk.context[1:] + hunk.post = hunk.post[: 3 - current_hunk_fuzz] # 初始位置是 context 的第一个 # 注意,前几个有可能是空 @@ -62,21 +70,21 @@ def apply_change( break # TODO: 这里不太对,要想一下怎么处理,不应该是加入 failed hunk list - # 仅在 -F 3 且只有添加行 的情况下出现 + # 仅在 -F 3 且只有添加行 的情况下出现(指与 GNU patch 行为不一致) # 也可以看一下这样的情况有多少 - if pos_origin is None: + if current_hunk_fuzz == fuzz and not pos_origin: failed_hunk_list.append(hunk) - hunk_list.remove(hunk) + logger.debug(f"Could not determine pos_origin") + logger.warning(f"Apply failed with hunk {hunk.index}") continue - offset_list = [pos + 1 - pos_origin for pos in pos_list] # 确认这里是否需要 1? - - if len(offset_list) == 0: + if len(pos_list) == 0: failed_hunk_list.append(hunk) + logger.debug(f"Could not determine proper position") logger.warning(f"Apply failed with hunk {hunk.index}") - # hunk_list.remove(hunk) continue - # raise Exception("offsets do not intersect") + + offset_list = [pos + 1 - pos_origin for pos in pos_list] # 确认这里是否需要 1? # 计算最小 offset min_offset = None @@ -84,11 +92,29 @@ def apply_change( if min_offset is None or abs(offset) < abs(min_offset): min_offset = offset + logger.info( + f"Apply hunk {hunk.index} with offset {min_offset} fuzz {current_hunk_fuzz}" + ) + + pos_new = pos_origin + min_offset - 1 + # 处理 pos_new 小于 last_pos 的情况 + logger.debug(f"pos_origin: {pos_origin}, last_pos: {last_pos}") + if last_pos is None: + last_pos = pos_new + elif pos_new < last_pos: + # 特别主要 pos_new 小于 last_pos 的情况 + logger.warning(f"Apply failed with hunk {hunk.index}") + logger.error(f"pos: {pos_new} is greater than last_pos: {last_pos}") + failed_hunk_list.append(hunk) + continue + else: + last_pos = pos_new + # 如果 reverse 为 True,则直接替换,不进行 flag 追踪 if reverse: # 直接按照 pos 进行替换 # 选择 offset 最小的 pos - pos_new = pos_origin + min_offset - 1 + # pos_new = pos_origin + min_offset - 1 # 移动到上面 old_lines = [ change.line @@ -105,7 +131,7 @@ def apply_change( for i in range(len(old_lines)): if target[pos_new + i].content != old_lines[i]: raise Exception( - f'line {pos_new + i}, "{target[pos_new + i].content}" does not match "{new_lines[i]}"' + f'line {pos_new + i}, "{target[pos_new + i].content}" does not match "{old_lines[i]}"' ) # 以切片的方式进行替换