pax_global_header00006660000000000000000000000064144254654470014531gustar00rootroot0000000000000052 comment=cdc645845fe5f17ac9586aa350a76b32fccf88ba whatthepatch-1.0.5/000077500000000000000000000000001442546544700142205ustar00rootroot00000000000000whatthepatch-1.0.5/.envrc000066400000000000000000000000101442546544700153250ustar00rootroot00000000000000use nix whatthepatch-1.0.5/.github/000077500000000000000000000000001442546544700155605ustar00rootroot00000000000000whatthepatch-1.0.5/.github/workflows/000077500000000000000000000000001442546544700176155ustar00rootroot00000000000000whatthepatch-1.0.5/.github/workflows/build.yml000066400000000000000000000036601442546544700214440ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Build on: push: branches: [main] pull_request: branches: [main] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} for ${{ matrix.os }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Display Python version run: python -c "import sys; print(sys.version)" - name: Install dependencies run: | python -m pip install --upgrade pip pip install build pip install flake8 pip install pytest - name: Build package run: | python -m build - name: Lint run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test run: | pytest --doctest-modules --doctest-glob='*.rst' --junitxml=junit/test-results-${{ matrix.python-version }}.xml - name: Upload pytest test results uses: actions/upload-artifact@v3 with: name: pytest-results-${{ matrix.python-version }} path: junit/test-results-${{ matrix.python-version }}.xml # Use always() to always run this step to publish test results when there are test failures if: ${{ always() }} whatthepatch-1.0.5/.github/workflows/publish.yml000066400000000000000000000016121442546544700220060ustar00rootroot00000000000000# This workflows will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Publish package on: release: types: [created] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.x" - name: Install dependencies run: | python -m pip install --upgrade pip pip install build - name: Build package run: python -m build - name: Publish package uses: pypa/gh-action-pypi-publish@37f50c210e3d2f9450da2cd423303d6a14a6e29f # release/v1 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} whatthepatch-1.0.5/.gitignore000066400000000000000000000060261442546544700162140ustar00rootroot00000000000000.direnv # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ junit/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ whatthepatch-1.0.5/.vscode/000077500000000000000000000000001442546544700155615ustar00rootroot00000000000000whatthepatch-1.0.5/.vscode/settings.json000066400000000000000000000003431442546544700203140ustar00rootroot00000000000000{ "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix", "python.testing.pytestArgs": ["tests"], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.formatting.provider": "black" } whatthepatch-1.0.5/CODE_OF_CONDUCT.md000066400000000000000000000121531442546544700170210ustar00rootroot00000000000000 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. whatthepatch-1.0.5/HISTORY.md000066400000000000000000000043171442546544700157100ustar00rootroot00000000000000# next Nothing yet :) # 1.0.5 - PR #57 bugfix:min line in binary diff (Thanks, @babenek and @abbradar) # 1.0.4 - PR #53 git binary patch support (Thanks, @babenek) - PR #51 Remove redundant wheel dep from pyproject.toml (Thanks, @mgorny) - Add basic nix release support # 1.0.3 - PR #46 Code optimization for unified diff parsing (Thanks, @babenek) - Package using build module and pyproject.toml - Support up to 3.11 - Drop support up to 3.6 # 1.0.2 - Support up to 3.9 - PR #42 Fix unified diff parse error (Thanks, @kkpattern) # 1.0.1 - PR #37 Replace nose with pytest (Thanks, @MeggyCal) - PR #39 Fix bug where context diffs would not parse (Thanks, @FallenSky2077) # 1.0.0 - Issue #26 fix where hardcoded "/tmp" reference was being used - Support up to Python 3.8 - Drop support for Python 2, 3.4 Dev-only: - Bump Code of Conduct to 2.0 - Setup Github Actions for package publishing - Setup Github Actions for build and testing - Move off Travis and Tox in favor of Github Actions # 0.0.6 - PR #13 Support for reverse patching (Thanks, @graingert) - This is a breaking change that converted the parsed tuples into namedtuples and added the hunk number to that tuple - PR #20 Support up to Python 3.7, drop support for 3.3 (Thanks, @graingert) - Issue #18 fix for empty file adds in git # 0.0.5 - PR #6 Added better support for binary files. (Thanks, @ramusus) - PR #3 Added support for git index revision ids that have more than 7 characters (Thanks, @jopereria) # 0.0.4 - PR #2 Bug fix for one-liner diffs (Thanks, @thoward) - Issue #1 fix where some old real test cases were left failing - Added a Code of Conduct - Added support for Python 3.5 # 0.0.3 - Better matching for almost all patch headers - Support patches that have entire hunks removed - Support git patches that are missing index header file modes - Moved to MIT license - Officially adopt Python 3 # 0.0.2 - Initial support to apply parsed patches - Support diffs that do not have headers # 0.0.1 - The very first release that included parsing support for patches in unified diff format, context diff format, ed diff format, git, bazaar, subversion, and cvs. whatthepatch-1.0.5/LICENSE000066400000000000000000000021101442546544700152170ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2012 -- 2020 Christopher S. Corley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. whatthepatch-1.0.5/MANIFEST.in000066400000000000000000000001341442546544700157540ustar00rootroot00000000000000include README.rst LICENSE recursive-include tests *.py recursive-include tests/casefiles * whatthepatch-1.0.5/Pipfile000066400000000000000000000002601442546544700155310ustar00rootroot00000000000000[[source]] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] pytest = "*" flake8 = "*" black = "*" build = "*" [pipenv] allow_prereleases = true whatthepatch-1.0.5/Pipfile.lock000066400000000000000000000202471442546544700164670ustar00rootroot00000000000000{ "_meta": { "hash": { "sha256": "52e1786b721fabd51a0e37861f0f6b6f2631e305b8eea2158f0cb52a8fa84d41" }, "pipfile-spec": 6, "requires": {}, "sources": [ { "name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true } ] }, "default": {}, "develop": { "attrs": { "hashes": [ "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" ], "markers": "python_version >= '3.5'", "version": "==22.1.0" }, "black": { "hashes": [ "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7", "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6", "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650", "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb", "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d", "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d", "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de", "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395", "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae", "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa", "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef", "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383", "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66", "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87", "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d", "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0", "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b", "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458", "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4", "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1", "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff" ], "index": "pypi", "version": "==22.10.0" }, "build": { "hashes": [ "sha256:1a07724e891cbd898923145eb7752ee7653674c511378eb9c7691aab1612bc3c", "sha256:38a7a2b7a0bdc61a42a0a67509d88c71ecfc37b393baba770fae34e20929ff69" ], "index": "pypi", "version": "==0.9.0" }, "click": { "hashes": [ "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" ], "markers": "python_version >= '3.7'", "version": "==8.1.3" }, "exceptiongroup": { "hashes": [ "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a", "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2" ], "markers": "python_version < '3.11'", "version": "==1.0.1" }, "flake8": { "hashes": [ "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248" ], "index": "pypi", "version": "==5.0.4" }, "iniconfig": { "hashes": [ "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" ], "version": "==1.1.1" }, "mccabe": { "hashes": [ "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" ], "markers": "python_version >= '3.6'", "version": "==0.7.0" }, "mypy-extensions": { "hashes": [ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" ], "version": "==0.4.3" }, "packaging": { "hashes": [ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], "markers": "python_version >= '3.6'", "version": "==21.3" }, "pathspec": { "hashes": [ "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5", "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0" ], "markers": "python_version >= '3.7'", "version": "==0.10.2" }, "pep517": { "hashes": [ "sha256:4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b", "sha256:ae69927c5c172be1add9203726d4b84cf3ebad1edcd5f71fcdc746e66e829f59" ], "markers": "python_version >= '3.6'", "version": "==0.13.0" }, "platformdirs": { "hashes": [ "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7", "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10" ], "markers": "python_version >= '3.7'", "version": "==2.5.4" }, "pluggy": { "hashes": [ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], "markers": "python_version >= '3.6'", "version": "==1.0.0" }, "pycodestyle": { "hashes": [ "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b" ], "markers": "python_version >= '3.6'", "version": "==2.9.1" }, "pyflakes": { "hashes": [ "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3" ], "markers": "python_version >= '3.6'", "version": "==2.5.0" }, "pyparsing": { "hashes": [ "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" ], "markers": "python_full_version >= '3.6.8'", "version": "==3.0.9" }, "pytest": { "hashes": [ "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71", "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59" ], "index": "pypi", "version": "==7.2.0" }, "tomli": { "hashes": [ "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" ], "markers": "python_full_version < '3.11.0a7'", "version": "==2.0.1" } } } whatthepatch-1.0.5/README.rst000066400000000000000000000137221442546544700157140ustar00rootroot00000000000000What The Patch!? ================ What The Patch!? is a library for both parsing and applying patch files. Status ------ .. image:: https://github.com/cscorley/whatthepatch/workflows/Build/badge.svg This has been released as 1.0, but has never had much active development. The functions are stable and have been reliable for several years, even if they are not ideally implemented. Pull requests will always be considered, merged, and released; however, issues may not ever be fixed by the maintainer. Contribute ^^^^^^^^^^ #. Fork this repository #. Create a new branch to work on #. Commit your tests and/or changes #. Push and create a pull request here! Features -------- - Parsing of almost all ``diff`` formats (except forwarded ed): - normal (default, --normal) - copied context (-c, --context) - unified context (-u, --unified) - ed script (-e, --ed) - rcs ed script (-n, --rcs) - Parsing of several SCM patches: - CVS - SVN - Git Installation ------------ This library is available on `PyPI `_ and can be installed via pip: .. code-block:: bash $ pip install whatthepatch Usage ===== Let us say we have a patch file containing some changes, aptly named 'somechanges.patch': .. code-block:: diff --- lao 2012-12-26 23:16:54.000000000 -0600 +++ tzu 2012-12-26 23:16:50.000000000 -0600 @@ -1,7 +1,6 @@ -The Way that can be told of is not the eternal Way; -The name that can be named is not the eternal name. The Nameless is the origin of Heaven and Earth; -The Named is the mother of all things. +The named is the mother of all things. + Therefore let there always be non-being, so we may see their subtlety, And let there always be being, @@ -9,3 +8,6 @@ The two are the same, But after they are produced, they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! Parsing ------- Here is how we would use What The Patch!? in Python to get the changeset for each diff in the patch: .. code-block:: python >>> import whatthepatch >>> import pprint >>> with open('tests/casefiles/diff-unified.diff') as f: ... text = f.read() ... >>> for diff in whatthepatch.parse_patch(text): ... print(diff) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... diff(header=header(index_path=None, old_path='lao', old_version='2013-01-05 16:56:19.000000000 -0600', new_path='tzu', new_version='2013-01-05 16:56:35.000000000 -0600'), changes=[Change(old=1, new=None, line='The Way that can be told of is not the eternal Way;', hunk=1), Change(old=2, new=None, line='The name that can be named is not the eternal name.', hunk=1), Change(old=3, new=1, line='The Nameless is the origin of Heaven and Earth;', hunk=1), Change(old=4, new=None, line='The Named is the mother of all things.', hunk=1), Change(old=None, new=2, line='The named is the mother of all things.', hunk=1), Change(old=None, new=3, line='', hunk=1), Change(old=5, new=4, line='Therefore let there always be non-being,', hunk=1), Change(old=6, new=5, line=' so we may see their subtlety,', hunk=1), Change(old=7, new=6, line='And let there always be being,', hunk=1), Change(old=9, new=8, line='The two are the same,', hunk=2), Change(old=10, new=9, line='But after they are produced,', hunk=2), Change(old=11, new=10, line=' they have different names.', hunk=2), Change(old=None, new=11, line='They both may be called deep and profound.', hunk=2), Change(old=None, new=12, line='Deeper and more profound,', hunk=2), Change(old=None, new=13, line='The door of all subtleties!', hunk=2)], text='...') The changes are listed as they are in the patch, but instead of the +/- syntax of the patch, we get a tuple of two numbers and the text of the line. What these numbers indicate are as follows: #. ``( old=1, new=None, ... )`` indicates line 1 of the file lao was **removed**. #. ``( old=None, new=2, ... )`` indicates line 2 of the file tzu was **inserted**. #. ``( old=5, new=4, ... )`` indicates that line 5 of lao and line 4 of tzu are **equal**. Please note that not all patch formats provide the actual lines modified, so some results will have the text portion of the tuple set to ``None``. Applying -------- To apply a diff to some lines of text, first read the patch and parse it. .. code-block:: python >>> import whatthepatch >>> with open('tests/casefiles/diff-default.diff') as f: ... text = f.read() ... >>> with open('tests/casefiles/lao') as f: ... lao = f.read() ... >>> diff = [x for x in whatthepatch.parse_patch(text)] >>> diff = diff[0] >>> tzu = whatthepatch.apply_diff(diff, lao) >>> tzu # doctest: +NORMALIZE_WHITESPACE ['The Nameless is the origin of Heaven and Earth;', 'The named is the mother of all things.', '', 'Therefore let there always be non-being,', ' so we may see their subtlety,', 'And let there always be being,', ' so we may see their outcome.', 'The two are the same,', 'But after they are produced,', ' they have different names.', 'They both may be called deep and profound.', 'Deeper and more profound,', 'The door of all subtleties!'] If apply does not satisfy your needs and you are on a system that has ``patch`` in ``PATH``, you can also call ``apply_diff(diff, lao, use_patch=True)``. The default is false, and patch is not necessary to apply diffs to text. whatthepatch-1.0.5/pyproject.toml000066400000000000000000000024551442546544700171420ustar00rootroot00000000000000[project] name = "whatthepatch" version = "1.0.5" maintainers = [{ name = "Christopher S. Corley", email = "cscorley@gmail.com" }] requires-python = ">=3.7" readme = "README.rst" description = "A patch parsing and application library." keywords = ["patch", "diff", "parser"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Version Control", "Topic :: Software Development", "Topic :: Text Processing", ] [project.urls] "Homepage" = "https://github.com/cscorley/whatthepatch" "Bug Tracker" = "https://github.com/cscorley/whatthepatch/issues" [build-system] requires = ["setuptools>=65.0.0"] build-backend = "setuptools.build_meta" whatthepatch-1.0.5/release.nix000066400000000000000000000010261442546544700163570ustar00rootroot00000000000000{ lib, python3Packages, setuptools }: with python3Packages; buildPythonPackage rec { pname = "whatthepatch"; version = "1.0.5"; format = "pyproject"; src = ./.; checkInputs = [ pytestCheckHook ]; pythonImportsCheck = [ "whatthepatch" ]; nativeBuildInputs = [ setuptools ]; meta = with lib; { description = "Python library for both parsing and applying patch files"; homepage = "https://github.com/cscorley/whatthepatch"; license = licenses.mit; maintainers = with maintainers; [ cscorley ]; }; } whatthepatch-1.0.5/shell.nix000066400000000000000000000003741442546544700160530ustar00rootroot00000000000000{ pkgs ? import { } }: let whatthepatch = p: p.callPackage ./release.nix { }; pythonEnv = pkgs.python3.withPackages (p: [ p.pytest p.flake8 p.black p.build p.docutils (whatthepatch p) ]); in pkgs.mkShell { packages = [ pythonEnv ]; } whatthepatch-1.0.5/src/000077500000000000000000000000001442546544700150075ustar00rootroot00000000000000whatthepatch-1.0.5/src/whatthepatch/000077500000000000000000000000001442546544700174735ustar00rootroot00000000000000whatthepatch-1.0.5/src/whatthepatch/__init__.py000066400000000000000000000001771442546544700216110ustar00rootroot00000000000000# -*- coding: utf-8 -*- from .patch import parse_patch from .apply import apply_diff __all__ = ["parse_patch", "apply_diff"] whatthepatch-1.0.5/src/whatthepatch/apply.py000066400000000000000000000073261442546544700212020ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os.path import subprocess import tempfile from . import patch from .exceptions import HunkApplyException, SubprocessException from .snippets import remove, which def apply_patch(diffs): """Not ready for use yet""" pass if isinstance(diffs, patch.diff): diffs = [diffs] for diff in diffs: if diff.header.old_path == "/dev/null": text = [] else: with open(diff.header.old_path) as f: text = f.read() new_text = apply_diff(diff, text) with open(diff.header.new_path, "w") as f: f.write(new_text) def _apply_diff_with_subprocess(diff, lines, reverse=False): # call out to patch program patchexec = which("patch") if not patchexec: raise SubprocessException("cannot find patch program", code=-1) tempdir = tempfile.gettempdir() filepath = os.path.join(tempdir, "wtp-" + str(hash(diff.header))) oldfilepath = filepath + ".old" newfilepath = filepath + ".new" rejfilepath = filepath + ".rej" patchfilepath = filepath + ".patch" with open(oldfilepath, "w") as f: f.write("\n".join(lines) + "\n") with open(patchfilepath, "w") as f: f.write(diff.text) args = [ patchexec, "--reverse" if reverse else "--forward", "--quiet", "-o", newfilepath, "-i", patchfilepath, "-r", rejfilepath, oldfilepath, ] ret = subprocess.call(args) with open(newfilepath) as f: lines = f.read().splitlines() try: with open(rejfilepath) as f: rejlines = f.read().splitlines() except IOError: rejlines = None remove(oldfilepath) remove(newfilepath) remove(rejfilepath) remove(patchfilepath) # do this last to ensure files get cleaned up if ret != 0: raise SubprocessException("patch program failed", code=ret) return lines, rejlines def _reverse(changes): def _reverse_change(c): return c._replace(old=c.new, new=c.old) return [_reverse_change(c) for c in changes] def apply_diff(diff, text, reverse=False, use_patch=False): try: lines = text.splitlines() except AttributeError: lines = list(text) if use_patch: return _apply_diff_with_subprocess(diff, lines, reverse) n_lines = len(lines) changes = _reverse(diff.changes) if reverse else diff.changes # check that the source text matches the context of the diff for old, new, line, hunk in changes: # might have to check for line is None here for ed scripts if old is not None and line is not None: if old > n_lines: raise HunkApplyException( 'context line {n}, "{line}" does not exist in source'.format( n=old, line=line ), hunk=hunk, ) if lines[old - 1] != line: raise HunkApplyException( 'context line {n}, "{line}" does not match "{sl}"'.format( n=old, line=line, sl=lines[old - 1] ), hunk=hunk, ) # for calculating the old line r = 0 i = 0 for old, new, line, hunk in changes: if old is not None and new is None: del lines[old - 1 - r + i] r += 1 elif old is None and new is not None: lines.insert(new - 1, line) i += 1 elif old is not None and new is not None: # Sometimes, people remove hunks from patches, making these # numbers completely unreliable. Because they're jerks. pass return lines whatthepatch-1.0.5/src/whatthepatch/exceptions.py000066400000000000000000000014001442546544700222210ustar00rootroot00000000000000class WhatThePatchException(Exception): pass class HunkException(WhatThePatchException): def __init__(self, msg, hunk=None): self.hunk = hunk if hunk is not None: super(HunkException, self).__init__( "{msg}, in hunk #{n}".format(msg=msg, n=hunk) ) else: super(HunkException, self).__init__(msg) class ApplyException(WhatThePatchException): pass class SubprocessException(ApplyException): def __init__(self, msg, code): super(SubprocessException, self).__init__(msg) self.code = code class HunkApplyException(HunkException, ApplyException, ValueError): pass class ParseException(HunkException, ValueError): pass whatthepatch-1.0.5/src/whatthepatch/patch.py000066400000000000000000000711541442546544700211540ustar00rootroot00000000000000# -*- coding: utf-8 -*- import base64 import re import zlib from collections import namedtuple from . import exceptions from .snippets import findall_regex, split_by_regex header = namedtuple( "header", "index_path old_path old_version new_path new_version", ) diffobj = namedtuple("diff", "header changes text") Change = namedtuple("Change", "old new line hunk") file_timestamp_str = "(.+?)(?:\t|:| +)(.*)" # .+? was previously [^:\t\n\r\f\v]+ # general diff regex diffcmd_header = re.compile("^diff.* (.+) (.+)$") unified_header_index = re.compile("^Index: (.+)$") unified_header_old_line = re.compile(r"^--- " + file_timestamp_str + "$") unified_header_new_line = re.compile(r"^\+\+\+ " + file_timestamp_str + "$") unified_hunk_start = re.compile(r"^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@(.*)$") unified_change = re.compile("^([-+ ])(.*)$") context_header_old_line = re.compile(r"^\*\*\* " + file_timestamp_str + "$") context_header_new_line = re.compile("^--- " + file_timestamp_str + "$") context_hunk_start = re.compile(r"^\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*$") context_hunk_old = re.compile(r"^\*\*\* (\d+),?(\d*) \*\*\*\*$") context_hunk_new = re.compile(r"^--- (\d+),?(\d*) ----$") context_change = re.compile("^([-+ !]) (.*)$") ed_hunk_start = re.compile(r"^(\d+),?(\d*)([acd])$") ed_hunk_end = re.compile("^.$") # much like forward ed, but no 'c' type rcs_ed_hunk_start = re.compile(r"^([ad])(\d+) ?(\d*)$") default_hunk_start = re.compile(r"^(\d+),?(\d*)([acd])(\d+),?(\d*)$") default_hunk_mid = re.compile("^---$") default_change = re.compile("^([><]) (.*)$") # Headers # git has a special index header and no end part git_diffcmd_header = re.compile("^diff --git a/(.+) b/(.+)$") git_header_index = re.compile(r"^index ([a-f0-9]+)..([a-f0-9]+) ?(\d*)$") git_header_old_line = re.compile("^--- (.+)$") git_header_new_line = re.compile(r"^\+\+\+ (.+)$") git_header_file_mode = re.compile(r"^(new|deleted) file mode \d{6}$") git_header_binary_file = re.compile("^Binary files (.+) and (.+) differ") git_binary_patch_start = re.compile(r"^GIT binary patch$") git_binary_literal_start = re.compile(r"^literal (\d+)$") git_binary_delta_start = re.compile(r"^delta (\d+)$") base85string = re.compile(r"^[0-9A-Za-z!#$%&()*+;<=>?@^_`{|}~-]+$") bzr_header_index = re.compile("=== (.+)") bzr_header_old_line = unified_header_old_line bzr_header_new_line = unified_header_new_line svn_header_index = unified_header_index svn_header_timestamp_version = re.compile(r"\((?:working copy|revision (\d+))\)") svn_header_timestamp = re.compile(r".*(\(.*\))$") cvs_header_index = unified_header_index cvs_header_rcs = re.compile(r"^RCS file: (.+)(?:,\w{1}$|$)") cvs_header_timestamp = re.compile(r"(.+)\t([\d.]+)") cvs_header_timestamp_colon = re.compile(r":([\d.]+)\t(.+)") old_cvs_diffcmd_header = re.compile("^diff.* (.+):(.*) (.+):(.*)$") def parse_patch(text): try: lines = text.splitlines() except AttributeError: lines = text # maybe use this to nuke all of those line endings? # lines = [x.splitlines()[0] for x in lines] lines = [x if len(x) == 0 else x.splitlines()[0] for x in lines] check = [ unified_header_index, diffcmd_header, cvs_header_rcs, git_header_index, context_header_old_line, unified_header_old_line, ] diffs = [] for c in check: diffs = split_by_regex(lines, c) if len(diffs) > 1: break for diff in diffs: difftext = "\n".join(diff) + "\n" h = parse_header(diff) d = parse_diff(diff) if h or d: yield diffobj(header=h, changes=d, text=difftext) def parse_header(text): h = parse_scm_header(text) if h is None: h = parse_diff_header(text) return h def parse_scm_header(text): try: lines = text.splitlines() except AttributeError: lines = text check = [ (git_header_index, parse_git_header), (old_cvs_diffcmd_header, parse_cvs_header), (cvs_header_rcs, parse_cvs_header), (svn_header_index, parse_svn_header), ] for regex, parser in check: diffs = findall_regex(lines, regex) if len(diffs) > 0: git_opt = findall_regex(lines, git_diffcmd_header) if len(git_opt) > 0: res = parser(lines) if res: old_path = res.old_path new_path = res.new_path if old_path.startswith("a/"): old_path = old_path[2:] if new_path.startswith("b/"): new_path = new_path[2:] return header( index_path=res.index_path, old_path=old_path, old_version=res.old_version, new_path=new_path, new_version=res.new_version, ) else: res = parser(lines) return res return None def parse_diff_header(text): try: lines = text.splitlines() except AttributeError: lines = text check = [ (unified_header_new_line, parse_unified_header), (context_header_old_line, parse_context_header), (diffcmd_header, parse_diffcmd_header), # TODO: # git_header can handle version-less unified headers, but # will trim a/ and b/ in the paths if they exist... (git_header_new_line, parse_git_header), ] for regex, parser in check: diffs = findall_regex(lines, regex) if len(diffs) > 0: return parser(lines) return None # no header? def parse_diff(text): try: lines = text.splitlines() except AttributeError: lines = text check = [ (unified_hunk_start, parse_unified_diff), (context_hunk_start, parse_context_diff), (default_hunk_start, parse_default_diff), (ed_hunk_start, parse_ed_diff), (rcs_ed_hunk_start, parse_rcs_ed_diff), (git_binary_patch_start, parse_git_binary_diff), ] for hunk, parser in check: diffs = findall_regex(lines, hunk) if len(diffs) > 0: return parser(lines) return None def parse_git_header(text): try: lines = text.splitlines() except AttributeError: lines = text old_version = None new_version = None old_path = None new_path = None cmd_old_path = None cmd_new_path = None for line in lines: hm = git_diffcmd_header.match(line) if hm: cmd_old_path = hm.group(1) cmd_new_path = hm.group(2) continue g = git_header_index.match(line) if g: old_version = g.group(1) new_version = g.group(2) continue # git always has it's own special headers o = git_header_old_line.match(line) if o: old_path = o.group(1) n = git_header_new_line.match(line) if n: new_path = n.group(1) binary = git_header_binary_file.match(line) if binary: old_path = binary.group(1) new_path = binary.group(2) if old_path and new_path: if old_path.startswith("a/"): old_path = old_path[2:] if new_path.startswith("b/"): new_path = new_path[2:] return header( index_path=None, old_path=old_path, old_version=old_version, new_path=new_path, new_version=new_version, ) # if we go through all of the text without finding our normal info, # use the cmd if available if cmd_old_path and cmd_new_path and old_version and new_version: if cmd_old_path.startswith("a/"): cmd_old_path = cmd_old_path[2:] if cmd_new_path.startswith("b/"): cmd_new_path = cmd_new_path[2:] return header( index_path=None, # wow, I kind of hate this: # assume /dev/null if the versions are zeroed out old_path="/dev/null" if old_version == "0000000" else cmd_old_path, old_version=old_version, new_path="/dev/null" if new_version == "0000000" else cmd_new_path, new_version=new_version, ) return None def parse_svn_header(text): try: lines = text.splitlines() except AttributeError: lines = text headers = findall_regex(lines, svn_header_index) if len(headers) == 0: return None while len(lines) > 0: i = svn_header_index.match(lines[0]) del lines[0] if not i: continue diff_header = parse_diff_header(lines) if not diff_header: return header( index_path=i.group(1), old_path=i.group(1), old_version=None, new_path=i.group(1), new_version=None, ) opath = diff_header.old_path over = diff_header.old_version if over: oend = svn_header_timestamp_version.match(over) if oend and oend.group(1): over = int(oend.group(1)) elif opath: ts = svn_header_timestamp.match(opath) if ts: opath = opath[: -len(ts.group(1))] oend = svn_header_timestamp_version.match(ts.group(1)) if oend and oend.group(1): over = int(oend.group(1)) npath = diff_header.new_path nver = diff_header.new_version if nver: nend = svn_header_timestamp_version.match(diff_header.new_version) if nend and nend.group(1): nver = int(nend.group(1)) elif npath: ts = svn_header_timestamp.match(npath) if ts: npath = npath[: -len(ts.group(1))] nend = svn_header_timestamp_version.match(ts.group(1)) if nend and nend.group(1): nver = int(nend.group(1)) if type(over) != int: over = None if type(nver) != int: nver = None return header( index_path=i.group(1), old_path=opath, old_version=over, new_path=npath, new_version=nver, ) return None def parse_cvs_header(text): try: lines = text.splitlines() except AttributeError: lines = text headers = findall_regex(lines, cvs_header_rcs) headers_old = findall_regex(lines, old_cvs_diffcmd_header) if headers: # parse rcs style headers while len(lines) > 0: i = cvs_header_index.match(lines[0]) del lines[0] if not i: continue diff_header = parse_diff_header(lines) if diff_header: over = diff_header.old_version if over: oend = cvs_header_timestamp.match(over) oend_c = cvs_header_timestamp_colon.match(over) if oend: over = oend.group(2) elif oend_c: over = oend_c.group(1) nver = diff_header.new_version if nver: nend = cvs_header_timestamp.match(nver) nend_c = cvs_header_timestamp_colon.match(nver) if nend: nver = nend.group(2) elif nend_c: nver = nend_c.group(1) return header( index_path=i.group(1), old_path=diff_header.old_path, old_version=over, new_path=diff_header.new_path, new_version=nver, ) return header( index_path=i.group(1), old_path=i.group(1), old_version=None, new_path=i.group(1), new_version=None, ) elif headers_old: # parse old style headers while len(lines) > 0: i = cvs_header_index.match(lines[0]) del lines[0] if not i: continue d = old_cvs_diffcmd_header.match(lines[0]) if not d: return header( index_path=i.group(1), old_path=i.group(1), old_version=None, new_path=i.group(1), new_version=None, ) # will get rid of the useless stuff for us parse_diff_header(lines) over = d.group(2) if d.group(2) else None nver = d.group(4) if d.group(4) else None return header( index_path=i.group(1), old_path=d.group(1), old_version=over, new_path=d.group(3), new_version=nver, ) return None def parse_diffcmd_header(text): try: lines = text.splitlines() except AttributeError: lines = text headers = findall_regex(lines, diffcmd_header) if len(headers) == 0: return None while len(lines) > 0: d = diffcmd_header.match(lines[0]) del lines[0] if d: return header( index_path=None, old_path=d.group(1), old_version=None, new_path=d.group(2), new_version=None, ) return None def parse_unified_header(text): try: lines = text.splitlines() except AttributeError: lines = text headers = findall_regex(lines, unified_header_new_line) if len(headers) == 0: return None while len(lines) > 1: o = unified_header_old_line.match(lines[0]) del lines[0] if o: n = unified_header_new_line.match(lines[0]) del lines[0] if n: over = o.group(2) if len(over) == 0: over = None nver = n.group(2) if len(nver) == 0: nver = None return header( index_path=None, old_path=o.group(1), old_version=over, new_path=n.group(1), new_version=nver, ) return None def parse_context_header(text): try: lines = text.splitlines() except AttributeError: lines = text headers = findall_regex(lines, context_header_old_line) if len(headers) == 0: return None while len(lines) > 1: o = context_header_old_line.match(lines[0]) del lines[0] if o: n = context_header_new_line.match(lines[0]) del lines[0] if n: over = o.group(2) if len(over) == 0: over = None nver = n.group(2) if len(nver) == 0: nver = None return header( index_path=None, old_path=o.group(1), old_version=over, new_path=n.group(1), new_version=nver, ) return None def parse_default_diff(text): try: lines = text.splitlines() except AttributeError: lines = text old = 0 new = 0 old_len = 0 new_len = 0 r = 0 i = 0 changes = list() hunks = split_by_regex(lines, default_hunk_start) for hunk_n, hunk in enumerate(hunks): if not len(hunk): continue r = 0 i = 0 while len(hunk) > 0: h = default_hunk_start.match(hunk[0]) c = default_change.match(hunk[0]) del hunk[0] if h: old = int(h.group(1)) if len(h.group(2)) > 0: old_len = int(h.group(2)) - old + 1 else: old_len = 0 new = int(h.group(4)) if len(h.group(5)) > 0: new_len = int(h.group(5)) - new + 1 else: new_len = 0 elif c: kind = c.group(1) line = c.group(2) if kind == "<" and (r != old_len or r == 0): changes.append(Change(old + r, None, line, hunk_n)) r += 1 elif kind == ">" and (i != new_len or i == 0): changes.append(Change(None, new + i, line, hunk_n)) i += 1 if len(changes) > 0: return changes return None def parse_unified_diff(text): try: lines = text.splitlines() except AttributeError: lines = text old = 0 new = 0 r = 0 i = 0 old_len = 0 new_len = 0 changes = list() hunks = split_by_regex(lines, unified_hunk_start) for hunk_n, hunk in enumerate(hunks): # reset counters r = 0 i = 0 while len(hunk) > 0: h = unified_hunk_start.match(hunk[0]) del hunk[0] if h: old = int(h.group(1)) if len(h.group(2)) > 0: old_len = int(h.group(2)) else: old_len = 0 new = int(h.group(3)) if len(h.group(4)) > 0: new_len = int(h.group(4)) else: new_len = 0 h = None break for n in hunk: c = unified_change.match(n) if c: kind = c.group(1) line = c.group(2) if kind == "-" and (r != old_len or r == 0): changes.append(Change(old + r, None, line, hunk_n)) r += 1 elif kind == "+" and (i != new_len or i == 0): changes.append(Change(None, new + i, line, hunk_n)) i += 1 elif kind == " ": if r != old_len and i != new_len: changes.append(Change(old + r, new + i, line, hunk_n)) r += 1 i += 1 if len(changes) > 0: return changes return None def parse_context_diff(text): try: lines = text.splitlines() except AttributeError: lines = text old = 0 new = 0 j = 0 k = 0 changes = list() hunks = split_by_regex(lines, context_hunk_start) for hunk_n, hunk in enumerate(hunks): if not len(hunk): continue j = 0 k = 0 parts = split_by_regex(hunk, context_hunk_new) if len(parts) != 2: raise exceptions.ParseException("Context diff invalid", hunk_n) old_hunk = parts[0] new_hunk = parts[1] while len(old_hunk) > 0: o = context_hunk_old.match(old_hunk[0]) del old_hunk[0] if not o: continue old = int(o.group(1)) old_len = int(o.group(2)) + 1 - old while len(new_hunk) > 0: n = context_hunk_new.match(new_hunk[0]) del new_hunk[0] if not n: continue new = int(n.group(1)) new_len = int(n.group(2)) + 1 - new break break # now have old and new set, can start processing? if len(old_hunk) > 0 and len(new_hunk) == 0: msg = "Got unexpected change in removal hunk: " # only removes left? while len(old_hunk) > 0: c = context_change.match(old_hunk[0]) del old_hunk[0] if not c: continue kind = c.group(1) line = c.group(2) if kind == "-" and (j != old_len or j == 0): changes.append(Change(old + j, None, line, hunk_n)) j += 1 elif kind == " " and ( (j != old_len and k != new_len) or (j == 0 or k == 0) ): changes.append(Change(old + j, new + k, line, hunk_n)) j += 1 k += 1 elif kind == "+" or kind == "!": raise exceptions.ParseException(msg + kind, hunk_n) continue if len(old_hunk) == 0 and len(new_hunk) > 0: msg = "Got unexpected change in removal hunk: " # only insertions left? while len(new_hunk) > 0: c = context_change.match(new_hunk[0]) del new_hunk[0] if not c: continue kind = c.group(1) line = c.group(2) if kind == "+" and (k != new_len or k == 0): changes.append(Change(None, new + k, line, hunk_n)) k += 1 elif kind == " " and ( (j != old_len and k != new_len) or (j == 0 or k == 0) ): changes.append(Change(old + j, new + k, line, hunk_n)) j += 1 k += 1 elif kind == "-" or kind == "!": raise exceptions.ParseException(msg + kind, hunk_n) continue # both while len(old_hunk) > 0 and len(new_hunk) > 0: oc = context_change.match(old_hunk[0]) nc = context_change.match(new_hunk[0]) okind = None nkind = None if oc: okind = oc.group(1) oline = oc.group(2) if nc: nkind = nc.group(1) nline = nc.group(2) if not (oc or nc): del old_hunk[0] del new_hunk[0] elif okind == " " and nkind == " " and oline == nline: changes.append(Change(old + j, new + k, oline, hunk_n)) j += 1 k += 1 del old_hunk[0] del new_hunk[0] elif okind == "-" or okind == "!" and (j != old_len or j == 0): changes.append(Change(old + j, None, oline, hunk_n)) j += 1 del old_hunk[0] elif nkind == "+" or nkind == "!" and (k != new_len or k == 0): changes.append(Change(None, new + k, nline, hunk_n)) k += 1 del new_hunk[0] else: return None if len(changes) > 0: return changes return None def parse_ed_diff(text): try: lines = text.splitlines() except AttributeError: lines = text old = 0 j = 0 k = 0 r = 0 i = 0 changes = list() hunks = split_by_regex(lines, ed_hunk_start) hunks.reverse() for hunk_n, hunk in enumerate(hunks): if not len(hunk): continue j = 0 k = 0 while len(hunk) > 0: o = ed_hunk_start.match(hunk[0]) del hunk[0] if not o: continue old = int(o.group(1)) old_end = int(o.group(2)) if len(o.group(2)) else old hunk_kind = o.group(3) if hunk_kind == "d": k = 0 while old_end >= old: changes.append(Change(old + k, None, None, hunk_n)) r += 1 k += 1 old_end -= 1 continue while len(hunk) > 0: e = ed_hunk_end.match(hunk[0]) if not e and hunk_kind == "c": k = 0 while old_end >= old: changes.append(Change(old + k, None, None, hunk_n)) r += 1 k += 1 old_end -= 1 # I basically have no idea why this works # for these tests. changes.append( Change( None, old - r + i + k + j, hunk[0], hunk_n, ) ) i += 1 j += 1 if not e and hunk_kind == "a": changes.append( Change( None, old - r + i + 1, hunk[0], hunk_n, ) ) i += 1 del hunk[0] if len(changes) > 0: return changes return None def parse_rcs_ed_diff(text): # much like forward ed, but no 'c' type try: lines = text.splitlines() except AttributeError: lines = text old = 0 j = 0 size = 0 total_change_size = 0 changes = list() hunks = split_by_regex(lines, rcs_ed_hunk_start) for hunk_n, hunk in enumerate(hunks): if len(hunk): j = 0 while len(hunk) > 0: o = rcs_ed_hunk_start.match(hunk[0]) del hunk[0] if not o: continue hunk_kind = o.group(1) old = int(o.group(2)) size = int(o.group(3)) if hunk_kind == "a": old += total_change_size + 1 total_change_size += size while size > 0 and len(hunk) > 0: changes.append(Change(None, old + j, hunk[0], hunk_n)) j += 1 size -= 1 del hunk[0] elif hunk_kind == "d": total_change_size -= size while size > 0: changes.append(Change(old + j, None, None, hunk_n)) j += 1 size -= 1 if len(changes) > 0: return changes return None def parse_git_binary_diff(text): try: lines = text.splitlines() except AttributeError: lines = text changes = list() old_version = None new_version = None cmd_old_path = None cmd_new_path = None # the sizes are used as latch-up old_size = None new_size = None old_encoded = "" new_encoded = "" for line in lines: if cmd_old_path is None and cmd_new_path is None: hm = git_diffcmd_header.match(line) if hm: cmd_old_path = hm.group(1) cmd_new_path = hm.group(2) continue if old_version is None and new_version is None: g = git_header_index.match(line) if g: old_version = g.group(1) new_version = g.group(2) continue # the first is added file if new_size is None: literal = git_binary_literal_start.match(line) if literal: new_size = int(literal.group(1)) continue delta = git_binary_delta_start.match(line) if delta: # not supported new_size = 0 continue elif new_size > 0: if base85string.match(line): assert len(line) >= 6 and ((len(line) - 1) % 5) == 0 new_encoded += line[1:] elif 0 == len(line): decoded = base64.b85decode(new_encoded) added_data = zlib.decompress(decoded) assert new_size == len(added_data) change = Change(None, 0, added_data, None) changes.append(change) new_size = 0 else: break # the second is removed file if old_size is None: literal = git_binary_literal_start.match(line) if literal: old_size = int(literal.group(1)) delta = git_binary_delta_start.match(line) if delta: # not supported old_size = 0 continue elif old_size > 0: if base85string.match(line): assert len(line) >= 6 and ((len(line) - 1) % 5) == 0 old_encoded += line[1:] elif 0 == len(line): decoded = base64.b85decode(old_encoded) removed_data = zlib.decompress(decoded) assert old_size == len(removed_data) change = Change(0, None, None, removed_data) changes.append(change) old_size = 0 else: break return changes whatthepatch-1.0.5/src/whatthepatch/snippets.py000066400000000000000000000025721442546544700217200ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os from shutil import rmtree def remove(path): if os.path.exists(path): if os.path.isdir(path): rmtree(path) else: os.remove(path) # find all indices of a list of strings that match a regex def findall_regex(items, regex): found = list() for i in range(0, len(items)): k = regex.match(items[i]) if k: found.append(i) k = None return found def split_by_regex(items, regex): splits = list() indices = findall_regex(items, regex) k = None for i in indices: if k is None: splits.append(items[0:i]) k = i else: splits.append(items[k:i]) k = i splits.append(items[k:]) return splits # http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python def which(program): def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None whatthepatch-1.0.5/tests/000077500000000000000000000000001442546544700153625ustar00rootroot00000000000000whatthepatch-1.0.5/tests/__init__.py000066400000000000000000000000001442546544700174610ustar00rootroot00000000000000whatthepatch-1.0.5/tests/casefiles/000077500000000000000000000000001442546544700173205ustar00rootroot00000000000000whatthepatch-1.0.5/tests/casefiles/abc000066400000000000000000000000601442546544700177640ustar00rootroot00000000000000The Nameless is the origin of Heaven and Earth; whatthepatch-1.0.5/tests/casefiles/apache-attachment-2241.diff000066400000000000000000000015421442546544700241110ustar00rootroot00000000000000*** src\main\org\apache\tools\ant\taskdefs\optional\pvcs\Pvcs.orig Sat Jun 22 16:11:58 2002 --- src\main\org\apache\tools\ant\taskdefs\optional\pvcs\Pvcs.java Fri Jun 28 10:55:50 2002 *************** *** 91,97 **** * * @author Thomas Christensen * @author Don Jeffery ! * @author Steven E. Newton */ public class Pvcs extends org.apache.tools.ant.Task { private String pvcsbin; --- 91,97 ---- * * @author Thomas Christensen * @author Don Jeffery ! * @author Steven E. Newton */ public class Pvcs extends org.apache.tools.ant.Task { private String pvcsbin; whatthepatch-1.0.5/tests/casefiles/apache-attachment-28223.diff000066400000000000000000000012401442546544700241740ustar00rootroot00000000000000diff --git a/src/script/.ant.swp b/src/script/.ant.swp index 7962473..b214d30 100644 Binary files a/src/script/.ant.swp and b/src/script/.ant.swp differ diff --git a/src/script/ant b/src/script/ant index 0dc84e0..11c1b59 100644 --- a/src/script/ant +++ b/src/script/ant @@ -176,10 +176,7 @@ if $rpm_mode && [ -x /usr/bin/build-classpath ] ; then *.rpmnew) ;; *) for dep in `cat "$file"`; do - case "$OPT_JAR_LIST" in - *"$dep"*) ;; - *) OPT_JAR_LIST="$OPT_JAR_LIST${OPT_JAR_LIST:+ }$dep" - esac + OPT_JAR_LIST="$OPT_JAR_LIST${OPT_JAR_LIST:+ }$dep" done esac fi whatthepatch-1.0.5/tests/casefiles/context-header.diff000066400000000000000000000001561442546544700230660ustar00rootroot00000000000000*** /tmp/o 2012-12-22 06:43:35.000000000 -0600 --- /tmp/n 2012-12-23 20:40:50.000000000 -0600 *************** whatthepatch-1.0.5/tests/casefiles/cvs-header.diff000066400000000000000000000012071442546544700221730ustar00rootroot00000000000000Index: org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java,v retrieving revision 1.6.4.1 retrieving revision 1.8 diff -u -r1.6.4.1 -r1.8 --- org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java 23 Jul 2001 17:51:45 -0000 1.6.4.1 +++ org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java 17 May 2002 20:27:56 -0000 1.8 @@ -1 +1 @@ whatthepatch-1.0.5/tests/casefiles/diff-context-blah.diff000066400000000000000000000016541442546544700234560ustar00rootroot00000000000000*** lao 2013-01-05 16:56:19.000000000 -0600 --- tzu 2013-01-05 16:56:35.000000000 -0600 *************** *** 1,7 **** - The Way that can be told of is not the eternal Way; - The name that can be named is not the eternal name. The Nameless is the origin of Heaven and Earth; ! The Named is the mother of all things. Therefore let there always be non-being, so we may see their subtlety, And let there always be being, --- 1,6 ---- The Nameless is the origin of Heaven and Earth; ! The named is the mother of all things. ! Therefore let there always be non-being, so we may see their subtlety, And let there always be being, *************** *** 9,11 **** --- 8,13 ---- The two are the same, But after they are produced, they have different names. + They both may be called deep and profound. + Deeper and more profound, + The door of all subtleties! + blah + blah + bleh None of these last 4 lines should parse. whatthepatch-1.0.5/tests/casefiles/diff-context.diff000066400000000000000000000015541442546544700225510ustar00rootroot00000000000000*** lao 2013-01-05 16:56:19.000000000 -0600 --- tzu 2013-01-05 16:56:35.000000000 -0600 *************** *** 1,7 **** - The Way that can be told of is not the eternal Way; - The name that can be named is not the eternal name. The Nameless is the origin of Heaven and Earth; ! The Named is the mother of all things. Therefore let there always be non-being, so we may see their subtlety, And let there always be being, --- 1,6 ---- The Nameless is the origin of Heaven and Earth; ! The named is the mother of all things. ! Therefore let there always be non-being, so we may see their subtlety, And let there always be being, *************** *** 9,11 **** --- 8,13 ---- The two are the same, But after they are produced, they have different names. + They both may be called deep and profound. + Deeper and more profound, + The door of all subtleties! whatthepatch-1.0.5/tests/casefiles/diff-default-blah.diff000066400000000000000000000006011442546544700234050ustar00rootroot000000000000001,2d0 < The Way that can be told of is not the eternal Way; < The name that can be named is not the eternal name. 4c2,3 < The Named is the mother of all things. --- > The named is the mother of all things. > 11a11,13 > They both may be called deep and profound. > Deeper and more profound, > The door of all subtleties! > blah > blah > bleh None of these last 4 lines should parse. whatthepatch-1.0.5/tests/casefiles/diff-default.diff000066400000000000000000000005011442546544700225000ustar00rootroot000000000000001,2d0 < The Way that can be told of is not the eternal Way; < The name that can be named is not the eternal name. 4c2,3 < The Named is the mother of all things. --- > The named is the mother of all things. > 11a11,13 > They both may be called deep and profound. > Deeper and more profound, > The door of all subtleties! whatthepatch-1.0.5/tests/casefiles/diff-ed.diff000066400000000000000000000002311442546544700214440ustar00rootroot0000000000000011a They both may be called deep and profound. Deeper and more profound, The door of all subtleties! . 4c The named is the mother of all things. . 1,2d whatthepatch-1.0.5/tests/casefiles/diff-rcs.diff000066400000000000000000000002361442546544700216500ustar00rootroot00000000000000d1 2 d4 1 a4 2 The named is the mother of all things. a11 3 They both may be called deep and profound. Deeper and more profound, The door of all subtleties! whatthepatch-1.0.5/tests/casefiles/diff-unified-bad.diff000066400000000000000000000012061442546544700232260ustar00rootroot00000000000000--- lao 2013-01-05 16:56:19.000000000 -0600 +++ tzu 2013-01-05 16:56:35.000000000 -0600 @@ -1,7 +1,6 @@ -The Way that can be told of is not the eternal Way; -The name that can be named is not the eternal name. The Nameless is the origin of Heaven and Earth; -The Named is the mother of all tings. +The named is the mother of all things. + Therefore let there always be non-being, so we may see their subtlety, And let there always be being, @@ -9,3 +8,6 @@ The two are the same, But after they are produced, they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! whatthepatch-1.0.5/tests/casefiles/diff-unified-bad2.diff000066400000000000000000000012061442546544700233100ustar00rootroot00000000000000--- lao 2013-01-05 16:56:19.000000000 -0600 +++ tzu 2013-01-05 16:56:35.000000000 -0600 @@ -1,7 +1,6 @@ -The Way that can be told of is not the eternal Way; -The name that can be named is not the eternal name. The Nameless is the origin of Heaven and Earth; -The Named is the mother of all things. +The named is the mother of all things. + Therefore let there always be non-being, so we may see their subtlety, And let there always be being, @@ -9,3 +8,6 @@ The two are te same, But after they are produced, they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! whatthepatch-1.0.5/tests/casefiles/diff-unified-blah.diff000066400000000000000000000013031442546544700234040ustar00rootroot00000000000000--- lao 2013-01-05 16:56:19.000000000 -0600 +++ tzu 2013-01-05 16:56:35.000000000 -0600 @@ -1,7 +1,6 @@ -The Way that can be told of is not the eternal Way; -The name that can be named is not the eternal name. The Nameless is the origin of Heaven and Earth; -The Named is the mother of all things. +The named is the mother of all things. + Therefore let there always be non-being, so we may see their subtlety, And let there always be being, @@ -9,3 +8,6 @@ The two are the same, But after they are produced, they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! -blah -blah +bleh None of these last 4 lines should parse. whatthepatch-1.0.5/tests/casefiles/diff-unified.diff000066400000000000000000000012071442546544700225030ustar00rootroot00000000000000--- lao 2013-01-05 16:56:19.000000000 -0600 +++ tzu 2013-01-05 16:56:35.000000000 -0600 @@ -1,7 +1,6 @@ -The Way that can be told of is not the eternal Way; -The name that can be named is not the eternal name. The Nameless is the origin of Heaven and Earth; -The Named is the mother of all things. +The named is the mother of all things. + Therefore let there always be non-being, so we may see their subtlety, And let there always be being, @@ -9,3 +8,6 @@ The two are the same, But after they are produced, they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! whatthepatch-1.0.5/tests/casefiles/diff-unified2.diff000066400000000000000000000002771442546544700225730ustar00rootroot00000000000000--- abc 2013-01-05 16:56:19.000000000 -0600 +++ efg 2013-01-05 16:56:35.000000000 -0600 @@ -1 +1,2 @@ The Nameless is the origin of Heaven and Earth; +The named is the mother of all things. whatthepatch-1.0.5/tests/casefiles/eclipse-attachment-126343.header000066400000000000000000000007401442546544700250050ustar00rootroot00000000000000Index: test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java =================================================================== RCS file: test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java diff -N test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,53 @@ whatthepatch-1.0.5/tests/casefiles/efg000066400000000000000000000001271442546544700200040ustar00rootroot00000000000000The Nameless is the origin of Heaven and Earth; The named is the mother of all things. whatthepatch-1.0.5/tests/casefiles/embedded-diff.comment000066400000000000000000000015001442546544700233370ustar00rootroot00000000000000In order to pass the initial test case of the bug reporter, a simple patch for "IRFactory#decompile(AstNode)" is necessary: --- --- a/src/org/mozilla/javascript/IRFactory.java +++ b/src/org/mozilla/javascript/IRFactory.java @@ -2182,6 +2182,9 @@ public final class IRFactory extends Parser case Token.GETELEM: decompileElementGet((ElementGet) node); break; + case Token.THIS: + decompiler.addToken(node.getType()); + break; default: Kit.codeBug("unexpected token: " + Token.typeToName(node.getType())); --- But that change won't be sufficient to cover the other tests of the JUnit test case. PS: The bug subject should be changed to mention "Destructuring Assignment" instead of "Array comprehension" whatthepatch-1.0.5/tests/casefiles/git-bin.patch000066400000000000000000000041561442546544700217000ustar00rootroot00000000000000--- fox.bin | Bin 0 -> 44 bytes fox.txt | 2 +- lorem.bin | Bin 0 -> 446 bytes lorem.zip | Bin 431 -> 432 bytes 4 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 fox.bin create mode 100644 lorem.bin diff --git a/fox.bin b/fox.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7683ad05fd121a9ca86cab5a827d471d29b4d4f GIT binary patch literal 44 ycmWH^NL45-%}mZ#NGi%N&r?XtuTaP;%`GTa$S+GRQYZmR=Ok8DDx~D6GXMZ literal 0 HcmV?d00001 diff --git a/fox.txt b/fox.txt index ff3bb63..8fe2a4b 100644 --- a/fox.txt +++ b/fox.txt @@ -1 +1 @@ -The quick brown fox jumps over the lazy dog \ No newline at end of file +The quick brown fox jumps over the lazy dog. \ No newline at end of file diff --git a/lorem.bin b/lorem.bin new file mode 100644 index 0000000000000000000000000000000000000000..aef2724fd9ff72caf4eb1ac8333f0b5b322d82fb GIT binary patch literal 446 zcmXw#&2d992!vD07T|eRB)42s0Fkh>Gy1ax9+w~Fm)wMaW%v8+Q!6-@SL9y$#G*l} z+6Ae%rODKMLNW(eV!J^Lqq#K40+haL&oHecme~?Bvp0hqihPGW)J|zdm0J@?;oarH zmq8nAXrppJ9#KlY;O<;#ecAL3ed0DLGNbT$ z;NzKem+$vrMRcVJ literal 0 HcmV?d00001 diff --git a/lorem.zip b/lorem.zip index 0f6beb70488e2b29fcaadf724b6f48ef0ab5bc4e..3c8a65bf1a97bb4180c83a0e31352b4edb4c245e 100644 GIT binary patch delta 275 zcmZ3_yn#6)z?+#xgn@y9gP}7+C2a4}O*1$c85s5gF(-ozLr#8CYOY>MMM-D~Cj+xl z?ABymATF)oW?*D_!OXw_CQK(BEOa*HaEZRjWV65P$+T=Pg^u?VBeY_ymt>hupAyrM zu_ldGFZao&vTKV}cPYzE-4`VHX!@1~7xxOUc%}T}z;fqZ+pf4>{#%je`L@mdh12$@ z##~QSCtp2z)oM{#R?hTKa+j9=zO;TxpG;eTRQ}78>li9e@lU*`!E # This is a basic script I wrote to run Bugxplore over the dataset > > 8,9c11,12 < from Main import main < from Main import _make_dir --- > from Bugxplore import main > from Bugxplore import _make_dir Index: bugtrace/trunk/src/bugtrace/Diffxplore.py =================================================================== 49c49 < optparser.set_defaults(output_dir='/tmp/sctdiffs',project_name='default_project') --- > optparser.set_defaults(output_dir='/tmp/diffs') 53c53 < optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document') --- > Index: bugtrace/trunk/src/bugtrace/Bugxplore.py =================================================================== 86c86 < optparser.set_defaults(output_dir='/tmp/bugs',project_name='default_project') --- > optparser.set_defaults(output_dir='/tmp/bugs') 91c91 < optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document') --- > whatthepatch-1.0.5/tests/casefiles/svn-ed.patch000066400000000000000000000011441442546544700215350ustar00rootroot00000000000000Index: bugtrace/trunk/src/bugtrace/csc.py =================================================================== 8,9c from Bugxplore import main from Bugxplore import _make_dir . 0a # This is a basic script I wrote to run Bugxplore over the dataset . Index: bugtrace/trunk/src/bugtrace/Diffxplore.py =================================================================== 53c . 49c optparser.set_defaults(output_dir='/tmp/diffs') . Index: bugtrace/trunk/src/bugtrace/Bugxplore.py =================================================================== 91c . 86c optparser.set_defaults(output_dir='/tmp/bugs') . whatthepatch-1.0.5/tests/casefiles/svn-git.patch000066400000000000000000000061721442546544700217360ustar00rootroot00000000000000Index: bugtrace/trunk/src/bugtrace/csc.py =================================================================== diff --git a/projects/bugs/bugtrace/trunk/src/bugtrace/csc.py b/projects/bugs/bugtrace/trunk/src/bugtrace/csc.py --- a/projects/bugs/bugtrace/trunk/src/bugtrace/csc.py (revision 12783) +++ b/projects/bugs/bugtrace/trunk/src/bugtrace/csc.py (revision 12784) @@ -1,3 +1,6 @@ +# This is a basic script I wrote to run Bugxplore over the dataset + + import os import sys import pickle @@ -5,8 +8,8 @@ import copy from datetime import date -from Main import main -from Main import _make_dir +from Bugxplore import main +from Bugxplore import _make_dir storageDir = '/tmp/csc/bugs/' argv = [] Index: bugtrace/trunk/src/bugtrace/Diffxplore.py =================================================================== diff --git a/projects/bugs/bugtrace/trunk/src/bugtrace/Diffxplore.py b/projects/bugs/bugtrace/trunk/src/bugtrace/Diffxplore.py --- a/projects/bugs/bugtrace/trunk/src/bugtrace/Diffxplore.py (revision 12783) +++ b/projects/bugs/bugtrace/trunk/src/bugtrace/Diffxplore.py (revision 12784) @@ -46,11 +46,11 @@ # Configure option parser optparser = OptionParser(usage='%prog [options] DIFF_FILE', version='0.1') - optparser.set_defaults(output_dir='/tmp/sctdiffs',project_name='default_project') + optparser.set_defaults(output_dir='/tmp/diffs') optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory') optparser.add_option('-p', '--project_name', dest='project_name', help='Project name') optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder') - optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document') + # Invoke option parser (options,args) = optparser.parse_args(argv) Index: bugtrace/trunk/src/bugtrace/Bugxplore.py =================================================================== diff --git a/projects/bugs/bugtrace/trunk/src/bugtrace/Bugxplore.py b/projects/bugs/bugtrace/trunk/src/bugtrace/Bugxplore.py --- a/projects/bugs/bugtrace/trunk/src/bugtrace/Bugxplore.py (revision 12783) +++ b/projects/bugs/bugtrace/trunk/src/bugtrace/Bugxplore.py (revision 12784) @@ -83,12 +83,12 @@ # Configure option parser optparser = OptionParser(usage='%prog [options] BUG_IDS', version='0.1') - optparser.set_defaults(output_dir='/tmp/bugs',project_name='default_project') + optparser.set_defaults(output_dir='/tmp/bugs') optparser.add_option('-u', '--bugzilla-url', dest='bugzilla_url', help='URL of Bugzilla installation root') optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory') optparser.add_option('-p', '--project_name', dest='project_name', help='Project name') optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder') - optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document') + # Invoke option parser (options,args) = optparser.parse_args(argv) whatthepatch-1.0.5/tests/casefiles/svn-header.diff000066400000000000000000000003571442546544700222130ustar00rootroot00000000000000Index: bugtrace/trunk/src/bugtrace/csc.py =================================================================== --- bugtrace/trunk/src/bugtrace/csc.py (revision 12783) +++ bugtrace/trunk/src/bugtrace/csc.py (revision 12784) @@ -1,3 +1,6 @@ whatthepatch-1.0.5/tests/casefiles/svn-mixed_line_ends.patch000066400000000000000000000170671442546544700243060ustar00rootroot00000000000000Index: java/org/apache/catalina/loader/WebappClassLoader.java =================================================================== --- java/org/apache/catalina/loader/WebappClassLoader.java (revision 1346371) +++ java/org/apache/catalina/loader/WebappClassLoader.java (working copy) @@ -2177,8 +2177,9 @@ } // TimerThread can be stopped safely so treat separately - if (thread.getClass().getName().equals( - "java.util.TimerThread") && + // "java.util.TimerThread" in Sun/Oracle JDK + // "java.util.Timer$TimerImpl" in Apache Harmony and in IBM JDK + if (thread.getClass().getName().startsWith("java.util.Timer") && clearReferencesStopTimerThreads) { clearReferencesStopTimerThread(thread); continue; @@ -2201,13 +2202,29 @@ // If the thread has been started via an executor, try // shutting down the executor try { - Field targetField = - thread.getClass().getDeclaredField("target"); - targetField.setAccessible(true); - Object target = targetField.get(thread); - + // Runnable wrapped by Thread + // "target" in Sun/Oracle JDK + // "runnable" in IBM JDK + // "action" in Apache Harmony + Object target = null; + for (String fieldName : new String[] { "target", + "runnable", "action" }) { + try { + Field targetField = thread.getClass() + .getDeclaredField(fieldName); + targetField.setAccessible(true); + target = targetField.get(thread); + break; + } catch (NoSuchFieldException nfe) { + continue; + } + } + + // "java.util.concurrent" code is in public domain, + // so all implementations are similar if (target != null && - target.getClass().getCanonicalName().equals( + target.getClass().getCanonicalName() != null + && target.getClass().getCanonicalName().equals( "java.util.concurrent.ThreadPoolExecutor.Worker")) { Field executorField = target.getClass().getDeclaredField("this$0"); @@ -2276,37 +2293,46 @@ private void clearReferencesStopTimerThread(Thread thread) { - + // Need to get references to: - // - newTasksMayBeScheduled field + // in Sun/Oracle JDK: + // - newTasksMayBeScheduled field (in java.util.TimerThread) // - queue field // - queue.clear() + // in IBM JDK, Apache Harmony: + // - cancel() method (in java.util.Timer$TimerImpl) + + try { + + try { + Field newTasksMayBeScheduledField = + thread.getClass().getDeclaredField("newTasksMayBeScheduled"); + newTasksMayBeScheduledField.setAccessible(true); + Field queueField = thread.getClass().getDeclaredField("queue"); + queueField.setAccessible(true); - try { - Field newTasksMayBeScheduledField = - thread.getClass().getDeclaredField("newTasksMayBeScheduled"); - newTasksMayBeScheduledField.setAccessible(true); - Field queueField = thread.getClass().getDeclaredField("queue"); - queueField.setAccessible(true); - - Object queue = queueField.get(thread); - - Method clearMethod = queue.getClass().getDeclaredMethod("clear"); - clearMethod.setAccessible(true); - - synchronized(queue) { - newTasksMayBeScheduledField.setBoolean(thread, false); - clearMethod.invoke(queue); - queue.notify(); // In case queue was already empty. + Object queue = queueField.get(thread); + + Method clearMethod = queue.getClass().getDeclaredMethod("clear"); + clearMethod.setAccessible(true); + + synchronized(queue) { + newTasksMayBeScheduledField.setBoolean(thread, false); + clearMethod.invoke(queue); + queue.notify(); // In case queue was already empty. + } + + }catch (NoSuchFieldException nfe){ + Method cancelMethod = thread.getClass().getDeclaredMethod("cancel"); + synchronized(thread) { + cancelMethod.setAccessible(true); + cancelMethod.invoke(thread); + } } - + log.error(sm.getString("webappClassLoader.warnTimerThread", contextName, thread.getName())); - } catch (NoSuchFieldException e) { - log.warn(sm.getString( - "webappClassLoader.stopTimerThreadFail", - thread.getName(), contextName), e); } catch (IllegalAccessException e) { log.warn(sm.getString( "webappClassLoader.stopTimerThreadFail", @@ -2340,17 +2366,27 @@ Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); Field tableField = tlmClass.getDeclaredField("table"); tableField.setAccessible(true); - + Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries"); + expungeStaleEntriesMethod.setAccessible(true); + for (int i = 0; i < threads.length; i++) { Object threadLocalMap; if (threads[i] != null) { + // Clear the first map threadLocalMap = threadLocalsField.get(threads[i]); - clearThreadLocalMap(threadLocalMap, tableField); + if (null != threadLocalMap){ + expungeStaleEntriesMethod.invoke(threadLocalMap); + checkThreadLocalMapForLeaks(threadLocalMap, tableField); + } + // Clear the second map threadLocalMap = inheritableThreadLocalsField.get(threads[i]); - clearThreadLocalMap(threadLocalMap, tableField); + if (null != threadLocalMap){ + expungeStaleEntriesMethod.invoke(threadLocalMap); + checkThreadLocalMapForLeaks(threadLocalMap, tableField); + } } } } catch (SecurityException e) { @@ -2383,7 +2419,7 @@ * points to the internal table to save re-calculating it on every * call to this method. */ - private void clearThreadLocalMap(Object map, Field internalTableField) + private void checkThreadLocalMapForLeaks(Object map, Field internalTableField) throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException, InvocationTargetException { if (map != null) { whatthepatch-1.0.5/tests/casefiles/svn-rcs.patch000066400000000000000000000011771442546544700217420ustar00rootroot00000000000000Index: bugtrace/trunk/src/bugtrace/csc.py =================================================================== a0 3 # This is a basic script I wrote to run Bugxplore over the dataset d8 2 a9 2 from Bugxplore import main from Bugxplore import _make_dir Index: bugtrace/trunk/src/bugtrace/Diffxplore.py =================================================================== d49 1 a49 1 optparser.set_defaults(output_dir='/tmp/diffs') d53 1 a53 1 Index: bugtrace/trunk/src/bugtrace/Bugxplore.py =================================================================== d86 1 a86 1 optparser.set_defaults(output_dir='/tmp/bugs') d91 1 a91 1 whatthepatch-1.0.5/tests/casefiles/svn-unified.patch000066400000000000000000000052551442546544700225770ustar00rootroot00000000000000Index: bugtrace/trunk/src/bugtrace/csc.py =================================================================== --- bugtrace/trunk/src/bugtrace/csc.py (revision 12783) +++ bugtrace/trunk/src/bugtrace/csc.py (revision 12784) @@ -1,3 +1,6 @@ +# This is a basic script I wrote to run Bugxplore over the dataset + + import os import sys import pickle @@ -5,8 +8,8 @@ import copy from datetime import date -from Main import main -from Main import _make_dir +from Bugxplore import main +from Bugxplore import _make_dir storageDir = '/tmp/csc/bugs/' argv = [] Index: bugtrace/trunk/src/bugtrace/Diffxplore.py =================================================================== --- bugtrace/trunk/src/bugtrace/Diffxplore.py (revision 12783) +++ bugtrace/trunk/src/bugtrace/Diffxplore.py (revision 12784) @@ -46,11 +46,11 @@ # Configure option parser optparser = OptionParser(usage='%prog [options] DIFF_FILE', version='0.1') - optparser.set_defaults(output_dir='/tmp/sctdiffs',project_name='default_project') + optparser.set_defaults(output_dir='/tmp/diffs') optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory') optparser.add_option('-p', '--project_name', dest='project_name', help='Project name') optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder') - optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document') + # Invoke option parser (options,args) = optparser.parse_args(argv) Index: bugtrace/trunk/src/bugtrace/Bugxplore.py =================================================================== --- bugtrace/trunk/src/bugtrace/Bugxplore.py (revision 12783) +++ bugtrace/trunk/src/bugtrace/Bugxplore.py (revision 12784) @@ -83,12 +83,12 @@ # Configure option parser optparser = OptionParser(usage='%prog [options] BUG_IDS', version='0.1') - optparser.set_defaults(output_dir='/tmp/bugs',project_name='default_project') + optparser.set_defaults(output_dir='/tmp/bugs') optparser.add_option('-u', '--bugzilla-url', dest='bugzilla_url', help='URL of Bugzilla installation root') optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory') optparser.add_option('-p', '--project_name', dest='project_name', help='Project name') optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder') - optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document') + # Invoke option parser (options,args) = optparser.parse_args(argv) whatthepatch-1.0.5/tests/casefiles/tzu000066400000000000000000000006201442546544700200630ustar00rootroot00000000000000The Nameless is the origin of Heaven and Earth; The named is the mother of all things. Therefore let there always be non-being, so we may see their subtlety, And let there always be being, so we may see their outcome. The two are the same, But after they are produced, they have different names. They both may be called deep and profound. Deeper and more profound, The door of all subtleties! whatthepatch-1.0.5/tests/casefiles/unified-header-notab.diff000066400000000000000000000001711442546544700241230ustar00rootroot00000000000000--- /tmp/some file 2012-12-22 06:43:35.000000000 -0600 +++ /tmp/n 2012-12-23 20:40:50.000000000 -0600 @@ -1,3 +1,9 @@ whatthepatch-1.0.5/tests/casefiles/unified-header.diff000066400000000000000000000001561442546544700230250ustar00rootroot00000000000000--- /tmp/o 2012-12-22 06:43:35.000000000 -0600 +++ /tmp/n 2012-12-23 20:40:50.000000000 -0600 @@ -1,3 +1,9 @@ whatthepatch-1.0.5/tests/test_apply.py000066400000000000000000000136461442546544700201320ustar00rootroot00000000000000# -*- coding: utf-8 -*- import unittest from unittest.case import SkipTest import pytest from src.whatthepatch import apply_diff, exceptions, parse_patch from src.whatthepatch.snippets import which def _apply(src, diff_text, reverse=False, use_patch=False): diff = next(parse_patch(diff_text)) return apply_diff(diff, src, reverse, use_patch) def _apply_r(src, diff_text, reverse=True, use_patch=False): return _apply(src, diff_text, reverse, use_patch) class ApplyTestSuite(unittest.TestCase): """Basic test cases.""" def setUp(self): with open("tests/casefiles/lao") as f: self.lao = f.read().splitlines() with open("tests/casefiles/tzu") as f: self.tzu = f.read().splitlines() with open("tests/casefiles/abc") as f: self.abc = f.read().splitlines() with open("tests/casefiles/efg") as f: self.efg = f.read().splitlines() def test_truth(self): self.assertEqual(type(self.lao), list) self.assertEqual(type(self.tzu), list) self.assertEqual(len(self.lao), 11) self.assertEqual(len(self.tzu), 13) def test_diff_default(self): with open("tests/casefiles/diff-default.diff") as f: diff_text = f.read() self.assertEqual(_apply(self.lao, diff_text), self.tzu) self.assertEqual(_apply_r(self.tzu, diff_text), self.lao) def test_diff_context(self): with open("tests/casefiles/diff-context.diff") as f: diff_text = f.read() self.assertEqual(_apply(self.lao, diff_text), self.tzu) self.assertEqual(_apply_r(self.tzu, diff_text), self.lao) def test_diff_unified(self): with open("tests/casefiles/diff-unified.diff") as f: diff_text = f.read() self.assertEqual(_apply(self.lao, diff_text), self.tzu) self.assertEqual(_apply_r(self.tzu, diff_text), self.lao) def test_diff_unified2(self): with open("tests/casefiles/diff-unified2.diff") as f: diff_text = f.read() self.assertEqual(_apply(self.abc, diff_text), self.efg) self.assertEqual(_apply_r(self.efg, diff_text), self.abc) def test_diff_unified_bad(self): with open("tests/casefiles/diff-unified-bad.diff") as f: diff_text = f.read() with pytest.raises(exceptions.ApplyException) as ec: _apply(self.lao, diff_text) e = ec.value e_str = str(e) assert "line 4" in e_str assert "The Named is the mother of all tings." in e_str assert "The Named is the mother of all things." in e_str assert e.hunk == 1 def test_diff_unified_bad2(self): with open("tests/casefiles/diff-unified-bad2.diff") as f: diff_text = f.read() with pytest.raises(exceptions.ApplyException) as ec: _apply(self.lao, diff_text) e = ec.value e_str = str(e) assert "line 9" in e_str assert "The two are te same," in e_str assert "The two are the same," in e_str assert e.hunk == 2 def test_diff_unified_bad_backward(self): with open("tests/casefiles/diff-unified-bad2.diff") as f: diff_text = f.read() with pytest.raises(exceptions.ApplyException) as ec: _apply(self.tzu, diff_text) e = ec.value e_str = str(e) assert "line 1" in e_str assert "The Way that can be told of is not the eternal Way;" in e_str assert "The Nameless is the origin of Heaven and Earth;" in e_str assert e.hunk == 1 def test_diff_unified_bad_empty_source(self): with open("tests/casefiles/diff-unified-bad2.diff") as f: diff_text = f.read() with pytest.raises(exceptions.ApplyException) as ec: _apply("", diff_text) e = ec.value e_str = str(e) assert "line 1" in e_str assert "The Way that can be told of is not the eternal Way;" in e_str assert "does not exist in source" assert e.hunk == 1 def test_diff_unified_patchutil(self): with open("tests/casefiles/diff-unified.diff") as f: diff_text = f.read() if not which("patch"): raise SkipTest() self.assertEqual(_apply(self.lao, diff_text, use_patch=True), (self.tzu, None)) self.assertEqual( _apply_r(self.tzu, diff_text, use_patch=True), (self.lao, None) ) new_text = _apply(self.lao, diff_text, use_patch=True) self.assertEqual(new_text, (self.tzu, None)) with pytest.raises(exceptions.ApplyException): _apply([""] + self.lao, diff_text, use_patch=True) def test_diff_unified2_patchutil(self): with open("tests/casefiles/diff-unified2.diff") as f: diff_text = f.read() if not which("patch"): raise SkipTest() self.assertEqual(_apply(self.abc, diff_text, use_patch=True), (self.efg, None)) self.assertEqual( _apply(self.abc, diff_text, use_patch=True), (_apply(self.abc, diff_text), None), ) self.assertEqual( _apply_r(self.efg, diff_text, use_patch=True), (self.abc, None) ) self.assertEqual( _apply_r(self.efg, diff_text, use_patch=True), (_apply_r(self.efg, diff_text), None), ) def test_diff_rcs(self): with open("tests/casefiles/diff-rcs.diff") as f: diff_text = f.read() new_text = _apply(self.lao, diff_text) self.assertEqual(new_text, self.tzu) def test_diff_ed(self): with open("tests/casefiles/diff-ed.diff") as f: diff_text = f.read() new_text = _apply(self.lao, diff_text) self.assertEqual(self.tzu, new_text) if __name__ == "__main__": unittest.main() whatthepatch-1.0.5/tests/test_patch.py000066400000000000000000001467121442546544700201050ustar00rootroot00000000000000# -*- coding: utf-8 -*- import hashlib import os import time import unittest from src import whatthepatch as wtp from src.whatthepatch.patch import Change, diffobj from src.whatthepatch.patch import header as headerobj module_path = os.path.dirname(__file__) def datapath(fname): return os.path.join(module_path, "casefiles", fname) def indent(amount, changes): indent_str = " " * amount return [(l, r, indent_str + t if t else t) for (l, r, t) in changes] CSC_CHANGES = [ ( None, 1, "# This is a basic script I wrote to run Bugxplore over the dataset", ), (None, 2, ""), (None, 3, ""), (1, 4, "import os"), (2, 5, "import sys"), (3, 6, "import pickle"), (5, 8, "import copy"), (6, 9, ""), (7, 10, "from datetime import date"), (8, None, "from Main import main"), (9, None, "from Main import _make_dir"), (None, 11, "from Bugxplore import main"), (None, 12, "from Bugxplore import _make_dir"), (10, 13, ""), (11, 14, "storageDir = '/tmp/csc/bugs/'"), (12, 15, "argv = []"), ] DIFFXPLORE_CHANGES = indent( 4, [ (46, 46, ""), (47, 47, "# Configure option parser"), ( 48, 48, "optparser = OptionParser(usage='%prog [options] DIFF_FILE', " "version='0.1')", ), ( 49, None, "optparser.set_defaults(output_dir='/tmp/sctdiffs'," "project_name='default_project')", ), (None, 49, "optparser.set_defaults(output_dir='/tmp/diffs')"), ( 50, 50, "optparser.add_option('-o', '--output-dir', dest='output_dir', " "help='Output directory')", ), ( 51, 51, "optparser.add_option('-p', '--project_name', " "dest='project_name', help='Project name')", ), ( 52, 52, "optparser.add_option('-d', '--delete_cvs_folder', " "dest='cvs_delete', help='Deletable CVS checkout folder')", ), ( 53, None, "optparser.add_option('-a', '--append', action='store_true', " "dest='app', default=False, " "help='Append to existing MethTerms2 document')", ), (None, 53, ""), (54, 54, "# Invoke option parser"), (55, 55, "(options,args) = optparser.parse_args(argv)"), (56, 56, ""), ], ) BUGXPLORE_CHANGES = indent( 4, [ (83, 83, ""), (84, 84, "# Configure option parser"), ( 85, 85, "optparser = OptionParser(usage='%prog [options] BUG_IDS', " "version='0.1')", ), ( 86, None, "optparser.set_defaults(output_dir='/tmp/bugs'," "project_name='default_project')", ), (None, 86, "optparser.set_defaults(output_dir='/tmp/bugs')"), ( 87, 87, "optparser.add_option('-u', '--bugzilla-url', " "dest='bugzilla_url', help='URL of Bugzilla installation root')", ), ( 88, 88, "optparser.add_option('-o', '--output-dir', dest='output_dir', " "help='Output directory')", ), ( 89, 89, "optparser.add_option('-p', '--project_name', " "dest='project_name', help='Project name')", ), ( 90, 90, "optparser.add_option('-d', '--delete_cvs_folder', " "dest='cvs_delete', help='Deletable CVS checkout folder')", ), ( 91, None, "optparser.add_option('-a', '--append', action='store_true', " "dest='app', default=False, " "help='Append to existing MethTerms2 document')", ), (None, 91, ""), (92, 92, "# Invoke option parser"), (93, 93, "(options,args) = optparser.parse_args(argv)"), ], ) + [(94, 94, " ")] class PatchTestSuite(unittest.TestCase): def assert_diffs_equal(self, a, b): def _process_change(c): return (c.old, c.new, c.line) def _process_diffobj(d): changes = d.changes or [] return d._replace(changes=[_process_change(c) for c in changes]) def _process(d_or_c): if isinstance(d_or_c, list): return [_process(o) for o in d_or_c] if isinstance(d_or_c, diffobj): return _process_diffobj(d_or_c) if isinstance(d_or_c, Change): return _process_change(d_or_c) return d_or_c return self.assertEqual(_process(a), b) def test_default_diff(self): with open(datapath("diff-default.diff")) as f: text = f.read() expected = [ (1, None, "The Way that can be told of is not the eternal Way;"), (2, None, "The name that can be named is not the eternal name."), (4, None, "The Named is the mother of all things."), (None, 2, "The named is the mother of all things."), (None, 3, ""), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] results = list(wtp.patch.parse_default_diff(text)) self.assert_diffs_equal(results, expected) expected_main = [diffobj(header=None, changes=expected, text=text)] results_main = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results_main, expected_main) def test_svn_unified_patch(self): with open("tests/casefiles/svn-unified.patch") as f: text = f.read() lines = text.splitlines() expected = [ diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/csc.py", old_path="bugtrace/trunk/src/bugtrace/csc.py", old_version=12783, new_path="bugtrace/trunk/src/bugtrace/csc.py", new_version=12784, ), changes=CSC_CHANGES, text="\n".join(lines[:22]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_version=12783, new_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", new_version=12784, ), changes=DIFFXPLORE_CHANGES, text="\n".join(lines[22:40]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_version=12783, new_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", new_version=12784, ), changes=BUGXPLORE_CHANGES, text="\n".join(lines[40:]) + "\n", ), ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_svn_context_patch(self): with open("tests/casefiles/svn-context.patch") as f: text = f.read() lines = text.splitlines() expected = [ diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/csc.py", old_path="bugtrace/trunk/src/bugtrace/csc.py", old_version=12783, new_path="bugtrace/trunk/src/bugtrace/csc.py", new_version=12784, ), changes=CSC_CHANGES, text="\n".join(lines[:32]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_version=12783, new_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", new_version=12784, ), changes=DIFFXPLORE_CHANGES, text="\n".join(lines[32:61]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_version=12783, new_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", new_version=12784, ), changes=BUGXPLORE_CHANGES, text="\n".join(lines[61:]) + "\n", ), ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_svn_git_patch(self): with open("tests/casefiles/svn-git.patch") as f: text = f.read() lines = text.splitlines() csc_diff = diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/csc.py", old_path="projects/bugs/bugtrace/trunk/src/bugtrace/csc.py", old_version=12783, new_path="projects/bugs/bugtrace/trunk/src/bugtrace/csc.py", new_version=12784, ), changes=CSC_CHANGES, text="\n".join(lines[:23]) + "\n", ) diffxplore_path = "bugtrace/trunk/src/bugtrace/Diffxplore.py" diffxplore_diff = diffobj( header=headerobj( index_path=diffxplore_path, old_path="projects/bugs/" + diffxplore_path, old_version=12783, new_path="projects/bugs/" + diffxplore_path, new_version=12784, ), changes=DIFFXPLORE_CHANGES, text="\n".join(lines[23:42]) + "\n", ) bugexplore_path = "bugtrace/trunk/src/bugtrace/Bugxplore.py" bugxplore_diff = diffobj( header=headerobj( index_path=bugexplore_path, old_path="projects/bugs/" + bugexplore_path, old_version=12783, new_path="projects/bugs/" + bugexplore_path, new_version=12784, ), changes=BUGXPLORE_CHANGES, text="\n".join(lines[42:]) + "\n", ) expected = [csc_diff, diffxplore_diff, bugxplore_diff] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_svn_rcs_patch(self): with open("tests/casefiles/svn-rcs.patch") as f: text = f.read() lines = text.splitlines() csc_changes = [ ( None, 1, "# This is a basic script I wrote to run " "Bugxplore over the dataset", ), (None, 2, ""), (None, 3, ""), (8, None, None), (9, None, None), (None, 11, "from Bugxplore import main"), (None, 12, "from Bugxplore import _make_dir"), ] diffxplore_changes = [ (49, None, None), (None, 49, " optparser.set_defaults(output_dir='/tmp/diffs')"), (53, None, None), (None, 53, ""), ] bugxplore_changes = [ (86, None, None), (None, 86, " optparser.set_defaults(output_dir='/tmp/bugs')"), (91, None, None), (None, 91, ""), ] expected = [ diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/csc.py", old_path="bugtrace/trunk/src/bugtrace/csc.py", old_version=None, new_path="bugtrace/trunk/src/bugtrace/csc.py", new_version=None, ), changes=csc_changes, text="\n".join(lines[:10]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_version=None, new_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", new_version=None, ), changes=diffxplore_changes, text="\n".join(lines[10:18]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_version=None, new_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", new_version=None, ), changes=bugxplore_changes, text="\n".join(lines[18:]) + "\n", ), ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_svn_default_patch(self): with open("tests/casefiles/svn-default.patch") as f: text = f.read() lines = text.splitlines() csc_changes = [ ( None, 1, "# This is a basic script I wrote to run " "Bugxplore over the dataset", ), (None, 2, ""), (None, 3, ""), (8, None, "from Main import main"), (9, None, "from Main import _make_dir"), (None, 11, "from Bugxplore import main"), (None, 12, "from Bugxplore import _make_dir"), ] diffxplore_changes = indent( 4, [ ( 49, None, "optparser.set_defaults(output_dir='/tmp/sctdiffs'," "project_name='default_project')", ), (None, 49, "optparser.set_defaults(output_dir='/tmp/diffs')"), ( 53, None, "optparser.add_option('-a', '--append', " "action='store_true', dest='app', default=False, " "help='Append to existing MethTerms2 document')", ), (None, 53, ""), ], ) bugxplore_changes = indent( 4, [ ( 86, None, "optparser.set_defaults(output_dir='/tmp/bugs'," "project_name='default_project')", ), (None, 86, "optparser.set_defaults(output_dir='/tmp/bugs')"), ( 91, None, "optparser.add_option('-a', '--append', " "action='store_true', dest='app', default=False, " "help='Append to existing MethTerms2 document')", ), (None, 91, ""), ], ) expected = [ diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/csc.py", old_path="bugtrace/trunk/src/bugtrace/csc.py", old_version=None, new_path="bugtrace/trunk/src/bugtrace/csc.py", new_version=None, ), changes=csc_changes, text="\n".join(lines[:12]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", old_version=None, new_path="bugtrace/trunk/src/bugtrace/Diffxplore.py", new_version=None, ), changes=diffxplore_changes, text="\n".join(lines[12:22]) + "\n", ), diffobj( header=headerobj( index_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", old_version=None, new_path="bugtrace/trunk/src/bugtrace/Bugxplore.py", new_version=None, ), changes=bugxplore_changes, text="\n".join(lines[22:]) + "\n", ), ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_git_patch(self): with open("tests/casefiles/git.patch") as f: text = f.read() lines = text.splitlines() novel_frame_changes = indent( 4, [ (135, 135, "public void actionPerformed(ActionEvent e) {"), (136, 136, ""), (137, 137, ' if (e.getActionCommand().equals("OPEN")) {'), (138, None, " prefsDialog(prefs.getImportPane());"), (None, 138, " prefs.selectImportPane();"), (None, 139, " prefsDialog();"), (139, 140, ' } else if (e.getActionCommand().equals("SET")) {'), (140, None, " prefsDialog(prefs.getRepoPane());"), (None, 141, " prefs.selectRepoPane();"), (None, 142, " prefsDialog();"), (141, 143, ' } else if (e.getActionCommand().equals("PREFS"))'), (142, 144, " prefsDialog();"), (143, 145, ' else if (e.getActionCommand().equals("EXIT"))'), (158, 160, " * Create dialog to handle user preferences"), (159, 161, " */"), (160, 162, "public void prefsDialog() {"), (161, None, ""), (162, 163, " prefs.setVisible(true);"), (163, 164, "}"), (164, 165, ""), (165, None, "public void prefsDialog(Component c) {"), (166, None, " prefs.setSelectedComponent(c);"), (167, None, " prefsDialog();"), (168, None, "}"), (169, None, ""), (170, 166, "/**"), ( 171, 167, " * Open software tutorials, " "most likely to be hosted online", ), (172, 168, " * "), ], ) novel_frame_path = "novel/src/java/edu/ua/eng/software/novel/NovelFrame.java" novel_frame = diffobj( header=headerobj( index_path=None, old_path=novel_frame_path, old_version="aae63fe", new_path=novel_frame_path, new_version="5abbc99", ), changes=novel_frame_changes, text="\n".join(lines[:34]) + "\n", ) novel_pref_frame_path = ( "novel/src/java/edu/ua/eng/software/novel/NovelPrefPane.java" ) novel_pref_frame = diffobj( header=headerobj( index_path=None, old_path=novel_pref_frame_path, old_version="a63b57e", new_path=novel_pref_frame_path, new_version="919f413", ), changes=[ (18, 18, ""), (19, 19, " public abstract void apply();"), (20, 20, ""), (None, 21, " public abstract void applyPrefs();"), (None, 22, ""), (21, 23, " public abstract boolean isChanged();"), (22, 24, ""), (23, 25, " protected Preferences prefs;"), ], text="\n".join(lines[34:]) + "\n", ) expected = [novel_frame, novel_pref_frame] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_git_oneline_add(self): with open("tests/casefiles/git-oneline-add.diff") as f: text = f.read() lines = text.splitlines() expected = [ diffobj( header=headerobj( index_path=None, old_path="/dev/null", old_version="0000000", new_path="oneline.txt", new_version="f56f98d", ), changes=[(None, 1, "Adding a one-line file.")], text="\n".join(lines[:34]) + "\n", ) ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_git_oneline_change(self): with open("tests/casefiles/git-oneline-change.diff") as f: text = f.read() lines = text.splitlines() expected = [ diffobj( header=headerobj( index_path=None, old_path="oneline.txt", old_version="f56f98d", new_path="oneline.txt", new_version="169ceeb", ), changes=[ (1, None, "Adding a one-line file."), (None, 1, "Changed a one-line file."), ], text="\n".join(lines[:34]) + "\n", ) ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_git_oneline_rm(self): with open("tests/casefiles/git-oneline-rm.diff") as f: text = f.read() lines = text.splitlines() expected = [ diffobj( header=headerobj( index_path=None, old_path="oneline.txt", old_version="169ceeb", new_path="/dev/null", new_version="0000000", ), changes=[(1, None, "Changed a one-line file.")], text="\n".join(lines[:34]) + "\n", ) ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_git_new_empty_file(self): with open("tests/casefiles/git-new-empty-file.diff") as f: text = f.read() lines = text.splitlines() expected = [ diffobj( header=headerobj( index_path=None, old_path="/dev/null", old_version="0000000", new_path="somefile.txt", new_version="e69de29", ), changes=[], text="\n".join(lines[:34]) + "\n", ) ] results = list(wtp.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_git_header(self): with open("tests/casefiles/git-header.diff") as f: text = f.read() expected = headerobj( index_path=None, old_path="bugtrace/patch.py", old_version="8910dfd", new_path="bugtrace/patch.py", new_version="456e34f", ) results = wtp.patch.parse_git_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_git_header_long(self): with open("tests/casefiles/git-header-long.diff") as f: text = f.read() expected = headerobj( index_path=None, old_path="bugtrace/patch.py", old_version="18910dfd", new_path="bugtrace/patch.py", new_version="2456e34f", ) results = wtp.patch.parse_git_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_git_binary_files(self): with open("tests/casefiles/git-binary-files.diff") as f: text = f.read() expected = headerobj( index_path=None, old_path="/dev/null", old_version="0000000", new_path="project/media/i/asc.gif", new_version="71e31ac", ) results = wtp.patch.parse_git_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_svn_header(self): with open("tests/casefiles/svn-header.diff") as f: text = f.read() expected = headerobj( index_path="bugtrace/trunk/src/bugtrace/csc.py", old_path="bugtrace/trunk/src/bugtrace/csc.py", old_version=12783, new_path="bugtrace/trunk/src/bugtrace/csc.py", new_version=12784, ) results = wtp.patch.parse_svn_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_cvs_header(self): with open("tests/casefiles/cvs-header.diff") as f: text = f.read() path = ( "org.eclipse.core.resources" "/src/org/eclipse/core/internal/localstore/" "SafeChunkyInputStream.java" ) expected = headerobj( index_path=path, old_path=path, old_version="1.6.4.1", new_path=path, new_version="1.8", ) results = wtp.patch.parse_cvs_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_unified_header(self): with open("tests/casefiles/unified-header.diff") as f: text = f.read() expected = headerobj( index_path=None, old_path="/tmp/o", old_version="2012-12-22 06:43:35.000000000 -0600", new_path="/tmp/n", new_version="2012-12-23 20:40:50.000000000 -0600", ) results = wtp.patch.parse_unified_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_unified_header_notab(self): with open("tests/casefiles/unified-header-notab.diff") as f: text = f.read() expected = headerobj( index_path=None, old_path="/tmp/some file", old_version="2012-12-22 06:43:35.000000000 -0600", new_path="/tmp/n", new_version="2012-12-23 20:40:50.000000000 -0600", ) results = wtp.patch.parse_unified_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_unified_diff(self): with open(datapath("diff-unified.diff")) as f: text = f.read() # off with your head! text_diff = "\n".join(text.splitlines()[2:]) + "\n" expected = [ (1, None, "The Way that can be told of is not the eternal Way;"), (2, None, "The name that can be named is not the eternal name."), (3, 1, "The Nameless is the origin of Heaven and Earth;"), (4, None, "The Named is the mother of all things."), (None, 2, "The named is the mother of all things."), (None, 3, ""), (5, 4, "Therefore let there always be non-being,"), (6, 5, " so we may see their subtlety,"), (7, 6, "And let there always be being,"), (9, 8, "The two are the same,"), (10, 9, "But after they are produced,"), (11, 10, " they have different names."), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] results = list(wtp.patch.parse_unified_diff(text_diff)) self.assert_diffs_equal(results, expected) expected_main = diffobj( header=headerobj( index_path=None, old_path="lao", old_version="2013-01-05 16:56:19.000000000 -0600", new_path="tzu", new_version="2013-01-05 16:56:35.000000000 -0600", ), changes=expected, text=text, ) results_main = next(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results_main, expected_main) def test_unified2_diff(self): with open(datapath("diff-unified2.diff")) as f: text = f.read() # off with your head! text_diff = "\n".join(text.splitlines()[2:]) + "\n" expected = [ (None, 2, "The named is the mother of all things."), ] results = list(wtp.patch.parse_unified_diff(text_diff)) self.assert_diffs_equal(results, expected) expected_main = diffobj( header=headerobj( index_path=None, old_path="abc", old_version="2013-01-05 16:56:19.000000000 -0600", new_path="efg", new_version="2013-01-05 16:56:35.000000000 -0600", ), changes=expected, text=text, ) results_main = next(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results_main, expected_main) def test_diff_unified_with_does_not_include_extra_lines(self): with open("tests/casefiles/diff-unified-blah.diff") as f: text = f.read() changes = [ (1, None, "The Way that can be told of is not the eternal Way;"), (2, None, "The name that can be named is not the eternal name."), (3, 1, "The Nameless is the origin of Heaven and Earth;"), (4, None, "The Named is the mother of all things."), (None, 2, "The named is the mother of all things."), (None, 3, ""), (5, 4, "Therefore let there always be non-being,"), (6, 5, " so we may see their subtlety,"), (7, 6, "And let there always be being,"), (9, 8, "The two are the same,"), (10, 9, "But after they are produced,"), (11, 10, " they have different names."), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] expected = [ diffobj( header=headerobj( index_path=None, old_path="lao", old_version="2013-01-05 16:56:19.000000000 -0600", new_path="tzu", new_version="2013-01-05 16:56:35.000000000 -0600", ), changes=changes, text=text, ) ] results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_diff_context_with_does_not_include_extra_lines(self): with open("tests/casefiles/diff-context-blah.diff") as f: text = f.read() changes = [ (1, None, "The Way that can be told of is not the eternal Way;"), (2, None, "The name that can be named is not the eternal name."), (3, 1, "The Nameless is the origin of Heaven and Earth;"), (4, None, "The Named is the mother of all things."), (None, 2, "The named is the mother of all things."), (None, 3, ""), (5, 4, "Therefore let there always be non-being,"), (6, 5, " so we may see their subtlety,"), (7, 6, "And let there always be being,"), (9, 8, "The two are the same,"), (10, 9, "But after they are produced,"), (11, 10, " they have different names."), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] expected = [ diffobj( header=headerobj( index_path=None, old_path="lao", old_version="2013-01-05 16:56:19.000000000 -0600", new_path="tzu", new_version="2013-01-05 16:56:35.000000000 -0600", ), changes=changes, text=text, ) ] results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_diff_default_with_does_not_include_extra_lines(self): with open("tests/casefiles/diff-default-blah.diff") as f: text = f.read() changes = [ (1, None, "The Way that can be told of is not the eternal Way;"), (2, None, "The name that can be named is not the eternal name."), (4, None, "The Named is the mother of all things."), (None, 2, "The named is the mother of all things."), (None, 3, ""), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] expected = [diffobj(header=None, changes=changes, text=text)] results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_context_header(self): with open("tests/casefiles/context-header.diff") as f: text = f.read() expected = headerobj( index_path=None, old_path="/tmp/o", old_version="2012-12-22 06:43:35.000000000 -0600", new_path="/tmp/n", new_version="2012-12-23 20:40:50.000000000 -0600", ) results = wtp.patch.parse_context_header(text) self.assertEqual(results, expected) results_main = wtp.patch.parse_header(text) self.assertEqual(results_main, expected) def test_context_diff(self): with open(datapath("diff-context.diff")) as f: text = f.read() # off with your head! text_diff = "\n".join(text.splitlines()[2:]) + "\n" expected = [ (1, None, "The Way that can be told of is not the eternal Way;"), (2, None, "The name that can be named is not the eternal name."), (3, 1, "The Nameless is the origin of Heaven and Earth;"), (4, None, "The Named is the mother of all things."), (None, 2, "The named is the mother of all things."), (None, 3, ""), (5, 4, "Therefore let there always be non-being,"), (6, 5, " so we may see their subtlety,"), (7, 6, "And let there always be being,"), (9, 8, "The two are the same,"), (10, 9, "But after they are produced,"), (11, 10, " they have different names."), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] results = list(wtp.patch.parse_context_diff(text_diff)) self.assert_diffs_equal(results, expected) expected_main = diffobj( header=headerobj( index_path=None, old_path="lao", old_version="2013-01-05 16:56:19.000000000 -0600", new_path="tzu", new_version="2013-01-05 16:56:35.000000000 -0600", ), changes=expected, text=text, ) results_main = next(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results_main, expected_main) def test_context_diff_issue39(self): with open(datapath("issue39-bash42-003.patch")) as f: text = f.read() # off with your head! text_diff = "\n".join(text.splitlines()[2:]) + "\n" expected = [ (295, 331, "\t\t{"), (296, 332, "\t\t pat++;"), (None, 333, "\t\t bracklen++;"), ( 297, 334, "\t\t if (*pat == ']')\t/* right bracket can appear as equivalence class " "*/", ), (298, None, "\t\t pat++;"), (None, 335, "\t\t {"), (None, 336, "\t\t pat++;"), (None, 337, "\t\t bracklen++;"), (None, 338, "\t\t }"), (299, 339, "\t\t in_equiv = 1;"), ] results = list(wtp.patch.parse_context_diff(text_diff)) self.assert_diffs_equal(results, expected) expected_main = diffobj( header=headerobj( index_path=None, old_path="../bash-4.2-patched/lib/glob/gmisc.c", old_version="2011-02-05 16:11:17.000000000 -0500", new_path="lib/glob/gmisc.c", new_version="2011-02-18 23:53:42.000000000 -0500", ), changes=expected, text=text, ) results_main = next(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results_main, expected_main) def test_ed_diff(self): with open(datapath("diff-ed.diff")) as f: text = f.read() expected = [ (1, None, None), (2, None, None), (4, None, None), (None, 2, "The named is the mother of all things."), (None, 3, ""), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] results = list(wtp.patch.parse_ed_diff(text)) self.assert_diffs_equal(results, expected) expected_main = [diffobj(header=None, changes=expected, text=text)] results_main = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results_main, expected_main) def test_rcs_diff(self): with open(datapath("diff-rcs.diff")) as f: text = f.read() expected = [ (1, None, None), (2, None, None), (4, None, None), (None, 2, "The named is the mother of all things."), (None, 3, ""), (None, 11, "They both may be called deep and profound."), (None, 12, "Deeper and more profound,"), (None, 13, "The door of all subtleties!"), ] results = list(wtp.patch.parse_rcs_ed_diff(text)) self.assert_diffs_equal(results, expected) expected_main = [diffobj(header=None, changes=expected, text=text)] results_main = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results_main, expected_main) def test_embedded_diff_in_comment(self): with open("tests/casefiles/embedded-diff.comment") as f: text = f.read() changes = indent( 10, [ (2182, 2182, "case Token.GETELEM:"), (2183, 2183, " decompileElementGet((ElementGet) node);"), (2184, 2184, " break;"), (None, 2185, "case Token.THIS:"), (None, 2186, " decompiler.addToken(node.getType());"), (None, 2187, " break;"), (2185, 2188, "default:"), (2186, 2189, ' Kit.codeBug("unexpected token: "'), (2187, 2190, " " "+ Token.typeToName(node.getType()));"), ], ) expected = [ diffobj( header=headerobj( index_path=None, old_path="src/org/mozilla/javascript/IRFactory.java", old_version=None, new_path="src/org/mozilla/javascript/IRFactory.java", new_version=None, ), changes=changes, text=text, ) ] results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_mozilla_527452_5_comment(self): with open("tests/casefiles/mozilla-527452-5.comment") as f: text = f.read() lines = text.splitlines() path = ( "js_instrumentation_proxy/src/org/mozilla/" "javascript/ast/StringLiteral.java" ) header = headerobj( index_path=path, old_path=path, old_version=5547, new_path=path, new_version=None, ) changes = indent( 8, [ ( 112, 112, "// TODO(stevey): make sure this unescapes " "everything properly", ), (113, 113, "String q = String.valueOf(getQuoteCharacter());"), (114, 114, r'String rep = "\\\\" + q;'), (115, None, "String s = value.replaceAll(q, rep);"), (None, 115, r'String s = value.replace("\\", "\\\\");'), (None, 116, "s = s.replaceAll(q, rep);"), (116, 117, r's = s.replaceAll("\n", "\\\\n");'), (117, 118, r's = s.replaceAll("\r", "\\\\r");'), (118, 119, r's = s.replaceAll("\t", "\\\\t");'), ], ) text = "\n".join(lines[2:]) + "\n" expected = [diffobj(header=header, changes=changes, text=text)] results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_dos_unified_cvs(self): with open("tests/casefiles/mozilla-560291.diff") as f: text = f.read() path = "src/org/mozilla/javascript/ast/ArrayComprehensionLoop.java" lines = text.splitlines() header = headerobj( index_path=path, old_path=path, old_version="1.1", new_path=path, new_version="15 Sep 2011 02:26:05 -0000", ) expected = [ diffobj( header=header, changes=[ (79, 79, " @Override"), (80, 80, " public String toSource(int depth) {"), (81, 81, " return makeIndent(depth)"), (82, None, ' + " for ("'), (None, 82, ' + " for " '), (None, 83, ' + (isForEach()?"each ":"")'), (None, 84, ' + "("'), (83, 85, " + iterator.toSource(0)"), (84, 86, ' + " in "'), (85, 87, " + iteratedObject.toSource(0)"), ], text="\n".join(lines[2:]) + "\n", ) ] results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_old_style_cvs(self): with open("tests/casefiles/mozilla-252983.diff") as f: text = f.read() changes = [ ( 1, None, "This file version: $Id: CHANGELOG,v 1.1.1.1 " "2007/01/25 15:59:02 inonit Exp $", ), ( None, 1, "This file version: $Id: CHANGELOG,v 1.1 " "2007/01/25 15:59:02 inonit Exp $", ), (2, 2, ""), (3, 3, "Changes since Rhino 1.6R5"), (4, 4, "========================="), ] expected = [ diffobj( header=headerobj( index_path="mozilla/js/rhino/CHANGELOG", old_path="mozilla/js/rhino/CHANGELOG", old_version="1.1.1.1", new_path="mozilla/js/rhino/CHANGELOG", new_version="1.1", # or 'Thu Jan 25 10:59:02 2007' ), changes=changes, text=text, ) ] results = wtp.patch.parse_cvs_header(text) self.assertEqual(results, expected[0].header) results = wtp.patch.parse_header(text) self.assertEqual(results, expected[0].header) results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_mozilla_252983_versionless(self): with open("tests/casefiles/mozilla-252983-versionless.diff") as f: text = f.read() changes = [ ( 1, None, "This file version: $Id: CHANGELOG,v 1.1.1.1 " "2007/01/25 15:59:02 inonit Exp $", ), ( None, 1, "This file version: $Id: CHANGELOG,v 1.1 " "2007/01/25 15:59:02 inonit Exp $", ), (2, 2, ""), (3, 3, "Changes since Rhino 1.6R5"), (4, 4, "========================="), ] expected = [ diffobj( header=headerobj( index_path="mozilla/js/rhino/CHANGELOG", old_path="mozilla/js/rhino/CHANGELOG", old_version=None, new_path="mozilla/js/rhino/CHANGELOG", new_version=None, ), changes=changes, text=text, ) ] results = wtp.patch.parse_header(text) self.assertEqual(results, expected[0].header) results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_apache_attachment_2241(self): with open("tests/casefiles/apache-attachment-2241.diff") as f: text = f.read() lines = text.splitlines() header = headerobj( index_path=None, old_path=( r"src\main\org\apache\tools\ant" r"\taskdefs\optional\pvcs\Pvcs.orig" ), old_version="Sat Jun 22 16:11:58 2002", new_path=( r"src\main\org\apache\tools\ant" r"\taskdefs\optional\pvcs\Pvcs.java" ), new_version="Fri Jun 28 10:55:50 2002", ) changes = [ (91, 91, " *"), ( 92, 92, ' * @author ' "Thomas Christensen", ), ( 93, 93, ' * @author ' "Don Jeffery", ), ( 94, None, ' * @author ' "Steven E. Newton", ), ( None, 94, ' * @author ' "Steven E. Newton", ), (95, 95, " */"), (96, 96, "public class Pvcs extends org.apache.tools.ant.Task {"), (97, 97, " private String pvcsbin;"), ] text = "\n".join(lines) + "\n" expected = [diffobj(header=header, changes=changes, text=text)] results = list(wtp.patch.parse_patch(text)) self.assert_diffs_equal(results, expected) def test_space_in_path_header(self): with open("tests/casefiles/eclipse-attachment-126343.header") as f: text = f.read() expected = headerobj( index_path=( "test plugin/org/eclipse/jdt/debug/testplugin/" "ResumeBreakpointListener.java" ), old_path="/dev/null", old_version="1 Jan 1970 00:00:00 -0000", new_path=( "test plugin/org/eclipse/jdt/debug/testplugin/" "ResumeBreakpointListener.java" ), new_version="1 Jan 1970 00:00:00 -0000", ) results = wtp.patch.parse_header(text) self.assertEqual(results, expected) def test_svn_mixed_line_ends(self): with open("tests/casefiles/svn-mixed_line_ends.patch") as f: text = f.read() expected_header = headerobj( index_path=("java/org/apache/catalina/loader/WebappClassLoader.java"), old_path="java/org/apache/catalina/loader/WebappClassLoader.java", old_version=1346371, new_path="java/org/apache/catalina/loader/WebappClassLoader.java", new_version=None, ) results = list(wtp.patch.parse_patch(text)) self.assertEqual(results[0].header, expected_header) def test_huge_patch(self): start_time = time.time() text = """diff --git a/huge.file b/huge.file index 0000000..1111111 100644 --- a/huge.file +++ a/huge.file @@ -3,13 +3,1000007 @@ 00000000 11111111 22222222 -33333333 -44444444 +55555555 +66666666 """ for n in range(0, 1000000): text += "+" + hex(n) + "\n" result = list(wtp.patch.parse_patch(text)) self.assertEqual(1, len(result)) self.assertEqual(1000007, len(result[0].changes)) # This is 2x the usual time for CI to allow for some slow tests # Really all we care about is that this parses faster than it used to (200s+) self.assertGreater(20, time.time() - start_time) def test_git_bin_patch(self): with open("tests/casefiles/git-bin.patch") as f: text = f.read() result = list(wtp.patch.parse_patch(text)) assert result assert len(result) == 4 assert ( result[0].changes[0].line == b"The quick brown fox jumps over the lazy dog\x00" ) assert ( result[1].changes[0].line == "The quick brown fox jumps over the lazy dog" ) assert ( result[1].changes[1].line == "The quick brown fox jumps over the lazy dog." ) assert ( result[2].changes[0].line == b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt" b" ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco" b" laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit" b" in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat" b" cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\x00" ) assert len(result[3].changes) == 0 def test_git_bin_patch_minline(self): # test path with minimal line in binary diff text = """--- 95 | Bin 94 -> 95 bytes 1 files changed, 0 insertions(+), 0 deletions(-) diff --git a/95 b/95 index cf104291536b187e299023ae37523f4649ca0600..edf50979da25419fbb399ffa6b93142e50dbbba7 100644 GIT binary patch literal 95 zcmV-l0HFT>FaHM=!1loEo7=$@IDCW@J2o!_PR6;*Rs73Fmit;^XEfl3aOa~j;?1+w z`|Sh7XeZ(}tCx^}pPZlER5Jer^*}gX^QK-R BGb8{2 literal 94 zcmV-k0HObM)Jh!P2BexYI{K1M2*Xhjg3TEDprVXYwS|c&%Px9;9nEE3cL}-^F2dKtTQun3NbDG}SFOY= AaR2}S --""" result = list(wtp.patch.parse_patch(text)) assert result assert len(result) == 1 assert ( hashlib.sha1(result[0].changes[0].line).hexdigest() == "732e7e005ff8b71ab4b72398db0320f2fa012b81" ) assert ( hashlib.sha1(result[0].changes[1].hunk).hexdigest() == "b07b94142cfce2094b5be04e9d30b653a7c63917" ) if __name__ == "__main__": unittest.main()