From edf60fe1530d5c17189e2e3bea9d8817d695570d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 00:44:55 -0500 Subject: [PATCH 01/13] Add pygit2 --- poetry.lock | 132 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index d6b29c90..5a348640 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,6 +14,70 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "colorama" version = "0.4.6" @@ -191,6 +255,56 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pygit2" +version = "1.13.3" +description = "Python bindings for libgit2." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygit2-1.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a3053850e29c1e102b1ab759d90b0dcc6402d7a434cbe810cfd2792294cf0ba6"}, + {file = "pygit2-1.13.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d2461db082c27231e2565e24e7ec3d6a60c7ceea5cda7364cb6eb81a6aedebd"}, + {file = "pygit2-1.13.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2216dc34edbe44e37c5cabc5f1530266445b87c66038fc8b3e0e7be64b3d4edb"}, + {file = "pygit2-1.13.3-cp310-cp310-win32.whl", hash = "sha256:5bc8c173ead087a4200e8763fad92105b4c9d40d03e007b9d9bbe47793716d31"}, + {file = "pygit2-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:c305adf3a86e02db8bcd89bb92e33e896a2ff36f58a5ad7ff15675491ab6a751"}, + {file = "pygit2-1.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8de720ca137624d8f98c8b8d57cdb1e461034adf3e158762ee5c3085620c8075"}, + {file = "pygit2-1.13.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a85521b8e218afd4111d5bd4e106d77ffaac7ecd9a1ed8c1eea9f8d9568d287"}, + {file = "pygit2-1.13.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c5cb78cc0a88f5cb2910e85996f33fcac99994251117f00f2e344f84d2616a"}, + {file = "pygit2-1.13.3-cp311-cp311-win32.whl", hash = "sha256:81686e3e06132f23eab32c6718c21930e8adda795c2ca76617f7562bff7b6b66"}, + {file = "pygit2-1.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:93cc7ffb403688c2ec3e169096f34b2b99bc709adc54487e9d11165cfd070948"}, + {file = "pygit2-1.13.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:07b17f766c88ce1d05d264b5819e75ad261f3b60e33e4105a71f02467d0f6d39"}, + {file = "pygit2-1.13.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81bfe9ca394cdc896b632f18cd5f9c656a5f6c03c61deb1570b9081f2406776b"}, + {file = "pygit2-1.13.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c83e6e5ac357a9e87698c1eb25f430846f208bce12362d2209e7c9ac214e00c"}, + {file = "pygit2-1.13.3-cp312-cp312-win32.whl", hash = "sha256:de481a2cee7ef98143109bd9d2b30690022aeb8ba849feeba082a3b48a53c214"}, + {file = "pygit2-1.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:0353b55f8bed1742dab15083ee9ee508ed91feb5c2563e2a612af277317030c6"}, + {file = "pygit2-1.13.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60152bb30bc2ab880d3c82f113be33aac7ced571d1148c51720ccefff9dfc9ce"}, + {file = "pygit2-1.13.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcf827ebe392e2181a50ebaf724947e30a1da076a74d8a6f9cec784158faced1"}, + {file = "pygit2-1.13.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73cb821acc5cc8ad62a96154d030ff47127073b56f71157e7c65b2e7ebb4d52f"}, + {file = "pygit2-1.13.3-cp38-cp38-win32.whl", hash = "sha256:112c4efd421c3c8b4bb4406d3cd4a3a6a18a925c1f8b08d8a6dd0b591c6c6049"}, + {file = "pygit2-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:21f73fd2863b6b21b4fbfed11a42af0ac1036472bb3716944b42a9719beaf07e"}, + {file = "pygit2-1.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e94f2598bdf68340609bb21fd4d21213812913b40b73e5fcba67f4fb01f4fba4"}, + {file = "pygit2-1.13.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5846aadb7b72802b3e4cb981f956965e92bc1692e7514ff4491bd7e24b20b358"}, + {file = "pygit2-1.13.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2823d097b103740d52e600ef2079e23966583edbde08ac122279f1ab3b2c3979"}, + {file = "pygit2-1.13.3-cp39-cp39-win32.whl", hash = "sha256:72fda35f88a3f5549eb9683c47ac73a6b674df943fc2490d93d539b46f518cbd"}, + {file = "pygit2-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:2087fd130181e4ba6b8599fcd406920781555a52a3f142bd1dec4de21b9c5792"}, + {file = "pygit2-1.13.3.tar.gz", hash = "sha256:0257c626011e4afb99bdb20875443f706f84201d4c92637f02215b98eac13ded"}, +] + +[package.dependencies] +cffi = ">=1.16.0" +setuptools = {version = "*", markers = "python_version >= \"3.12\""} + [[package]] name = "pylint" version = "3.0.2" @@ -269,6 +383,22 @@ files = [ {file = "ruff-0.1.7.tar.gz", hash = "sha256:dffd699d07abf54833e5f6cc50b85a6ff043715da8788c4a79bcd4ab4734d306"}, ] +[[package]] +name = "setuptools" +version = "69.0.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "tomli" version = "2.0.1" @@ -305,4 +435,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "3bbc8f248c88f7352ac9fe82b3b7385aa7e309e719a26c80bc72db7dac7d1166" +content-hash = "c717003701674fc908621a7ceb7d070ed96309ce34c788898d29c1d60479eac8" diff --git a/pyproject.toml b/pyproject.toml index 41c96742..08425655 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ version = "0.1.0" [tool.poetry.dependencies] python = ">=3.8" +pygit2 = "^1.13.3" [tool.poetry.group.dev.dependencies] mypy = "^1.7.0" From dfd669c70f67c682909a1c2bff02efd5407fb9b6 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 01:17:44 -0500 Subject: [PATCH 02/13] Messing around with pygit2 TODO: Add "scrap add" to take a scrap, serialize it, and commit it to a scrapyard. --- git.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 git.py diff --git a/git.py b/git.py new file mode 100644 index 00000000..2c6900b9 --- /dev/null +++ b/git.py @@ -0,0 +1,31 @@ +import pathlib + +import pygit2 + + +yard = "/tmp/scrapyard" +repo = pygit2.init_repository(yard, bare=True) + +root = repo.TreeBuilder() +obj_id = repo.create_blob(b"hello world from pygit2!") +root.insert("test", obj_id, pygit2.GIT_FILEMODE_BLOB) +tree_id = root.write() + +ref = "HEAD" +author = pygit2.Signature("Alice Author", "alice@authors.tld") +message = "Initial commit" +parents: object = [] +repo.create_commit(ref, author, author, message, tree_id, parents) + +root = repo.TreeBuilder() +obj_id = repo.create_blob(b"goodbye world from pygit2!") +other_obj_id = repo.create_blob(b"hello chris") +root.insert("test", obj_id, pygit2.GIT_FILEMODE_BLOB) +root.insert("another_one", other_obj_id, pygit2.GIT_FILEMODE_BLOB) +tree_id = root.write() + +ref = repo.head.name +author = pygit2.Signature("Alice Author", "alice@authors.tld") +message = "Second commit" +parents = [repo.head.target] +repo.create_commit(ref, author, author, message, tree_id, parents) From 7d8cbdbd324b9dda5b5124c16ee18e8a5d3fd94a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 13:40:27 -0500 Subject: [PATCH 03/13] Implement scrap yard init/commit Use pygit2 and store scrapyards as git repositories. Scraps are blobs/files (for now? maybe they should be trees if they refer to other scraps). The representation on-disk is the serialized result of evaluating the scrap. --- scrapscript.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index cb54adc0..3f00b535 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -3203,6 +3203,60 @@ def test_command(args: argparse.Namespace) -> None: unittest.main(argv=[__file__, *args.unittest_args]) +class ScrapError(Exception): + pass + + +def _ensure_pygit2() -> ModuleType: + try: + import pygit2 + + assert isinstance(pygit2, ModuleType) + return pygit2 + except ImportError: + raise ScrapError("Please install pygit2 to work with scrapyards") + + +def yard_init_command(args: argparse.Namespace) -> None: + git = _ensure_pygit2() + repo = git.init_repository(args.directory, bare=True) + if not repo.is_empty: + raise ScrapError("Scrapyard already initialized; cannot init scrapyard again") + # Make an initial commit so that all other commands can do the normal + # commit flow + # TODO(max): Settle on an agreed-upon branch name like 'trunk' + ref = "HEAD" + author = repo.default_signature + # Make an empty tree + tree_id = repo.TreeBuilder().write() + message = "Initialize scrapyard" + parents: object = [] + repo.create_commit(ref, author, author, message, tree_id, parents) + + +def yard_commit_command(args: argparse.Namespace) -> None: + git = _ensure_pygit2() + # Find the scrapyard + repo_path = git.discover_repository(args.yard) + if repo_path is None: + raise ScrapError(f"Please create a scrapyard; {args.yard!r} is not initialized") + repo = git.Repository(repo_path, git.GIT_REPOSITORY_OPEN_BARE) + result = eval_exp(STDLIB, parse(tokenize(args.program_file.read()))) + serialized = serialize(result) + # Make a git tree + root = repo.TreeBuilder() + obj_id = repo.create_blob(serialized) + # TODO(max): Figure out how to handle names like a/b; make directories? + root.insert(args.scrap_name, obj_id, git.GIT_FILEMODE_BLOB) + tree_id = root.write() + # Commit the tree + ref = repo.head.name + author = repo.default_signature + message = f"Update {args.scrap_name}" + parents = [repo.head.target] + repo.create_commit(ref, author, author, message, tree_id, parents) + + def main() -> None: parser = argparse.ArgumentParser(prog="scrapscript") subparsers = parser.add_subparsers(dest="command") @@ -3226,6 +3280,16 @@ def main() -> None: apply.add_argument("program") apply.add_argument("--debug", action="store_true") + yard = subparsers.add_parser("yard").add_subparsers(dest="command", required=True) + yard_init = yard.add_parser("init") + yard_init.set_defaults(func=yard_init_command) + yard_init.add_argument("directory") + yard_commit = yard.add_parser("commit") + yard_commit.set_defaults(func=yard_commit_command) + yard_commit.add_argument("yard") + yard_commit.add_argument("scrap_name") + yard_commit.add_argument("program_file", type=argparse.FileType("r")) + args = parser.parse_args() if not args.command: args.debug = False From 1a5a0d6b5f04c24e8efd9d669a6734e4c2f6cc42 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 13:52:22 -0500 Subject: [PATCH 04/13] Remove test file --- git.py | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 git.py diff --git a/git.py b/git.py deleted file mode 100644 index 2c6900b9..00000000 --- a/git.py +++ /dev/null @@ -1,31 +0,0 @@ -import pathlib - -import pygit2 - - -yard = "/tmp/scrapyard" -repo = pygit2.init_repository(yard, bare=True) - -root = repo.TreeBuilder() -obj_id = repo.create_blob(b"hello world from pygit2!") -root.insert("test", obj_id, pygit2.GIT_FILEMODE_BLOB) -tree_id = root.write() - -ref = "HEAD" -author = pygit2.Signature("Alice Author", "alice@authors.tld") -message = "Initial commit" -parents: object = [] -repo.create_commit(ref, author, author, message, tree_id, parents) - -root = repo.TreeBuilder() -obj_id = repo.create_blob(b"goodbye world from pygit2!") -other_obj_id = repo.create_blob(b"hello chris") -root.insert("test", obj_id, pygit2.GIT_FILEMODE_BLOB) -root.insert("another_one", other_obj_id, pygit2.GIT_FILEMODE_BLOB) -tree_id = root.write() - -ref = repo.head.name -author = pygit2.Signature("Alice Author", "alice@authors.tld") -message = "Second commit" -parents = [repo.head.target] -repo.create_commit(ref, author, author, message, tree_id, parents) From 5e56b0489422a46b61093ca7e4b81cc43378e0a6 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 13:55:51 -0500 Subject: [PATCH 05/13] Add docs --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 4794ff6f..4cb191fc 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,15 @@ docker run -i -t ghcr.io/tekknolagi/scrapscript:trunk apply "1 + 2" docker run -i -t ghcr.io/tekknolagi/scrapscript:trunk repl ``` +## Advanced usage + +```bash +# Create a scrapyard +python3 scrapscript.py yard init /desired/path/to/your/scrapyard +# Evaluates the file and stores the serialized scrap object to the yard +python3 scrapscript.py yard commit /desired/path/to/your/scrapyard SCRAP_NAME SCRAPSCRIPT_FILE +``` + ## Running Tests ```bash From e935c5233d6657da14bed1e4f2995f4bd305e18a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 14:17:47 -0500 Subject: [PATCH 06/13] s/directory/yard --- scrapscript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index 3f00b535..a56bdd28 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -3219,7 +3219,7 @@ def _ensure_pygit2() -> ModuleType: def yard_init_command(args: argparse.Namespace) -> None: git = _ensure_pygit2() - repo = git.init_repository(args.directory, bare=True) + repo = git.init_repository(args.yard, bare=True) if not repo.is_empty: raise ScrapError("Scrapyard already initialized; cannot init scrapyard again") # Make an initial commit so that all other commands can do the normal @@ -3283,7 +3283,7 @@ def main() -> None: yard = subparsers.add_parser("yard").add_subparsers(dest="command", required=True) yard_init = yard.add_parser("init") yard_init.set_defaults(func=yard_init_command) - yard_init.add_argument("directory") + yard_init.add_argument("yard") yard_commit = yard.add_parser("commit") yard_commit.set_defaults(func=yard_commit_command) yard_commit.add_argument("yard") From 2aa2ce5181e6cc6a84d39a3d6352ec8b8b9f12a4 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 14:28:20 -0500 Subject: [PATCH 07/13] Use trunk as scrapyard branch name --- scrapscript.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index a56bdd28..d12bd404 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -3219,12 +3219,15 @@ def _ensure_pygit2() -> ModuleType: def yard_init_command(args: argparse.Namespace) -> None: git = _ensure_pygit2() - repo = git.init_repository(args.yard, bare=True) - if not repo.is_empty: + repo = git.init_repository(args.yard, initial_head="trunk", bare=True) + try: + repo.head + except git.GitError: + pass + else: raise ScrapError("Scrapyard already initialized; cannot init scrapyard again") # Make an initial commit so that all other commands can do the normal # commit flow - # TODO(max): Settle on an agreed-upon branch name like 'trunk' ref = "HEAD" author = repo.default_signature # Make an empty tree From 6a6e7d9cae7da12fa758fbc5ef635399d3d29e46 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 14:40:18 -0500 Subject: [PATCH 08/13] Make sure that branch and HEAD move when creating commits --- scrapscript.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index d12bd404..d3aab54a 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -3219,7 +3219,8 @@ def _ensure_pygit2() -> ModuleType: def yard_init_command(args: argparse.Namespace) -> None: git = _ensure_pygit2() - repo = git.init_repository(args.yard, initial_head="trunk", bare=True) + branch_name = "trunk" + repo = git.init_repository(args.yard, initial_head=branch_name, bare=True) try: repo.head except git.GitError: @@ -3234,7 +3235,9 @@ def yard_init_command(args: argparse.Namespace) -> None: tree_id = repo.TreeBuilder().write() message = "Initialize scrapyard" parents: object = [] - repo.create_commit(ref, author, author, message, tree_id, parents) + commit = repo.create_commit(ref, author, author, message, tree_id, parents) + assert commit == repo.branches[branch_name].target + assert commit == repo.head.target def yard_commit_command(args: argparse.Namespace) -> None: @@ -3257,7 +3260,9 @@ def yard_commit_command(args: argparse.Namespace) -> None: author = repo.default_signature message = f"Update {args.scrap_name}" parents = [repo.head.target] - repo.create_commit(ref, author, author, message, tree_id, parents) + commit = repo.create_commit(ref, author, author, message, tree_id, parents) + assert commit == repo.branches["trunk"].target + assert commit == repo.head.target def main() -> None: From 9996e5e35e523d6d8506bebe08ac4536db39d45e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 15:29:31 -0500 Subject: [PATCH 09/13] Don't restart the tree from scratch on commit --- scrapscript.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index d3aab54a..d57469d7 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -3249,8 +3249,9 @@ def yard_commit_command(args: argparse.Namespace) -> None: repo = git.Repository(repo_path, git.GIT_REPOSITORY_OPEN_BARE) result = eval_exp(STDLIB, parse(tokenize(args.program_file.read()))) serialized = serialize(result) - # Make a git tree - root = repo.TreeBuilder() + # Make a git tree starting from previous commit's tree + prev_commit = repo.get(repo.head.target) + root = repo.TreeBuilder(prev_commit.tree) obj_id = repo.create_blob(serialized) # TODO(max): Figure out how to handle names like a/b; make directories? root.insert(args.scrap_name, obj_id, git.GIT_FILEMODE_BLOB) From d046920658e90694e32a542e236f86ec47fa18ac Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 19:30:21 -0500 Subject: [PATCH 10/13] Add HashVar for content-addressible values --- scrapscript.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index d57469d7..e5b596fa 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -338,8 +338,13 @@ def parse(tokens: typing.List[Token], p: float = 0) -> "Object": if isinstance(token, NumLit): l = Int(token.value) elif isinstance(token, Name): - # TODO: Handle kebab case vars - l = Var(token.value) + sha_prefix = "$sha1'" + if token.value.startswith(sha_prefix): + hash = token.value[len(sha_prefix) :] + l = HashVar(hash) + else: + # TODO: Handle kebab case vars + l = Var(token.value) elif isinstance(token, BytesLit): base = token.base if base == 85: @@ -562,6 +567,20 @@ def deserialize(msg: Dict[str, object]) -> "Var": return Var(msg["name"]) +@dataclass(eq=True, frozen=True, unsafe_hash=True) +class HashVar(Object): + name: str + + def serialize(self) -> Dict[bytes, object]: + return {b"type": b"HashVar", b"name": self.name.encode("utf-8")} + + @staticmethod + def deserialize(msg: Dict[str, object]) -> "HashVar": + assert msg["type"] == "HashVar" + assert isinstance(msg["name"], str) + return HashVar(msg["name"]) + + @dataclass(eq=True, frozen=True, unsafe_hash=True) class Bool(Object): value: bool @@ -1448,8 +1467,8 @@ def test_parse_negative_int_binds_tighter_than_apply(self) -> None: def test_parse_var_returns_var(self) -> None: self.assertEqual(parse([Name("abc_123")]), Var("abc_123")) - def test_parse_sha_var_returns_var(self) -> None: - self.assertEqual(parse([Name("$sha1'abc")]), Var("$sha1'abc")) + def test_parse_sha_var_returns_hash_var(self) -> None: + self.assertEqual(parse([Name("$sha1'abc")]), HashVar("abc")) def test_parse_sha_var_without_quote_returns_var(self) -> None: self.assertEqual(parse([Name("$sha1abc")]), Var("$sha1abc")) From 8bb760edcb2a1e321746137d5552d9b393428287 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 20:31:36 -0500 Subject: [PATCH 11/13] Add content-addressible lookup to interpreter Hash vars will get looked up in the scrapyard specified by $$scrapyard in the current environment. --- scrapscript.py | 54 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index e5b596fa..88359cae 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -9,13 +9,14 @@ import os import re import sys +import tempfile import typing import unittest import urllib.request from dataclasses import dataclass from enum import auto from types import ModuleType, FunctionType -from typing import Any, Callable, Dict, Mapping, Optional, Union +from typing import Any, Callable, Dict, Mapping, Optional, Tuple, Union readline: Optional[ModuleType] try: @@ -1032,6 +1033,20 @@ def eval_exp(env: Env, exp: Object) -> Object: clo_inner = eval_exp(env, exp.inner) clo_outer = eval_exp(env, exp.outer) return Closure({}, Function(Var("x"), Apply(clo_outer, Apply(clo_inner, Var("x"))))) + if isinstance(exp, HashVar): + yard_location = env["$$scrapyard"] + assert isinstance(yard_location, String) + git = _ensure_pygit2() + repo_path = git.discover_repository(yard_location.value) + if repo_path is None: + raise ScrapError(f"Please create a scrapyard; {yard_location.value!r} is not initialized") + repo = git.Repository(repo_path, git.GIT_REPOSITORY_OPEN_BARE) + obj = repo.get(exp.name) + if obj is None: + raise ScrapError(f"Could not find object $sha1'{exp.name}") + if not isinstance(obj, git.Blob): + raise ScrapError(f"Object $sha1'{exp.name} is not a blob") + return deserialize(obj.data.decode("utf-8")) raise NotImplementedError(f"eval_exp not implemented for {exp}") @@ -2357,6 +2372,15 @@ def test_boolean_or_on_int_raises_type_error(self) -> None: with self.assertRaisesRegex(TypeError, re.escape("expected Bool, got Int")): eval_exp({}, exp) + def test_hash_var_looks_up_in_scrapyard(self) -> None: + # TODO(max): Do this in-memory instead of on-disk + with tempfile.TemporaryDirectory() as tempdir: + yard_init(tempdir) + yard_commit(tempdir, "a_test_object", Int(100)) + env = {"$$scrapyard": String(tempdir)} + result = eval_exp(env, HashVar("a8788e4ad848bac02d1c6ad76375aad65b7c5387")) + self.assertEqual(result, Int(100)) + class EndToEndTests(unittest.TestCase): def _run(self, text: str, env: Optional[Env] = None) -> Object: @@ -3236,10 +3260,10 @@ def _ensure_pygit2() -> ModuleType: raise ScrapError("Please install pygit2 to work with scrapyards") -def yard_init_command(args: argparse.Namespace) -> None: +def yard_init(path: str) -> None: git = _ensure_pygit2() branch_name = "trunk" - repo = git.init_repository(args.yard, initial_head=branch_name, bare=True) + repo = git.init_repository(path, initial_head=branch_name, bare=True) try: repo.head except git.GitError: @@ -3259,30 +3283,40 @@ def yard_init_command(args: argparse.Namespace) -> None: assert commit == repo.head.target -def yard_commit_command(args: argparse.Namespace) -> None: +def yard_init_command(args: argparse.Namespace) -> None: + yard_init(args.yard) + + +def yard_commit(path: str, scrap_name: str, obj: Object) -> Tuple[str, str]: git = _ensure_pygit2() # Find the scrapyard - repo_path = git.discover_repository(args.yard) + repo_path = git.discover_repository(path) if repo_path is None: - raise ScrapError(f"Please create a scrapyard; {args.yard!r} is not initialized") + raise ScrapError(f"Please create a scrapyard; {path!r} is not initialized") repo = git.Repository(repo_path, git.GIT_REPOSITORY_OPEN_BARE) - result = eval_exp(STDLIB, parse(tokenize(args.program_file.read()))) - serialized = serialize(result) + serialized = serialize(obj) # Make a git tree starting from previous commit's tree prev_commit = repo.get(repo.head.target) root = repo.TreeBuilder(prev_commit.tree) obj_id = repo.create_blob(serialized) # TODO(max): Figure out how to handle names like a/b; make directories? - root.insert(args.scrap_name, obj_id, git.GIT_FILEMODE_BLOB) + root.insert(scrap_name, obj_id, git.GIT_FILEMODE_BLOB) tree_id = root.write() # Commit the tree ref = repo.head.name author = repo.default_signature - message = f"Update {args.scrap_name}" + message = f"Update {scrap_name}" parents = [repo.head.target] commit = repo.create_commit(ref, author, author, message, tree_id, parents) assert commit == repo.branches["trunk"].target assert commit == repo.head.target + return scrap_name, obj_id + + +def yard_commit_command(args: argparse.Namespace) -> None: + obj = eval_exp(STDLIB, parse(tokenize(args.program_file.read()))) + scrap_name, obj_id = yard_commit(args.yard, args.scrap_name, obj) + print(args.scrap_name, obj_id) def main() -> None: From f54864c7234a388e28ab859ea1dc1172f3093324 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sun, 10 Dec 2023 22:29:56 -0500 Subject: [PATCH 12/13] Do feature detection for scrapyard tests --- scrapscript.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index 88359cae..984bb8bd 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1965,6 +1965,15 @@ def test_parse_match_with_right_apply(self) -> None: ) +def _dont_have_pygit2() -> bool: + try: + import pygit2 + + return False + except ImportError: + return True + + class EvalTests(unittest.TestCase): def test_eval_int_returns_int(self) -> None: exp = Int(5) @@ -2372,10 +2381,12 @@ def test_boolean_or_on_int_raises_type_error(self) -> None: with self.assertRaisesRegex(TypeError, re.escape("expected Bool, got Int")): eval_exp({}, exp) + @unittest.skipIf(_dont_have_pygit2(), "Can't run test without pygit2") def test_hash_var_looks_up_in_scrapyard(self) -> None: # TODO(max): Do this in-memory instead of on-disk with tempfile.TemporaryDirectory() as tempdir: yard_init(tempdir) + # TODO(max): Use object id yard_commit(tempdir, "a_test_object", Int(100)) env = {"$$scrapyard": String(tempdir)} result = eval_exp(env, HashVar("a8788e4ad848bac02d1c6ad76375aad65b7c5387")) From 120f4b15f378755989e1bf9346397b334e4171c5 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 12 Dec 2023 12:50:59 -0500 Subject: [PATCH 13/13] Add recursive test that fails --- scrapscript.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index 984bb8bd..f0a56c04 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -2806,6 +2806,19 @@ def test_boolean_and_binds_tighter_than_or(self) -> None: def test_compare_binds_tighter_than_boolean_and(self) -> None: self.assertEqual(self._run("1 < 2 && 2 < 1"), Bool(False)) + def test_hash_var_looks_up_in_scrapyard(self) -> None: + func = self._run("fac = | 0 -> 1 | n -> n * fac (n-1)") + print(func) + # TODO(max): Do this in-memory instead of on-disk + with tempfile.TemporaryDirectory() as tempdir: + yard_init(tempdir) + # TODO(max): Use object id + _, obj_id = yard_commit(tempdir, "a_test_object", func) + result = eval_exp(env, HashVar(obj_id)) + self.assertEqual(result, Int(100)) + env = {"$$scrapyard": String(tempdir)} + self.assertEqual(self._run(f"$sha1'{obj_id} 5", env), Int(120)) + class BencodeTests(unittest.TestCase): def test_bencode_int(self) -> None: