@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
at recaptime-dev/main 270 lines 8.9 kB view raw
1<?php 2 3final class HeraldPreCommitContentAdapter extends HeraldPreCommitAdapter { 4 5 private $changesets; 6 private $commitRef; 7 private $fields; 8 private $revision = false; 9 10 private $affectedPackages; 11 private $identityCache = array(); 12 13 public function getAdapterContentName() { 14 return pht('Commit Hook: Commit Content'); 15 } 16 17 public function getAdapterSortOrder() { 18 return 2500; 19 } 20 21 public function getAdapterContentDescription() { 22 return pht( 23 "React to commits being pushed to hosted repositories.\n". 24 "Hook rules can block changes and send push summary mail."); 25 } 26 27 public function isPreCommitRefAdapter() { 28 return false; 29 } 30 31 public function getHeraldName() { 32 return pht('Push Log (Content)'); 33 } 34 35 public function isDiffEnormous() { 36 $this->getDiffContent('*'); 37 return ($this->changesets instanceof Exception); 38 } 39 40 public function getDiffContent($type) { 41 if ($this->changesets === null) { 42 try { 43 $this->changesets = $this->getHookEngine()->getChangesetsForCommit( 44 $this->getObject()->getRefNew()); 45 } catch (Exception $ex) { 46 $this->changesets = $ex; 47 } 48 } 49 50 if ($this->changesets instanceof Exception) { 51 $ex_class = get_class($this->changesets); 52 $ex_message = $this->changesets->getMessage(); 53 if ($type === 'name') { 54 return array("<{$ex_class}: {$ex_message}>"); 55 } else { 56 return array("<{$ex_class}>" => $ex_message); 57 } 58 } 59 60 $result = array(); 61 if ($type === 'name') { 62 foreach ($this->changesets as $change) { 63 $result[] = $change->getFilename(); 64 } 65 } else { 66 foreach ($this->changesets as $change) { 67 $lines = array(); 68 foreach ($change->getHunks() as $hunk) { 69 switch ($type) { 70 case '-': 71 $lines[] = $hunk->makeOldFile(); 72 break; 73 case '+': 74 $lines[] = $hunk->makeNewFile(); 75 break; 76 case '*': 77 default: 78 $lines[] = $hunk->makeChanges(); 79 break; 80 } 81 } 82 $result[$change->getFilename()] = implode('', $lines); 83 } 84 } 85 86 return $result; 87 } 88 89 public function getCommitRef() { 90 if ($this->commitRef === null) { 91 $this->commitRef = $this->getHookEngine()->loadCommitRefForCommit( 92 $this->getObject()->getRefNew()); 93 } 94 return $this->commitRef; 95 } 96 97 public function getAuthorPHID() { 98 $repository = $this->getHookEngine()->getRepository(); 99 $vcs = $repository->getVersionControlSystem(); 100 switch ($vcs) { 101 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 102 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 103 $ref = $this->getCommitRef(); 104 $author = $ref->getAuthor(); 105 if (!strlen($author)) { 106 return null; 107 } 108 return $this->lookupUser($author); 109 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 110 // In Subversion, the pusher is always the author. 111 return $this->getHookEngine()->getViewer()->getPHID(); 112 } 113 } 114 115 public function getCommitterPHID() { 116 $repository = $this->getHookEngine()->getRepository(); 117 $vcs = $repository->getVersionControlSystem(); 118 switch ($vcs) { 119 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 120 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 121 // If there's no committer information, we're going to return the 122 // author instead. However, if there's committer information and we 123 // can't resolve it, return `null`. 124 $ref = $this->getCommitRef(); 125 $committer = $ref->getCommitter(); 126 if (!strlen($committer)) { 127 return $this->getAuthorPHID(); 128 } 129 return $this->lookupUser($committer); 130 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 131 // In Subversion, the pusher is always the committer. 132 return $this->getHookEngine()->getViewer()->getPHID(); 133 } 134 } 135 136 public function getAuthorRaw() { 137 $repository = $this->getHookEngine()->getRepository(); 138 $vcs = $repository->getVersionControlSystem(); 139 switch ($vcs) { 140 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 141 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 142 $ref = $this->getCommitRef(); 143 return $ref->getAuthor(); 144 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 145 // In Subversion, the pusher is always the author. 146 return $this->getHookEngine()->getViewer()->getUsername(); 147 } 148 } 149 150 public function getCommitterRaw() { 151 $repository = $this->getHookEngine()->getRepository(); 152 $vcs = $repository->getVersionControlSystem(); 153 switch ($vcs) { 154 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 155 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 156 // Here, if there's no committer, we're going to return the author 157 // instead. 158 $ref = $this->getCommitRef(); 159 $committer = $ref->getCommitter(); 160 if (strlen($committer)) { 161 return $committer; 162 } 163 return $ref->getAuthor(); 164 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 165 // In Subversion, the pusher is always the committer. 166 return $this->getHookEngine()->getViewer()->getUsername(); 167 } 168 } 169 170 private function lookupUser($raw_identity) { 171 // See T13480. After the move to repository identities, we want to look 172 // users up in the identity table. If you push a commit which is authored 173 // by "A Duck <duck@example.org>" and that identity is bound to user 174 // "@mallard" in the identity table, Herald should see the author of the 175 // commit as "@mallard" when evaluating pre-commit content rules. 176 177 if (!array_key_exists($raw_identity, $this->identityCache)) { 178 $repository = $this->getHookEngine()->getRepository(); 179 $viewer = $this->getHookEngine()->getViewer(); 180 181 $identity_engine = id(new DiffusionRepositoryIdentityEngine()) 182 ->setViewer($viewer); 183 184 // We must provide a "sourcePHID" when resolving identities, but don't 185 // have a legitimate one yet. Just use the repository PHID as a 186 // reasonable value. This won't actually be written to storage. 187 $source_phid = $repository->getPHID(); 188 $identity_engine->setSourcePHID($source_phid); 189 190 // If the identity doesn't exist yet, we don't want to create it if 191 // we haven't seen it before. It will be created later when we actually 192 // import the commit. 193 $identity_engine->setDryRun(true); 194 195 $author_identity = $identity_engine->newResolvedIdentity($raw_identity); 196 197 $effective_phid = $author_identity->getCurrentEffectiveUserPHID(); 198 199 $this->identityCache[$raw_identity] = $effective_phid; 200 } 201 202 return $this->identityCache[$raw_identity]; 203 } 204 205 private function getCommitFields() { 206 if ($this->fields === null) { 207 $this->fields = id(new DiffusionLowLevelCommitFieldsQuery()) 208 ->setRepository($this->getHookEngine()->getRepository()) 209 ->withCommitRef($this->getCommitRef()) 210 ->execute(); 211 } 212 return $this->fields; 213 } 214 215 public function getRevision() { 216 if ($this->revision === false) { 217 $fields = $this->getCommitFields(); 218 $revision_id = idx($fields, 'revisionID'); 219 if (!$revision_id) { 220 $this->revision = null; 221 } else { 222 $this->revision = id(new DifferentialRevisionQuery()) 223 ->setViewer(PhabricatorUser::getOmnipotentUser()) 224 ->withIDs(array($revision_id)) 225 ->needReviewers(true) 226 ->executeOne(); 227 } 228 } 229 230 return $this->revision; 231 } 232 233 public function getIsMergeCommit() { 234 $repository = $this->getHookEngine()->getRepository(); 235 $vcs = $repository->getVersionControlSystem(); 236 switch ($vcs) { 237 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 238 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 239 $parents = id(new DiffusionLowLevelParentsQuery()) 240 ->setRepository($repository) 241 ->withIdentifier($this->getObject()->getRefNew()) 242 ->execute(); 243 244 return (count($parents) > 1); 245 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 246 // NOTE: For now, we ignore "svn:mergeinfo" at all levels. We might 247 // change this some day, but it's not nearly as clear a signal as 248 // ancestry is in Git/Mercurial. 249 return false; 250 } 251 } 252 253 public function getBranches() { 254 return $this->getHookEngine()->loadBranches( 255 $this->getObject()->getRefNew()); 256 } 257 258 public function loadAffectedPackages() { 259 if ($this->affectedPackages === null) { 260 $packages = PhabricatorOwnersPackage::loadAffectedPackages( 261 $this->getHookEngine()->getRepository(), 262 $this->getDiffContent('name')); 263 $this->affectedPackages = $packages; 264 } 265 266 return $this->affectedPackages; 267 } 268 269 270}