@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<?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}