@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Restructure HookEngine to use PushLog records for all operations

Summary:
Ref T4195. This pulls the central logic of HookEngine up one level and makes all the git stuff genrate PushLogs.

In future diffs, everything will generate PushLogs and we can hand those off to Herald.

Test Plan:
Pushed a pile of valid/invalid stuff:

{F89256}

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4195

Differential Revision: https://secure.phabricator.com/D7761

+298 -178
+6
src/applications/diffusion/controller/DiffusionPushLogListController.php
··· 83 83 'href' => '/r'.$callsign.$log->getRefNew(), 84 84 ), 85 85 $log->getRefNewShort()), 86 + 87 + // TODO: Make these human-readable. 88 + $log->getChangeFlags(), 89 + $log->getRejectCode(), 86 90 phabricator_datetime($log->getEpoch(), $viewer), 87 91 ); 88 92 } ··· 98 102 pht('Name'), 99 103 pht('Old'), 100 104 pht('New'), 105 + pht('Flags'), 106 + pht('Code'), 101 107 pht('Date'), 102 108 )) 103 109 ->setColumnClasses(
+266 -178
src/applications/diffusion/engine/DiffusionCommitHookEngine.php
··· 1 1 <?php 2 2 3 3 /** 4 - * @task git Git Hooks 5 - * @task hg Mercurial Hooks 6 - * @task svn Subversion Hooks 4 + * @task config Configuring the Hook Engine 5 + * @task hook Hook Execution 6 + * @task git Git Hooks 7 + * @task hg Mercurial Hooks 8 + * @task svn Subversion Hooks 9 + * @task internal Internals 7 10 */ 8 11 final class DiffusionCommitHookEngine extends Phobject { 9 12 10 13 const ENV_USER = 'PHABRICATOR_USER'; 11 14 const ENV_REMOTE_ADDRESS = 'PHABRICATOR_REMOTE_ADDRESS'; 12 15 const ENV_REMOTE_PROTOCOL = 'PHABRICATOR_REMOTE_PROTOCOL'; 16 + 17 + const EMPTY_HASH = '0000000000000000000000000000000000000000'; 13 18 14 19 private $viewer; 15 20 private $repository; ··· 19 24 private $remoteAddress; 20 25 private $remoteProtocol; 21 26 private $transactionKey; 27 + 28 + 29 + /* -( Config )------------------------------------------------------------- */ 30 + 22 31 23 32 public function setRemoteProtocol($remote_protocol) { 24 33 $this->remoteProtocol = $remote_protocol; ··· 88 97 return $this->viewer; 89 98 } 90 99 100 + 101 + /* -( Hook Execution )----------------------------------------------------- */ 102 + 103 + 91 104 public function execute() { 105 + $ref_updates = $this->findRefUpdates(); 106 + $all_updates = $ref_updates; 107 + 108 + $caught = null; 109 + try { 110 + 111 + try { 112 + $this->rejectDangerousChanges($ref_updates); 113 + } catch (DiffusionCommitHookRejectException $ex) { 114 + // If we're rejecting dangerous changes, flag everything that we've 115 + // seen as rejected so it's clear that none of it was accepted. 116 + foreach ($all_updates as $update) { 117 + $update->setRejectCode( 118 + PhabricatorRepositoryPushLog::REJECT_DANGEROUS); 119 + } 120 + throw $ex; 121 + } 122 + 123 + // TODO: Fire ref herald rules. 124 + 125 + $content_updates = $this->findContentUpdates($ref_updates); 126 + $all_updates = array_merge($all_updates, $content_updates); 127 + 128 + // TODO: Fire content Herald rules. 129 + // TODO: Fire external hooks. 130 + 131 + // If we make it this far, we're accepting these changes. Mark all the 132 + // logs as accepted. 133 + foreach ($all_updates as $update) { 134 + $update->setRejectCode(PhabricatorRepositoryPushLog::REJECT_ACCEPT); 135 + } 136 + } catch (Exception $ex) { 137 + // We'll throw this again in a minute, but we want to save all the logs 138 + // first. 139 + $caught = $ex; 140 + } 141 + 142 + // Save all the logs no matter what the outcome was. 143 + foreach ($all_updates as $update) { 144 + $update->save(); 145 + } 146 + 147 + if ($caught) { 148 + throw $caught; 149 + } 150 + 151 + return 0; 152 + } 153 + 154 + private function findRefUpdates() { 92 155 $type = $this->getRepository()->getVersionControlSystem(); 93 156 switch ($type) { 94 157 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 95 - $err = $this->executeGitHook(); 96 - break; 97 - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 98 - $err = $this->executeSubversionHook(); 99 - break; 158 + return $this->findGitRefUpdates(); 100 159 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 101 - $err = $this->executeMercurialHook(); 102 - break; 160 + return $this->findMercurialRefUpdates(); 161 + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 162 + return $this->findSubversionRefUpdates(); 103 163 default: 104 164 throw new Exception(pht('Unsupported repository type "%s"!', $type)); 105 165 } 106 - 107 - return $err; 108 166 } 109 167 110 - private function newPushLog() { 111 - return PhabricatorRepositoryPushLog::initializeNewLog($this->getViewer()) 112 - ->setRepositoryPHID($this->getRepository()->getPHID()) 113 - ->setEpoch(time()) 114 - ->setRemoteAddress($this->getRemoteAddressForLog()) 115 - ->setRemoteProtocol($this->getRemoteProtocol()) 116 - ->setTransactionKey($this->getTransactionKey()) 117 - ->setRejectCode(PhabricatorRepositoryPushLog::REJECT_ACCEPT) 118 - ->setRejectDetails(null); 119 - } 168 + private function rejectDangerousChanges(array $ref_updates) { 169 + assert_instances_of($ref_updates, 'PhabricatorRepositoryPushLog'); 120 170 171 + $repository = $this->getRepository(); 172 + if ($repository->shouldAllowDangerousChanges()) { 173 + return; 174 + } 121 175 122 - /** 123 - * @task git 124 - */ 125 - private function executeGitHook() { 126 - $updates = $this->parseGitUpdates($this->getStdin()); 176 + $flag_dangerous = PhabricatorRepositoryPushLog::CHANGEFLAG_DANGEROUS; 127 177 128 - $this->rejectGitDangerousChanges($updates); 129 - 130 - // TODO: Do cheap checks: non-ff commits, mutating refs without access, 131 - // creating or deleting things you can't touch. We can do all non-content 132 - // checks here. 133 - 134 - $updates = $this->findGitNewCommits($updates); 178 + foreach ($ref_updates as $ref_update) { 179 + if (!$ref_update->hasChangeFlags($flag_dangerous)) { 180 + // This is not a dangerous change. 181 + continue; 182 + } 135 183 136 - // TODO: Now, do content checks. 184 + // We either have a branch deletion or a non fast-forward branch update. 185 + // Format a message and reject the push. 137 186 138 - // TODO: Generalize this; just getting some data in the database for now. 187 + $message = pht( 188 + "DANGEROUS CHANGE: %s\n". 189 + "Dangerous change protection is enabled for this repository.\n". 190 + "Edit the repository configuration before making dangerous changes.", 191 + $ref_update->getDangerousChangeDescription()); 139 192 140 - $logs = array(); 141 - foreach ($updates as $update) { 142 - $log = $this->newPushLog() 143 - ->setRefType($update['type']) 144 - ->setRefNameHash(PhabricatorHash::digestForIndex($update['ref'])) 145 - ->setRefNameRaw($update['ref']) 146 - ->setRefNameEncoding(phutil_is_utf8($update['ref']) ? 'utf8' : null) 147 - ->setRefOld($update['old']) 148 - ->setRefNew($update['new']) 149 - ->setMergeBase(idx($update, 'merge-base')); 193 + throw new DiffusionCommitHookRejectException($message); 194 + } 195 + } 150 196 151 - $flags = 0; 152 - if ($update['operation'] == 'create') { 153 - $flags = $flags | PhabricatorRepositoryPushLog::CHANGEFLAG_ADD; 154 - } else if ($update['operation'] == 'delete') { 155 - $flags = $flags | PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE; 156 - } else { 157 - // TODO: This isn't correct; these might be APPEND or REWRITE, and 158 - // if they're REWRITE they might be DANGEROUS. Fix this when this 159 - // gets generalized. 160 - $flags = $flags | PhabricatorRepositoryPushLog::CHANGEFLAG_APPEND; 161 - } 197 + private function findContentUpdates(array $ref_updates) { 198 + assert_instances_of($ref_updates, 'PhabricatorRepositoryPushLog'); 162 199 163 - $log->setChangeFlags($flags); 164 - $logs[] = $log; 200 + $type = $this->getRepository()->getVersionControlSystem(); 201 + switch ($type) { 202 + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 203 + return $this->findGitContentUpdates($ref_updates); 204 + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 205 + return $this->findMercurialContentUpdates($ref_updates); 206 + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 207 + return $this->findSubversionContentUpdates($ref_updates); 208 + default: 209 + throw new Exception(pht('Unsupported repository type "%s"!', $type)); 165 210 } 211 + } 166 212 167 - // Now, build logs for all the commits. 168 - // TODO: Generalize this, too. 169 - $commits = array_mergev(ipull($updates, 'commits')); 170 - $commits = array_unique($commits); 171 - foreach ($commits as $commit) { 172 - $log = $this->newPushLog() 173 - ->setRefType(PhabricatorRepositoryPushLog::REFTYPE_COMMIT) 174 - ->setRefNew($commit) 175 - ->setChangeFlags(PhabricatorRepositoryPushLog::CHANGEFLAG_ADD); 176 - $logs[] = $log; 177 - } 178 213 179 - foreach ($logs as $log) { 180 - $log->save(); 181 - } 214 + /* -( Git )---------------------------------------------------------------- */ 182 215 183 - return 0; 184 - } 185 216 217 + private function findGitRefUpdates() { 218 + $ref_updates = array(); 186 219 187 - /** 188 - * @task git 189 - */ 190 - private function parseGitUpdates($stdin) { 191 - $updates = array(); 220 + // First, parse stdin, which lists all the ref changes. The input looks 221 + // like this: 222 + // 223 + // <old hash> <new hash> <ref> 192 224 225 + $stdin = $this->getStdin(); 193 226 $lines = phutil_split_lines($stdin, $retain_endings = false); 194 227 foreach ($lines as $line) { 195 228 $parts = explode(' ', $line, 3); 196 229 if (count($parts) != 3) { 197 230 throw new Exception(pht('Expected "old new ref", got "%s".', $line)); 198 231 } 199 - $update = array( 200 - 'old' => $parts[0], 201 - 'old.short' => substr($parts[0], 0, 8), 202 - 'new' => $parts[1], 203 - 'new.short' => substr($parts[1], 0, 8), 204 - 'ref' => $parts[2], 205 - ); 232 + 233 + $ref_old = $parts[0]; 234 + $ref_new = $parts[1]; 235 + $ref_raw = $parts[2]; 206 236 207 - if (preg_match('(^refs/heads/)', $update['ref'])) { 208 - $update['type'] = 'branch'; 209 - $update['ref.short'] = substr($update['ref'], strlen('refs/heads/')); 210 - } else if (preg_match('(^refs/tags/)', $update['ref'])) { 211 - $update['type'] = 'tag'; 212 - $update['ref.short'] = substr($update['ref'], strlen('refs/tags/')); 237 + if (preg_match('(^refs/heads/)', $ref_raw)) { 238 + $ref_type = PhabricatorRepositoryPushLog::REFTYPE_BRANCH; 239 + } else if (preg_match('(^refs/tags/)', $ref_raw)) { 240 + $ref_type = PhabricatorRepositoryPushLog::REFTYPE_TAG; 213 241 } else { 214 - $update['type'] = 'unknown'; 242 + $ref_type = PhabricatorRepositoryPushLog::REFTYPE_UNKNOWN; 215 243 } 216 244 217 - $updates[] = $update; 245 + $ref_update = $this->newPushLog() 246 + ->setRefType($ref_type) 247 + ->setRefName($ref_raw) 248 + ->setRefOld($ref_old) 249 + ->setRefNew($ref_new); 250 + 251 + $ref_updates[] = $ref_update; 218 252 } 219 253 220 - $updates = $this->findGitMergeBases($updates); 254 + $this->findGitMergeBases($ref_updates); 255 + $this->findGitChangeFlags($ref_updates); 221 256 222 - return $updates; 257 + return $ref_updates; 223 258 } 224 259 225 260 226 - /** 227 - * @task git 228 - */ 229 - private function findGitMergeBases(array $updates) { 230 - $empty = str_repeat('0', 40); 261 + private function findGitMergeBases(array $ref_updates) { 262 + assert_instances_of($ref_updates, 'PhabricatorRepositoryPushLog'); 231 263 232 264 $futures = array(); 233 - foreach ($updates as $key => $update) { 234 - // Updates are in the form: 235 - // 236 - // <old hash> <new hash> <ref> 237 - // 265 + foreach ($ref_updates as $key => $ref_update) { 238 266 // If the old hash is "00000...", the ref is being created (either a new 239 267 // branch, or a new tag). If the new hash is "00000...", the ref is being 240 268 // deleted. If both are nonempty, the ref is being updated. For updates, 241 269 // we'll figure out the `merge-base` of the old and new objects here. This 242 270 // lets us reject non-FF changes cheaply; later, we'll figure out exactly 243 271 // which commits are new. 272 + $ref_old = $ref_update->getRefOld(); 273 + $ref_new = $ref_update->getRefNew(); 244 274 245 - if ($update['old'] == $empty) { 246 - $updates[$key]['operation'] = 'create'; 247 - } else if ($update['new'] == $empty) { 248 - $updates[$key]['operation'] = 'delete'; 249 - } else { 250 - $updates[$key]['operation'] = 'update'; 251 - $futures[$key] = $this->getRepository()->getLocalCommandFuture( 252 - 'merge-base %s %s', 253 - $update['old'], 254 - $update['new']); 275 + if (($ref_old === self::EMPTY_HASH) || 276 + ($ref_new === self::EMPTY_HASH)) { 277 + continue; 255 278 } 279 + 280 + $futures[$key] = $this->getRepository()->getLocalCommandFuture( 281 + 'merge-base %s %s', 282 + $ref_old, 283 + $ref_new); 256 284 } 257 285 258 286 foreach (Futures($futures)->limit(8) as $key => $future) { ··· 264 292 // T4224. Assume this means there are no ancestors. 265 293 266 294 list($err, $stdout) = $future->resolve(); 295 + 267 296 if ($err) { 268 - $updates[$key]['merge-base'] = null; 297 + $merge_base = null; 298 + } else { 299 + $merge_base = rtrim($stdout, "\n"); 300 + } 301 + 302 + $ref_update->setMergeBase($merge_base); 303 + } 304 + 305 + return $ref_updates; 306 + } 307 + 308 + 309 + private function findGitChangeFlags(array $ref_updates) { 310 + assert_instances_of($ref_updates, 'PhabricatorRepositoryPushLog'); 311 + 312 + foreach ($ref_updates as $key => $ref_update) { 313 + $ref_old = $ref_update->getRefOld(); 314 + $ref_new = $ref_update->getRefNew(); 315 + $ref_type = $ref_update->getRefType(); 316 + 317 + $ref_flags = 0; 318 + $dangerous = null; 319 + 320 + if ($ref_old === self::EMPTY_HASH) { 321 + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_ADD; 322 + } else if ($ref_new === self::EMPTY_HASH) { 323 + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE; 324 + if ($ref_type == PhabricatorRepositoryPushLog::REFTYPE_BRANCH) { 325 + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_DANGEROUS; 326 + $dangerous = pht( 327 + "The change you're attempting to push deletes the branch '%s'.", 328 + $ref_update->getRefName()); 329 + } 269 330 } else { 270 - $updates[$key]['merge-base'] = rtrim($stdout, "\n"); 331 + $merge_base = $ref_update->getMergeBase(); 332 + if ($merge_base == $ref_old) { 333 + // This is a fast-forward update to an existing branch. 334 + // These are safe. 335 + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_APPEND; 336 + } else { 337 + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_REWRITE; 338 + 339 + // For now, we don't consider deleting or moving tags to be a 340 + // "dangerous" update. It's way harder to get wrong and should be easy 341 + // to recover from once we have better logging. Only add the dangerous 342 + // flag if this ref is a branch. 343 + 344 + if ($ref_type == PhabricatorRepositoryPushLog::REFTYPE_BRANCH) { 345 + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_DANGEROUS; 346 + 347 + $dangerous = pht( 348 + "DANGEROUS CHANGE: The change you're attempting to push updates ". 349 + "the branch '%s' from '%s' to '%s', but this is not a ". 350 + "fast-forward. Pushes which rewrite published branch history ". 351 + "are dangerous.", 352 + $ref_update->getRefName(), 353 + $ref_update->getRefOldShort(), 354 + $ref_update->getRefNewShort()); 355 + } 356 + } 357 + } 358 + 359 + $ref_update->setChangeFlags($ref_flags); 360 + if ($dangerous !== null) { 361 + $ref_update->attachDangerousChangeDescription($dangerous); 271 362 } 272 363 } 273 364 274 - return $updates; 365 + return $ref_updates; 275 366 } 276 367 277 - private function findGitNewCommits(array $updates) { 368 + 369 + private function findGitContentUpdates(array $ref_updates) { 370 + $flag_delete = PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE; 371 + 278 372 $futures = array(); 279 - foreach ($updates as $key => $update) { 280 - if ($update['operation'] == 'delete') { 373 + foreach ($ref_updates as $key => $ref_update) { 374 + if ($ref_update->hasChangeFlags($flag_delete)) { 281 375 // Deleting a branch or tag can never create any new commits. 282 376 continue; 283 377 } ··· 289 383 $futures[$key] = $this->getRepository()->getLocalCommandFuture( 290 384 'log --format=%s %s --not --all', 291 385 '%H', 292 - $update['new']); 386 + $ref_update->getRefNew()); 293 387 } 294 388 389 + $content_updates = array(); 295 390 foreach (Futures($futures)->limit(8) as $key => $future) { 296 391 list($stdout) = $future->resolvex(); 392 + 393 + if (!strlen(trim($stdout))) { 394 + // This change doesn't have any new commits. One common case of this 395 + // is creating a new tag which points at an existing commit. 396 + continue; 397 + } 398 + 297 399 $commits = phutil_split_lines($stdout, $retain_newlines = false); 298 - $updates[$key]['commits'] = $commits; 400 + 401 + foreach ($commits as $commit) { 402 + $content_updates[$commit] = $this->newPushLog() 403 + ->setRefType(PhabricatorRepositoryPushLog::REFTYPE_COMMIT) 404 + ->setRefNew($commit) 405 + ->setChangeFlags(PhabricatorRepositoryPushLog::CHANGEFLAG_ADD); 406 + } 299 407 } 300 408 301 - return $updates; 409 + return $content_updates; 302 410 } 303 411 304 - private function rejectGitDangerousChanges(array $updates) { 305 - $repository = $this->getRepository(); 306 - if ($repository->shouldAllowDangerousChanges()) { 307 - return; 308 - } 309 412 310 - foreach ($updates as $update) { 311 - if ($update['type'] != 'branch') { 312 - // For now, we don't consider deleting or moving tags to be a 313 - // "dangerous" update. It's way harder to get wrong and should be easy 314 - // to recover from once we have better logging. 315 - continue; 316 - } 413 + /* -( Mercurial )---------------------------------------------------------- */ 317 414 318 - if ($update['operation'] == 'create') { 319 - // Creating a branch is never dangerous. 320 - continue; 321 - } 322 415 323 - if ($update['operation'] == 'update') { 324 - if ($update['old'] == $update['merge-base']) { 325 - // This is a fast-forward update to an existing branch. 326 - // These are safe. 327 - continue; 328 - } 329 - } 416 + private function findMercurialRefUpdates() { 417 + // TODO: Implement. 418 + return array(); 419 + } 330 420 331 - // We either have a branch deletion or a non fast-forward branch update. 332 - // Format a message and reject the push. 421 + private function findMercurialContentUpdates(array $ref_updates) { 422 + // TODO: Implement. 423 + return array(); 424 + } 333 425 334 - if ($update['operation'] == 'delete') { 335 - $message = pht( 336 - "DANGEROUS CHANGE: The change you're attempting to push deletes ". 337 - "the branch '%s'.", 338 - $update['ref.short']); 339 - } else { 340 - $message = pht( 341 - "DANGEROUS CHANGE: The change you're attempting to push updates ". 342 - "the branch '%s' from '%s' to '%s', but this is not a fast-forward. ". 343 - "Pushes which rewrite published branch history are dangerous.", 344 - $update['ref.short'], 345 - $update['old.short'], 346 - $update['new.short']); 347 - } 348 426 349 - $boilerplate = pht( 350 - "Dangerous change protection is enabled for this repository.\n". 351 - "Edit the repository configuration before making dangerous changes."); 427 + /* -( Subversion )--------------------------------------------------------- */ 352 428 353 - $message = $message."\n".$boilerplate; 354 429 355 - throw new DiffusionCommitHookRejectException($message); 356 - } 430 + private function findSubversionRefUpdates() { 431 + // TODO: Implement. 432 + return array(); 357 433 } 358 434 359 - private function executeSubversionHook() { 435 + private function findSubversionContentUpdates(array $ref_updates) { 436 + // TODO: Implement. 437 + return array(); 438 + } 360 439 361 - // TODO: Do useful things here, too. 362 440 363 - return 0; 364 - } 441 + /* -( Internals )---------------------------------------------------------- */ 365 442 366 - private function executeMercurialHook() { 367 443 368 - // TODO: Here, too, useful things should be done. 444 + private function newPushLog() { 445 + // NOTE: By default, we create these with REJECT_BROKEN as the reject 446 + // code. This indicates a broken hook, and covers the case where we 447 + // encounter some unexpected exception and consequently reject the changes. 369 448 370 - return 0; 449 + return PhabricatorRepositoryPushLog::initializeNewLog($this->getViewer()) 450 + ->attachRepository($this->getRepository()) 451 + ->setRepositoryPHID($this->getRepository()->getPHID()) 452 + ->setEpoch(time()) 453 + ->setRemoteAddress($this->getRemoteAddressForLog()) 454 + ->setRemoteProtocol($this->getRemoteProtocol()) 455 + ->setTransactionKey($this->getTransactionKey()) 456 + ->setRejectCode(PhabricatorRepositoryPushLog::REJECT_BROKEN) 457 + ->setRejectDetails(null); 371 458 } 459 + 372 460 }
+26
src/applications/repository/storage/PhabricatorRepositoryPushLog.php
··· 18 18 const REFTYPE_BOOKMARK = 'bookmark'; 19 19 const REFTYPE_SVN = 'svn'; 20 20 const REFTYPE_COMMIT = 'commit'; 21 + const REFTYPE_UNKNOWN = 'unknown'; 21 22 22 23 const CHANGEFLAG_ADD = 1; 23 24 const CHANGEFLAG_DELETE = 2; ··· 29 30 const REJECT_DANGEROUS = 1; 30 31 const REJECT_HERALD = 2; 31 32 const REJECT_EXTERNAL = 3; 33 + const REJECT_BROKEN = 4; 32 34 33 35 protected $repositoryPHID; 34 36 protected $epoch; ··· 47 49 protected $rejectCode; 48 50 protected $rejectDetails; 49 51 52 + private $dangerousChangeDescription = self::ATTACHABLE; 50 53 private $repository = self::ATTACHABLE; 51 54 52 55 public static function initializeNewLog(PhabricatorUser $viewer) { ··· 74 77 return $this->getRefNameRaw(); 75 78 } 76 79 return phutil_utf8ize($this->getRefNameRaw()); 80 + } 81 + 82 + public function setRefName($ref_raw) { 83 + $encoding = phutil_is_utf8($ref_raw) ? 'utf8' : null; 84 + 85 + $this->setRefNameRaw($ref_raw); 86 + $this->setRefNameHash(PhabricatorHash::digestForIndex($ref_raw)); 87 + $this->setRefNameEncoding($encoding); 88 + 89 + return $this; 77 90 } 78 91 79 92 public function getRefOldShort() { ··· 88 101 return $this->getRefNew(); 89 102 } 90 103 return substr($this->getRefNew(), 0, 12); 104 + } 105 + 106 + public function hasChangeFlags($mask) { 107 + return ($this->changeFlags & $mask); 108 + } 109 + 110 + public function attachDangerousChangeDescription($description) { 111 + $this->dangerousChangeDescription = $description; 112 + return $this; 113 + } 114 + 115 + public function getDangerousChangeDescription() { 116 + return $this->assertAttached($this->dangerousChangeDescription); 91 117 } 92 118 93 119