Skip to content

Push MD: reliable fresh-site seeding (resolve by slug) + export path-collision hardening#66

Draft
robertsreberski wants to merge 4 commits into
trunkfrom
push-md-duplicate-path-hardening
Draft

Push MD: reliable fresh-site seeding (resolve by slug) + export path-collision hardening#66
robertsreberski wants to merge 4 commits into
trunkfrom
push-md-duplicate-path-hardening

Conversation

@robertsreberski

Copy link
Copy Markdown

Summary

Makes Push MD reliable when seeding production content onto a fresh WordPress site (the push_md_allow_create_on_missing_id flow used by local Studio setup), and hardens export path-collision handling. This branch is a small stack:

  1. Reject duplicate export paths (34daeddc) — when multiple WordPress items map to the same Markdown file path, fail the export with a clear Git error and report the collisions on Tools → Push MD.
  2. Seed id'd content onto fresh sites (c92c6b65) — push_md_allow_create_on_missing_id lets a push create posts whose front-matter id doesn't exist locally (plus a front-matter scalar parsing fix).
  3. Resolve by slug when seeding, ignoring the front-matter id (e1c832e4) — the fix below.

The seeding bug this fixes

Push MD treats the Markdown front-matter id as the local WordPress post ID (get_post($frontmatter_id)), which is correct for round-trip on one site but wrong when seeding production content onto a fresh site: those production IDs don't exist locally, so created posts get fresh sequential auto-increment IDs. push_md_allow_create_on_missing_id only rescued the missing-id case. When a production front-matter ID happened to collide with an unrelated local post's auto-ID, the (atomic) push was hard-rejected:

  • "Push rejected because Markdown front matter id references a different WordPress post type."
  • "Push rejected because multiple Markdown files reference the same WordPress post ID."

One collision blocked the entire push (e.g. ~1,224 objects for jetpack.com).

Change

In find_post_id_by_path_metadata(), gate the two id-resolver branches on ! self::allow_create_on_missing_id(). When the option is on, resolution falls through to the existing slug/path resolvers and the front-matter id is ignored entirely, so the type/duplicate/path-conflict rejects can't fire. Production pushes leave the option off and keep strict id-based resolution unchanged. Bumped to 0.6.7.

Test plan

  • Four new E2E methods in Tests/EndToEndTest.php (run by .github/workflows/push-md-e2e.yml):
    • seeding resolves by slug when the front-matter id points at another same-type post;
    • seeding resolves by slug when the id collides with a different post type;
    • two files with colliding ids both resolve correctly (no duplicate-id rejection);
    • production mode (option off) still rejects a cross-type id mismatch.
  • A CI-only REST toggle for push_md_allow_create_on_missing_id added to Tests/ci-mu-test-helper.php (the test process has no wp-cli).
  • phpcs clean; the Push MD suite is green locally (E2E skips without PUSH_MD_E2E_*).
  • Verified end-to-end: seeded a fresh Studio site with the v0.6.7 build — 988 published posts + 143 published pages landed at low auto-increment IDs (8–1440), with 1,223 objects in the previously collision-prone ID range and zero rejections.

When two WordPress entities map to the same Push MD file path, fail the
export instead of papering over it with synthetic --wp-<id> filenames.
The fail-closed behavior is surfaced to both consumers:

- Agents cloning/fetching get an actionable Git error naming the
  conflicting post IDs (or, before the initial import finishes, the
  seeder failure message via not_ready_message()).
- Site admins see a "Path collisions" panel on Tools -> Push MD listing
  the conflicting posts with edit links and how to resolve them.

Details:
- export_wordpress_content() throw now names both post IDs plus a fix
  hint; add detect_export_path_collisions() as the shared detection
  helper used by the seeder and admin surfaces.
- Seeder initialize() detects collisions and fails loudly through the
  existing record_failure() path instead of silently overwriting the
  losing post during the initial import.
- Admin status route and page render a live-refreshing collision panel
  (briefly cached detection, JS renderCollisions(), warning styling).
- Tests cover the actionable throw and detection (group, unique-path,
  unbuildable-path skip); readme FAQ + version bumped to 0.6.6.
… parsing

- allow_create_on_missing_id (option/filter, default off): when set, an unknown
  front-matter id resolves by slug (update-or-create) instead of rejecting, and
  missing parent pages are tolerated with ancestor pages applied before children.
  Production keeps rejecting unknown ids (guard intact).
- seed_markdown_file(): public wrapper over upsert_post_from_markdown so non-git
  transports (e.g. a WordPress Playground runPHP step) can seed a single file.
- Fix parse_frontmatter_scalar: anchor the YAML sequence/mapping detection regex
  so quoted titles/descriptions containing "{" or "- " are not mis-parsed as
  empty arrays.
When push_md_allow_create_on_missing_id is enabled (local one-way seeding onto
a fresh site), resolve posts and pages purely by slug/path and ignore the
Markdown front-matter id. Production ids in the seed content can collide with
unrelated local auto-increment post ids; previously such a collision hard-
rejected the whole (atomic) push with "references a different WordPress post
type", "multiple Markdown files reference the same WordPress post ID", or the
path/id conflict error.

Gate the two id-resolver branches in find_post_id_by_path_metadata() on
! allow_create_on_missing_id(). Production pushes leave the option off and keep
strict id-based resolution unchanged. Bump to 0.6.7; add four e2e regression
tests plus a CI-only REST toggle for the option.
@robertsreberski
robertsreberski force-pushed the push-md-duplicate-path-hardening branch from e1c832e to ce38fe9 Compare June 18, 2026 16:18
@robertsreberski
robertsreberski marked this pull request as draft June 18, 2026 16:23
@robertsreberski

Copy link
Copy Markdown
Author

Heads-up after rebasing onto trunk: this branch's scoped build currently fatals on push-md's Markdown import (seeding). Trunk's scope-push-md-bundle.php mangles inline qualified references (double-prefix, e.g. PushMDVendor\Nette\Schema\PushMDVendor\Nette\StaticClass). Filed #68 with the precise repro + root cause.

The resolve-by-slug change in this PR is independent and ready; the scoped build just can't import Markdown until #68 is fixed. jetpack-com is pinned to the pre-scoping build as an interim.

…th-hardening

# Conflicts:
#	plugins/push-md/class-push-md-admin.php
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant