Page MenuHomePhabricator

D21671.id51570.diff
No OneTemporary

D21671.id51570.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -4613,6 +4613,7 @@
'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php',
'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
+ 'PhabricatorRepositoryManagementLockWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLockWorkflow.php',
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php',
@@ -11382,6 +11383,7 @@
'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
+ 'PhabricatorRepositoryManagementLockWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementLockWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementLockWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementLockWorkflow.php
@@ -0,0 +1,139 @@
+<?php
+
+final class PhabricatorRepositoryManagementLockWorkflow
+ extends PhabricatorRepositoryManagementWorkflow {
+
+ protected function didConstruct() {
+ $this
+ ->setName('lock')
+ ->setExamples('**lock** [options] __repository__ ...')
+ ->setSynopsis(
+ pht(
+ 'Temporarily lock clustered repositories to perform maintenance.'))
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'repositories',
+ 'wildcard' => true,
+ ),
+ ));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $viewer = $this->getViewer();
+
+ $repositories = $this->loadRepositories($args, 'repositories');
+ if (!$repositories) {
+ throw new PhutilArgumentUsageException(
+ pht('Specify one or more repositories to lock.'));
+ }
+
+ foreach ($repositories as $repository) {
+ $display_name = $repository->getDisplayName();
+
+ if (!$repository->isHosted()) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Unable to lock repository "%s": only hosted repositories may be '.
+ 'locked.',
+ $display_name));
+ }
+
+ if (!$repository->supportsSynchronization()) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Unable to lock repository "%s": only repositories that support '.
+ 'clustering may be locked.',
+ $display_name));
+ }
+
+ if (!$repository->getAlmanacServicePHID()) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Unable to lock repository "%s": only clustered repositories '.
+ 'may be locked.',
+ $display_name));
+ }
+ }
+
+ $diffusion_phid = id(new PhabricatorDiffusionApplication())
+ ->getPHID();
+
+ $locks = array();
+ foreach ($repositories as $repository) {
+ $engine = id(new DiffusionRepositoryClusterEngine())
+ ->setViewer($viewer)
+ ->setActingAsPHID($diffusion_phid)
+ ->setRepository($repository);
+
+ $event = $engine->newMaintenanceEvent();
+
+ $logs = array();
+ $logs[] = $engine->newMaintenanceLog();
+
+ $locks[] = array(
+ 'repository' => $repository,
+ 'engine' => $engine,
+ 'event' => $event,
+ 'logs' => $logs,
+ );
+ }
+
+ $display_list = new PhutilConsoleList();
+ foreach ($repositories as $repository) {
+ $display_list->addItem(
+ pht(
+ '%s %s',
+ $repository->getMonogram(),
+ $repository->getName()));
+ }
+
+ echo tsprintf(
+ "%s\n\n%B\n",
+ pht('These repositories will be locked:'),
+ $display_list->drawConsoleString());
+
+ echo tsprintf(
+ "%s\n",
+ pht(
+ 'While the lock is held: users will be unable to write to this '.
+ 'repository, and you may safely perform working copy maintenance '.
+ 'on this node in another terminal window.'));
+
+ $query = pht('Lock repositories and begin maintenance?');
+ if (!phutil_console_confirm($query)) {
+ throw new ArcanistUserAbortException();
+ }
+
+ foreach ($locks as $key => $lock) {
+ $engine = $lock['engine'];
+ $engine->synchronizeWorkingCopyBeforeWrite();
+ }
+
+ echo tsprintf(
+ "%s\n",
+ pht(
+ 'Repositories are now locked. You may begin maintenance in '.
+ 'another terminal window. Keep this process running until '.
+ 'you complete the maintenance, then confirm that you are ready to '.
+ 'release the locks.'));
+
+ while (!phutil_console_confirm('Ready to release the locks?')) {
+ // Wait for the user to confirm that they're ready.
+ }
+
+ foreach ($locks as $key => $lock) {
+ $lock['event']->saveWithLogs($lock['logs']);
+
+ $engine = $lock['engine'];
+ $engine->synchronizeWorkingCopyAfterWrite();
+ }
+
+ echo tsprintf(
+ "%s\n",
+ pht('Done.'));
+
+ return 0;
+ }
+
+}
diff --git a/src/docs/user/cluster/cluster_repositories.diviner b/src/docs/user/cluster/cluster_repositories.diviner
--- a/src/docs/user/cluster/cluster_repositories.diviner
+++ b/src/docs/user/cluster/cluster_repositories.diviner
@@ -523,6 +523,87 @@
data more easily in a wider range of disaster situations.
+Ad-Hoc Maintenance Locks
+========================
+
+Occasionally, you may want to perform maintenance to a clustered repository
+which requires you modify the actual content of the repository.
+
+For example: you might want to delete a large number of old or temporary
+branches; or you might want to merge a very large number of commits from
+another source.
+
+These operations may be prohibitively slow or complex to perform using normal
+pushes. In cases where you would prefer to directly modify a working copy, you
+can use a maintenance lock to safely make a working copy mutable.
+
+If you simply perform this kind of content-modifying maintenance by directly
+modifying the repository on disk with commands like `git update-ref`, your
+changes may either encounter conflicts or encounter problems with change
+propagation.
+
+You can encounter conflicts because directly modifying the working copy on disk
+won't prevent users or Phabricator itself from performing writes to the same
+working copy at the same time. Phabricator does not compromise the lower-level
+locks provided by the VCS so this is theoretically safe -- and this rarely
+causes any significant problems in practice -- but doesn't make things any
+simpler or easier.
+
+Your changes may fail to propagate because writing directly to the repository
+doesn't turn it into the new cluster leader after your writes complete. If
+another node accepts the next push, it will become the new leader -- without
+your changes -- and all other nodes will synchronize from it.
+
+Note that some maintenance operations (like `git gc`, `git prune`, or
+`git repack`) do not modify repository content. In theory, these operations do
+not require a maintenance lock: lower-level Git locks should protect
+them from conflicts, and they can not be affected by propagation issues because
+they do not propagate. In practice, these operations are not conflict-free in
+all circumstances. Using a maintenance lock may be overkill, but it's probably
+still a good idea.
+
+To use a maintenance lock:
+
+ - Open two terminal windows. You'll use one window to hold the lock and a
+ second window to perform maintenance.
+ - Run `bin/repository lock <repository> ...` in one terminal.
+ - When the process reports that repositories are locked, switch to the second
+ terminal and perform maintenance. The `repository lock` process should
+ still be running in your first terminal.
+ - After maintenance completes, switch back to the first terminal and answer
+ the prompt to confirm maintenance is complete.
+
+The workflow looks something like this:
+
+```
+$ ./bin/repository lock R2
+
+These repositories will be locked:
+
+ - R2 Git Test Repository
+
+While the lock is held: users will be unable to write to this repository,
+and you may safely perform working copy maintenance on this node in another
+terminal window.
+
+ Lock repositories and begin maintenance? [y/N] y
+
+Repositories are now locked. You may begin maintenance in another terminal
+window. Keep this process running until you complete the maintenance, then
+confirm that you are ready to release the locks.
+
+ Ready to release the locks? [y/N] y
+
+Done.
+```
+
+As maintenance completes, the push log for the repository will be updated to
+reflect that you performed maintenance.
+
+If the lock is interrupted, you may encounter a "Write Interruptions" condition
+described earlier in this document. See that section for details. In most
+cases, you can resolve this issue by demoting the node you are working on.
+
Next Steps
==========

File Metadata

Mime Type
text/plain
Expires
Thu, Mar 27, 2:08 AM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7704127
Default Alt Text
D21671.id51570.diff (9 KB)

Event Timeline