Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/storage/DifferentialChangeset.php
12256 views
1
<?php
2
3
final class DifferentialChangeset
4
extends DifferentialDAO
5
implements
6
PhabricatorPolicyInterface,
7
PhabricatorDestructibleInterface,
8
PhabricatorConduitResultInterface {
9
10
protected $diffID;
11
protected $oldFile;
12
protected $filename;
13
protected $awayPaths;
14
protected $changeType;
15
protected $fileType;
16
protected $metadata = array();
17
protected $oldProperties;
18
protected $newProperties;
19
protected $addLines;
20
protected $delLines;
21
22
private $unsavedHunks = array();
23
private $hunks = self::ATTACHABLE;
24
private $diff = self::ATTACHABLE;
25
26
private $authorityPackages;
27
private $changesetPackages;
28
29
private $newFileObject = self::ATTACHABLE;
30
private $oldFileObject = self::ATTACHABLE;
31
32
private $hasOldState;
33
private $hasNewState;
34
private $oldStateMetadata;
35
private $newStateMetadata;
36
private $oldFileType;
37
private $newFileType;
38
39
const TABLE_CACHE = 'differential_changeset_parse_cache';
40
41
const METADATA_TRUSTED_ATTRIBUTES = 'attributes.trusted';
42
const METADATA_UNTRUSTED_ATTRIBUTES = 'attributes.untrusted';
43
const METADATA_EFFECT_HASH = 'hash.effect';
44
45
const ATTRIBUTE_GENERATED = 'generated';
46
47
protected function getConfiguration() {
48
return array(
49
self::CONFIG_AUX_PHID => true,
50
self::CONFIG_SERIALIZATION => array(
51
'metadata' => self::SERIALIZATION_JSON,
52
'oldProperties' => self::SERIALIZATION_JSON,
53
'newProperties' => self::SERIALIZATION_JSON,
54
'awayPaths' => self::SERIALIZATION_JSON,
55
),
56
self::CONFIG_COLUMN_SCHEMA => array(
57
'oldFile' => 'bytes?',
58
'filename' => 'bytes',
59
'changeType' => 'uint32',
60
'fileType' => 'uint32',
61
'addLines' => 'uint32',
62
'delLines' => 'uint32',
63
64
// T6203/NULLABILITY
65
// These should all be non-nullable, and store reasonable default
66
// JSON values if empty.
67
'awayPaths' => 'text?',
68
'metadata' => 'text?',
69
'oldProperties' => 'text?',
70
'newProperties' => 'text?',
71
),
72
self::CONFIG_KEY_SCHEMA => array(
73
'diffID' => array(
74
'columns' => array('diffID'),
75
),
76
),
77
) + parent::getConfiguration();
78
}
79
80
public function getPHIDType() {
81
return DifferentialChangesetPHIDType::TYPECONST;
82
}
83
84
public function getAffectedLineCount() {
85
return $this->getAddLines() + $this->getDelLines();
86
}
87
88
public function attachHunks(array $hunks) {
89
assert_instances_of($hunks, 'DifferentialHunk');
90
$this->hunks = $hunks;
91
return $this;
92
}
93
94
public function getHunks() {
95
return $this->assertAttached($this->hunks);
96
}
97
98
public function getDisplayFilename() {
99
$name = $this->getFilename();
100
if ($this->getFileType() == DifferentialChangeType::FILE_DIRECTORY) {
101
$name .= '/';
102
}
103
return $name;
104
}
105
106
public function getOwnersFilename() {
107
// TODO: For Subversion, we should adjust these paths to be relative to
108
// the repository root where possible.
109
110
$path = $this->getFilename();
111
112
if (!isset($path[0])) {
113
return '/';
114
}
115
116
if ($path[0] != '/') {
117
$path = '/'.$path;
118
}
119
120
return $path;
121
}
122
123
public function addUnsavedHunk(DifferentialHunk $hunk) {
124
if ($this->hunks === self::ATTACHABLE) {
125
$this->hunks = array();
126
}
127
$this->hunks[] = $hunk;
128
$this->unsavedHunks[] = $hunk;
129
return $this;
130
}
131
132
public function setAuthorityPackages(array $authority_packages) {
133
$this->authorityPackages = mpull($authority_packages, null, 'getPHID');
134
return $this;
135
}
136
137
public function getAuthorityPackages() {
138
return $this->authorityPackages;
139
}
140
141
public function setChangesetPackages($changeset_packages) {
142
$this->changesetPackages = mpull($changeset_packages, null, 'getPHID');
143
return $this;
144
}
145
146
public function getChangesetPackages() {
147
return $this->changesetPackages;
148
}
149
150
public function setHasOldState($has_old_state) {
151
$this->hasOldState = $has_old_state;
152
return $this;
153
}
154
155
public function setHasNewState($has_new_state) {
156
$this->hasNewState = $has_new_state;
157
return $this;
158
}
159
160
public function hasOldState() {
161
if ($this->hasOldState !== null) {
162
return $this->hasOldState;
163
}
164
165
$change_type = $this->getChangeType();
166
return !DifferentialChangeType::isCreateChangeType($change_type);
167
}
168
169
public function hasNewState() {
170
if ($this->hasNewState !== null) {
171
return $this->hasNewState;
172
}
173
174
$change_type = $this->getChangeType();
175
return !DifferentialChangeType::isDeleteChangeType($change_type);
176
}
177
178
public function save() {
179
$this->openTransaction();
180
$ret = parent::save();
181
foreach ($this->unsavedHunks as $hunk) {
182
$hunk->setChangesetID($this->getID());
183
$hunk->save();
184
}
185
$this->saveTransaction();
186
return $ret;
187
}
188
189
public function delete() {
190
$this->openTransaction();
191
192
$hunks = id(new DifferentialHunk())->loadAllWhere(
193
'changesetID = %d',
194
$this->getID());
195
foreach ($hunks as $hunk) {
196
$hunk->delete();
197
}
198
199
$this->unsavedHunks = array();
200
201
queryfx(
202
$this->establishConnection('w'),
203
'DELETE FROM %T WHERE id = %d',
204
self::TABLE_CACHE,
205
$this->getID());
206
207
$ret = parent::delete();
208
$this->saveTransaction();
209
return $ret;
210
}
211
212
/**
213
* Test if this changeset and some other changeset put the affected file in
214
* the same state.
215
*
216
* @param DifferentialChangeset Changeset to compare against.
217
* @return bool True if the two changesets have the same effect.
218
*/
219
public function hasSameEffectAs(DifferentialChangeset $other) {
220
if ($this->getFilename() !== $other->getFilename()) {
221
return false;
222
}
223
224
$hash_key = self::METADATA_EFFECT_HASH;
225
226
$u_hash = $this->getChangesetMetadata($hash_key);
227
if ($u_hash === null) {
228
return false;
229
}
230
231
$v_hash = $other->getChangesetMetadata($hash_key);
232
if ($v_hash === null) {
233
return false;
234
}
235
236
if ($u_hash !== $v_hash) {
237
return false;
238
}
239
240
// Make sure the final states for the file properties (like the "+x"
241
// executable bit) match one another.
242
$u_props = $this->getNewProperties();
243
$v_props = $other->getNewProperties();
244
ksort($u_props);
245
ksort($v_props);
246
247
if ($u_props !== $v_props) {
248
return false;
249
}
250
251
return true;
252
}
253
254
public function getSortKey() {
255
$sort_key = $this->getFilename();
256
// Sort files with ".h" in them first, so headers (.h, .hpp) come before
257
// implementations (.c, .cpp, .cs).
258
$sort_key = str_replace('.h', '.!h', $sort_key);
259
return $sort_key;
260
}
261
262
public function makeNewFile() {
263
$file = mpull($this->getHunks(), 'makeNewFile');
264
return implode('', $file);
265
}
266
267
public function makeOldFile() {
268
$file = mpull($this->getHunks(), 'makeOldFile');
269
return implode('', $file);
270
}
271
272
public function makeChangesWithContext($num_lines = 3) {
273
$with_context = array();
274
foreach ($this->getHunks() as $hunk) {
275
$context = array();
276
$changes = explode("\n", $hunk->getChanges());
277
foreach ($changes as $l => $line) {
278
$type = substr($line, 0, 1);
279
if ($type == '+' || $type == '-') {
280
$context += array_fill($l - $num_lines, 2 * $num_lines + 1, true);
281
}
282
}
283
$with_context[] = array_intersect_key($changes, $context);
284
}
285
return array_mergev($with_context);
286
}
287
288
public function getAnchorName() {
289
return 'change-'.PhabricatorHash::digestForAnchor($this->getFilename());
290
}
291
292
public function getAbsoluteRepositoryPath(
293
PhabricatorRepository $repository = null,
294
DifferentialDiff $diff = null) {
295
296
$base = '/';
297
if ($diff && $diff->getSourceControlPath()) {
298
$base = id(new PhutilURI($diff->getSourceControlPath()))->getPath();
299
}
300
301
$path = $this->getFilename();
302
$path = rtrim($base, '/').'/'.ltrim($path, '/');
303
304
$svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
305
if ($repository && $repository->getVersionControlSystem() == $svn) {
306
$prefix = $repository->getDetail('remote-uri');
307
$prefix = id(new PhutilURI($prefix))->getPath();
308
if (!strncmp($path, $prefix, strlen($prefix))) {
309
$path = substr($path, strlen($prefix));
310
}
311
$path = '/'.ltrim($path, '/');
312
}
313
314
return $path;
315
}
316
317
public function attachDiff(DifferentialDiff $diff) {
318
$this->diff = $diff;
319
return $this;
320
}
321
322
public function getDiff() {
323
return $this->assertAttached($this->diff);
324
}
325
326
public function getOldStatePathVector() {
327
$path = $this->getOldFile();
328
if ($path === null || !strlen($path)) {
329
$path = $this->getFilename();
330
}
331
332
$path = trim($path, '/');
333
$path = explode('/', $path);
334
335
return $path;
336
}
337
338
public function getNewStatePathVector() {
339
if (!$this->hasNewState()) {
340
return null;
341
}
342
343
$path = $this->getFilename();
344
$path = trim($path, '/');
345
$path = explode('/', $path);
346
347
return $path;
348
}
349
350
public function newFileTreeIcon() {
351
$icon = $this->getPathIconIcon();
352
$color = $this->getPathIconColor();
353
354
return id(new PHUIIconView())
355
->setIcon("{$icon} {$color}");
356
}
357
358
public function getIsOwnedChangeset() {
359
$authority_packages = $this->getAuthorityPackages();
360
$changeset_packages = $this->getChangesetPackages();
361
362
if (!$authority_packages || !$changeset_packages) {
363
return false;
364
}
365
366
return (bool)array_intersect_key($authority_packages, $changeset_packages);
367
}
368
369
public function getIsLowImportanceChangeset() {
370
if (!$this->hasNewState()) {
371
return true;
372
}
373
374
if ($this->isGeneratedChangeset()) {
375
return true;
376
}
377
378
return false;
379
}
380
381
public function getPathIconIcon() {
382
return idx($this->getPathIconDetails(), 'icon');
383
}
384
385
public function getPathIconColor() {
386
return idx($this->getPathIconDetails(), 'color');
387
}
388
389
private function getPathIconDetails() {
390
$change_icons = array(
391
DifferentialChangeType::TYPE_DELETE => array(
392
'icon' => 'fa-times',
393
'color' => 'delete-color',
394
),
395
DifferentialChangeType::TYPE_ADD => array(
396
'icon' => 'fa-plus',
397
'color' => 'create-color',
398
),
399
DifferentialChangeType::TYPE_MOVE_AWAY => array(
400
'icon' => 'fa-circle-o',
401
'color' => 'grey',
402
),
403
DifferentialChangeType::TYPE_MULTICOPY => array(
404
'icon' => 'fa-circle-o',
405
'color' => 'grey',
406
),
407
DifferentialChangeType::TYPE_MOVE_HERE => array(
408
'icon' => 'fa-plus-circle',
409
'color' => 'create-color',
410
),
411
DifferentialChangeType::TYPE_COPY_HERE => array(
412
'icon' => 'fa-plus-circle',
413
'color' => 'create-color',
414
),
415
);
416
417
$change_type = $this->getChangeType();
418
if (isset($change_icons[$change_type])) {
419
return $change_icons[$change_type];
420
}
421
422
if ($this->isGeneratedChangeset()) {
423
return array(
424
'icon' => 'fa-cogs',
425
'color' => 'grey',
426
);
427
}
428
429
$file_type = $this->getFileType();
430
$icon = DifferentialChangeType::getIconForFileType($file_type);
431
432
return array(
433
'icon' => $icon,
434
'color' => 'bluetext',
435
);
436
}
437
438
public function setChangesetMetadata($key, $value) {
439
if (!is_array($this->metadata)) {
440
$this->metadata = array();
441
}
442
443
$this->metadata[$key] = $value;
444
445
return $this;
446
}
447
448
public function getChangesetMetadata($key, $default = null) {
449
if (!is_array($this->metadata)) {
450
return $default;
451
}
452
453
return idx($this->metadata, $key, $default);
454
}
455
456
private function setInternalChangesetAttribute($trusted, $key, $value) {
457
if ($trusted) {
458
$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;
459
} else {
460
$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;
461
}
462
463
$attributes = $this->getChangesetMetadata($meta_key, array());
464
$attributes[$key] = $value;
465
$this->setChangesetMetadata($meta_key, $attributes);
466
467
return $this;
468
}
469
470
private function getInternalChangesetAttributes($trusted) {
471
if ($trusted) {
472
$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;
473
} else {
474
$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;
475
}
476
477
return $this->getChangesetMetadata($meta_key, array());
478
}
479
480
public function setTrustedChangesetAttribute($key, $value) {
481
return $this->setInternalChangesetAttribute(true, $key, $value);
482
}
483
484
public function getTrustedChangesetAttributes() {
485
return $this->getInternalChangesetAttributes(true);
486
}
487
488
public function getTrustedChangesetAttribute($key, $default = null) {
489
$map = $this->getTrustedChangesetAttributes();
490
return idx($map, $key, $default);
491
}
492
493
public function setUntrustedChangesetAttribute($key, $value) {
494
return $this->setInternalChangesetAttribute(false, $key, $value);
495
}
496
497
public function getUntrustedChangesetAttributes() {
498
return $this->getInternalChangesetAttributes(false);
499
}
500
501
public function getUntrustedChangesetAttribute($key, $default = null) {
502
$map = $this->getUntrustedChangesetAttributes();
503
return idx($map, $key, $default);
504
}
505
506
public function getChangesetAttributes() {
507
// Prefer trusted values over untrusted values when both exist.
508
return
509
$this->getTrustedChangesetAttributes() +
510
$this->getUntrustedChangesetAttributes();
511
}
512
513
public function getChangesetAttribute($key, $default = null) {
514
$map = $this->getChangesetAttributes();
515
return idx($map, $key, $default);
516
}
517
518
public function isGeneratedChangeset() {
519
return $this->getChangesetAttribute(self::ATTRIBUTE_GENERATED);
520
}
521
522
public function getNewFileObjectPHID() {
523
$metadata = $this->getMetadata();
524
return idx($metadata, 'new:binary-phid');
525
}
526
527
public function getOldFileObjectPHID() {
528
$metadata = $this->getMetadata();
529
return idx($metadata, 'old:binary-phid');
530
}
531
532
public function attachNewFileObject(PhabricatorFile $file) {
533
$this->newFileObject = $file;
534
return $this;
535
}
536
537
public function getNewFileObject() {
538
return $this->assertAttached($this->newFileObject);
539
}
540
541
public function attachOldFileObject(PhabricatorFile $file) {
542
$this->oldFileObject = $file;
543
return $this;
544
}
545
546
public function getOldFileObject() {
547
return $this->assertAttached($this->oldFileObject);
548
}
549
550
public function newComparisonChangeset(
551
DifferentialChangeset $against = null) {
552
553
$left = $this;
554
$right = $against;
555
556
$left_data = $left->makeNewFile();
557
$left_properties = $left->getNewProperties();
558
$left_metadata = $left->getNewStateMetadata();
559
$left_state = $left->hasNewState();
560
$shared_metadata = $left->getMetadata();
561
$left_type = $left->getNewFileType();
562
if ($right) {
563
$right_data = $right->makeNewFile();
564
$right_properties = $right->getNewProperties();
565
$right_metadata = $right->getNewStateMetadata();
566
$right_state = $right->hasNewState();
567
$shared_metadata = $right->getMetadata();
568
$right_type = $right->getNewFileType();
569
570
$file_name = $right->getFilename();
571
} else {
572
$right_data = $left->makeOldFile();
573
$right_properties = $left->getOldProperties();
574
$right_metadata = $left->getOldStateMetadata();
575
$right_state = $left->hasOldState();
576
$right_type = $left->getOldFileType();
577
578
$file_name = $left->getFilename();
579
}
580
581
$engine = new PhabricatorDifferenceEngine();
582
583
$synthetic = $engine->generateChangesetFromFileContent(
584
$left_data,
585
$right_data);
586
587
$comparison = id(new self())
588
->makeEphemeral(true)
589
->attachDiff($left->getDiff())
590
->setOldFile($left->getFilename())
591
->setFilename($file_name);
592
593
// TODO: Change type?
594
// TODO: Away paths?
595
// TODO: View state key?
596
597
$comparison->attachHunks($synthetic->getHunks());
598
599
$comparison->setOldProperties($left_properties);
600
$comparison->setNewProperties($right_properties);
601
602
$comparison
603
->setOldStateMetadata($left_metadata)
604
->setNewStateMetadata($right_metadata)
605
->setHasOldState($left_state)
606
->setHasNewState($right_state)
607
->setOldFileType($left_type)
608
->setNewFileType($right_type);
609
610
// NOTE: Some metadata is not stored statefully, like the "generated"
611
// flag. For now, use the rightmost "new state" metadata to fill in these
612
// values.
613
614
$metadata = $comparison->getMetadata();
615
$metadata = $metadata + $shared_metadata;
616
$comparison->setMetadata($metadata);
617
618
return $comparison;
619
}
620
621
622
public function setNewFileType($new_file_type) {
623
$this->newFileType = $new_file_type;
624
return $this;
625
}
626
627
public function getNewFileType() {
628
if ($this->newFileType !== null) {
629
return $this->newFileType;
630
}
631
632
return $this->getFiletype();
633
}
634
635
public function setOldFileType($old_file_type) {
636
$this->oldFileType = $old_file_type;
637
return $this;
638
}
639
640
public function getOldFileType() {
641
if ($this->oldFileType !== null) {
642
return $this->oldFileType;
643
}
644
645
return $this->getFileType();
646
}
647
648
public function hasSourceTextBody() {
649
$type_map = array(
650
DifferentialChangeType::FILE_TEXT => true,
651
DifferentialChangeType::FILE_SYMLINK => true,
652
);
653
654
$old_body = isset($type_map[$this->getOldFileType()]);
655
$new_body = isset($type_map[$this->getNewFileType()]);
656
657
return ($old_body || $new_body);
658
}
659
660
public function getNewStateMetadata() {
661
return $this->getMetadataWithPrefix('new:');
662
}
663
664
public function setNewStateMetadata(array $metadata) {
665
return $this->setMetadataWithPrefix($metadata, 'new:');
666
}
667
668
public function getOldStateMetadata() {
669
return $this->getMetadataWithPrefix('old:');
670
}
671
672
public function setOldStateMetadata(array $metadata) {
673
return $this->setMetadataWithPrefix($metadata, 'old:');
674
}
675
676
private function getMetadataWithPrefix($prefix) {
677
$length = strlen($prefix);
678
679
$result = array();
680
foreach ($this->getMetadata() as $key => $value) {
681
if (strncmp($key, $prefix, $length)) {
682
continue;
683
}
684
685
$key = substr($key, $length);
686
$result[$key] = $value;
687
}
688
689
return $result;
690
}
691
692
private function setMetadataWithPrefix(array $metadata, $prefix) {
693
foreach ($metadata as $key => $value) {
694
$key = $prefix.$key;
695
$this->metadata[$key] = $value;
696
}
697
698
return $this;
699
}
700
701
702
/* -( PhabricatorPolicyInterface )----------------------------------------- */
703
704
705
public function getCapabilities() {
706
return array(
707
PhabricatorPolicyCapability::CAN_VIEW,
708
);
709
}
710
711
public function getPolicy($capability) {
712
return $this->getDiff()->getPolicy($capability);
713
}
714
715
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
716
return $this->getDiff()->hasAutomaticCapability($capability, $viewer);
717
}
718
719
720
/* -( PhabricatorDestructibleInterface )----------------------------------- */
721
722
723
public function destroyObjectPermanently(
724
PhabricatorDestructionEngine $engine) {
725
$this->openTransaction();
726
727
$hunks = id(new DifferentialHunk())->loadAllWhere(
728
'changesetID = %d',
729
$this->getID());
730
foreach ($hunks as $hunk) {
731
$engine->destroyObject($hunk);
732
}
733
734
$this->delete();
735
736
$this->saveTransaction();
737
}
738
739
/* -( PhabricatorConduitResultInterface )---------------------------------- */
740
741
public function getFieldSpecificationsForConduit() {
742
return array(
743
id(new PhabricatorConduitSearchFieldSpecification())
744
->setKey('diffPHID')
745
->setType('phid')
746
->setDescription(pht('The diff the changeset is attached to.')),
747
);
748
}
749
750
public function getFieldValuesForConduit() {
751
$diff = $this->getDiff();
752
753
$repository = null;
754
if ($diff) {
755
$revision = $diff->getRevision();
756
if ($revision) {
757
$repository = $revision->getRepository();
758
}
759
}
760
761
$absolute_path = $this->getAbsoluteRepositoryPath($repository, $diff);
762
if (strlen($absolute_path)) {
763
$absolute_path = base64_encode($absolute_path);
764
} else {
765
$absolute_path = null;
766
}
767
768
$display_path = $this->getDisplayFilename();
769
770
return array(
771
'diffPHID' => $diff->getPHID(),
772
'path' => array(
773
'displayPath' => $display_path,
774
'absolutePath.base64' => $absolute_path,
775
),
776
);
777
}
778
779
public function getConduitSearchAttachments() {
780
return array();
781
}
782
783
784
}
785
786