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 thaw.'));
     }
     $promote = $args->getArg('promote');
     $demote = $args->getArg('demote');
     if (!$promote && !$demote) {
         throw new PhutilArgumentUsageException(pht('You must choose a device to --promote or --demote.'));
     }
     if ($promote && $demote) {
         throw new PhutilArgumentUsageException(pht('Specify either --promote or --demote, but not both.'));
     }
     $device_name = nonempty($promote, $demote);
     $device = id(new AlmanacDeviceQuery())->setViewer($viewer)->withNames(array($device_name))->executeOne();
     if (!$device) {
         throw new PhutilArgumentUsageException(pht('No device "%s" exists.', $device_name));
     }
     if ($promote) {
         $risk_message = pht('Promoting a device can cause the loss of any repository data which ' . 'only exists on other devices. The version of the repository on the ' . 'promoted device will become authoritative.');
     } else {
         $risk_message = pht('Demoting a device can cause the loss of any repository data which ' . 'only exists on the demoted device. The version of the repository ' . 'on some other device will become authoritative.');
     }
     echo tsprintf("**<bg:red> %s </bg>** %s\n", pht('DATA AT RISK'), $risk_message);
     $is_force = $args->getArg('force');
     $prompt = pht('Accept the possibilty of permanent data loss?');
     if (!$is_force && !phutil_console_confirm($prompt)) {
         throw new PhutilArgumentUsageException(pht('User aborted the workflow.'));
     }
     foreach ($repositories as $repository) {
         $repository_phid = $repository->getPHID();
         $write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock($repository_phid);
         echo tsprintf("%s\n", pht('Waiting to acquire write lock for "%s"...', $repository->getDisplayName()));
         $write_lock->lock(phutil_units('5 minutes in seconds'));
         try {
             $service = $repository->loadAlmanacService();
             if (!$service) {
                 throw new PhutilArgumentUsageException(pht('Repository "%s" is not a cluster repository: it is not ' . 'bound to an Almanac service.', $repository->getDisplayName()));
             }
             $bindings = $service->getActiveBindings();
             $bindings = mpull($bindings, null, 'getDevicePHID');
             if (empty($bindings[$device->getPHID()])) {
                 throw new PhutilArgumentUsageException(pht('Repository "%s" has no active binding to device "%s". Only ' . 'actively bound devices can be promoted or demoted.', $repository->getDisplayName(), $device->getName()));
             }
             $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions($repository->getPHID());
             $versions = mpull($versions, null, 'getDevicePHID');
             $versions = array_select_keys($versions, array_keys($bindings));
             if ($versions && $promote) {
                 throw new PhutilArgumentUsageException(pht('Unable to promote "%s" for repository "%s": the leaders for ' . 'this cluster are not ambiguous.', $device->getName(), $repository->getDisplayName()));
             }
             if ($promote) {
                 PhabricatorRepositoryWorkingCopyVersion::updateVersion($repository->getPHID(), $device->getPHID(), 0);
                 echo tsprintf("%s\n", pht('Promoted "%s" to become a leader for "%s".', $device->getName(), $repository->getDisplayName()));
             }
             if ($demote) {
                 PhabricatorRepositoryWorkingCopyVersion::demoteDevice($repository->getPHID(), $device->getPHID());
                 echo tsprintf("%s\n", pht('Demoted "%s" from leadership of repository "%s".', $device->getName(), $repository->getDisplayName()));
             }
         } catch (Exception $ex) {
             $write_lock->unlock();
             throw $ex;
         }
         $write_lock->unlock();
     }
     return 0;
 }
 /**
  * @task sync
  */
 public function synchronizeWorkingCopyBeforeWrite()
 {
     if (!$this->shouldEnableSynchronization()) {
         return;
     }
     $repository = $this->getRepository();
     $viewer = $this->getViewer();
     $repository_phid = $repository->getPHID();
     $device = AlmanacKeys::getLiveDevice();
     $device_phid = $device->getPHID();
     $table = new PhabricatorRepositoryWorkingCopyVersion();
     $locked_connection = $table->establishConnection('w');
     $write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock($repository_phid);
     $write_lock->useSpecificConnection($locked_connection);
     $lock_wait = phutil_units('2 minutes in seconds');
     $this->logLine(pht('Waiting up to %s second(s) for a cluster write lock...', new PhutilNumber($lock_wait)));
     try {
         $start = PhabricatorTime::getNow();
         $write_lock->lock($lock_wait);
         $waited = PhabricatorTime::getNow() - $start;
         if ($waited) {
             $this->logLine(pht('Acquired write lock after %s second(s).', new PhutilNumber($waited)));
         } else {
             $this->logLine(pht('Acquired write lock immediately.'));
         }
     } catch (Exception $ex) {
         throw new PhutilProxyException(pht('Failed to acquire write lock after waiting %s second(s). You ' . 'may be able to retry later.', new PhutilNumber($lock_wait)));
     }
     $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions($repository_phid);
     foreach ($versions as $version) {
         if (!$version->getIsWriting()) {
             continue;
         }
         throw new Exception(pht('An previous write to this repository was interrupted; refusing ' . 'new writes. This issue requires operator intervention to resolve, ' . 'see "Write Interruptions" in the "Cluster: Repositories" in the ' . 'documentation for instructions.'));
     }
     try {
         $max_version = $this->synchronizeWorkingCopyBeforeRead();
     } catch (Exception $ex) {
         $write_lock->unlock();
         throw $ex;
     }
     $pid = getmypid();
     $hash = Filesystem::readRandomCharacters(12);
     $this->clusterWriteOwner = "{$pid}.{$hash}";
     PhabricatorRepositoryWorkingCopyVersion::willWrite($locked_connection, $repository_phid, $device_phid, array('userPHID' => $viewer->getPHID(), 'epoch' => PhabricatorTime::getNow(), 'devicePHID' => $device_phid), $this->clusterWriteOwner);
     $this->clusterWriteVersion = $max_version;
     $this->clusterWriteLock = $write_lock;
 }