Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/storage/DifferentialTransaction.php
12256 views
1
<?php
2
3
final class DifferentialTransaction
4
extends PhabricatorModularTransaction {
5
6
private $isCommandeerSideEffect;
7
8
const TYPE_INLINE = 'differential:inline';
9
const TYPE_ACTION = 'differential:action';
10
11
const MAILTAG_REVIEWERS = 'differential-reviewers';
12
const MAILTAG_CLOSED = 'differential-committed';
13
const MAILTAG_CC = 'differential-cc';
14
const MAILTAG_COMMENT = 'differential-comment';
15
const MAILTAG_UPDATED = 'differential-updated';
16
const MAILTAG_REVIEW_REQUEST = 'differential-review-request';
17
const MAILTAG_OTHER = 'differential-other';
18
19
public function getBaseTransactionClass() {
20
return 'DifferentialRevisionTransactionType';
21
}
22
23
protected function newFallbackModularTransactionType() {
24
// TODO: This allows us to render modern strings for older transactions
25
// without doing a migration. At some point, we should do a migration and
26
// throw this away.
27
28
// NOTE: Old reviewer edits are raw edge transactions. They could be
29
// migrated to modular transactions when the rest of this migrates.
30
31
$xaction_type = $this->getTransactionType();
32
if ($xaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {
33
switch ($this->getMetadataValue('customfield:key')) {
34
case 'differential:title':
35
return new DifferentialRevisionTitleTransaction();
36
case 'differential:test-plan':
37
return new DifferentialRevisionTestPlanTransaction();
38
case 'differential:repository':
39
return new DifferentialRevisionRepositoryTransaction();
40
}
41
}
42
43
return parent::newFallbackModularTransactionType();
44
}
45
46
47
public function setIsCommandeerSideEffect($is_side_effect) {
48
$this->isCommandeerSideEffect = $is_side_effect;
49
return $this;
50
}
51
52
public function getIsCommandeerSideEffect() {
53
return $this->isCommandeerSideEffect;
54
}
55
56
public function getApplicationName() {
57
return 'differential';
58
}
59
60
public function getApplicationTransactionType() {
61
return DifferentialRevisionPHIDType::TYPECONST;
62
}
63
64
public function getApplicationTransactionCommentObject() {
65
return new DifferentialTransactionComment();
66
}
67
68
public function shouldHide() {
69
$old = $this->getOldValue();
70
$new = $this->getNewValue();
71
72
switch ($this->getTransactionType()) {
73
case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE:
74
// Don't hide the initial "X requested review: ..." transaction from
75
// mail or feed even when it occurs during creation. We need this
76
// transaction to survive so we'll generate mail and feed stories when
77
// revisions immediately leave the draft state. See T13035 for
78
// discussion.
79
return false;
80
}
81
82
return parent::shouldHide();
83
}
84
85
public function shouldHideForMail(array $xactions) {
86
switch ($this->getTransactionType()) {
87
case DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE:
88
// Don't hide the initial "X added reviewers: ..." transaction during
89
// object creation from mail. See T12118 and PHI54.
90
return false;
91
}
92
93
return parent::shouldHideForMail($xactions);
94
}
95
96
97
public function isInlineCommentTransaction() {
98
switch ($this->getTransactionType()) {
99
case self::TYPE_INLINE:
100
return true;
101
}
102
103
return parent::isInlineCommentTransaction();
104
}
105
106
public function getRequiredHandlePHIDs() {
107
$phids = parent::getRequiredHandlePHIDs();
108
109
$old = $this->getOldValue();
110
$new = $this->getNewValue();
111
112
switch ($this->getTransactionType()) {
113
case self::TYPE_ACTION:
114
if ($new == DifferentialAction::ACTION_CLOSE &&
115
$this->getMetadataValue('isCommitClose')) {
116
$phids[] = $this->getMetadataValue('commitPHID');
117
if ($this->getMetadataValue('committerPHID')) {
118
$phids[] = $this->getMetadataValue('committerPHID');
119
}
120
if ($this->getMetadataValue('authorPHID')) {
121
$phids[] = $this->getMetadataValue('authorPHID');
122
}
123
}
124
break;
125
}
126
127
return $phids;
128
}
129
130
public function getActionStrength() {
131
switch ($this->getTransactionType()) {
132
case self::TYPE_ACTION:
133
return 300;
134
}
135
136
return parent::getActionStrength();
137
}
138
139
140
public function getActionName() {
141
switch ($this->getTransactionType()) {
142
case self::TYPE_INLINE:
143
return pht('Commented On');
144
case self::TYPE_ACTION:
145
$map = array(
146
DifferentialAction::ACTION_ACCEPT => pht('Accepted'),
147
DifferentialAction::ACTION_REJECT => pht('Requested Changes To'),
148
DifferentialAction::ACTION_RETHINK => pht('Planned Changes To'),
149
DifferentialAction::ACTION_ABANDON => pht('Abandoned'),
150
DifferentialAction::ACTION_CLOSE => pht('Closed'),
151
DifferentialAction::ACTION_REQUEST => pht('Requested A Review Of'),
152
DifferentialAction::ACTION_RESIGN => pht('Resigned From'),
153
DifferentialAction::ACTION_ADDREVIEWERS => pht('Added Reviewers'),
154
DifferentialAction::ACTION_CLAIM => pht('Commandeered'),
155
DifferentialAction::ACTION_REOPEN => pht('Reopened'),
156
);
157
$name = idx($map, $this->getNewValue());
158
if ($name !== null) {
159
return $name;
160
}
161
break;
162
}
163
164
return parent::getActionName();
165
}
166
167
public function getMailTags() {
168
$tags = array();
169
170
switch ($this->getTransactionType()) {
171
case PhabricatorTransactions::TYPE_SUBSCRIBERS;
172
$tags[] = self::MAILTAG_CC;
173
break;
174
case self::TYPE_ACTION:
175
switch ($this->getNewValue()) {
176
case DifferentialAction::ACTION_CLOSE:
177
$tags[] = self::MAILTAG_CLOSED;
178
break;
179
}
180
break;
181
case DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE:
182
$old = $this->getOldValue();
183
if ($old === null) {
184
$tags[] = self::MAILTAG_REVIEW_REQUEST;
185
} else {
186
$tags[] = self::MAILTAG_UPDATED;
187
}
188
break;
189
case PhabricatorTransactions::TYPE_COMMENT:
190
case self::TYPE_INLINE:
191
$tags[] = self::MAILTAG_COMMENT;
192
break;
193
case DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE:
194
$tags[] = self::MAILTAG_REVIEWERS;
195
break;
196
case DifferentialRevisionCloseTransaction::TRANSACTIONTYPE:
197
$tags[] = self::MAILTAG_CLOSED;
198
break;
199
}
200
201
if (!$tags) {
202
$tags[] = self::MAILTAG_OTHER;
203
}
204
205
return $tags;
206
}
207
208
public function getTitle() {
209
$author_phid = $this->getAuthorPHID();
210
$author_handle = $this->renderHandleLink($author_phid);
211
212
$old = $this->getOldValue();
213
$new = $this->getNewValue();
214
215
switch ($this->getTransactionType()) {
216
case self::TYPE_INLINE:
217
return pht(
218
'%s added inline comments.',
219
$author_handle);
220
case self::TYPE_ACTION:
221
switch ($new) {
222
case DifferentialAction::ACTION_CLOSE:
223
if (!$this->getMetadataValue('isCommitClose')) {
224
return DifferentialAction::getBasicStoryText(
225
$new,
226
$author_handle);
227
}
228
$commit_name = $this->renderHandleLink(
229
$this->getMetadataValue('commitPHID'));
230
$committer_phid = $this->getMetadataValue('committerPHID');
231
$author_phid = $this->getMetadataValue('authorPHID');
232
if ($this->getHandleIfExists($committer_phid)) {
233
$committer_name = $this->renderHandleLink($committer_phid);
234
} else {
235
$committer_name = $this->getMetadataValue('committerName');
236
}
237
if ($this->getHandleIfExists($author_phid)) {
238
$author_name = $this->renderHandleLink($author_phid);
239
} else {
240
$author_name = $this->getMetadataValue('authorName');
241
}
242
243
if ($committer_name && ($committer_name != $author_name)) {
244
return pht(
245
'Closed by commit %s (authored by %s, committed by %s).',
246
$commit_name,
247
$author_name,
248
$committer_name);
249
} else {
250
return pht(
251
'Closed by commit %s (authored by %s).',
252
$commit_name,
253
$author_name);
254
}
255
break;
256
default:
257
return DifferentialAction::getBasicStoryText($new, $author_handle);
258
}
259
break;
260
}
261
262
return parent::getTitle();
263
}
264
265
public function renderExtraInformationLink() {
266
if ($this->getMetadataValue('revisionMatchData')) {
267
$details_href =
268
'/differential/revision/closedetails/'.$this->getPHID().'/';
269
$details_link = javelin_tag(
270
'a',
271
array(
272
'href' => $details_href,
273
'sigil' => 'workflow',
274
),
275
pht('Explain Why'));
276
return $details_link;
277
}
278
return parent::renderExtraInformationLink();
279
}
280
281
public function getTitleForFeed() {
282
$author_phid = $this->getAuthorPHID();
283
$object_phid = $this->getObjectPHID();
284
285
$old = $this->getOldValue();
286
$new = $this->getNewValue();
287
288
$author_link = $this->renderHandleLink($author_phid);
289
$object_link = $this->renderHandleLink($object_phid);
290
291
switch ($this->getTransactionType()) {
292
case self::TYPE_INLINE:
293
return pht(
294
'%s added inline comments to %s.',
295
$author_link,
296
$object_link);
297
case self::TYPE_ACTION:
298
switch ($new) {
299
case DifferentialAction::ACTION_ACCEPT:
300
return pht(
301
'%s accepted %s.',
302
$author_link,
303
$object_link);
304
case DifferentialAction::ACTION_REJECT:
305
return pht(
306
'%s requested changes to %s.',
307
$author_link,
308
$object_link);
309
case DifferentialAction::ACTION_RETHINK:
310
return pht(
311
'%s planned changes to %s.',
312
$author_link,
313
$object_link);
314
case DifferentialAction::ACTION_ABANDON:
315
return pht(
316
'%s abandoned %s.',
317
$author_link,
318
$object_link);
319
case DifferentialAction::ACTION_CLOSE:
320
if (!$this->getMetadataValue('isCommitClose')) {
321
return pht(
322
'%s closed %s.',
323
$author_link,
324
$object_link);
325
} else {
326
$commit_name = $this->renderHandleLink(
327
$this->getMetadataValue('commitPHID'));
328
$committer_phid = $this->getMetadataValue('committerPHID');
329
$author_phid = $this->getMetadataValue('authorPHID');
330
331
if ($this->getHandleIfExists($committer_phid)) {
332
$committer_name = $this->renderHandleLink($committer_phid);
333
} else {
334
$committer_name = $this->getMetadataValue('committerName');
335
}
336
337
if ($this->getHandleIfExists($author_phid)) {
338
$author_name = $this->renderHandleLink($author_phid);
339
} else {
340
$author_name = $this->getMetadataValue('authorName');
341
}
342
343
// Check if the committer and author are the same. They're the
344
// same if both resolved and are the same user, or if neither
345
// resolved and the text is identical.
346
if ($committer_phid && $author_phid) {
347
$same_author = ($committer_phid == $author_phid);
348
} else if (!$committer_phid && !$author_phid) {
349
$same_author = ($committer_name == $author_name);
350
} else {
351
$same_author = false;
352
}
353
354
if ($committer_name && !$same_author) {
355
return pht(
356
'%s closed %s by committing %s (authored by %s).',
357
$author_link,
358
$object_link,
359
$commit_name,
360
$author_name);
361
} else {
362
return pht(
363
'%s closed %s by committing %s.',
364
$author_link,
365
$object_link,
366
$commit_name);
367
}
368
}
369
break;
370
371
case DifferentialAction::ACTION_REQUEST:
372
return pht(
373
'%s requested review of %s.',
374
$author_link,
375
$object_link);
376
case DifferentialAction::ACTION_RECLAIM:
377
return pht(
378
'%s reclaimed %s.',
379
$author_link,
380
$object_link);
381
case DifferentialAction::ACTION_RESIGN:
382
return pht(
383
'%s resigned from %s.',
384
$author_link,
385
$object_link);
386
case DifferentialAction::ACTION_CLAIM:
387
return pht(
388
'%s commandeered %s.',
389
$author_link,
390
$object_link);
391
case DifferentialAction::ACTION_REOPEN:
392
return pht(
393
'%s reopened %s.',
394
$author_link,
395
$object_link);
396
}
397
break;
398
}
399
400
return parent::getTitleForFeed();
401
}
402
403
public function getIcon() {
404
switch ($this->getTransactionType()) {
405
case self::TYPE_INLINE:
406
return 'fa-comment';
407
case self::TYPE_ACTION:
408
switch ($this->getNewValue()) {
409
case DifferentialAction::ACTION_CLOSE:
410
return 'fa-check';
411
case DifferentialAction::ACTION_ACCEPT:
412
return 'fa-check-circle-o';
413
case DifferentialAction::ACTION_REJECT:
414
return 'fa-times-circle-o';
415
case DifferentialAction::ACTION_ABANDON:
416
return 'fa-plane';
417
case DifferentialAction::ACTION_RETHINK:
418
return 'fa-headphones';
419
case DifferentialAction::ACTION_REQUEST:
420
return 'fa-refresh';
421
case DifferentialAction::ACTION_RECLAIM:
422
case DifferentialAction::ACTION_REOPEN:
423
return 'fa-bullhorn';
424
case DifferentialAction::ACTION_RESIGN:
425
return 'fa-flag';
426
case DifferentialAction::ACTION_CLAIM:
427
return 'fa-flag';
428
}
429
case PhabricatorTransactions::TYPE_EDGE:
430
switch ($this->getMetadataValue('edge:type')) {
431
case DifferentialRevisionHasReviewerEdgeType::EDGECONST:
432
return 'fa-user';
433
}
434
}
435
436
return parent::getIcon();
437
}
438
439
public function shouldDisplayGroupWith(array $group) {
440
441
// Never group status changes with other types of actions, they're indirect
442
// and don't make sense when combined with direct actions.
443
444
if ($this->isStatusTransaction($this)) {
445
return false;
446
}
447
448
foreach ($group as $xaction) {
449
if ($this->isStatusTransaction($xaction)) {
450
return false;
451
}
452
}
453
454
return parent::shouldDisplayGroupWith($group);
455
}
456
457
private function isStatusTransaction($xaction) {
458
$status_type = DifferentialRevisionStatusTransaction::TRANSACTIONTYPE;
459
if ($xaction->getTransactionType() == $status_type) {
460
return true;
461
}
462
463
return false;
464
}
465
466
467
public function getColor() {
468
switch ($this->getTransactionType()) {
469
case self::TYPE_ACTION:
470
switch ($this->getNewValue()) {
471
case DifferentialAction::ACTION_CLOSE:
472
return PhabricatorTransactions::COLOR_INDIGO;
473
case DifferentialAction::ACTION_ACCEPT:
474
return PhabricatorTransactions::COLOR_GREEN;
475
case DifferentialAction::ACTION_REJECT:
476
return PhabricatorTransactions::COLOR_RED;
477
case DifferentialAction::ACTION_ABANDON:
478
return PhabricatorTransactions::COLOR_INDIGO;
479
case DifferentialAction::ACTION_RETHINK:
480
return PhabricatorTransactions::COLOR_RED;
481
case DifferentialAction::ACTION_REQUEST:
482
return PhabricatorTransactions::COLOR_SKY;
483
case DifferentialAction::ACTION_RECLAIM:
484
return PhabricatorTransactions::COLOR_SKY;
485
case DifferentialAction::ACTION_REOPEN:
486
return PhabricatorTransactions::COLOR_SKY;
487
case DifferentialAction::ACTION_RESIGN:
488
return PhabricatorTransactions::COLOR_ORANGE;
489
case DifferentialAction::ACTION_CLAIM:
490
return PhabricatorTransactions::COLOR_YELLOW;
491
}
492
}
493
494
495
return parent::getColor();
496
}
497
498
public function getNoEffectDescription() {
499
switch ($this->getTransactionType()) {
500
case self::TYPE_ACTION:
501
switch ($this->getNewValue()) {
502
case DifferentialAction::ACTION_CLOSE:
503
return pht('This revision is already closed.');
504
case DifferentialAction::ACTION_ABANDON:
505
return pht('This revision has already been abandoned.');
506
case DifferentialAction::ACTION_RECLAIM:
507
return pht(
508
'You can not reclaim this revision because his revision is '.
509
'not abandoned.');
510
case DifferentialAction::ACTION_REOPEN:
511
return pht(
512
'You can not reopen this revision because this revision is '.
513
'not closed.');
514
case DifferentialAction::ACTION_RETHINK:
515
return pht('This revision already requires changes.');
516
case DifferentialAction::ACTION_CLAIM:
517
return pht(
518
'You can not commandeer this revision because you already own '.
519
'it.');
520
}
521
break;
522
}
523
524
return parent::getNoEffectDescription();
525
}
526
527
public function renderAsTextForDoorkeeper(
528
DoorkeeperFeedStoryPublisher $publisher,
529
PhabricatorFeedStory $story,
530
array $xactions) {
531
532
$body = parent::renderAsTextForDoorkeeper($publisher, $story, $xactions);
533
534
$inlines = array();
535
foreach ($xactions as $xaction) {
536
if ($xaction->getTransactionType() == self::TYPE_INLINE) {
537
$inlines[] = $xaction;
538
}
539
}
540
541
// TODO: This is a bit gross, but far less bad than it used to be. It
542
// could be further cleaned up at some point.
543
544
if ($inlines) {
545
$engine = PhabricatorMarkupEngine::newMarkupEngine(array())
546
->setConfig('viewer', new PhabricatorUser())
547
->setMode(PhutilRemarkupEngine::MODE_TEXT);
548
549
$body .= "\n\n";
550
$body .= pht('Inline Comments');
551
$body .= "\n";
552
553
$changeset_ids = array();
554
foreach ($inlines as $inline) {
555
$changeset_ids[] = $inline->getComment()->getChangesetID();
556
}
557
558
$changesets = id(new DifferentialChangeset())->loadAllWhere(
559
'id IN (%Ld)',
560
$changeset_ids);
561
562
foreach ($inlines as $inline) {
563
$comment = $inline->getComment();
564
$changeset = idx($changesets, $comment->getChangesetID());
565
if (!$changeset) {
566
continue;
567
}
568
569
$filename = $changeset->getDisplayFilename();
570
$linenumber = $comment->getLineNumber();
571
$inline_text = $engine->markupText($comment->getContent());
572
$inline_text = rtrim($inline_text);
573
574
$body .= "{$filename}:{$linenumber} {$inline_text}\n";
575
}
576
}
577
578
return $body;
579
}
580
581
public function newWarningForTransactions($object, array $xactions) {
582
$warning = new PhabricatorTransactionWarning();
583
584
switch ($this->getTransactionType()) {
585
case self::TYPE_INLINE:
586
$warning->setTitleText(pht('Warning: Editing Inlines'));
587
$warning->setContinueActionText(pht('Save Inlines and Continue'));
588
589
$count = phutil_count($xactions);
590
591
$body = array();
592
$body[] = pht(
593
'You are currently editing %s inline comment(s) on this '.
594
'revision.',
595
$count);
596
$body[] = pht(
597
'These %s inline comment(s) will be saved and published.',
598
$count);
599
600
$warning->setWarningParagraphs($body);
601
break;
602
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
603
$warning->setTitleText(pht('Warning: Draft Revision'));
604
$warning->setContinueActionText(pht('Tell No One'));
605
606
$body = array();
607
608
$body[] = pht(
609
'This is a draft revision that will not publish any '.
610
'notifications until the author requests review.');
611
612
$body[] = pht('Mentioned or subscribed users will not be notified.');
613
614
$warning->setWarningParagraphs($body);
615
break;
616
}
617
618
return $warning;
619
}
620
621
622
}
623
624