Plugin Directory

Changeset 3492839


Ignore:
Timestamp:
03/27/2026 05:07:43 PM (7 hours ago)
Author:
jeromesteunenberg
Message:

Release version 0.0.9

Location:
pushpull
Files:
168 added
11 edited

Legend:

Unmodified
Added
Removed
  • pushpull/trunk/README.md

    r3492727 r3492839  
    66Tested up to: 6.9
    77Requires PHP: 8.1
    8 Stable tag: 0.0.8
     8Stable tag: 0.0.9
    99License: GPLv2
    1010License URI: [http://www.gnu.org/licenses/gpl-2.0.html](http://www.gnu.org/licenses/gpl-2.0.html)
     
    158158## Changelog
    159159
     160### 0.0.9
     161
     1621. Added asynchronous branch actions in the `All Managed Sets` overview so `Fetch`, `Pull`, and `Push` no longer rely on a blocking full-page POST flow.
     1632. Added modal-based operation progress UI, with indeterminate progress for fetch and determinate progress for push.
     1643. Added a first-commit guard so PushPull now requires `Fetch` before creating the first local commit when the remote branch already has history.
     1654. Fixed push planning so unchanged remote objects are reused in the normal linear-history case instead of being counted and uploaded again.
     166
    160167### 0.0.8
    161168
  • pushpull/trunk/plugin-assets/css/admin.css

    r3491811 r3492839  
    311311}
    312312
     313.pushpull-modal-open {
     314    overflow: hidden;
     315}
     316
     317.pushpull-async-modal[hidden] {
     318    display: none;
     319}
     320
     321.pushpull-async-modal {
     322    position: fixed;
     323    inset: 0;
     324    z-index: 100000;
     325}
     326
     327.pushpull-async-modal__backdrop {
     328    position: absolute;
     329    inset: 0;
     330    background: rgba(15, 23, 42, 0.45);
     331}
     332
     333.pushpull-async-modal__dialog {
     334    position: relative;
     335    width: min(520px, calc(100vw - 32px));
     336    margin: 12vh auto 0;
     337    background: #ffffff;
     338    border-radius: 12px;
     339    box-shadow: 0 20px 50px rgba(15, 23, 42, 0.2);
     340    padding: 24px;
     341}
     342
     343.pushpull-async-modal__dialog h2 {
     344    margin-top: 0;
     345}
     346
     347.pushpull-async-modal__message {
     348    margin-bottom: 18px;
     349}
     350
     351.pushpull-async-modal__progress {
     352    margin-bottom: 18px;
     353}
     354
     355.pushpull-async-modal__progress-bar {
     356    position: relative;
     357    width: 100%;
     358    height: 10px;
     359    border-radius: 999px;
     360    background: #e2e8f0;
     361    overflow: hidden;
     362}
     363
     364.pushpull-async-modal__progress-fill {
     365    display: block;
     366    width: 0;
     367    height: 100%;
     368    background: #2271b1;
     369    transition: width 120ms linear;
     370}
     371
     372.pushpull-async-modal__progress-bar.is-indeterminate .pushpull-async-modal__progress-fill {
     373    width: 40%;
     374    animation: pushpull-progress-indeterminate 1.2s ease-in-out infinite;
     375}
     376
     377.pushpull-async-modal__progress-label {
     378    margin: 8px 0 0;
     379    color: #475569;
     380    font-size: 12px;
     381}
     382
     383.pushpull-async-modal__spinner {
     384    width: 28px;
     385    height: 28px;
     386    border-radius: 999px;
     387    border: 3px solid #d0d7de;
     388    border-top-color: #2271b1;
     389    animation: pushpull-spin 0.8s linear infinite;
     390}
     391
     392.pushpull-async-modal__close {
     393    margin-top: 18px;
     394}
     395
     396@keyframes pushpull-spin {
     397    to {
     398        transform: rotate(360deg);
     399    }
     400}
     401
     402@keyframes pushpull-progress-indeterminate {
     403    0% {
     404        transform: translateX(-120%);
     405    }
     406
     407    100% {
     408        transform: translateX(280%);
     409    }
     410}
     411
    313412@media (max-width: 960px) {
    314413    .pushpull-layout {
  • pushpull/trunk/pushpull.php

    r3492727 r3492839  
    55 * Plugin URI: https://github.com/creativemoods/pushpull
    66 * Description: Git-backed content workflows for selected WordPress content domains.
    7  * Version: 0.0.8
     7 * Version: 0.0.9
    88 * Requires at least: 6.0
    99 * Requires PHP: 8.1
     
    2323define('PUSHPULL_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2424define('PUSHPULL_PLUGIN_URL', plugin_dir_url(__FILE__));
    25 define('PUSHPULL_VERSION', '0.0.8');
     25define('PUSHPULL_VERSION', '0.0.9');
    2626
    2727if (is_readable(PUSHPULL_PLUGIN_DIR . 'vendor/autoload.php')) {
  • pushpull/trunk/readme.txt

    r3492727 r3492839  
    55Tested up to: 6.9
    66Requires PHP: 8.1
    7 Stable tag: 0.0.8
     7Stable tag: 0.0.9
    88License: GPLv2
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    164164== Changelog ==
    165165
     166= 0.0.9 =
     167
     1681. Added asynchronous branch actions in the `All Managed Sets` overview so `Fetch`, `Pull`, and `Push` no longer rely on a blocking full-page POST flow.
     1692. Added modal-based operation progress UI, with indeterminate progress for fetch and determinate progress for push.
     1703. Added a first-commit guard so PushPull now requires `Fetch` before creating the first local commit when the remote branch already has history.
     1714. Fixed push planning so unchanged remote objects are reused in the normal linear-history case instead of being counted and uploaded again.
     172
    166173= 0.0.8 =
    167174
  • pushpull/trunk/src/Admin/ManagedContentPage.php

    r3492727 r3492839  
    1919use PushPull\Settings\SettingsRepository;
    2020use PushPull\Support\Capabilities;
     21use PushPull\Support\Operations\AsyncBranchOperationRunner;
    2122use PushPull\Support\Operations\OperationExecutor;
    2223use RuntimeException;
     
    4243        private readonly WorkingStateRepository $workingStateRepository,
    4344        private readonly ManagedSetConflictResolutionService $conflictResolutionService,
    44         private readonly OperationExecutor $operationExecutor
     45        private readonly OperationExecutor $operationExecutor,
     46        private readonly AsyncBranchOperationRunner $asyncBranchOperationRunner
    4547    ) {
    4648    }
     
    7072            PUSHPULL_VERSION
    7173        );
     74
     75        wp_enqueue_script(
     76            'pushpull-managed-content',
     77            PUSHPULL_PLUGIN_URL . 'plugin-assets/js/managed-content.js',
     78            [],
     79            PUSHPULL_VERSION,
     80            true
     81        );
     82        wp_localize_script('pushpull-managed-content', 'pushpullManagedContent', [
     83            'ajaxUrl' => admin_url('admin-ajax.php'),
     84            'ajaxNonce' => wp_create_nonce('pushpull_async_branch_action'),
     85            'strings' => [
     86                'working' => __('Working…', 'pushpull'),
     87                'close' => __('Close', 'pushpull'),
     88                'failed' => __('The PushPull operation could not be completed.', 'pushpull'),
     89                /* translators: %d: completion percentage. */
     90                'progressPercent' => __('%d% complete', 'pushpull'),
     91            ],
     92        ]);
    7293    }
    7394
     
    98119            $this->renderManagedSetDetail($settings, $this->currentAdapter());
    99120        }
     121        $this->renderAsyncOperationModal();
    100122        echo '</div>';
    101123    }
     
    788810
    789811        if (! $this->isManagedSetEnabled($settings, $managedSetKey)) {
    790             $this->redirectWithNotice('error', sprintf('%s is not enabled in settings.', $managedContentAdapter->getManagedSetLabel()), $managedSetKey);
     812            $this->redirectWithNotice('error', sprintf('%s is not enabled in settings.', $managedContentAdapter->getManagedSetLabel()), null);
    791813        }
    792814
     
    846868        } catch (ManagedContentExportException | ProviderException | RuntimeException $exception) {
    847869            $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage();
    848             $this->redirectWithNotice('error', $message, $managedSetKey);
     870            $this->redirectWithNotice('error', $message, null);
    849871        }
    850872
     
    861883        );
    862884
    863         $this->redirectWithNotice('success', $message, $managedSetKey);
     885        $this->redirectWithNotice('success', $message, null);
     886    }
     887
     888    public function handleAjaxStartBranchAction(): void
     889    {
     890        if (! current_user_can(Capabilities::MANAGE_PLUGIN)) {
     891            wp_send_json_error(['message' => __('You do not have permission to manage PushPull.', 'pushpull')], 403);
     892        }
     893
     894        check_ajax_referer('pushpull_async_branch_action', 'nonce');
     895
     896        $operationType = isset($_POST['operation_type']) ? sanitize_key(wp_unslash((string) $_POST['operation_type'])) : '';
     897        $managedSetKey = isset($_POST['managed_set']) ? sanitize_key(wp_unslash((string) $_POST['managed_set'])) : '';
     898
     899        if ($managedSetKey === '' || ! $this->managedSetRegistry->has($managedSetKey)) {
     900            wp_send_json_error(['message' => __('The managed set is not supported.', 'pushpull')], 400);
     901        }
     902
     903        $settings = $this->settingsRepository->get();
     904        $managedContentAdapter = $this->managedSetRegistry->get($managedSetKey);
     905
     906        if (! $this->isManagedSetEnabled($settings, $managedSetKey)) {
     907            wp_send_json_error(['message' => sprintf('%s is not enabled in settings.', $managedContentAdapter->getManagedSetLabel())], 400);
     908        }
     909
     910        try {
     911            $started = $this->asyncBranchOperationRunner->start($managedSetKey, $operationType);
     912        } catch (ProviderException | RuntimeException $exception) {
     913            $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage();
     914            wp_send_json_error(['message' => $message], 400);
     915        }
     916
     917        wp_send_json_success([
     918            'operationId' => $started['operationId'],
     919            'message' => $started['progressMessage'],
     920            'done' => $started['done'],
     921            'status' => $started['status'] ?? 'running',
     922            'redirectUrl' => $started['redirectUrl'] ?? null,
     923            'progress' => $started['progress'],
     924        ]);
     925    }
     926
     927    public function handleAjaxContinueBranchAction(): void
     928    {
     929        if (! current_user_can(Capabilities::MANAGE_PLUGIN)) {
     930            wp_send_json_error(['message' => __('You do not have permission to manage PushPull.', 'pushpull')], 403);
     931        }
     932
     933        check_ajax_referer('pushpull_async_branch_action', 'nonce');
     934
     935        $operationId = isset($_POST['operation_id']) ? absint(wp_unslash((string) $_POST['operation_id'])) : 0;
     936
     937        if ($operationId <= 0) {
     938            wp_send_json_error(['message' => __('The async operation could not be found.', 'pushpull')], 400);
     939        }
     940
     941        try {
     942            $response = $this->asyncBranchOperationRunner->continue($operationId);
     943        } catch (ProviderException | RuntimeException $exception) {
     944            $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage();
     945            wp_send_json_error(['message' => $message], 400);
     946        }
     947
     948        if (! $response['done']) {
     949            wp_send_json_success($response);
     950        }
     951
     952        wp_send_json_success($response + [
     953            'redirectUrl' => $this->noticeUrl($response['status'], $response['message'], null),
     954        ]);
    864955    }
    865956
     
    877968
    878969        if (! $this->isManagedSetEnabled($settings, $managedSetKey)) {
    879             $this->redirectWithNotice('error', sprintf('%s is not enabled in settings.', $managedContentAdapter->getManagedSetLabel()), $managedSetKey);
     970            $this->redirectWithNotice('error', sprintf('%s is not enabled in settings.', $managedContentAdapter->getManagedSetLabel()), null);
    880971        }
    881972
     
    889980        } catch (ManagedContentExportException | ProviderException | RuntimeException $exception) {
    890981            $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage();
    891             $this->redirectWithNotice('error', $message, $managedSetKey);
     982            $this->redirectWithNotice('error', $message, null);
    892983        }
    893984
     
    907998        );
    908999
    909         $this->redirectWithNotice($result->mergeResult->hasConflicts() ? 'error' : 'success', $message, $managedSetKey);
     1000        $this->redirectWithNotice($result->mergeResult->hasConflicts() ? 'error' : 'success', $message, null);
    9101001    }
    9111002
     
    9231014
    9241015        if (! $this->isManagedSetEnabled($settings, $managedSetKey)) {
    925             $this->redirectWithNotice('error', sprintf('%s is not enabled in settings.', $managedContentAdapter->getManagedSetLabel()), $managedSetKey);
     1016            $this->redirectWithNotice('error', sprintf('%s is not enabled in settings.', $managedContentAdapter->getManagedSetLabel()), null);
    9261017        }
    9271018
     
    9351026        } catch (ManagedContentExportException | ProviderException | RuntimeException $exception) {
    9361027            $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage();
    937             $this->redirectWithNotice('error', $message, $managedSetKey);
     1028            $this->redirectWithNotice('error', $message, null);
    9381029        }
    9391030
     
    9861077        );
    9871078
    988         $this->redirectWithNotice('success', $message, $managedSetKey);
     1079        $this->redirectWithNotice('success', $message, null);
    9891080    }
    9901081
     
    11691260        }
    11701261
    1171         echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
     1262        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="pushpull-async-branch-form" data-pushpull-async-operation="fetch" data-pushpull-async-label="' . esc_attr__('Fetch', 'pushpull') . '">';
    11721263        echo '<input type="hidden" name="action" value="' . esc_attr(self::FETCH_ACTION) . '" />';
    11731264        echo '<input type="hidden" name="managed_set" value="' . esc_attr((string) $managedSetKey) . '" />';
     
    11881279        }
    11891280
    1190         echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
     1281        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="pushpull-async-branch-form" data-pushpull-async-operation="pull" data-pushpull-async-label="' . esc_attr__('Pull', 'pushpull') . '">';
    11911282        echo '<input type="hidden" name="action" value="' . esc_attr(self::PULL_ACTION) . '" />';
    11921283        echo '<input type="hidden" name="managed_set" value="' . esc_attr((string) $managedSetKey) . '" />';
     
    12261317        }
    12271318
    1228         echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
     1319        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="pushpull-async-branch-form" data-pushpull-async-operation="push" data-pushpull-async-label="' . esc_attr__('Push', 'pushpull') . '">';
    12291320        echo '<input type="hidden" name="action" value="' . esc_attr(self::PUSH_ACTION) . '" />';
    12301321        echo '<input type="hidden" name="managed_set" value="' . esc_attr((string) $managedSetKey) . '" />';
     
    15201611    private function redirectWithNotice(string $status, string $message, ?string $managedSetKey = null): never
    15211612    {
    1522         $url = add_query_arg(
    1523             [
    1524                 'page' => self::MENU_SLUG,
    1525                 'managed_set' => $managedSetKey ?? $this->currentAdapter()->getManagedSetKey(),
    1526                 'pushpull_commit_status' => $status,
    1527                 'pushpull_commit_message' => $message,
    1528             ],
    1529             admin_url('admin.php')
    1530         );
    1531 
    1532         wp_safe_redirect($url);
     1613        wp_safe_redirect($this->noticeUrl($status, $message, $managedSetKey));
    15331614        exit;
     1615    }
     1616
     1617    private function noticeUrl(string $status, string $message, ?string $managedSetKey = null): string
     1618    {
     1619        $queryArgs = [
     1620            'page' => self::MENU_SLUG,
     1621            'pushpull_commit_status' => $status,
     1622            'pushpull_commit_message' => $message,
     1623        ];
     1624
     1625        if ($managedSetKey !== null) {
     1626            $queryArgs['managed_set'] = $managedSetKey;
     1627        }
     1628
     1629        return add_query_arg($queryArgs, admin_url('admin.php'));
     1630    }
     1631
     1632    private function renderAsyncOperationModal(): void
     1633    {
     1634        echo '<div class="pushpull-async-modal" hidden="hidden">';
     1635        echo '<div class="pushpull-async-modal__backdrop"></div>';
     1636        echo '<div class="pushpull-async-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="pushpull-async-modal-title">';
     1637        echo '<h2 id="pushpull-async-modal-title">' . esc_html__('Working…', 'pushpull') . '</h2>';
     1638        echo '<p class="pushpull-async-modal__message">' . esc_html__('Preparing operation…', 'pushpull') . '</p>';
     1639        echo '<div class="pushpull-async-modal__progress" hidden="hidden">';
     1640        echo '<div class="pushpull-async-modal__progress-bar is-indeterminate" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">';
     1641        echo '<span class="pushpull-async-modal__progress-fill"></span>';
     1642        echo '</div>';
     1643        echo '<p class="pushpull-async-modal__progress-label"></p>';
     1644        echo '</div>';
     1645        echo '<div class="pushpull-async-modal__spinner" aria-hidden="true"></div>';
     1646        echo '<button type="button" class="button button-secondary pushpull-async-modal__close" hidden="hidden">' . esc_html__('Close', 'pushpull') . '</button>';
     1647        echo '</div>';
     1648        echo '</div>';
    15341649    }
    15351650
  • pushpull/trunk/src/Domain/Repository/DatabaseLocalRepository.php

    r3490393 r3492839  
    66
    77// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names are internal constants, values still use prepare().
     8// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception construction is not HTML output.
    89
    910use PushPull\Persistence\TableNames;
     
    1213use PushPull\Provider\RemoteTree;
    1314use PushPull\Support\Json\CanonicalJson;
     15use RuntimeException;
    1416use wpdb;
    1517
    1618final class DatabaseLocalRepository implements LocalRepositoryInterface
    1719{
     20    private const BLOB_ENCODING_PREFIX = 'base64:';
     21
    1822    private readonly TableNames $tables;
    1923
     
    4448
    4549        $now = $this->now();
    46 
    47         $this->wpdb->insert(
     50        $storedContent = $this->encodeStoredBlobContent($content);
     51
     52        $inserted = $this->wpdb->insert(
    4853            $this->tables->repoBlobs(),
    4954            [
    5055                'hash' => $hash,
    51                 'content' => $content,
     56                'content' => $storedContent,
    5257                'size' => strlen($content),
    5358                'created_at' => $now,
     
    5560            ['%s', '%s', '%d', '%s']
    5661        );
     62
     63        if ($inserted === false) {
     64            throw new RuntimeException(sprintf('Failed to store local blob %s.', $hash));
     65        }
    5766
    5867        return new Blob($hash, $content, strlen($content), $now);
     
    7382        }
    7483
     84        $content = $this->decodeStoredBlobContent((string) $row['content']);
     85
    7586        return new Blob(
    7687            (string) $row['hash'],
    77             (string) $row['content'],
     88            $content,
    7889            (int) $row['size'],
    7990            (string) $row['created_at']
     
    90101
    91102        $now = $this->now();
    92         $this->wpdb->insert(
     103        $storedContent = $this->encodeStoredBlobContent($remoteBlob->content);
     104        $inserted = $this->wpdb->insert(
    93105            $this->tables->repoBlobs(),
    94106            [
    95107                'hash' => $remoteBlob->hash,
    96                 'content' => $remoteBlob->content,
     108                'content' => $storedContent,
    97109                'size' => strlen($remoteBlob->content),
    98110                'created_at' => $now,
     
    101113        );
    102114
     115        if ($inserted === false) {
     116            throw new RuntimeException(sprintf('Failed to import remote blob %s.', $remoteBlob->hash));
     117        }
     118
    103119        return new Blob($remoteBlob->hash, $remoteBlob->content, strlen($remoteBlob->content), $now);
     120    }
     121
     122    private function encodeStoredBlobContent(string $content): string
     123    {
     124        return self::BLOB_ENCODING_PREFIX . base64_encode($content);
     125    }
     126
     127    private function decodeStoredBlobContent(string $storedContent): string
     128    {
     129        if (! str_starts_with($storedContent, self::BLOB_ENCODING_PREFIX)) {
     130            return $storedContent;
     131        }
     132
     133        $decoded = base64_decode(substr($storedContent, strlen(self::BLOB_ENCODING_PREFIX)), true);
     134
     135        if (! is_string($decoded)) {
     136            throw new RuntimeException('Stored blob content could not be decoded.');
     137        }
     138
     139        return $decoded;
    104140    }
    105141
  • pushpull/trunk/src/Domain/Sync/LocalSyncService.php

    r3491690 r3492839  
    2020use PushPull\Domain\Push\ResetRemoteBranchResult;
    2121use PushPull\Domain\Repository\LocalRepositoryInterface;
     22use PushPull\Provider\Exception\ProviderException;
    2223use PushPull\Provider\GitProviderFactoryInterface;
    2324use PushPull\Provider\GitRemoteConfig;
     
    5556        $adapter = $this->requireAdapter($managedSetKey);
    5657        $committer = $this->requireCommitter($managedSetKey);
     58        $this->guardAgainstUnfetchedRemoteBootstrap($request->branch);
    5759
    5860        return $committer->commitSnapshot(
     
    162164    }
    163165
     166    private function guardAgainstUnfetchedRemoteBootstrap(string $branch): void
     167    {
     168        $localRef = $this->localRepository->getRef('refs/heads/' . $branch);
     169
     170        if ($localRef !== null && $localRef->commitHash !== '') {
     171            return;
     172        }
     173
     174        $trackingRef = $this->localRepository->getRef('refs/remotes/origin/' . $branch);
     175
     176        if ($trackingRef !== null && $trackingRef->commitHash !== '') {
     177            return;
     178        }
     179
     180        $settings = $this->settingsRepository->get();
     181        [$provider, $remoteConfig] = $this->resolveValidatedProvider($settings);
     182
     183        try {
     184            $remoteRef = $provider->getRef($remoteConfig, 'refs/heads/' . $branch);
     185        } catch (ProviderException $exception) {
     186            if ($exception->category === ProviderException::EMPTY_REPOSITORY) {
     187                return;
     188            }
     189
     190            throw $exception;
     191        }
     192
     193        if ($remoteRef === null || $remoteRef->commitHash === '') {
     194            return;
     195        }
     196
     197        throw new RuntimeException(sprintf(
     198            'Remote branch %s already has commits. Fetch it before creating the first local commit.',
     199            $branch
     200        ));
     201    }
     202
    164203    /**
    165204     * @return array{0: \PushPull\Provider\GitProviderInterface, 1: GitRemoteConfig}
  • pushpull/trunk/src/Persistence/Operations/OperationLogRepository.php

    r3490393 r3492839  
    6060    }
    6161
     62    /**
     63     * @param array<string, mixed> $result
     64     */
     65    public function updateRunning(int $operationId, array $result = []): OperationRecord
     66    {
     67        return $this->replaceRecord($operationId, self::STATUS_RUNNING, $result, null);
     68    }
     69
    6270    public function find(int $operationId): ?OperationRecord
    6371    {
     
    111119    private function updateStatus(int $operationId, string $status, array $result): OperationRecord
    112120    {
     121        return $this->replaceRecord($operationId, $status, $result, current_time('mysql', true));
     122    }
     123
     124    /**
     125     * @param array<string, mixed> $result
     126     */
     127    private function replaceRecord(int $operationId, string $status, array $result, ?string $finishedAt): OperationRecord
     128    {
    113129        $record = $this->find($operationId);
    114130
     
    126142            'created_by' => $record->createdBy,
    127143            'created_at' => $record->createdAt,
    128             'finished_at' => current_time('mysql', true),
     144            'finished_at' => $finishedAt,
    129145        ]);
    130146
  • pushpull/trunk/src/Plugin/Plugin.php

    r3492727 r3492839  
    4141use PushPull\Settings\SettingsRepository;
    4242use PushPull\Support\Operations\OperationExecutor;
     43use PushPull\Support\Operations\AsyncBranchOperationRunner;
    4344use PushPull\Support\Operations\OperationLockService;
    4445
     
    6263        $remoteRepositoryInitializer = new RemoteRepositoryInitializer($providerFactory, $localRepository);
    6364        $operationLogRepository = new OperationLogRepository($wpdb);
    64         $operationExecutor = new OperationExecutor($operationLogRepository, new OperationLockService());
     65        $operationLockService = new OperationLockService();
     66        $operationExecutor = new OperationExecutor($operationLogRepository, $operationLockService);
    6567        $generateBlocksStylesAdapter = new GenerateBlocksGlobalStylesAdapter();
    6668        $generateBlocksConditionsAdapter = new GenerateBlocksConditionsAdapter();
     
    136138            $workingStateRepository,
    137139            $conflictResolutionService,
    138             $operationExecutor
     140            $operationExecutor,
     141            new AsyncBranchOperationRunner(
     142                $operationLogRepository,
     143                $operationLockService,
     144                $settingsRepository,
     145                $localRepository,
     146                $providerFactory,
     147                $syncService
     148            )
    139149        );
    140150        $attachmentSyncField = new AttachmentSyncField();
     
    155165        add_action('admin_post_pushpull_apply_managed_set', [$managedContentPage, 'handleApply']);
    156166        add_action('admin_post_pushpull_push_managed_set', [$managedContentPage, 'handlePush']);
     167        add_action('wp_ajax_pushpull_start_branch_action', [$managedContentPage, 'handleAjaxStartBranchAction']);
     168        add_action('wp_ajax_pushpull_continue_branch_action', [$managedContentPage, 'handleAjaxContinueBranchAction']);
    157169        add_action('admin_post_pushpull_resolve_conflict_managed_set', [$managedContentPage, 'handleResolveConflict']);
    158170        add_action('admin_post_pushpull_finalize_merge_managed_set', [$managedContentPage, 'handleFinalizeMerge']);
  • pushpull/trunk/src/Support/Operations/OperationLockService.php

    r3490393 r3492839  
    5858    }
    5959
     60    public function restore(string $token): OperationLock
     61    {
     62        return new OperationLock(self::LOCK_OPTION, $token);
     63    }
     64
     65    public function refresh(OperationLock $lock): void
     66    {
     67        $existing = get_option($lock->optionKey, null);
     68
     69        if (! is_array($existing)) {
     70            return;
     71        }
     72
     73        if (($existing['token'] ?? null) !== $lock->token) {
     74            return;
     75        }
     76
     77        $existing['expiresAt'] = time() + $this->ttlSeconds;
     78        update_option($lock->optionKey, $existing, false);
     79    }
     80
    6081    /**
    6182     * @param array<string, mixed> $payload
  • pushpull/trunk/vendor/composer/installed.php

    r3492727 r3492839  
    22    'root' => array(
    33        'name' => 'creativemoods/pushpull',
    4         'pretty_version' => 'v0.0.8',
    5         'version' => '0.0.8.0',
    6         'reference' => '719835b06afa70676ea951adcdd91e32f32f5955',
     4        'pretty_version' => 'v0.0.9',
     5        'version' => '0.0.9.0',
     6        'reference' => '4524c732909df6fb69ddc2c6a44203c5a6d31f5a',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    1212    'versions' => array(
    1313        'creativemoods/pushpull' => array(
    14             'pretty_version' => 'v0.0.8',
    15             'version' => '0.0.8.0',
    16             'reference' => '719835b06afa70676ea951adcdd91e32f32f5955',
     14            'pretty_version' => 'v0.0.9',
     15            'version' => '0.0.9.0',
     16            'reference' => '4524c732909df6fb69ddc2c6a44203c5a6d31f5a',
    1717            'type' => 'wordpress-plugin',
    1818            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.