Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/engine/DifferentialDiffExtractionEngine.php
12256 views
1
<?php
2
3
final class DifferentialDiffExtractionEngine extends Phobject {
4
5
private $viewer;
6
private $authorPHID;
7
8
public function setViewer(PhabricatorUser $viewer) {
9
$this->viewer = $viewer;
10
return $this;
11
}
12
13
public function getViewer() {
14
return $this->viewer;
15
}
16
17
public function setAuthorPHID($author_phid) {
18
$this->authorPHID = $author_phid;
19
return $this;
20
}
21
22
public function getAuthorPHID() {
23
return $this->authorPHID;
24
}
25
26
public function newDiffFromCommit(PhabricatorRepositoryCommit $commit) {
27
$viewer = $this->getViewer();
28
29
// If we already have an unattached diff for this commit, just reuse it.
30
// This stops us from repeatedly generating diffs if something goes wrong
31
// later in the process. See T10968 for context.
32
$existing_diffs = id(new DifferentialDiffQuery())
33
->setViewer($viewer)
34
->withCommitPHIDs(array($commit->getPHID()))
35
->withHasRevision(false)
36
->needChangesets(true)
37
->execute();
38
if ($existing_diffs) {
39
return head($existing_diffs);
40
}
41
42
$repository = $commit->getRepository();
43
$identifier = $commit->getCommitIdentifier();
44
$monogram = $commit->getMonogram();
45
46
$drequest = DiffusionRequest::newFromDictionary(
47
array(
48
'user' => $viewer,
49
'repository' => $repository,
50
));
51
52
$diff_info = DiffusionQuery::callConduitWithDiffusionRequest(
53
$viewer,
54
$drequest,
55
'diffusion.rawdiffquery',
56
array(
57
'commit' => $identifier,
58
));
59
60
$file_phid = $diff_info['filePHID'];
61
$diff_file = id(new PhabricatorFileQuery())
62
->setViewer($viewer)
63
->withPHIDs(array($file_phid))
64
->executeOne();
65
if (!$diff_file) {
66
throw new Exception(
67
pht(
68
'Failed to load file ("%s") returned by "%s".',
69
$file_phid,
70
'diffusion.rawdiffquery'));
71
}
72
73
$raw_diff = $diff_file->loadFileData();
74
75
// TODO: Support adds, deletes and moves under SVN.
76
if (strlen($raw_diff)) {
77
$changes = id(new ArcanistDiffParser())->parseDiff($raw_diff);
78
} else {
79
// This is an empty diff, maybe made with `git commit --allow-empty`.
80
// NOTE: These diffs have the same tree hash as their ancestors, so
81
// they may attach to revisions in an unexpected way. Just let this
82
// happen for now, although it might make sense to special case it
83
// eventually.
84
$changes = array();
85
}
86
87
$diff = DifferentialDiff::newFromRawChanges($viewer, $changes)
88
->setRepositoryPHID($repository->getPHID())
89
->setCommitPHID($commit->getPHID())
90
->setCreationMethod('commit')
91
->setSourceControlSystem($repository->getVersionControlSystem())
92
->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP)
93
->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP)
94
->setDateCreated($commit->getEpoch())
95
->setDescription($monogram);
96
97
$author_phid = $this->getAuthorPHID();
98
if ($author_phid !== null) {
99
$diff->setAuthorPHID($author_phid);
100
}
101
102
$parents = DiffusionQuery::callConduitWithDiffusionRequest(
103
$viewer,
104
$drequest,
105
'diffusion.commitparentsquery',
106
array(
107
'commit' => $identifier,
108
));
109
110
if ($parents) {
111
$diff->setSourceControlBaseRevision(head($parents));
112
}
113
114
// TODO: Attach binary files.
115
116
return $diff->save();
117
}
118
119
public function isDiffChangedBeforeCommit(
120
PhabricatorRepositoryCommit $commit,
121
DifferentialDiff $old,
122
DifferentialDiff $new) {
123
124
$viewer = $this->getViewer();
125
$repository = $commit->getRepository();
126
$identifier = $commit->getCommitIdentifier();
127
128
$vs_changesets = array();
129
foreach ($old->getChangesets() as $changeset) {
130
$path = $changeset->getAbsoluteRepositoryPath($repository, $old);
131
$path = ltrim($path, '/');
132
$vs_changesets[$path] = $changeset;
133
}
134
135
$changesets = array();
136
foreach ($new->getChangesets() as $changeset) {
137
$path = $changeset->getAbsoluteRepositoryPath($repository, $new);
138
$path = ltrim($path, '/');
139
$changesets[$path] = $changeset;
140
}
141
142
if (array_fill_keys(array_keys($changesets), true) !=
143
array_fill_keys(array_keys($vs_changesets), true)) {
144
return true;
145
}
146
147
$file_phids = array();
148
foreach ($vs_changesets as $changeset) {
149
$metadata = $changeset->getMetadata();
150
$file_phid = idx($metadata, 'new:binary-phid');
151
if ($file_phid) {
152
$file_phids[$file_phid] = $file_phid;
153
}
154
}
155
156
$files = array();
157
if ($file_phids) {
158
$files = id(new PhabricatorFileQuery())
159
->setViewer(PhabricatorUser::getOmnipotentUser())
160
->withPHIDs($file_phids)
161
->execute();
162
$files = mpull($files, null, 'getPHID');
163
}
164
165
foreach ($changesets as $path => $changeset) {
166
$vs_changeset = $vs_changesets[$path];
167
168
$file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid');
169
if ($file_phid) {
170
if (!isset($files[$file_phid])) {
171
return true;
172
}
173
174
$drequest = DiffusionRequest::newFromDictionary(
175
array(
176
'user' => $viewer,
177
'repository' => $repository,
178
));
179
180
try {
181
$response = DiffusionQuery::callConduitWithDiffusionRequest(
182
$viewer,
183
$drequest,
184
'diffusion.filecontentquery',
185
array(
186
'commit' => $identifier,
187
'path' => $path,
188
));
189
} catch (Exception $ex) {
190
// TODO: See PHI1044. This call may fail if the diff deleted the
191
// file. If the call fails, just detect a change for now. This should
192
// generally be made cleaner in the future.
193
return true;
194
}
195
196
$new_file_phid = $response['filePHID'];
197
if (!$new_file_phid) {
198
return true;
199
}
200
201
$new_file = id(new PhabricatorFileQuery())
202
->setViewer($viewer)
203
->withPHIDs(array($new_file_phid))
204
->executeOne();
205
if (!$new_file) {
206
return true;
207
}
208
209
if ($files[$file_phid]->loadFileData() != $new_file->loadFileData()) {
210
return true;
211
}
212
} else {
213
$context = implode("\n", $changeset->makeChangesWithContext());
214
$vs_context = implode("\n", $vs_changeset->makeChangesWithContext());
215
216
// We couldn't just compare $context and $vs_context because following
217
// diffs will be considered different:
218
//
219
// -(empty line)
220
// -echo 'test';
221
// (empty line)
222
//
223
// (empty line)
224
// -echo "test";
225
// -(empty line)
226
227
$hunk = id(new DifferentialHunk())->setChanges($context);
228
$vs_hunk = id(new DifferentialHunk())->setChanges($vs_context);
229
if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() ||
230
$hunk->makeNewFile() != $vs_hunk->makeNewFile()) {
231
return true;
232
}
233
}
234
}
235
236
return false;
237
}
238
239
public function updateRevisionWithCommit(
240
DifferentialRevision $revision,
241
PhabricatorRepositoryCommit $commit,
242
array $more_xactions,
243
PhabricatorContentSource $content_source) {
244
245
$viewer = $this->getViewer();
246
$new_diff = $this->newDiffFromCommit($commit);
247
248
$old_diff = $revision->getActiveDiff();
249
$changed_uri = null;
250
if ($old_diff) {
251
$old_diff = id(new DifferentialDiffQuery())
252
->setViewer($viewer)
253
->withIDs(array($old_diff->getID()))
254
->needChangesets(true)
255
->executeOne();
256
if ($old_diff) {
257
$has_changed = $this->isDiffChangedBeforeCommit(
258
$commit,
259
$old_diff,
260
$new_diff);
261
if ($has_changed) {
262
$revision_monogram = $revision->getMonogram();
263
$old_id = $old_diff->getID();
264
$new_id = $new_diff->getID();
265
266
$changed_uri = "/{$revision_monogram}?vs={$old_id}&id={$new_id}#toc";
267
$changed_uri = PhabricatorEnv::getProductionURI($changed_uri);
268
}
269
}
270
}
271
272
$xactions = array();
273
274
// If the revision isn't closed or "Accepted", write a warning into the
275
// transaction log. This makes it more clear when users bend the rules.
276
if (!$revision->isClosed() && !$revision->isAccepted()) {
277
$wrong_type = DifferentialRevisionWrongStateTransaction::TRANSACTIONTYPE;
278
279
$xactions[] = id(new DifferentialTransaction())
280
->setTransactionType($wrong_type)
281
->setNewValue($revision->getModernRevisionStatus());
282
}
283
284
$concerning_builds = self::loadConcerningBuilds(
285
$this->getViewer(),
286
$revision,
287
$strict = false);
288
289
if ($concerning_builds) {
290
$build_list = array();
291
foreach ($concerning_builds as $build) {
292
$build_list[] = array(
293
'phid' => $build->getPHID(),
294
'status' => $build->getBuildStatus(),
295
);
296
}
297
298
$wrong_builds =
299
DifferentialRevisionWrongBuildsTransaction::TRANSACTIONTYPE;
300
301
$xactions[] = id(new DifferentialTransaction())
302
->setTransactionType($wrong_builds)
303
->setNewValue($build_list);
304
}
305
306
$type_update = DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE;
307
308
$xactions[] = id(new DifferentialTransaction())
309
->setTransactionType($type_update)
310
->setIgnoreOnNoEffect(true)
311
->setNewValue($new_diff->getPHID())
312
->setMetadataValue('isCommitUpdate', true)
313
->setMetadataValue('commitPHIDs', array($commit->getPHID()));
314
315
foreach ($more_xactions as $more_xaction) {
316
$xactions[] = $more_xaction;
317
}
318
319
$editor = id(new DifferentialTransactionEditor())
320
->setActor($viewer)
321
->setContinueOnMissingFields(true)
322
->setContinueOnNoEffect(true)
323
->setContentSource($content_source)
324
->setChangedPriorToCommitURI($changed_uri)
325
->setIsCloseByCommit(true);
326
327
$author_phid = $this->getAuthorPHID();
328
if ($author_phid !== null) {
329
$editor->setActingAsPHID($author_phid);
330
}
331
332
$editor->applyTransactions($revision, $xactions);
333
}
334
335
public static function loadConcerningBuilds(
336
PhabricatorUser $viewer,
337
DifferentialRevision $revision,
338
$strict) {
339
340
$diff = $revision->getActiveDiff();
341
342
$buildables = id(new HarbormasterBuildableQuery())
343
->setViewer($viewer)
344
->withBuildablePHIDs(array($diff->getPHID()))
345
->needBuilds(true)
346
->withManualBuildables(false)
347
->execute();
348
if (!$buildables) {
349
return array();
350
}
351
352
$land_key = HarbormasterBuildPlanBehavior::BEHAVIOR_LANDWARNING;
353
$behavior = HarbormasterBuildPlanBehavior::getBehavior($land_key);
354
355
$key_never = HarbormasterBuildPlanBehavior::LANDWARNING_NEVER;
356
$key_building = HarbormasterBuildPlanBehavior::LANDWARNING_IF_BUILDING;
357
$key_complete = HarbormasterBuildPlanBehavior::LANDWARNING_IF_COMPLETE;
358
359
$concerning_builds = array();
360
foreach ($buildables as $buildable) {
361
$builds = $buildable->getBuilds();
362
foreach ($builds as $build) {
363
$plan = $build->getBuildPlan();
364
$option = $behavior->getPlanOption($plan);
365
$behavior_value = $option->getKey();
366
367
$if_never = ($behavior_value === $key_never);
368
if ($if_never) {
369
continue;
370
}
371
372
$if_building = ($behavior_value === $key_building);
373
if ($if_building && $build->isComplete()) {
374
continue;
375
}
376
377
$if_complete = ($behavior_value === $key_complete);
378
if ($if_complete) {
379
if (!$build->isComplete()) {
380
continue;
381
}
382
383
// TODO: If you "arc land" and a build with "Warn: If Complete"
384
// is still running, you may not see a warning, and push the revision
385
// in good faith. The build may then complete before we get here, so
386
// we now see a completed, failed build.
387
388
// For now, just err on the side of caution and assume these builds
389
// were in a good state when we prompted the user, even if they're in
390
// a bad state now.
391
392
// We could refine this with a rule like "if the build finished
393
// within a couple of minutes before the push happened, assume it was
394
// in good faith", but we don't currently have an especially
395
// convenient way to check when the build finished or when the commit
396
// was pushed or discovered, and this would create some issues in
397
// cases where the repository is observed and the fetch pipeline
398
// stalls for a while.
399
400
// If we're in strict mode (from a pre-commit content hook), we do
401
// not ignore these, since we're doing an instantaneous check against
402
// the current state.
403
404
if (!$strict) {
405
continue;
406
}
407
}
408
409
if ($build->isPassed()) {
410
continue;
411
}
412
413
$concerning_builds[] = $build;
414
}
415
}
416
417
return $concerning_builds;
418
}
419
420
}
421
422