Changeset 3492839
- Timestamp:
- 03/27/2026 05:07:43 PM (7 hours ago)
- Location:
- pushpull
- Files:
-
- 168 added
- 11 edited
-
tags/0.0.9 (added)
-
tags/0.0.9/README.md (added)
-
tags/0.0.9/composer.json (added)
-
tags/0.0.9/plugin-assets (added)
-
tags/0.0.9/plugin-assets/css (added)
-
tags/0.0.9/plugin-assets/css/admin.css (added)
-
tags/0.0.9/plugin-assets/js (added)
-
tags/0.0.9/plugin-assets/js/managed-content.js (added)
-
tags/0.0.9/pushpull.php (added)
-
tags/0.0.9/readme.txt (added)
-
tags/0.0.9/src (added)
-
tags/0.0.9/src/Admin (added)
-
tags/0.0.9/src/Admin/AttachmentSyncField.php (added)
-
tags/0.0.9/src/Admin/ManagedContentPage.php (added)
-
tags/0.0.9/src/Admin/OperationsPage.php (added)
-
tags/0.0.9/src/Admin/SettingsPage.php (added)
-
tags/0.0.9/src/Content (added)
-
tags/0.0.9/src/Content/AbstractWordPressPostTypeAdapter.php (added)
-
tags/0.0.9/src/Content/Exception (added)
-
tags/0.0.9/src/Content/Exception/ManagedContentExportException.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksCanonicalHasher.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksConditionKeyGenerator.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksConditionsAdapter.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksConditionsSnapshot.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksGlobalStylesAdapter.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksGlobalStylesSnapshot.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksLogicalKeyGenerator.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/GenerateBlocksRepositoryLayout.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/WordPressBlockPatternsAdapter.php (added)
-
tags/0.0.9/src/Content/GenerateBlocks/WordPressBlockPatternsSnapshot.php (added)
-
tags/0.0.9/src/Content/ManagedCollectionManifest.php (added)
-
tags/0.0.9/src/Content/ManagedContentAdapterInterface.php (added)
-
tags/0.0.9/src/Content/ManagedContentItem.php (added)
-
tags/0.0.9/src/Content/ManagedContentSnapshot.php (added)
-
tags/0.0.9/src/Content/ManagedSetRegistry.php (added)
-
tags/0.0.9/src/Content/ManifestManagedContentAdapterInterface.php (added)
-
tags/0.0.9/src/Content/WordPress (added)
-
tags/0.0.9/src/Content/WordPress/WordPressAttachmentsAdapter.php (added)
-
tags/0.0.9/src/Content/WordPress/WordPressAttachmentsSnapshot.php (added)
-
tags/0.0.9/src/Content/WordPress/WordPressCustomCssAdapter.php (added)
-
tags/0.0.9/src/Content/WordPress/WordPressCustomCssSnapshot.php (added)
-
tags/0.0.9/src/Content/WordPress/WordPressPagesAdapter.php (added)
-
tags/0.0.9/src/Content/WordPress/WordPressPagesSnapshot.php (added)
-
tags/0.0.9/src/Content/WordPressManagedContentAdapterInterface.php (added)
-
tags/0.0.9/src/Domain (added)
-
tags/0.0.9/src/Domain/Apply (added)
-
tags/0.0.9/src/Domain/Apply/ApplyManagedSetResult.php (added)
-
tags/0.0.9/src/Domain/Apply/ManagedSetApplyService.php (added)
-
tags/0.0.9/src/Domain/Diff (added)
-
tags/0.0.9/src/Domain/Diff/CanonicalDiffEntry.php (added)
-
tags/0.0.9/src/Domain/Diff/CanonicalDiffResult.php (added)
-
tags/0.0.9/src/Domain/Diff/CanonicalManagedFile.php (added)
-
tags/0.0.9/src/Domain/Diff/CanonicalManagedState.php (added)
-
tags/0.0.9/src/Domain/Diff/ManagedSetDiffResult.php (added)
-
tags/0.0.9/src/Domain/Diff/ManagedSetDiffService.php (added)
-
tags/0.0.9/src/Domain/Diff/RepositoryRelationship.php (added)
-
tags/0.0.9/src/Domain/Diff/RepositoryStateReader.php (added)
-
tags/0.0.9/src/Domain/Merge (added)
-
tags/0.0.9/src/Domain/Merge/FinalizeMergeResult.php (added)
-
tags/0.0.9/src/Domain/Merge/JsonThreeWayMerger.php (added)
-
tags/0.0.9/src/Domain/Merge/ManagedSetConflictResolutionService.php (added)
-
tags/0.0.9/src/Domain/Merge/ManagedSetMergeService.php (added)
-
tags/0.0.9/src/Domain/Merge/MergeConflict.php (added)
-
tags/0.0.9/src/Domain/Merge/MergeConflictState.php (added)
-
tags/0.0.9/src/Domain/Merge/MergeManagedSetResult.php (added)
-
tags/0.0.9/src/Domain/Merge/ResolveConflictResult.php (added)
-
tags/0.0.9/src/Domain/Push (added)
-
tags/0.0.9/src/Domain/Push/ManagedSetPushService.php (added)
-
tags/0.0.9/src/Domain/Push/PushManagedSetResult.php (added)
-
tags/0.0.9/src/Domain/Push/RemoteBranchResetService.php (added)
-
tags/0.0.9/src/Domain/Push/ResetRemoteBranchResult.php (added)
-
tags/0.0.9/src/Domain/Repository (added)
-
tags/0.0.9/src/Domain/Repository/Blob.php (added)
-
tags/0.0.9/src/Domain/Repository/Commit.php (added)
-
tags/0.0.9/src/Domain/Repository/CommitRequest.php (added)
-
tags/0.0.9/src/Domain/Repository/DatabaseLocalRepository.php (added)
-
tags/0.0.9/src/Domain/Repository/LocalRepositoryInterface.php (added)
-
tags/0.0.9/src/Domain/Repository/Ref.php (added)
-
tags/0.0.9/src/Domain/Repository/Tree.php (added)
-
tags/0.0.9/src/Domain/Repository/TreeEntry.php (added)
-
tags/0.0.9/src/Domain/Sync (added)
-
tags/0.0.9/src/Domain/Sync/CommitManagedSetRequest.php (added)
-
tags/0.0.9/src/Domain/Sync/CommitManagedSetResult.php (added)
-
tags/0.0.9/src/Domain/Sync/FetchManagedSetResult.php (added)
-
tags/0.0.9/src/Domain/Sync/InitializeRemoteRepositoryResult.php (added)
-
tags/0.0.9/src/Domain/Sync/LocalSyncService.php (added)
-
tags/0.0.9/src/Domain/Sync/ManagedSetRepositoryCommitter.php (added)
-
tags/0.0.9/src/Domain/Sync/PullManagedSetResult.php (added)
-
tags/0.0.9/src/Domain/Sync/RemoteBranchFetcher.php (added)
-
tags/0.0.9/src/Domain/Sync/RemoteRepositoryInitializer.php (added)
-
tags/0.0.9/src/Domain/Sync/SyncServiceInterface.php (added)
-
tags/0.0.9/src/Persistence (added)
-
tags/0.0.9/src/Persistence/ContentMap (added)
-
tags/0.0.9/src/Persistence/ContentMap/ContentMapEntry.php (added)
-
tags/0.0.9/src/Persistence/ContentMap/ContentMapRepository.php (added)
-
tags/0.0.9/src/Persistence/LocalRepositoryResetService.php (added)
-
tags/0.0.9/src/Persistence/Migrations (added)
-
tags/0.0.9/src/Persistence/Migrations/SchemaMigrator.php (added)
-
tags/0.0.9/src/Persistence/Operations (added)
-
tags/0.0.9/src/Persistence/Operations/OperationLogRepository.php (added)
-
tags/0.0.9/src/Persistence/Operations/OperationRecord.php (added)
-
tags/0.0.9/src/Persistence/TableNames.php (added)
-
tags/0.0.9/src/Persistence/WorkingState (added)
-
tags/0.0.9/src/Persistence/WorkingState/WorkingStateRecord.php (added)
-
tags/0.0.9/src/Persistence/WorkingState/WorkingStateRepository.php (added)
-
tags/0.0.9/src/Plugin (added)
-
tags/0.0.9/src/Plugin/Autoloader.php (added)
-
tags/0.0.9/src/Plugin/Plugin.php (added)
-
tags/0.0.9/src/Provider (added)
-
tags/0.0.9/src/Provider/CreateRemoteCommitRequest.php (added)
-
tags/0.0.9/src/Provider/Exception (added)
-
tags/0.0.9/src/Provider/Exception/ProviderException.php (added)
-
tags/0.0.9/src/Provider/Exception/ProviderOperationNotImplementedException.php (added)
-
tags/0.0.9/src/Provider/Exception/UnsupportedProviderException.php (added)
-
tags/0.0.9/src/Provider/GitHub (added)
-
tags/0.0.9/src/Provider/GitHub/GitHubProvider.php (added)
-
tags/0.0.9/src/Provider/GitProviderFactory.php (added)
-
tags/0.0.9/src/Provider/GitProviderFactoryInterface.php (added)
-
tags/0.0.9/src/Provider/GitProviderInterface.php (added)
-
tags/0.0.9/src/Provider/GitRemoteConfig.php (added)
-
tags/0.0.9/src/Provider/Http (added)
-
tags/0.0.9/src/Provider/Http/HttpRequest.php (added)
-
tags/0.0.9/src/Provider/Http/HttpResponse.php (added)
-
tags/0.0.9/src/Provider/Http/HttpTransportInterface.php (added)
-
tags/0.0.9/src/Provider/Http/WordPressHttpTransport.php (added)
-
tags/0.0.9/src/Provider/ProviderCapabilities.php (added)
-
tags/0.0.9/src/Provider/ProviderConnectionResult.php (added)
-
tags/0.0.9/src/Provider/ProviderValidationResult.php (added)
-
tags/0.0.9/src/Provider/RemoteBlob.php (added)
-
tags/0.0.9/src/Provider/RemoteCommit.php (added)
-
tags/0.0.9/src/Provider/RemoteRef.php (added)
-
tags/0.0.9/src/Provider/RemoteTree.php (added)
-
tags/0.0.9/src/Provider/UpdateRefResult.php (added)
-
tags/0.0.9/src/Provider/UpdateRemoteRefRequest.php (added)
-
tags/0.0.9/src/Settings (added)
-
tags/0.0.9/src/Settings/PushPullSettings.php (added)
-
tags/0.0.9/src/Settings/SettingsRegistrar.php (added)
-
tags/0.0.9/src/Settings/SettingsRepository.php (added)
-
tags/0.0.9/src/Support (added)
-
tags/0.0.9/src/Support/Capabilities.php (added)
-
tags/0.0.9/src/Support/Json (added)
-
tags/0.0.9/src/Support/Json/CanonicalJson.php (added)
-
tags/0.0.9/src/Support/Operations (added)
-
tags/0.0.9/src/Support/Operations/AsyncBranchOperationRunner.php (added)
-
tags/0.0.9/src/Support/Operations/ConcurrentOperationException.php (added)
-
tags/0.0.9/src/Support/Operations/OperationExecutor.php (added)
-
tags/0.0.9/src/Support/Operations/OperationLock.php (added)
-
tags/0.0.9/src/Support/Operations/OperationLockService.php (added)
-
tags/0.0.9/src/Support/Urls (added)
-
tags/0.0.9/src/Support/Urls/EnvironmentUrlCanonicalizer.php (added)
-
tags/0.0.9/vendor (added)
-
tags/0.0.9/vendor/autoload.php (added)
-
tags/0.0.9/vendor/composer (added)
-
tags/0.0.9/vendor/composer/ClassLoader.php (added)
-
tags/0.0.9/vendor/composer/InstalledVersions.php (added)
-
tags/0.0.9/vendor/composer/LICENSE (added)
-
tags/0.0.9/vendor/composer/autoload_classmap.php (added)
-
tags/0.0.9/vendor/composer/autoload_namespaces.php (added)
-
tags/0.0.9/vendor/composer/autoload_psr4.php (added)
-
tags/0.0.9/vendor/composer/autoload_real.php (added)
-
tags/0.0.9/vendor/composer/autoload_static.php (added)
-
tags/0.0.9/vendor/composer/installed.json (added)
-
tags/0.0.9/vendor/composer/installed.php (added)
-
tags/0.0.9/vendor/composer/platform_check.php (added)
-
trunk/README.md (modified) (2 diffs)
-
trunk/plugin-assets/css/admin.css (modified) (1 diff)
-
trunk/plugin-assets/js (added)
-
trunk/plugin-assets/js/managed-content.js (added)
-
trunk/pushpull.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/src/Admin/ManagedContentPage.php (modified) (17 diffs)
-
trunk/src/Domain/Repository/DatabaseLocalRepository.php (modified) (7 diffs)
-
trunk/src/Domain/Sync/LocalSyncService.php (modified) (3 diffs)
-
trunk/src/Persistence/Operations/OperationLogRepository.php (modified) (3 diffs)
-
trunk/src/Plugin/Plugin.php (modified) (4 diffs)
-
trunk/src/Support/Operations/AsyncBranchOperationRunner.php (added)
-
trunk/src/Support/Operations/OperationLockService.php (modified) (1 diff)
-
trunk/vendor/composer/installed.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
pushpull/trunk/README.md
r3492727 r3492839 6 6 Tested up to: 6.9 7 7 Requires PHP: 8.1 8 Stable tag: 0.0. 88 Stable tag: 0.0.9 9 9 License: GPLv2 10 10 License URI: [http://www.gnu.org/licenses/gpl-2.0.html](http://www.gnu.org/licenses/gpl-2.0.html) … … 158 158 ## Changelog 159 159 160 ### 0.0.9 161 162 1. 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. 163 2. Added modal-based operation progress UI, with indeterminate progress for fetch and determinate progress for push. 164 3. Added a first-commit guard so PushPull now requires `Fetch` before creating the first local commit when the remote branch already has history. 165 4. Fixed push planning so unchanged remote objects are reused in the normal linear-history case instead of being counted and uploaded again. 166 160 167 ### 0.0.8 161 168 -
pushpull/trunk/plugin-assets/css/admin.css
r3491811 r3492839 311 311 } 312 312 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 313 412 @media (max-width: 960px) { 314 413 .pushpull-layout { -
pushpull/trunk/pushpull.php
r3492727 r3492839 5 5 * Plugin URI: https://github.com/creativemoods/pushpull 6 6 * Description: Git-backed content workflows for selected WordPress content domains. 7 * Version: 0.0. 87 * Version: 0.0.9 8 8 * Requires at least: 6.0 9 9 * Requires PHP: 8.1 … … 23 23 define('PUSHPULL_PLUGIN_DIR', plugin_dir_path(__FILE__)); 24 24 define('PUSHPULL_PLUGIN_URL', plugin_dir_url(__FILE__)); 25 define('PUSHPULL_VERSION', '0.0. 8');25 define('PUSHPULL_VERSION', '0.0.9'); 26 26 27 27 if (is_readable(PUSHPULL_PLUGIN_DIR . 'vendor/autoload.php')) { -
pushpull/trunk/readme.txt
r3492727 r3492839 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.1 7 Stable tag: 0.0. 87 Stable tag: 0.0.9 8 8 License: GPLv2 9 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 164 164 == Changelog == 165 165 166 = 0.0.9 = 167 168 1. 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. 169 2. Added modal-based operation progress UI, with indeterminate progress for fetch and determinate progress for push. 170 3. Added a first-commit guard so PushPull now requires `Fetch` before creating the first local commit when the remote branch already has history. 171 4. Fixed push planning so unchanged remote objects are reused in the normal linear-history case instead of being counted and uploaded again. 172 166 173 = 0.0.8 = 167 174 -
pushpull/trunk/src/Admin/ManagedContentPage.php
r3492727 r3492839 19 19 use PushPull\Settings\SettingsRepository; 20 20 use PushPull\Support\Capabilities; 21 use PushPull\Support\Operations\AsyncBranchOperationRunner; 21 22 use PushPull\Support\Operations\OperationExecutor; 22 23 use RuntimeException; … … 42 43 private readonly WorkingStateRepository $workingStateRepository, 43 44 private readonly ManagedSetConflictResolutionService $conflictResolutionService, 44 private readonly OperationExecutor $operationExecutor 45 private readonly OperationExecutor $operationExecutor, 46 private readonly AsyncBranchOperationRunner $asyncBranchOperationRunner 45 47 ) { 46 48 } … … 70 72 PUSHPULL_VERSION 71 73 ); 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 ]); 72 93 } 73 94 … … 98 119 $this->renderManagedSetDetail($settings, $this->currentAdapter()); 99 120 } 121 $this->renderAsyncOperationModal(); 100 122 echo '</div>'; 101 123 } … … 788 810 789 811 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); 791 813 } 792 814 … … 846 868 } catch (ManagedContentExportException | ProviderException | RuntimeException $exception) { 847 869 $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage(); 848 $this->redirectWithNotice('error', $message, $managedSetKey);870 $this->redirectWithNotice('error', $message, null); 849 871 } 850 872 … … 861 883 ); 862 884 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 ]); 864 955 } 865 956 … … 877 968 878 969 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); 880 971 } 881 972 … … 889 980 } catch (ManagedContentExportException | ProviderException | RuntimeException $exception) { 890 981 $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage(); 891 $this->redirectWithNotice('error', $message, $managedSetKey);982 $this->redirectWithNotice('error', $message, null); 892 983 } 893 984 … … 907 998 ); 908 999 909 $this->redirectWithNotice($result->mergeResult->hasConflicts() ? 'error' : 'success', $message, $managedSetKey);1000 $this->redirectWithNotice($result->mergeResult->hasConflicts() ? 'error' : 'success', $message, null); 910 1001 } 911 1002 … … 923 1014 924 1015 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); 926 1017 } 927 1018 … … 935 1026 } catch (ManagedContentExportException | ProviderException | RuntimeException $exception) { 936 1027 $message = $exception instanceof ProviderException ? $exception->debugSummary() : $exception->getMessage(); 937 $this->redirectWithNotice('error', $message, $managedSetKey);1028 $this->redirectWithNotice('error', $message, null); 938 1029 } 939 1030 … … 986 1077 ); 987 1078 988 $this->redirectWithNotice('success', $message, $managedSetKey);1079 $this->redirectWithNotice('success', $message, null); 989 1080 } 990 1081 … … 1169 1260 } 1170 1261 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') . '">'; 1172 1263 echo '<input type="hidden" name="action" value="' . esc_attr(self::FETCH_ACTION) . '" />'; 1173 1264 echo '<input type="hidden" name="managed_set" value="' . esc_attr((string) $managedSetKey) . '" />'; … … 1188 1279 } 1189 1280 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') . '">'; 1191 1282 echo '<input type="hidden" name="action" value="' . esc_attr(self::PULL_ACTION) . '" />'; 1192 1283 echo '<input type="hidden" name="managed_set" value="' . esc_attr((string) $managedSetKey) . '" />'; … … 1226 1317 } 1227 1318 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') . '">'; 1229 1320 echo '<input type="hidden" name="action" value="' . esc_attr(self::PUSH_ACTION) . '" />'; 1230 1321 echo '<input type="hidden" name="managed_set" value="' . esc_attr((string) $managedSetKey) . '" />'; … … 1520 1611 private function redirectWithNotice(string $status, string $message, ?string $managedSetKey = null): never 1521 1612 { 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)); 1533 1614 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>'; 1534 1649 } 1535 1650 -
pushpull/trunk/src/Domain/Repository/DatabaseLocalRepository.php
r3490393 r3492839 6 6 7 7 // 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. 8 9 9 10 use PushPull\Persistence\TableNames; … … 12 13 use PushPull\Provider\RemoteTree; 13 14 use PushPull\Support\Json\CanonicalJson; 15 use RuntimeException; 14 16 use wpdb; 15 17 16 18 final class DatabaseLocalRepository implements LocalRepositoryInterface 17 19 { 20 private const BLOB_ENCODING_PREFIX = 'base64:'; 21 18 22 private readonly TableNames $tables; 19 23 … … 44 48 45 49 $now = $this->now(); 46 47 $this->wpdb->insert( 50 $storedContent = $this->encodeStoredBlobContent($content); 51 52 $inserted = $this->wpdb->insert( 48 53 $this->tables->repoBlobs(), 49 54 [ 50 55 'hash' => $hash, 51 'content' => $ content,56 'content' => $storedContent, 52 57 'size' => strlen($content), 53 58 'created_at' => $now, … … 55 60 ['%s', '%s', '%d', '%s'] 56 61 ); 62 63 if ($inserted === false) { 64 throw new RuntimeException(sprintf('Failed to store local blob %s.', $hash)); 65 } 57 66 58 67 return new Blob($hash, $content, strlen($content), $now); … … 73 82 } 74 83 84 $content = $this->decodeStoredBlobContent((string) $row['content']); 85 75 86 return new Blob( 76 87 (string) $row['hash'], 77 (string) $row['content'],88 $content, 78 89 (int) $row['size'], 79 90 (string) $row['created_at'] … … 90 101 91 102 $now = $this->now(); 92 $this->wpdb->insert( 103 $storedContent = $this->encodeStoredBlobContent($remoteBlob->content); 104 $inserted = $this->wpdb->insert( 93 105 $this->tables->repoBlobs(), 94 106 [ 95 107 'hash' => $remoteBlob->hash, 96 'content' => $ remoteBlob->content,108 'content' => $storedContent, 97 109 'size' => strlen($remoteBlob->content), 98 110 'created_at' => $now, … … 101 113 ); 102 114 115 if ($inserted === false) { 116 throw new RuntimeException(sprintf('Failed to import remote blob %s.', $remoteBlob->hash)); 117 } 118 103 119 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; 104 140 } 105 141 -
pushpull/trunk/src/Domain/Sync/LocalSyncService.php
r3491690 r3492839 20 20 use PushPull\Domain\Push\ResetRemoteBranchResult; 21 21 use PushPull\Domain\Repository\LocalRepositoryInterface; 22 use PushPull\Provider\Exception\ProviderException; 22 23 use PushPull\Provider\GitProviderFactoryInterface; 23 24 use PushPull\Provider\GitRemoteConfig; … … 55 56 $adapter = $this->requireAdapter($managedSetKey); 56 57 $committer = $this->requireCommitter($managedSetKey); 58 $this->guardAgainstUnfetchedRemoteBootstrap($request->branch); 57 59 58 60 return $committer->commitSnapshot( … … 162 164 } 163 165 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 164 203 /** 165 204 * @return array{0: \PushPull\Provider\GitProviderInterface, 1: GitRemoteConfig} -
pushpull/trunk/src/Persistence/Operations/OperationLogRepository.php
r3490393 r3492839 60 60 } 61 61 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 62 70 public function find(int $operationId): ?OperationRecord 63 71 { … … 111 119 private function updateStatus(int $operationId, string $status, array $result): OperationRecord 112 120 { 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 { 113 129 $record = $this->find($operationId); 114 130 … … 126 142 'created_by' => $record->createdBy, 127 143 'created_at' => $record->createdAt, 128 'finished_at' => current_time('mysql', true),144 'finished_at' => $finishedAt, 129 145 ]); 130 146 -
pushpull/trunk/src/Plugin/Plugin.php
r3492727 r3492839 41 41 use PushPull\Settings\SettingsRepository; 42 42 use PushPull\Support\Operations\OperationExecutor; 43 use PushPull\Support\Operations\AsyncBranchOperationRunner; 43 44 use PushPull\Support\Operations\OperationLockService; 44 45 … … 62 63 $remoteRepositoryInitializer = new RemoteRepositoryInitializer($providerFactory, $localRepository); 63 64 $operationLogRepository = new OperationLogRepository($wpdb); 64 $operationExecutor = new OperationExecutor($operationLogRepository, new OperationLockService()); 65 $operationLockService = new OperationLockService(); 66 $operationExecutor = new OperationExecutor($operationLogRepository, $operationLockService); 65 67 $generateBlocksStylesAdapter = new GenerateBlocksGlobalStylesAdapter(); 66 68 $generateBlocksConditionsAdapter = new GenerateBlocksConditionsAdapter(); … … 136 138 $workingStateRepository, 137 139 $conflictResolutionService, 138 $operationExecutor 140 $operationExecutor, 141 new AsyncBranchOperationRunner( 142 $operationLogRepository, 143 $operationLockService, 144 $settingsRepository, 145 $localRepository, 146 $providerFactory, 147 $syncService 148 ) 139 149 ); 140 150 $attachmentSyncField = new AttachmentSyncField(); … … 155 165 add_action('admin_post_pushpull_apply_managed_set', [$managedContentPage, 'handleApply']); 156 166 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']); 157 169 add_action('admin_post_pushpull_resolve_conflict_managed_set', [$managedContentPage, 'handleResolveConflict']); 158 170 add_action('admin_post_pushpull_finalize_merge_managed_set', [$managedContentPage, 'handleFinalizeMerge']); -
pushpull/trunk/src/Support/Operations/OperationLockService.php
r3490393 r3492839 58 58 } 59 59 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 60 81 /** 61 82 * @param array<string, mixed> $payload -
pushpull/trunk/vendor/composer/installed.php
r3492727 r3492839 2 2 'root' => array( 3 3 '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', 7 7 'type' => 'wordpress-plugin', 8 8 'install_path' => __DIR__ . '/../../', … … 12 12 'versions' => array( 13 13 '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', 17 17 'type' => 'wordpress-plugin', 18 18 'install_path' => __DIR__ . '/../../',
Note: See TracChangeset
for help on using the changeset viewer.