{"id":31974437,"url":"https://github.com/71/ifctc","last_synced_at":"2025-10-14T20:16:55.392Z","repository":{"id":318707407,"uuid":"1061133200","full_name":"71/ifctc","owner":"71","description":"[WIP] Enforce that code blocks stay in sync.","archived":false,"fork":false,"pushed_at":"2025-10-06T09:24:19.000Z","size":53,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-09T22:24:28.857Z","etag":null,"topics":["linter","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/71.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-21T10:05:39.000Z","updated_at":"2025-10-07T05:49:01.000Z","dependencies_parsed_at":"2025-10-09T22:24:31.342Z","dependency_job_id":"e789cd91-fe34-44c2-95e6-9ed391335569","html_url":"https://github.com/71/ifctc","commit_stats":null,"previous_names":["71/ifctc"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/71/ifctc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Fifctc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Fifctc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Fifctc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Fifctc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/71","download_url":"https://codeload.github.com/71/ifctc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Fifctc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279021014,"owners_count":26086947,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["linter","zig"],"created_at":"2025-10-14T20:16:52.586Z","updated_at":"2025-10-14T20:16:55.381Z","avatar_url":"https://github.com/71.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ifctc\n\nA Zig implementation of Google's\n[IFTTT (IfThisThenThat) lint](https://fuchsia.dev/fuchsia-src/development/source_code/presubmit_checks).\nIFCTC stands for \"IF Changes Then Change\".\n\n\u003e [!WARNING]\n\u003e\n\u003e This project isn't actively used yet. Bugs may be lurking.\n\n## Usage\n\nFeed `ifctc` a unified patch file, and it will verify that all files were\nmodified as specified. Files are read relative to the current working directory.\n\n```sh\n$ git diff --unified | ifctc\n```\n\nRecognized directives are:\n\n- `LINT.IfChange` (or `LINT.IfChange(label)`), which starts a code block.\n\n- `LINT.ThenChange(paths, ...)`, which reports an error if its contents were\n  modified, but not the contents in `paths`.\n\n  Paths may contain a `:label`, in which case the code with that label must be\n  modified.\n\n  Paths may be relative, in which case they are relative to the file which has\n  the directive. They may also be absolute (i.e. start with `/`), in which case\n  they are relative to the directory where `ifctc` is invoked.\n\n### Example\n\nAs a concrete example, let's say you have two files with the same constant,\nwhich must be kept in sync:\n\n\u003c!-- LINT.IfChange(example) --\u003e\n\n```py\n# constants.py\nMIN_VERSION = \"0.2.0\"\n```\n\n```rs\n// constants.rs\nconst MIN_VERSION: \u0026str = \"0.2.0\";\n```\n\nWe'll add `LINT` directives to keep them in sync:\n\n```py\n# LINT.IfChange\nMIN_VERSION = \"0.2.0\"\n# LINT.ThenChange(constants.rs)\n```\n\n```rs\n// LINT.IfChange\nconst MIN_VERSION: \u0026str = \"0.2.0\";\n// LINT.ThenChange(constants.py)\n```\n\nAnd run `ifctc` -- everything should be okay:\n\n```sh\n$ git diff --unified | ifctc \u0026\u0026 echo ok\nok\n```\n\nThen, if we change one version and forget to update the other:\n\n```diff\ndiff --git a/constants.rs b/constants.rs\n--- a/constants.rs\n+++ b/constants.rs\n@@ -1,3 +1,3 @@\n // LINT.IfChange\n-const MIN_VERSION: \u0026str = \"0.2.0\";\n+const MIN_VERSION: \u0026str = \"0.3.0\";\n // LINT.ThenChange(constants.py)\n```\n\nThen `ifctc` will report an error:\n\n```sh\n$ git diff --unified | ifctc\nconstants.rs:3: file was not modified: constants.py\n```\n\nHowever, if both files are modified instead:\n\n```diff\ndiff --git a/constants.py b/constants.py\n--- a/constants.py\n+++ b/constants.py\n@@ -1,3 +1,3 @@\n # LINT.IfChange\n-MIN_VERSION = \"0.2.0\"\n+MIN_VERSION = \"0.3.0\"\n # LINT.ThenChange(constants.rs)\ndiff --git a/constants.rs b/constants.rs\n--- a/constants.rs\n+++ b/constants.rs\n@@ -1,3 +1,3 @@\n // LINT.IfChange\n-const MIN_VERSION: \u0026str = \"0.2.0\";\n+const MIN_VERSION: \u0026str = \"0.3.0\";\n // LINT.ThenChange(constants.py)\n```\n\nThen `ifctc` succeeds:\n\n```sh\n$ git diff --unified | ifctc \u0026\u0026 echo ok\nok\n```\n\n\u003c!-- LINT.ThenChange(src/Analysis_test.zig:example) --\u003e\n\n## Testing\n\nUse `zig build test` to run tests.\n\nBecause the parsers in this repository have both fast paths and slow paths, they\nare typically tested with [`SplitBufferIterator`](src/test_helpers.zig), which\nensures that they behave the same way on different chunk sizes.\n\nFuzzing is not currently used because it is\n[not available on macOS](https://github.com/ziglang/zig/issues/20986).\n\n## Implementation notes\n\nI made this for two reasons:\n\n1. I needed an implementation of IFTTT, which only seems to exist\n   [here](https://github.com/ebrevdo/ifttt-lint), but isn't compatible with\n   Google's (e.g. it uses \"#\" for labels, instead of \":\").\n\n2. I wanted to make a small project using Zig.\n\nAnd because it's Zig, I really wanted to play the game and tried to optimize the\ntool quite deeply, at the cost of a more complex implementation:\n\n1. Scanning happens in multiple threads at once, without any locking.\n\n   - We achieve this by allocating the map of possibly modified files at the\n     start of the program, before we start scanning files. After that, most\n     state is thread-local, and consolidated once all threads have finished\n     scanning for changes.\n\n1. `LINT.IfChange` and `LINT.ThenChange` directives are found using SIMD.\n\n1. Overall, allocations and copies are kept to a minimum:\n\n   - Lines from the patch that don't contribute to the output are skipped\n     without copying them.\n\n   - Nothing from scanned files is copied, except for arguments of `IfChange`\n     and `ThenChange` directives.\n\n   - When allocations _are_ needed, that's usually in an arena allocator backed\n     by a stack-fallback allocator. Unless you have long paths in your\n     `LINT.ThenChange` arguments, no allocation will take place once threads\n     have spawned.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F71%2Fifctc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F71%2Fifctc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F71%2Fifctc/lists"}