Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vendor-cleanup

Open .gitattributes export-ignore PRs at scale against PHP/Composer packages that still ship dev files (tests/, phpunit.xml, .github/, …) in their composer dist.

Designed to be polite, evidence-citing, and safe: each PR body references the last .gitattributes change in the repo plus the commit/PR that introduced every file we want excluded. Pre-flight checks skip repos where the maintainer has already closed similar PRs, and drop entries whose paths no longer exist upstream.

Why

Many PHP libraries ship tests/, IDE configs and CI workflows in the composer dist tarball, bloating every server's vendor/ directory with files that are only useful for development. Adding export-ignore rules in .gitattributes is the supported fix; this tool batches the PR-opening across many libraries.

Features

  • Rejection-history check: skip the repo if a non-author closed a similar PR in the last 5 years (catches maintainers who said no before). build runs it too, so refusing repos are warned about on stderr and land in the emitted skipped: list instead of targets:; move one back under targets: by hand to override.
  • Upstream existence check: drop entries whose paths no longer exist in upstream HEAD (catches stale-rename false positives like phpunit.xml -> phpunit.xml.dist).
  • Style detection: preserve the existing .gitattributes style:
    • Alpha-sort if the existing file is sorted.
    • Pad with spaces if export-ignore is aligned in a fixed column.
  • CLA-org awareness: for GoogleCloudPlatform/*, googleapis/*, google/*, replace Co-Authored-By trailer with a plain-text attribution so the Google CLA bot doesn't block on the Anthropic co-author email.
  • HTTPS auth via gh auth git-credential: no SSH key / yubikey taps mid campaign.
  • Idempotent: skip repos where a PR already exists on the same head branch.
  • Skip list with reasons: track repos that maintainers won't accept.
  • Broad dev-file detection: ~110 default candidates covering test runners, static analysis, docs generators (phpdoc.dist.xml, .phpdoc/), toolchain pinning (.tool-versions, .phive/, phive.xml, .mise.toml), CI services and dev-environment files. Files consumers legitimately read out of vendor/ (CHANGELOG.md, UPGRADE.md) are deliberately never proposed.

Install

Requires Rust 1.75+ and the gh CLI (logged in via gh auth login).

cargo install --path .

This produces a single vendor-cleanup binary on your $PATH.

Quick start

gh auth login                # if not already

# 1. Build a campaign YAML from a project's vendor/ folder.
#    Scans every package, resolves the upstream repo via composer.json,
#    drops anything already excluded by upstream's .gitattributes,
#    drops paths that no longer exist upstream, and cites the last
#    commit ref + date that touched each surviving entry.
vendor-cleanup build \
    --vendor /path/to/your/project/vendor \
    --user-login your-gh-handle \
    --fork-dir /path/to/your/forks \
    --output campaign.yaml

# Or build from an explicit repo list (uses the default candidate set).
vendor-cleanup build \
    --repo schmittjoh/serializer \
    --repo phar-io/manifest \
    --user-login your-gh-handle \
    --output campaign.yaml

# 2. Dry-run the campaign to review every diff.
vendor-cleanup run --config campaign.yaml

# 3. Fire when ready (random 10-20s sleep between PRs).
vendor-cleanup run --config campaign.yaml --go
vendor-cleanup run --config campaign.yaml --go --limit 2   # stop after 2

# Enrich a registry YAML with per-PR upstream byte sizes.
vendor-cleanup enrich-savings registry/williamdes-cleanup-prs.yaml

Real example: building a campaign from a hand-vendored project

Dolibarr ships dependencies under htdocs/includes/ with an irregular layout (not standard composer install). For non-standard layouts, pass explicit --repo flags. After manually identifying which embedded packages still ship dev files in their upstream dist:

vendor-cleanup build \
    --repo fruux/sabre-event \
    --repo fruux/sabre-http \
    --repo globalcitizen/php-iban \
    --repo mike42/escpos-php \
    --repo phayes/geoPHP \
    --repo php-fig/simple-cache \
    --repo serbanghita/Mobile-Detect \
    --repo webklex/php-imap \
    --user-login your-gh-handle \
    --fork-dir /mnt/Dev/forks \
    --output dolibarr-campaign.yaml

vendor-cleanup run --config dolibarr-campaign.yaml          # dry-run
vendor-cleanup run --config dolibarr-campaign.yaml --go     # fire

What happens when the project migrated from Travis to GitHub Actions

If an upstream's .gitattributes still has .travis.yml export-ignore but the file no longer exists in upstream HEAD, build detects the stale entry, proposes removing it, and (when .github/ exists upstream and isn't already excluded) proposes adding /.github/ export-ignore alongside. The resulting PR body is structured into two sections: "Stale entries to remove" and "Entries to add", so reviewers see the intent at a glance.

Configuration

YAML file. See examples/ for a real sample.

defaults:
  fork_dir: /home/me/forks
  user_login: my-gh-handle
  branch: gh-handle/gitattributes-export-ignore
  pr_title: "Update .gitattributes to exclude dev files from composer dist"

targets:
  - repo: schmittjoh/serializer
    branch: master
    create: false
    last_gitattributes_ref: "cd24e3c (2023-01-06)"
    entries:
      - line: "/doc/ export-ignore"
        ref: "e5baafe 2025-07-17 (#1604)"
      - line: "/phpstan.neon.dist export-ignore"
        ref: "3d937ad 2024-11-25"
      - line: "/CONTRIBUTING.md export-ignore"
        ref: "f674fba 2026-03-26"

  - repo: shuchkin/simplexlsx
    branch: master
    create: true               # no existing .gitattributes upstream
    entries:
      - line: "/examples/ export-ignore"
        ref: "ce6559b 2022-02-21"

skipped:
  - repo: maennchen/ZipStream-PHP
    reason: |
      Owner has repeatedly closed export-ignore PRs (#206, #285, #339, #422).
      They want GitHub Source Download to include tests. Do not re-propose.

How the PR body looks

This avoids shipping dev/tooling files in the composer dist that aren't
needed at runtime.

Last `.gitattributes` update was 2aa81ac (2022-10-19, #601). The files below
have been added or modified since, and are still included in the composer dist:
- `/phpstan.neon export-ignore` - last touched in 1e01c7b 2023-08-03 (#740)
- `/phpstan-baseline.neon export-ignore` - last touched in 1d1a873 2026-03-12 (#1103)
- `/.php-cs-fixer.php export-ignore` - last touched in 5a3f81b 2024-11-24 (#954)
- `/CONTRIBUTING.md export-ignore` - last touched in fbcd9bd 2024-01-23 (#839)

Background reading: https://blog.madewithlove.be/post/gitattributes/

Helping populate the config

A companion scanner that reads a project's composer.lock + vendor/ and emits a draft YAML config with last-touch commit refs per entry is on the roadmap.

Development

cargo build
cargo test            # 77 tests (unit + integration)
cargo clippy --all-targets -- -D warnings

License

MPL-2.0. See LICENSE.

About

Open .gitattributes export-ignore PRs at scale against PHP/Composer packages that still ship dev files in their composer dist.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages