Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ jobs:
- name: Release preflight checks
run: make test-release

- name: Cache runtime build
uses: actions/cache@v4
with:
path: .build
key: >-
ci-runtime-x86_64-unknown-linux-musl-${{ hashFiles(
'scripts/build-dist.sh',
'experiments/branchfs/php-ext/branchfs.c',
'experiments/branchfs/php-ext/branchfs.h',
'experiments/branchfs/build/spc-patch.php'
) }}

- name: Build production dist bundle
run: scripts/build-dist.sh
env:
Expand Down Expand Up @@ -111,6 +123,15 @@ jobs:
brew install composer gpatch automake re2c bison pkg-config
brew install php || true

- name: Cache runtime build
uses: actions/cache@v4
with:
path: .build
key: >-
ci-runtime-${{ matrix.target }}-${{ hashFiles(
'scripts/build-dist.sh'
) }}

- name: Build production dist bundle
run: scripts/build-dist.sh
env:
Expand Down
13 changes: 12 additions & 1 deletion crates/forkpress-cli/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2143,6 +2143,8 @@ fn start_background_command(args: StartArgs) -> Result<i32> {
let mut command = Command::new(current_exe);
command.arg("start");
append_start_args(&mut command, &args, &layout);
forward_env_if_present(&mut command, "FORKPRESS_COW_GIT_TEST_FAILPOINT");
forward_env_if_present(&mut command, "FORKPRESS_COW_GIT_TEST_FAILPOINT_ACTION");
if _cow_lifecycle_lock.is_some() {
command.env(FORKPRESS_COW_PARENT_LIFECYCLE_LOCK, "1");
}
Expand Down Expand Up @@ -2245,6 +2247,12 @@ fn append_start_args(command: &mut Command, args: &StartArgs, layout: &Layout) {
}
}

fn forward_env_if_present(command: &mut Command, key: &str) {
if let Ok(value) = std::env::var(key) {
command.env(key, value);
}
}

fn server_start_info(args: &StartArgs) -> ServerStartInfo {
ServerStartInfo {
host: args.host.clone(),
Expand Down Expand Up @@ -4720,7 +4728,10 @@ fn start_cow_php_server(
.env("FORKPRESS_DEBUG_LOG", &layout.debug_log)
.env("FORKPRESS_PLAIN_STRATEGY", "cow")
.env("FORKPRESS_ROOT_HOST", &args.root_host)
.env_remove(FORKPRESS_COW_PARENT_LIFECYCLE_LOCK)
.env_remove(FORKPRESS_COW_PARENT_LIFECYCLE_LOCK);
forward_env_if_present(&mut command, "FORKPRESS_COW_GIT_TEST_FAILPOINT");
forward_env_if_present(&mut command, "FORKPRESS_COW_GIT_TEST_FAILPOINT_ACTION");
command
.stdout(Stdio::from(log))
.stderr(Stdio::from(log_err));

Expand Down
1 change: 1 addition & 0 deletions crates/forkpress-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ pub fn create_cow_branch_from_tree(
)
})?;
staging_published = true;
run_cow_bootstrap_script(layout, runtime, shared, &dest, "ForkPress", "admin")?;
ensure_cow_public_branch_root(layout, branch, &dest, file_view)?;
write_cow_branch_list(layout)?;
Ok(())
Expand Down
26 changes: 14 additions & 12 deletions scripts/cow/git_server.php
Original file line number Diff line number Diff line change
Expand Up @@ -1238,18 +1238,20 @@ function cow_git_cleanup_stale_update_artifacts(string $branches_dir, string $st
}

foreach ($parents as $parent) {
foreach (glob($parent . '/.forkpress-update-{backup,stage,failed}-*', GLOB_BRACE) ?: [] as $path) {
$name = basename($path);
if (!preg_match('/^\.forkpress-update-(?:backup|stage|failed)-(.+)-[0-9]+-[0-9a-f]+$/', $name, $matches)) {
continue;
}
$branch = $matches[1];
if (!cow_git_valid_branch_name($branch)) {
continue;
}
$storage = cow_git_branch_storage_root($storage_branches_dir, $branches_dir, $branch);
if (is_dir($storage) && is_file(rtrim($storage, "/\\") . '/wp-load.php')) {
cow_git_remove_tree($path);
foreach (['backup', 'stage', 'failed'] as $kind) {
foreach (glob($parent . '/.forkpress-update-' . $kind . '-*') ?: [] as $path) {
$name = basename($path);
if (!preg_match('/^\.forkpress-update-(?:backup|stage|failed)-(.+)-[0-9]+-[0-9a-f]+$/', $name, $matches)) {
continue;
}
$branch = $matches[1];
if (!cow_git_valid_branch_name($branch)) {
continue;
}
$storage = cow_git_branch_storage_root($storage_branches_dir, $branches_dir, $branch);
if (is_dir($storage) && is_file(rtrim($storage, "/\\") . '/wp-load.php')) {
cow_git_remove_tree($path);
}
}
}
}
Expand Down
86 changes: 86 additions & 0 deletions scripts/cow/merge.php
Original file line number Diff line number Diff line change
Expand Up @@ -5042,6 +5042,85 @@ function cow_merge_wordpress_option_reference_violation(
return null;
}

function cow_merge_wordpress_theme_mods_nav_locations_merge(?string $base_value, ?string $source_value, ?string $target_value): ?string {
if ($base_value === null || $source_value === null || $target_value === null) {
return null;
}
$base = @unserialize($base_value, ['allowed_classes' => false]);
$source = @unserialize($source_value, ['allowed_classes' => false]);
$target = @unserialize($target_value, ['allowed_classes' => false]);
if (!is_array($base) || !is_array($source) || !is_array($target)) {
return null;
}

$base_locations = $base['nav_menu_locations'] ?? [];
$source_locations = $source['nav_menu_locations'] ?? [];
$target_locations = $target['nav_menu_locations'] ?? [];
if (!is_array($base_locations) || !is_array($source_locations) || !is_array($target_locations)) {
return null;
}
$base_without_locations = $base;
$source_without_locations = $source;
unset($base_without_locations['nav_menu_locations'], $source_without_locations['nav_menu_locations']);
if (!cow_merge_values_equal($source_without_locations, $base_without_locations)) {
return null;
}

$merged_locations = $target_locations;
$keys = array_values(array_unique(array_merge(
array_keys($base_locations),
array_keys($source_locations),
array_keys($target_locations)
)));
foreach ($keys as $key) {
$base_has = array_key_exists($key, $base_locations);
$source_has = array_key_exists($key, $source_locations);
$target_has = array_key_exists($key, $target_locations);
$base_location = $base_has ? $base_locations[$key] : null;
$source_location = $source_has ? $source_locations[$key] : null;
$target_location = $target_has ? $target_locations[$key] : null;
$source_changed = $base_has !== $source_has || !cow_merge_values_equal($source_location, $base_location);
$target_changed = $base_has !== $target_has || !cow_merge_values_equal($target_location, $base_location);
if (!$source_changed) {
continue;
}
if ($target_changed && ($source_has !== $target_has || !cow_merge_values_equal($source_location, $target_location))) {
return null;
}
if ($source_has) {
$merged_locations[$key] = $source_location;
} else {
unset($merged_locations[$key]);
}
}

$merged = $target;
$merged['nav_menu_locations'] = $merged_locations;
return serialize($merged);
}

function cow_merge_wordpress_cell_auto_merge(string $table, string $column, ?array $base_row, ?array $source_row, ?array $target_row): ?array {
if (!($table === 'wp_options' || str_ends_with($table, '_options')) || $column !== 'option_value') {
return null;
}
$option_name = (string)($source_row['option_name'] ?? $target_row['option_name'] ?? $base_row['option_name'] ?? '');
if (!str_starts_with($option_name, 'theme_mods_')) {
return null;
}
$merged = cow_merge_wordpress_theme_mods_nav_locations_merge(
isset($base_row['option_value']) ? (string)$base_row['option_value'] : null,
isset($source_row['option_value']) ? (string)$source_row['option_value'] : null,
isset($target_row['option_value']) ? (string)$target_row['option_value'] : null
);
if ($merged === null) {
return null;
}
return [
'value' => $merged,
'reason' => "merged disjoint WordPress nav_menu_locations in $option_name",
];
}

function cow_merge_wordpress_insert_reference_violation(
SQLite3 $source,
SQLite3 $target,
Expand Down Expand Up @@ -13239,6 +13318,13 @@ function cow_merge_table_rows(
}
continue;
}
$auto_merge = cow_merge_wordpress_cell_auto_merge($table, $col, $base_row, $source_row, $target_row);
if ($auto_merge !== null) {
$merged[$col] = $auto_merge['value'];
$pending_source_cell_decisions[] = [$col, (string)$auto_merge['reason'], $b, $s, $t, $auto_merge['value']];
$row_applied++;
continue;
}
$active = cow_merge_record_conflict($meta, $run_id, $table, $key, $col, 'cell-conflict', $b, $s, $t, $t);
cow_merge_record_decision($meta, $run_id, $table, $key, $col, $active ? 'target-wins' : 'target-accepted', $active ? 'source and target changed the same cell differently' : 'reviewed target resolution already accepts same-cell conflict', $b, $s, $t, $t);
if ($active) {
Expand Down
Loading
Loading