1<?php 2 3/** 4 * Represents a change to an individual path. 5 */ 6final class ArcanistDiffChange extends Phobject { 7 8 protected $metadata = array(); 9 10 protected $oldPath; 11 protected $currentPath; 12 protected $awayPaths = array(); 13 14 protected $oldProperties = array(); 15 protected $newProperties = array(); 16 17 protected $commitHash; 18 protected $type = ArcanistDiffChangeType::TYPE_CHANGE; 19 protected $fileType = ArcanistDiffChangeType::FILE_TEXT; 20 21 protected $hunks = array(); 22 23 private $needsSyntheticGitHunks; 24 25 private $currentFileData; 26 private $originalFileData; 27 28 public function setOriginalFileData($original_file_data) { 29 $this->originalFileData = $original_file_data; 30 return $this; 31 } 32 33 public function getOriginalFileData() { 34 return $this->originalFileData; 35 } 36 37 public function setCurrentFileData($current_file_data) { 38 $this->currentFileData = $current_file_data; 39 return $this; 40 } 41 42 public function getCurrentFileData() { 43 return $this->currentFileData; 44 } 45 46 public function toDictionary() { 47 $hunks = array(); 48 foreach ($this->hunks as $hunk) { 49 $hunks[] = $hunk->toDictionary(); 50 } 51 52 return array( 53 'metadata' => $this->metadata, 54 'oldPath' => $this->oldPath, 55 'currentPath' => $this->currentPath, 56 'awayPaths' => $this->awayPaths, 57 'oldProperties' => $this->oldProperties, 58 'newProperties' => $this->newProperties, 59 'type' => $this->type, 60 'fileType' => $this->fileType, 61 'commitHash' => $this->commitHash, 62 'hunks' => $hunks, 63 ); 64 } 65 66 public static function newFromDictionary(array $dict) { 67 $hunks = array(); 68 foreach ($dict['hunks'] as $hunk) { 69 $hunks[] = ArcanistDiffHunk::newFromDictionary($hunk); 70 } 71 72 $obj = new ArcanistDiffChange(); 73 $obj->metadata = $dict['metadata']; 74 $obj->oldPath = $dict['oldPath']; 75 $obj->currentPath = $dict['currentPath']; 76 // TODO: The backend is shipping down some bogus data, e.g. diff 199453. 77 // Should probably clean this up. 78 $obj->awayPaths = nonempty($dict['awayPaths'], array()); 79 $obj->oldProperties = nonempty($dict['oldProperties'], array()); 80 $obj->newProperties = nonempty($dict['newProperties'], array()); 81 $obj->type = $dict['type']; 82 $obj->fileType = $dict['fileType']; 83 $obj->commitHash = $dict['commitHash']; 84 $obj->hunks = $hunks; 85 86 return $obj; 87 } 88 89 public static function newFromConduit(array $dicts) { 90 $changes = array(); 91 foreach ($dicts as $dict) { 92 $changes[] = self::newFromDictionary($dict); 93 } 94 return $changes; 95 } 96 97 public function getChangedLines($type) { 98 $lines = array(); 99 foreach ($this->hunks as $hunk) { 100 $lines += $hunk->getChangedLines($type); 101 } 102 return $lines; 103 } 104 105 public function getAllMetadata() { 106 return $this->metadata; 107 } 108 109 public function setMetadata($key, $value) { 110 $this->metadata[$key] = $value; 111 return $this; 112 } 113 114 public function getMetadata($key) { 115 return idx($this->metadata, $key); 116 } 117 118 public function setCommitHash($hash) { 119 $this->commitHash = $hash; 120 return $this; 121 } 122 123 public function getCommitHash() { 124 return $this->commitHash; 125 } 126 127 public function addAwayPath($path) { 128 $this->awayPaths[] = $path; 129 return $this; 130 } 131 132 public function getAwayPaths() { 133 return $this->awayPaths; 134 } 135 136 public function setFileType($type) { 137 $this->fileType = $type; 138 return $this; 139 } 140 141 public function getFileType() { 142 return $this->fileType; 143 } 144 145 public function setType($type) { 146 $this->type = $type; 147 return $this; 148 } 149 150 public function getType() { 151 return $this->type; 152 } 153 154 public function setOldProperty($key, $value) { 155 $this->oldProperties[$key] = $value; 156 return $this; 157 } 158 159 public function setNewProperty($key, $value) { 160 $this->newProperties[$key] = $value; 161 return $this; 162 } 163 164 public function getOldProperties() { 165 return $this->oldProperties; 166 } 167 168 public function getNewProperties() { 169 return $this->newProperties; 170 } 171 172 public function setCurrentPath($path) { 173 $this->currentPath = $this->filterPath($path); 174 return $this; 175 } 176 177 public function getCurrentPath() { 178 return $this->currentPath; 179 } 180 181 public function setOldPath($path) { 182 $this->oldPath = $this->filterPath($path); 183 return $this; 184 } 185 186 public function getOldPath() { 187 return $this->oldPath; 188 } 189 190 public function addHunk(ArcanistDiffHunk $hunk) { 191 $this->hunks[] = $hunk; 192 return $this; 193 } 194 195 public function dropHunks() { 196 $this->hunks = array(); 197 return $this; 198 } 199 200 public function getHunks() { 201 return $this->hunks; 202 } 203 204 /** 205 * @return array $old => array($new, ) 206 */ 207 public function buildLineMap() { 208 $line_map = array(); 209 $old = 1; 210 $new = 1; 211 foreach ($this->getHunks() as $hunk) { 212 for ($n = $old; $n < $hunk->getOldOffset(); $n++) { 213 $line_map[$n] = array($n + $new - $old); 214 } 215 $old = $hunk->getOldOffset(); 216 $new = $hunk->getNewOffset(); 217 $olds = array(); 218 $news = array(); 219 $lines = explode("\n", $hunk->getCorpus()); 220 foreach ($lines as $line) { 221 $type = substr($line, 0, 1); 222 if ($type == '-' || $type == ' ') { 223 $olds[] = $old; 224 $old++; 225 } 226 if ($type == '+' || $type == ' ') { 227 $news[] = $new; 228 $new++; 229 } 230 if ($type == ' ' || $type == '') { 231 $line_map += array_fill_keys($olds, $news); 232 $olds = array(); 233 $news = array(); 234 } 235 } 236 } 237 return $line_map; 238 } 239 240 public function convertToBinaryChange(ArcanistRepositoryAPI $api) { 241 242 // Fill in the binary data from the working copy. 243 244 $this->setOriginalFileData( 245 $api->getOriginalFileData( 246 $this->getOldPath())); 247 248 $this->setCurrentFileData( 249 $api->getCurrentFileData( 250 $this->getCurrentPath())); 251 252 $this->hunks = array(); 253 $this->setFileType(ArcanistDiffChangeType::FILE_BINARY); 254 return $this; 255 } 256 257 protected function filterPath($path) { 258 if ($path == '/dev/null') { 259 return null; 260 } 261 return $path; 262 } 263 264 public function renderTextSummary() { 265 266 $type = $this->getType(); 267 $file = $this->getFileType(); 268 269 $char = ArcanistDiffChangeType::getSummaryCharacterForChangeType($type); 270 $attr = ArcanistDiffChangeType::getShortNameForFileType($file); 271 if ($attr) { 272 $attr = '('.$attr.')'; 273 } 274 275 $summary = array(); 276 $summary[] = sprintf( 277 '%s %5.5s %s', 278 $char, 279 $attr, 280 $this->getCurrentPath()); 281 if (ArcanistDiffChangeType::isOldLocationChangeType($type)) { 282 foreach ($this->getAwayPaths() as $path) { 283 $summary[] = ' to: '.$path; 284 } 285 } 286 if (ArcanistDiffChangeType::isNewLocationChangeType($type)) { 287 $summary[] = ' from: '.$this->getOldPath(); 288 } 289 290 return implode("\n", $summary); 291 } 292 293 public function getSymlinkTarget() { 294 if ($this->getFileType() != ArcanistDiffChangeType::FILE_SYMLINK) { 295 throw new Exception(pht('Not a symlink!')); 296 } 297 $hunks = $this->getHunks(); 298 $hunk = reset($hunks); 299 $corpus = $hunk->getCorpus(); 300 $match = null; 301 if (!preg_match('/^\+(?:link )?(.*)$/m', $corpus, $match)) { 302 throw new Exception(pht('Failed to extract link target!')); 303 } 304 return trim($match[1]); 305 } 306 307 public function setNeedsSyntheticGitHunks($needs_synthetic_git_hunks) { 308 $this->needsSyntheticGitHunks = $needs_synthetic_git_hunks; 309 return $this; 310 } 311 312 public function getNeedsSyntheticGitHunks() { 313 return $this->needsSyntheticGitHunks; 314 } 315 316} 317