@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.

Make WorkingCopyBlueprint responsible for performing merges

Summary:
Ref T182. Currently, the "RepositoryLand" operation is responsible for performing merges when landing a revision.

However, we'd like to be able to perform these merges in a larger set of cases in the future. For example:

- After Releeph is revamped, when someone says "I want to merge bug fix X into stable branch Y", it would probably be nice to make that a Buildable and let tests run against it without requring that it actually be pushed anywhere.
- Same deal if we want a merge-from-Diffusion or cherry-pick-from-Diffusion operation.
- Similar deal if we want a "random web UI edits from Diffusion".

Move the merging part into WorkingCopy so more applications can share/use it in the future.

A big chunk of this is me making stuff up for now (the ol' undocumented dictionary full of arbitrary magic keys), but I anticipate formalizing it as we move along.

Test Plan: Pushed rGITTEST0d58eef3ce0fa5a10732d2efefc56aec126bc219 up from my local install via "Land Revision".

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T182

Differential Revision: https://secure.phabricator.com/D14337

+138 -65
+34 -3
src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
··· 237 237 $cmd = array(); 238 238 $arg = array(); 239 239 240 - $cmd[] = 'cd %s'; 241 - $arg[] = "{$root}/repo/{$directory}/"; 240 + $interface->pushWorkingDirectory("{$root}/repo/{$directory}/"); 242 241 243 242 $cmd[] = 'git clean -d --force'; 244 243 $cmd[] = 'git fetch'; ··· 285 284 if (idx($spec, 'default')) { 286 285 $default = $directory; 287 286 } 287 + 288 + $merges = idx($spec, 'merges'); 289 + if ($merges) { 290 + foreach ($merges as $merge) { 291 + $this->applyMerge($interface, $merge); 292 + } 293 + } 294 + 295 + $interface->popWorkingDirectory(); 288 296 } 289 297 290 298 if ($default === null) { ··· 333 341 $command_interface = $host_lease->getInterface($type); 334 342 335 343 $path = $lease->getAttribute('workingcopy.default'); 336 - $command_interface->setWorkingDirectory($path); 344 + $command_interface->pushWorkingDirectory($path); 337 345 338 346 return $command_interface; 339 347 } ··· 405 413 406 414 protected function shouldUseConcurrentResourceLimit() { 407 415 return true; 416 + } 417 + 418 + private function applyMerge( 419 + DrydockCommandInterface $interface, 420 + array $merge) { 421 + 422 + $src_uri = $merge['src.uri']; 423 + $src_ref = $merge['src.ref']; 424 + 425 + $interface->execx( 426 + 'git fetch --no-tags -- %s +%s:%s', 427 + $src_uri, 428 + $src_ref, 429 + $src_ref); 430 + 431 + try { 432 + $interface->execx( 433 + 'git merge --no-stat --squash --ff-only -- %s', 434 + $src_ref); 435 + } catch (CommandException $ex) { 436 + // TODO: Specifically note this as a merge conflict. 437 + throw $ex; 438 + } 408 439 } 409 440 410 441
+21 -7
src/applications/drydock/interface/command/DrydockCommandInterface.php
··· 4 4 5 5 const INTERFACE_TYPE = 'command'; 6 6 7 - private $workingDirectory; 7 + private $workingDirectoryStack = array(); 8 8 9 - public function setWorkingDirectory($working_directory) { 10 - $this->workingDirectory = $working_directory; 9 + public function pushWorkingDirectory($working_directory) { 10 + $this->workingDirectoryStack[] = $working_directory; 11 11 return $this; 12 12 } 13 13 14 - public function getWorkingDirectory() { 15 - return $this->workingDirectory; 14 + public function popWorkingDirectory() { 15 + if (!$this->workingDirectoryStack) { 16 + throw new Exception( 17 + pht( 18 + 'Unable to pop working directory, directory stack is empty.')); 19 + } 20 + return array_pop($this->workingDirectoryStack); 21 + } 22 + 23 + public function peekWorkingDirectory() { 24 + if ($this->workingDirectoryStack) { 25 + return last($this->workingDirectoryStack); 26 + } 27 + return null; 16 28 } 17 29 18 30 final public function getInterfaceType() { ··· 38 50 abstract public function getExecFuture($command); 39 51 40 52 protected function applyWorkingDirectoryToArgv(array $argv) { 41 - if ($this->getWorkingDirectory() !== null) { 53 + $directory = $this->peekWorkingDirectory(); 54 + 55 + if ($directory !== null) { 42 56 $cmd = $argv[0]; 43 57 $cmd = "(cd %s && {$cmd})"; 44 58 $argv = array_merge( 45 59 array($cmd), 46 - array($this->getWorkingDirectory()), 60 + array($directory), 47 61 array_slice($argv, 1)); 48 62 } 49 63
+69 -52
src/applications/drydock/operation/DrydockLandRepositoryOperation.php
··· 35 35 } 36 36 } 37 37 38 + public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) { 39 + $repository = $operation->getRepository(); 40 + $merges = array(); 41 + 42 + $object = $operation->getObject(); 43 + if ($object instanceof DifferentialRevision) { 44 + $diff = $this->loadDiff($operation); 45 + $merges[] = array( 46 + 'src.uri' => $repository->getStagingURI(), 47 + 'src.ref' => $diff->getStagingRef(), 48 + ); 49 + } else { 50 + throw new Exception( 51 + pht( 52 + 'Invalid or unknown object ("%s") for land operation, expected '. 53 + 'Differential Revision.', 54 + $operation->getObjectPHID())); 55 + } 56 + 57 + return $merges; 58 + } 59 + 38 60 public function applyOperation( 39 61 DrydockRepositoryOperation $operation, 40 62 DrydockInterface $interface) { ··· 48 70 if ($object instanceof DifferentialRevision) { 49 71 $revision = $object; 50 72 51 - $diff_phid = $operation->getProperty('differential.diffPHID'); 52 - 53 - $diff = id(new DifferentialDiffQuery()) 54 - ->setViewer($viewer) 55 - ->withPHIDs(array($diff_phid)) 56 - ->executeOne(); 57 - if (!$diff) { 58 - throw new Exception( 59 - pht( 60 - 'Unable to load diff "%s".', 61 - $diff_phid)); 62 - } 63 - 64 - $diff_revid = $diff->getRevisionID(); 65 - $revision_id = $revision->getID(); 66 - if ($diff_revid != $revision_id) { 67 - throw new Exception( 68 - pht( 69 - 'Diff ("%s") has wrong revision ID ("%s", expected "%s").', 70 - $diff_phid, 71 - $diff_revid, 72 - $revision_id)); 73 - } 74 - 75 - $cmd[] = 'git fetch --no-tags -- %s +%s:%s'; 76 - $arg[] = $repository->getStagingURI(); 77 - $arg[] = $diff->getStagingRef(); 78 - $arg[] = $diff->getStagingRef(); 79 - 80 - $merge_src = $diff->getStagingRef(); 73 + $diff = $this->loadDiff($operation); 81 74 82 75 $dict = $diff->getDiffAuthorshipDict(); 83 76 $author_name = idx($dict, 'authorName'); ··· 104 97 switch ($type) { 105 98 case 'branch': 106 99 $push_dst = 'refs/heads/'.$name; 107 - $merge_dst = 'refs/remotes/origin/'.$name; 108 100 break; 109 101 default: 110 102 throw new Exception( ··· 116 108 117 109 $committer_info = $this->getCommitterInfo($operation); 118 110 119 - $cmd[] = 'git checkout %s'; 120 - $arg[] = $merge_dst; 111 + // NOTE: We're doing this commit with "-F -" so we don't run into trouble 112 + // with enormous commit messages which might otherwise exceed the maximum 113 + // size of a command. 121 114 122 - $cmd[] = 'git merge --no-stat --squash --ff-only -- %s'; 123 - $arg[] = $merge_src; 115 + $future = $interface->getExecFuture( 116 + 'git -c user.name=%s -c user.email=%s commit --author %s -F - --', 117 + $committer_info['name'], 118 + $committer_info['email'], 119 + "{$author_name} <{$author_email}>"); 124 120 125 - $cmd[] = 'git -c user.name=%s -c user.email=%s commit --author %s -m %s'; 121 + $future 122 + ->write($commit_message) 123 + ->resolvex(); 126 124 127 - $arg[] = $committer_info['name']; 128 - $arg[] = $committer_info['email']; 129 - 130 - $arg[] = "{$author_name} <{$author_email}>"; 131 - $arg[] = $commit_message; 132 - 133 - $cmd[] = 'git push origin -- %s:%s'; 134 - $arg[] = 'HEAD'; 135 - $arg[] = $push_dst; 136 - 137 - $cmd = implode(' && ', $cmd); 138 - $argv = array_merge(array($cmd), $arg); 139 - 140 - $result = call_user_func_array( 141 - array($interface, 'execx'), 142 - $argv); 125 + $interface->execx( 126 + 'git push origin -- %s:%s', 127 + 'HEAD', 128 + $push_dst); 143 129 } 144 130 145 131 private function getCommitterInfo(DrydockRepositoryOperation $operation) { ··· 170 156 'name' => $committer_name, 171 157 'email' => 'autocommitter@example.com', 172 158 ); 159 + } 160 + 161 + private function loadDiff(DrydockRepositoryOperation $operation) { 162 + $viewer = $this->getViewer(); 163 + $revision = $operation->getObject(); 164 + 165 + $diff_phid = $operation->getProperty('differential.diffPHID'); 166 + 167 + $diff = id(new DifferentialDiffQuery()) 168 + ->setViewer($viewer) 169 + ->withPHIDs(array($diff_phid)) 170 + ->executeOne(); 171 + if (!$diff) { 172 + throw new Exception( 173 + pht( 174 + 'Unable to load diff "%s".', 175 + $diff_phid)); 176 + } 177 + 178 + $diff_revid = $diff->getRevisionID(); 179 + $revision_id = $revision->getID(); 180 + if ($diff_revid != $revision_id) { 181 + throw new Exception( 182 + pht( 183 + 'Diff ("%s") has wrong revision ID ("%s", expected "%s").', 184 + $diff_phid, 185 + $diff_revid, 186 + $revision_id)); 187 + } 188 + 189 + return $diff; 173 190 } 174 191 175 192 }
+4
src/applications/drydock/operation/DrydockRepositoryOperationType.php
··· 16 16 DrydockRepositoryOperation $operation, 17 17 PhabricatorUser $viewer); 18 18 19 + public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) { 20 + return array(); 21 + } 22 + 19 23 final public function setViewer(PhabricatorUser $viewer) { 20 24 $this->viewer = $viewer; 21 25 return $this;
+5
src/applications/drydock/storage/DrydockRepositoryOperation.php
··· 158 158 return false; 159 159 } 160 160 161 + public function getWorkingCopyMerges() { 162 + return $this->getImplementation()->getWorkingCopyMerges( 163 + $this); 164 + } 165 + 161 166 162 167 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 163 168
+5 -3
src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php
··· 53 53 // waiting for a lease we're holding. 54 54 55 55 try { 56 + $operation->getImplementation() 57 + ->setViewer($viewer); 58 + 56 59 $lease = $this->loadWorkingCopyLease($operation); 57 60 58 61 $interface = $lease->getInterface( ··· 60 63 61 64 // No matter what happens here, destroy the lease away once we're done. 62 65 $lease->releaseOnDestruction(true); 63 - 64 - $operation->getImplementation() 65 - ->setViewer($viewer); 66 66 67 67 $operation->applyOperation($interface); 68 68 ··· 165 165 $type, 166 166 $target)); 167 167 } 168 + 169 + $spec['merges'] = $operation->getWorkingCopyMerges(); 168 170 169 171 $map = array(); 170 172 $map[$repository->getCloneName()] = array(