@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<?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}