Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php
12242 views
1
<?php
2
3
final class PhabricatorInlineCommentAdjustmentEngine
4
extends Phobject {
5
6
private $viewer;
7
private $inlines;
8
private $revision;
9
private $oldChangesets;
10
private $newChangesets;
11
12
public function setViewer(PhabricatorUser $viewer) {
13
$this->viewer = $viewer;
14
return $this;
15
}
16
17
public function getViewer() {
18
return $this->viewer;
19
}
20
21
public function setInlines(array $inlines) {
22
assert_instances_of($inlines, 'DifferentialInlineComment');
23
$this->inlines = $inlines;
24
return $this;
25
}
26
27
public function getInlines() {
28
return $this->inlines;
29
}
30
31
public function setOldChangesets(array $old_changesets) {
32
assert_instances_of($old_changesets, 'DifferentialChangeset');
33
$this->oldChangesets = $old_changesets;
34
return $this;
35
}
36
37
public function getOldChangesets() {
38
return $this->oldChangesets;
39
}
40
41
public function setNewChangesets(array $new_changesets) {
42
assert_instances_of($new_changesets, 'DifferentialChangeset');
43
$this->newChangesets = $new_changesets;
44
return $this;
45
}
46
47
public function getNewChangesets() {
48
return $this->newChangesets;
49
}
50
51
public function setRevision(DifferentialRevision $revision) {
52
$this->revision = $revision;
53
return $this;
54
}
55
56
public function getRevision() {
57
return $this->revision;
58
}
59
60
public function execute() {
61
$viewer = $this->getViewer();
62
$inlines = $this->getInlines();
63
$revision = $this->getRevision();
64
$old = $this->getOldChangesets();
65
$new = $this->getNewChangesets();
66
67
$no_ghosts = $viewer->compareUserSetting(
68
PhabricatorOlderInlinesSetting::SETTINGKEY,
69
PhabricatorOlderInlinesSetting::VALUE_GHOST_INLINES_DISABLED);
70
if ($no_ghosts) {
71
return $inlines;
72
}
73
74
$all = array_merge($old, $new);
75
76
$changeset_ids = mpull($inlines, 'getChangesetID');
77
$changeset_ids = array_unique($changeset_ids);
78
79
$all_map = mpull($all, null, 'getID');
80
81
// We already have at least some changesets, and we might not need to do
82
// any more data fetching. Remove everything we already have so we can
83
// tell if we need new stuff.
84
foreach ($changeset_ids as $key => $id) {
85
if (isset($all_map[$id])) {
86
unset($changeset_ids[$key]);
87
}
88
}
89
90
if ($changeset_ids) {
91
$changesets = id(new DifferentialChangesetQuery())
92
->setViewer($viewer)
93
->withIDs($changeset_ids)
94
->execute();
95
$changesets = mpull($changesets, null, 'getID');
96
} else {
97
$changesets = array();
98
}
99
$changesets += $all_map;
100
101
$id_map = array();
102
foreach ($all as $changeset) {
103
$id_map[$changeset->getID()] = $changeset->getID();
104
}
105
106
// Generate filename maps for older and newer comments. If we're bringing
107
// an older comment forward in a diff-of-diffs, we want to put it on the
108
// left side of the screen, not the right side. Both sides are "new" files
109
// with the same name, so they're both appropriate targets, but the left
110
// is a better target conceptually for users because it's more consistent
111
// with the rest of the UI, which shows old information on the left and
112
// new information on the right.
113
$move_here = DifferentialChangeType::TYPE_MOVE_HERE;
114
115
$name_map_old = array();
116
$name_map_new = array();
117
$move_map = array();
118
foreach ($all as $changeset) {
119
$changeset_id = $changeset->getID();
120
121
$filenames = array();
122
$filenames[] = $changeset->getFilename();
123
124
// If this is the target of a move, also map comments on the old filename
125
// to this changeset.
126
if ($changeset->getChangeType() == $move_here) {
127
$old_file = $changeset->getOldFile();
128
$filenames[] = $old_file;
129
$move_map[$changeset_id][$old_file] = true;
130
}
131
132
foreach ($filenames as $filename) {
133
// We update the old map only if we don't already have an entry (oldest
134
// changeset persists).
135
if (empty($name_map_old[$filename])) {
136
$name_map_old[$filename] = $changeset_id;
137
}
138
139
// We always update the new map (newest changeset overwrites).
140
$name_map_new[$changeset->getFilename()] = $changeset_id;
141
}
142
}
143
144
$new_id_map = mpull($new, null, 'getID');
145
146
$results = array();
147
foreach ($inlines as $inline) {
148
$changeset_id = $inline->getChangesetID();
149
if (isset($id_map[$changeset_id])) {
150
// This inline is legitimately on one of the current changesets, so
151
// we can include it in the result set unmodified.
152
$results[] = $inline;
153
continue;
154
}
155
156
$changeset = idx($changesets, $changeset_id);
157
if (!$changeset) {
158
// Just discard this inline, as it has bogus data.
159
continue;
160
}
161
162
$target_id = null;
163
164
if (isset($new_id_map[$changeset_id])) {
165
$name_map = $name_map_new;
166
$is_new = true;
167
} else {
168
$name_map = $name_map_old;
169
$is_new = false;
170
}
171
172
$filename = $changeset->getFilename();
173
if (isset($name_map[$filename])) {
174
// This changeset is on a file with the same name as the current
175
// changeset, so we're going to port it forward or backward.
176
$target_id = $name_map[$filename];
177
178
$is_move = isset($move_map[$target_id][$filename]);
179
if ($is_new) {
180
if ($is_move) {
181
$reason = pht(
182
'This comment was made on a file with the same name as the '.
183
'file this file was moved from, but in a newer diff.');
184
} else {
185
$reason = pht(
186
'This comment was made on a file with the same name, but '.
187
'in a newer diff.');
188
}
189
} else {
190
if ($is_move) {
191
$reason = pht(
192
'This comment was made on a file with the same name as the '.
193
'file this file was moved from, but in an older diff.');
194
} else {
195
$reason = pht(
196
'This comment was made on a file with the same name, but '.
197
'in an older diff.');
198
}
199
}
200
}
201
202
203
// If we didn't find a target and this change is the target of a move,
204
// look for a match against the old filename.
205
if (!$target_id) {
206
if ($changeset->getChangeType() == $move_here) {
207
$filename = $changeset->getOldFile();
208
if (isset($name_map[$filename])) {
209
$target_id = $name_map[$filename];
210
if ($is_new) {
211
$reason = pht(
212
'This comment was made on a file which this file was moved '.
213
'to, but in a newer diff.');
214
} else {
215
$reason = pht(
216
'This comment was made on a file which this file was moved '.
217
'to, but in an older diff.');
218
}
219
}
220
}
221
}
222
223
224
// If we found a changeset to port this comment to, bring it forward
225
// or backward and mark it.
226
if ($target_id) {
227
$diff_id = $changeset->getDiffID();
228
$inline_id = $inline->getID();
229
$revision_id = $revision->getID();
230
$href = "/D{$revision_id}?id={$diff_id}#inline-{$inline_id}";
231
232
$inline
233
->makeEphemeral(true)
234
->setChangesetID($target_id)
235
->setIsGhost(
236
array(
237
'new' => $is_new,
238
'reason' => $reason,
239
'href' => $href,
240
'originalID' => $changeset->getID(),
241
));
242
243
$results[] = $inline;
244
}
245
}
246
247
// Filter out the inlines we ported forward which won't be visible because
248
// they appear on the wrong side of a file.
249
$keep_map = array();
250
foreach ($old as $changeset) {
251
$keep_map[$changeset->getID()][0] = true;
252
}
253
foreach ($new as $changeset) {
254
$keep_map[$changeset->getID()][1] = true;
255
}
256
257
foreach ($results as $key => $inline) {
258
$is_new = (int)$inline->getIsNewFile();
259
$changeset_id = $inline->getChangesetID();
260
if (!isset($keep_map[$changeset_id][$is_new])) {
261
unset($results[$key]);
262
continue;
263
}
264
}
265
266
// Adjust inline line numbers to account for content changes across
267
// updates and rebases.
268
$plan = array();
269
$need = array();
270
foreach ($results as $inline) {
271
$ghost = $inline->getIsGhost();
272
if (!$ghost) {
273
// If this isn't a "ghost" inline, ignore it.
274
continue;
275
}
276
277
$src_id = $ghost['originalID'];
278
$dst_id = $inline->getChangesetID();
279
280
$xforms = array();
281
282
// If the comment is on the right, transform it through the inverse map
283
// back to the left.
284
if ($inline->getIsNewFile()) {
285
$xforms[] = array($src_id, $src_id, true);
286
}
287
288
// Transform it across rebases.
289
$xforms[] = array($src_id, $dst_id, false);
290
291
// If the comment is on the right, transform it back onto the right.
292
if ($inline->getIsNewFile()) {
293
$xforms[] = array($dst_id, $dst_id, false);
294
}
295
296
$key = array();
297
foreach ($xforms as $xform) {
298
list($u, $v, $inverse) = $xform;
299
300
$short = $u.'/'.$v;
301
$need[$short] = array($u, $v);
302
303
$part = $u.($inverse ? '<' : '>').$v;
304
$key[] = $part;
305
}
306
$key = implode(',', $key);
307
308
if (empty($plan[$key])) {
309
$plan[$key] = array(
310
'xforms' => $xforms,
311
'inlines' => array(),
312
);
313
}
314
315
$plan[$key]['inlines'][] = $inline;
316
}
317
318
if ($need) {
319
$maps = DifferentialLineAdjustmentMap::loadMaps($need);
320
} else {
321
$maps = array();
322
}
323
324
foreach ($plan as $step) {
325
$xforms = $step['xforms'];
326
327
$chain = null;
328
foreach ($xforms as $xform) {
329
list($u, $v, $inverse) = $xform;
330
$map = idx(idx($maps, $u, array()), $v);
331
if (!$map) {
332
continue 2;
333
}
334
335
if ($inverse) {
336
$map = DifferentialLineAdjustmentMap::newInverseMap($map);
337
} else {
338
$map = clone $map;
339
}
340
341
if ($chain) {
342
$chain->addMapToChain($map);
343
} else {
344
$chain = $map;
345
}
346
}
347
348
foreach ($step['inlines'] as $inline) {
349
$head_line = $inline->getLineNumber();
350
$tail_line = ($head_line + $inline->getLineLength());
351
352
$head_info = $chain->mapLine($head_line, false);
353
$tail_info = $chain->mapLine($tail_line, true);
354
355
list($head_deleted, $head_offset, $head_line) = $head_info;
356
list($tail_deleted, $tail_offset, $tail_line) = $tail_info;
357
358
if ($head_offset !== false) {
359
$inline->setLineNumber($head_line + $head_offset);
360
} else {
361
$inline->setLineNumber($head_line);
362
$inline->setLineLength($tail_line - $head_line);
363
}
364
}
365
}
366
367
return $results;
368
}
369
370
}
371
372