@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
at recaptime-dev/main 182 lines 4.3 kB view raw
1<?php 2 3abstract class DiffusionBlameQuery extends DiffusionQuery { 4 5 private $timeout; 6 private $paths; 7 8 public function setTimeout($timeout) { 9 $this->timeout = $timeout; 10 return $this; 11 } 12 13 public function getTimeout() { 14 return $this->timeout; 15 } 16 17 public function setPaths(array $paths) { 18 $this->paths = $paths; 19 return $this; 20 } 21 22 public function getPaths() { 23 return $this->paths; 24 } 25 26 abstract protected function newBlameFuture(DiffusionRequest $request, $path); 27 28 abstract protected function resolveBlameFuture(ExecFuture $future); 29 30 final public static function newFromDiffusionRequest( 31 DiffusionRequest $request) { 32 return parent::newQueryObject(self::class, $request); 33 } 34 35 final protected function executeQuery() { 36 $paths = $this->getPaths(); 37 38 $blame = array(); 39 40 // Load cache keys: these are the commits at which each path was last 41 // touched. 42 $keys = $this->loadCacheKeys($paths); 43 44 // Try to read blame data from cache. 45 $cache = $this->readCacheData($keys); 46 foreach ($paths as $key => $path) { 47 if (!isset($cache[$path])) { 48 continue; 49 } 50 51 $blame[$path] = $cache[$path]; 52 unset($paths[$key]); 53 } 54 55 // If we have no paths left, we filled everything from cache and can 56 // bail out early. 57 if (!$paths) { 58 return $blame; 59 } 60 61 $request = $this->getRequest(); 62 $timeout = $this->getTimeout(); 63 64 // We're still missing at least some data, so we need to run VCS commands 65 // to pull it. 66 $futures = array(); 67 foreach ($paths as $path) { 68 $future = $this->newBlameFuture($request, $path); 69 70 if ($timeout) { 71 $future->setTimeout($timeout); 72 } 73 74 $futures[$path] = $future; 75 } 76 77 $futures = id(new FutureIterator($futures)) 78 ->limit(4); 79 80 foreach ($futures as $path => $future) { 81 $path_blame = $this->resolveBlameFuture($future); 82 if ($path_blame !== null) { 83 $blame[$path] = $path_blame; 84 } 85 } 86 87 // Fill the cache with anything we generated. 88 $this->writeCacheData( 89 array_select_keys($keys, $paths), 90 $blame); 91 92 return $blame; 93 } 94 95 private function loadCacheKeys(array $paths) { 96 $request = $this->getRequest(); 97 $viewer = $request->getUser(); 98 99 $repository = $request->getRepository(); 100 $repository_id = $repository->getID(); 101 102 $last_modified = parent::callConduitWithDiffusionRequest( 103 $viewer, 104 $request, 105 'diffusion.lastmodifiedquery', 106 array( 107 'paths' => array_fill_keys($paths, $request->getCommit()), 108 )); 109 110 $map = array(); 111 foreach ($paths as $path) { 112 $identifier = idx($last_modified, $path); 113 if ($identifier === null) { 114 continue; 115 } 116 117 $path_hash = PhabricatorHash::digestForIndex($path); 118 119 $map[$path] = "blame({$repository_id}, {$identifier}, {$path_hash}, raw)"; 120 } 121 122 return $map; 123 } 124 125 private function readCacheData(array $keys) { 126 $cache = PhabricatorCaches::getImmutableCache(); 127 $data = $cache->getKeys($keys); 128 129 $results = array(); 130 foreach ($keys as $path => $key) { 131 if (!isset($data[$key])) { 132 continue; 133 } 134 $results[$path] = $data[$key]; 135 } 136 137 // Decode the cache storage format. 138 foreach ($results as $path => $cache) { 139 list($head, $body) = explode("\n", $cache, 2); 140 switch ($head) { 141 case 'raw': 142 $body = explode("\n", $body); 143 break; 144 default: 145 $body = null; 146 break; 147 } 148 149 if ($body === null) { 150 unset($results[$path]); 151 } else { 152 $results[$path] = $body; 153 } 154 } 155 156 return $results; 157 } 158 159 private function writeCacheData(array $keys, array $blame) { 160 $writes = array(); 161 foreach ($keys as $path => $key) { 162 $value = idx($blame, $path); 163 if ($value === null) { 164 continue; 165 } 166 167 // For now, just store the entire value with a "raw" header. In the 168 // future, we could compress this or use IDs instead. 169 $value = "raw\n".implode("\n", $value); 170 171 $writes[$key] = $value; 172 } 173 174 if (!$writes) { 175 return; 176 } 177 178 $cache = PhabricatorCaches::getImmutableCache(); 179 $data = $cache->setKeys($writes, phutil_units('14 days in seconds')); 180 } 181 182}