Skip to content

Extract PullIndexJournal from ImportClient - #468

Open
adamziel wants to merge 5 commits into
trunkfrom
adamziel/design-import-write-class
Open

Extract PullIndexJournal from ImportClient#468
adamziel wants to merge 5 commits into
trunkfrom
adamziel/design-import-write-class

Conversation

@adamziel

@adamziel adamziel commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

files-pull now keeps pull-index publication in PullIndexJournal and sequential remote-index entry reads in RemoteIndexReader, instead of mixing both responsibilities into ImportClient.

PullIndexJournal owns pull/index.wal: appending completed mutations, flushing them before cursor checkpoints, merging complete records into the remote and local indexes, and removing the empty lifecycle marker. Publication remains restart-safe: the remote index and any required local-index replacement are published before the records are cleared, so a stopped application replays the same batch.

RemoteIndexReader is read-only. It covers entry-shaped reads of remote-index.jsonl and remote-index.next.jsonl: decoding validated paths, skipping blank lines, and exposing byte offsets for diff resume. It does not read local_index.jsonl; that relative-path schema includes the optional empty field and remains owned by PushPlan and the local-index merge helpers. Raw line counters and symlink-record scans also remain in ImportClient because they either do not parse entries or need target and intermediate, which are outside the reader's entry shape.

Merge ordering, selection, preserve-local behavior, deletion policy, writers, and raw record handling remain with their existing owners.

Testing

The journal tests exercise its public API. The reader tests cover sequential reads, blank lines, a missing index, byte-offset resume, and continuing after an invalid line.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Pull pipeline performance — large-directory

Site: large-directory · 2,000+ plus targeted file-transfer scenarios files · 10,000 posts · 25,000 postmeta · PHP 8.5.9

Stage PR trunk Δ Status Details
playground-sqlite-db-pull 7.63 s 7.54 s ⚪ +81 ms (+1.1%) condition=db-pull in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=lexer
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=selected
trunk: condition=db-pull in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=lexer
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=selected
playground-sqlite-db-apply 3.07 s 3.07 s ⚪ -4 ms (-0.1%) condition=db-apply to SQLite in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=parser
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=verified
native_ast=WP_MySQL_Native_Parser_Node
sqlite_driver_parser=verified
trunk: condition=db-apply to SQLite in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=parser
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=verified
native_ast=WP_MySQL_Native_Parser_Node
sqlite_driver_parser=verified
Total 10.70 s 10.62 s ⚪ +77 ms (+0.7%)

Numbers carry runner noise; treat single-run deltas as directional, not authoritative.

📈 Trunk performance history — commit-by-commit timeline.

@adamziel
adamziel force-pushed the adamziel/design-import-write-class branch from ead787c to 4030e6e Compare August 7, 2026 00:55
@adamziel
adamziel force-pushed the adamziel/design-import-write-class branch from 4030e6e to 84a87f3 Compare August 10, 2026 10:43
@adamziel
adamziel changed the base branch from trunk to codex/file-index-diff-processor August 10, 2026 10:45
@adamziel
adamziel force-pushed the adamziel/design-import-write-class branch 8 times, most recently from 39c8c30 to e069996 Compare August 11, 2026 00:15
adamziel added a commit that referenced this pull request Aug 11, 2026
… between push and pull (#541)

Adds `FileIndexDiffProcessor`, a single-pass, resumable traversal over
an old and new path-sorted filesystem index. It labels each current path
as `added`, `modified`, `deleted`, or `unchanged`, so callers choose an
operation without rebuilding the index comparison. `PushPlan` now uses
the same processor.

## Usage

Create the comparison and select its first path:

```php
$index_diff = FileIndexDiffProcessor::create(
    $old_index_file,
    $new_index_file
);
$has_path = $index_diff->next_path();
```

`get_path_transition()` describes the difference between the old and new
index records. A path present in both indexes is `modified` when its
type, size, or ctime differs:

```php
while ($has_path) {
    $path = $index_diff->get_path();

    switch ($index_diff->get_path_transition()) {
        case "added":
            handle_added_path($path);
            break;
        case "modified":
            handle_modified_path($path);
            break;
        case "deleted":
            handle_deleted_path($path);
            break;
        case "unchanged":
            break;
    }

    $has_path = $index_diff->next_path();
    save_cursor($index_diff->get_cursor());
}

$index_diff->close();
```

The per-index getters expose record details when an operation needs
them:

```php
if ($index_diff->get_path_transition() === "modified") {
    compare_sizes(
        $index_diff->get_size_in_old_index(),
        $index_diff->get_size_in_new_index()
    );
}
```

When a path is absent from an index, the neighboring-path getters
describe the position where it would occur. For a path deleted from the
new index:

```php
if ($index_diff->get_path_transition() === "deleted") {
    $preceding_path = $index_diff->get_preceding_path_in_new_index();
    $following_path = $index_diff->get_following_path_in_new_index();
}
```

For a path added to the new index, inspect the following path in the old
index:

```php
if ($index_diff->get_path_transition() === "added") {
    $following_path = $index_diff->get_following_path_in_old_index();
}
```

A following-path getter requires the current path to be absent from that
index. Otherwise the processor has not read the entry after the current
path and rejects the call.

Resume from the cursor stored after `next_path()` advances past a
processed path:

```php
$index_diff = FileIndexDiffProcessor::resume(
    $old_index_file,
    $new_index_file,
    $stored_cursor
);
```

A selected path is not part of the cursor until the following
`next_path()` call advances past it. Closing before that call leaves the
path available for replay after resume. Both index files must remain
unchanged while a cursor may be resumed.

The processor holds at most one unread entry from each index. Its
dedicated tests cover all four transition labels, decoded-path ordering,
preceding and following paths, cursor movement, resume, missing indexes,
EOF, and close.

#468 follows this PR in the stack.

## Testing

```
cd tests
../vendor/bin/phpunit Import/FileIndexDiffProcessorTest.php Import/PushPlanTest.php
../vendor/bin/phpcs ../packages/reprint-client/src/lib/index/class-file-index-diff-processor.php Import/FileIndexDiffProcessorTest.php
cd ..
vendor/bin/phpstan analyze --memory-limit=1G
git diff --check
```
Move the pull/index.wal writer and the merge which publishes its records
into the remote and local indexes out of ImportClient into a dedicated
PullIndexJournal class. ImportClient keeps the cursor and state ordering;
the journal only guarantees that flushed records are durable before each
cursor checkpoint.
Move sequential remote-index parsing, blank-line skipping, and byte-offset seeking into a read-only reader. Use it for diffing, journal application, prefix lookup, and statistics while leaving raw record scans untouched.
@adamziel
adamziel force-pushed the adamziel/design-import-write-class branch from e069996 to 644e58f Compare August 11, 2026 00:28
@adamziel
adamziel changed the base branch from codex/file-index-diff-processor to trunk August 11, 2026 00:28
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