@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 319 lines 11 kB view raw
1<?php 2 3final class PhabricatorRepositoryManagementThawWorkflow 4 extends PhabricatorRepositoryManagementWorkflow { 5 6 protected function didConstruct() { 7 $this 8 ->setName('thaw') 9 ->setExamples('**thaw** [options] __repository__ ...') 10 ->setSynopsis( 11 pht( 12 'Resolve issues with frozen cluster repositories. Very advanced '. 13 'and dangerous.')) 14 ->setArguments( 15 array( 16 array( 17 'name' => 'demote', 18 'param' => 'device|service', 19 'help' => pht( 20 'Demote a device (or all devices in a service) discarding '. 21 'unsynchronized changes. Clears stuck write locks and recovers '. 22 'from lost leaders.'), 23 ), 24 array( 25 'name' => 'promote', 26 'param' => 'device', 27 'help' => pht( 28 'Promote a device, discarding changes on other devices. '. 29 'Resolves ambiguous leadership and recovers from demotion '. 30 'mistakes.'), 31 ), 32 array( 33 'name' => 'force', 34 'help' => pht('Run operations without asking for confirmation.'), 35 ), 36 array( 37 'name' => 'all-repositories', 38 'help' => pht( 39 'Apply the promotion or demotion to all repositories hosted '. 40 'on the device.'), 41 ), 42 array( 43 'name' => 'repositories', 44 'wildcard' => true, 45 ), 46 )); 47 } 48 49 public function execute(PhutilArgumentParser $args) { 50 $viewer = $this->getViewer(); 51 52 $promote = $args->getArg('promote'); 53 $demote = $args->getArg('demote'); 54 55 if (!$promote && !$demote) { 56 throw new PhutilArgumentUsageException( 57 pht('You must choose a device to --promote or --demote.')); 58 } 59 60 if ($promote && $demote) { 61 throw new PhutilArgumentUsageException( 62 pht('Specify either --promote or --demote, but not both.')); 63 } 64 65 $target_name = nonempty($promote, $demote); 66 67 $devices = id(new AlmanacDeviceQuery()) 68 ->setViewer($viewer) 69 ->withNames(array($target_name)) 70 ->execute(); 71 if (!$devices) { 72 $service = id(new AlmanacServiceQuery()) 73 ->setViewer($viewer) 74 ->withNames(array($target_name)) 75 ->executeOne(); 76 77 if (!$service) { 78 throw new PhutilArgumentUsageException( 79 pht('No device or service named "%s" exists.', $target_name)); 80 } 81 82 if ($promote) { 83 throw new PhutilArgumentUsageException( 84 pht( 85 'You can not "--promote" an entire service ("%s"). Only a single '. 86 'device may be promoted.', 87 $target_name)); 88 } 89 90 $bindings = id(new AlmanacBindingQuery()) 91 ->setViewer($viewer) 92 ->withServicePHIDs(array($service->getPHID())) 93 ->execute(); 94 if (!$bindings) { 95 throw new PhutilArgumentUsageException( 96 pht( 97 'Service "%s" is not bound to any devices.', 98 $target_name)); 99 } 100 101 $interfaces = id(new AlmanacInterfaceQuery()) 102 ->setViewer($viewer) 103 ->withPHIDs(mpull($bindings, 'getInterfacePHID')) 104 ->execute(); 105 106 $device_phids = mpull($interfaces, 'getDevicePHID'); 107 108 $devices = id(new AlmanacDeviceQuery()) 109 ->setViewer($viewer) 110 ->withPHIDs($device_phids) 111 ->execute(); 112 } 113 114 $repository_names = $args->getArg('repositories'); 115 $all_repositories = $args->getArg('all-repositories'); 116 if ($repository_names && $all_repositories) { 117 throw new PhutilArgumentUsageException( 118 pht( 119 'Specify a list of repositories or "--all-repositories", '. 120 'but not both.')); 121 } else if (!$repository_names && !$all_repositories) { 122 throw new PhutilArgumentUsageException( 123 pht( 124 'Select repositories to affect by providing a list of repositories '. 125 'or using the "--all-repositories" flag.')); 126 } 127 128 if ($repository_names) { 129 $repositories = $this->loadRepositories($args, 'repositories'); 130 if (!$repositories) { 131 throw new PhutilArgumentUsageException( 132 pht('Specify one or more repositories to thaw.')); 133 } 134 } else { 135 $repositories = array(); 136 137 $services = id(new AlmanacServiceQuery()) 138 ->setViewer($viewer) 139 ->withDevicePHIDs(mpull($devices, 'getPHID')) 140 ->execute(); 141 if ($services) { 142 $repositories = id(new PhabricatorRepositoryQuery()) 143 ->setViewer($viewer) 144 ->withAlmanacServicePHIDs(mpull($services, 'getPHID')) 145 ->execute(); 146 } 147 148 if (!$repositories) { 149 throw new PhutilArgumentUsageException( 150 pht('There are no repositories on the selected device or service.')); 151 } 152 } 153 154 $display_list = new PhutilConsoleList(); 155 foreach ($repositories as $repository) { 156 $display_list->addItem( 157 pht( 158 '%s %s', 159 $repository->getMonogram(), 160 $repository->getName())); 161 } 162 163 echo tsprintf( 164 "%s\n\n%B\n", 165 pht('These repositories will be thawed:'), 166 $display_list->drawConsoleString()); 167 168 if ($promote) { 169 $risk_message = pht( 170 'Promoting a device can cause the loss of any repository data which '. 171 'only exists on other devices. The version of the repository on the '. 172 'promoted device will become authoritative.'); 173 } else { 174 $risk_message = pht( 175 'Demoting a device can cause the loss of any repository data which '. 176 'only exists on the demoted device. The version of the repository '. 177 'on some other device will become authoritative.'); 178 } 179 180 echo tsprintf( 181 "**<bg:red> %s </bg>** %s\n", 182 pht('DATA AT RISK'), 183 $risk_message); 184 185 $is_force = $args->getArg('force'); 186 $prompt = pht('Accept the possibility of permanent data loss?'); 187 if (!$is_force && !phutil_console_confirm($prompt)) { 188 throw new PhutilArgumentUsageException( 189 pht('User aborted the workflow.')); 190 } 191 192 foreach ($devices as $device) { 193 foreach ($repositories as $repository) { 194 $repository_phid = $repository->getPHID(); 195 196 $write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock( 197 $repository_phid); 198 199 echo tsprintf( 200 "%s\n", 201 pht( 202 'Waiting to acquire write lock for "%s"...', 203 $repository->getDisplayName())); 204 205 $write_lock->lock(phutil_units('5 minutes in seconds')); 206 try { 207 208 $service = $repository->loadAlmanacService(); 209 if (!$service) { 210 throw new PhutilArgumentUsageException( 211 pht( 212 'Repository "%s" is not a cluster repository: it is not '. 213 'bound to an Almanac service.', 214 $repository->getDisplayName())); 215 } 216 217 if ($promote) { 218 // You can only promote active devices. (You may demote active or 219 // inactive devices.) 220 $bindings = $service->getActiveBindings(); 221 $bindings = mpull($bindings, null, 'getDevicePHID'); 222 if (empty($bindings[$device->getPHID()])) { 223 throw new PhutilArgumentUsageException( 224 pht( 225 'Repository "%s" has no active binding to device "%s". '. 226 'Only actively bound devices can be promoted.', 227 $repository->getDisplayName(), 228 $device->getName())); 229 } 230 231 $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions( 232 $repository->getPHID()); 233 $versions = mpull($versions, null, 'getDevicePHID'); 234 235 // Before we promote, make sure there are no outstanding versions 236 // on devices with inactive bindings. If there are, you need to 237 // demote these first. 238 $inactive = array(); 239 foreach ($versions as $device_phid => $version) { 240 if (isset($bindings[$device_phid])) { 241 continue; 242 } 243 $inactive[$device_phid] = $version; 244 } 245 246 if ($inactive) { 247 $handles = $viewer->loadHandles(array_keys($inactive)); 248 249 $handle_list = iterator_to_array($handles); 250 $handle_list = mpull($handle_list, 'getName'); 251 $handle_list = implode(', ', $handle_list); 252 253 throw new PhutilArgumentUsageException( 254 pht( 255 'Repository "%s" has versions on inactive devices. Demote '. 256 '(or reactivate) these devices before promoting a new '. 257 'leader: %s.', 258 $repository->getDisplayName(), 259 $handle_list)); 260 } 261 262 // Now, make sure there are no outstanding versions on devices with 263 // active bindings. These also need to be demoted (or promoting is 264 // a mistake or already happened). 265 $active = array_select_keys($versions, array_keys($bindings)); 266 if ($active) { 267 $handles = $viewer->loadHandles(array_keys($active)); 268 269 $handle_list = iterator_to_array($handles); 270 $handle_list = mpull($handle_list, 'getName'); 271 $handle_list = implode(', ', $handle_list); 272 273 throw new PhutilArgumentUsageException( 274 pht( 275 'Unable to promote "%s" for repository "%s" because this '. 276 'cluster already has one or more unambiguous leaders: %s.', 277 $device->getName(), 278 $repository->getDisplayName(), 279 $handle_list)); 280 } 281 282 PhabricatorRepositoryWorkingCopyVersion::updateVersion( 283 $repository->getPHID(), 284 $device->getPHID(), 285 0); 286 287 echo tsprintf( 288 "%s\n", 289 pht( 290 'Promoted "%s" to become a leader for "%s".', 291 $device->getName(), 292 $repository->getDisplayName())); 293 } 294 295 if ($demote) { 296 PhabricatorRepositoryWorkingCopyVersion::demoteDevice( 297 $repository->getPHID(), 298 $device->getPHID()); 299 300 echo tsprintf( 301 "%s\n", 302 pht( 303 'Demoted "%s" from leadership of repository "%s".', 304 $device->getName(), 305 $repository->getDisplayName())); 306 } 307 } catch (Exception $ex) { 308 $write_lock->unlock(); 309 throw $ex; 310 } 311 312 $write_lock->unlock(); 313 } 314 } 315 316 return 0; 317 } 318 319}