@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 DiffusionController extends PhabricatorController {
4
5 private $diffusionRequest;
6
7 /**
8 * @return DiffusionRequest
9 */
10 protected function getDiffusionRequest() {
11 if (!$this->diffusionRequest) {
12 throw new PhutilInvalidStateException('loadDiffusionContext');
13 }
14 return $this->diffusionRequest;
15 }
16
17 protected function hasDiffusionRequest() {
18 return (bool)$this->diffusionRequest;
19 }
20
21 public function willBeginExecution() {
22 $request = $this->getRequest();
23
24 // Check if this is a VCS request, e.g. from "git clone", "hg clone", or
25 // "svn checkout". If it is, we jump off into repository serving code to
26 // process the request.
27
28 $serve_controller = new DiffusionServeController();
29 if ($serve_controller->isVCSRequest($request)) {
30 return $this->delegateToController($serve_controller);
31 }
32
33 return parent::willBeginExecution();
34 }
35
36 protected function loadDiffusionContextForEdit() {
37 return $this->loadContext(
38 array(
39 'edit' => true,
40 ));
41 }
42
43 protected function loadDiffusionContext() {
44 return $this->loadContext(array());
45 }
46
47 private function loadContext(array $options) {
48 $request = $this->getRequest();
49 $viewer = $this->getViewer();
50 require_celerity_resource('diffusion-repository-css');
51
52 $identifier = $this->getRepositoryIdentifierFromRequest($request);
53
54 $params = $options + array(
55 'repository' => $identifier,
56 'user' => $viewer,
57 'blob' => $this->getDiffusionBlobFromRequest($request),
58 'commit' => $request->getURIData('commit'),
59 'path' => $request->getURIData('path'),
60 'line' => $request->getURIData('line'),
61 'branch' => $request->getURIData('branch'),
62 'lint' => $request->getStr('lint'),
63 );
64
65 $drequest = DiffusionRequest::newFromDictionary($params);
66
67 if (!$drequest) {
68 return new Aphront404Response();
69 }
70
71 // If the client is making a request like "/diffusion/1/...", but the
72 // repository has a different canonical path like "/diffusion/XYZ/...",
73 // redirect them to the canonical path.
74
75 // Skip this redirect if the request is an AJAX request, like the requests
76 // that Owners makes to complete and validate paths.
77
78 if (!$request->isAjax()) {
79 $request_path = $request->getPath();
80 $repository = $drequest->getRepository();
81
82 $canonical_path = $repository->getCanonicalPath($request_path);
83 if ($canonical_path !== null) {
84 if ($canonical_path != $request_path) {
85 return id(new AphrontRedirectResponse())->setURI($canonical_path);
86 }
87 }
88 }
89
90 $this->diffusionRequest = $drequest;
91
92 return null;
93 }
94
95 protected function getDiffusionBlobFromRequest(AphrontRequest $request) {
96 return $request->getURIData('dblob');
97 }
98
99 protected function getRepositoryIdentifierFromRequest(
100 AphrontRequest $request) {
101
102 $short_name = $request->getURIData('repositoryShortName');
103 if (phutil_nonempty_string($short_name)) {
104 // If the short name ends in ".git", ignore it.
105 $short_name = preg_replace('/\\.git\z/', '', $short_name);
106 return $short_name;
107 }
108
109 $identifier = $request->getURIData('repositoryCallsign');
110 if (phutil_nonempty_string($identifier)) {
111 return $identifier;
112 }
113
114 $id = $request->getURIData('repositoryID');
115 if (phutil_nonempty_string($id)) {
116 return (int)$id;
117 }
118
119 return null;
120 }
121
122 public function buildCrumbs(array $spec = array()) {
123 $crumbs = $this->buildApplicationCrumbs();
124 $crumb_list = $this->buildCrumbList($spec);
125 foreach ($crumb_list as $crumb) {
126 $crumbs->addCrumb($crumb);
127 }
128 return $crumbs;
129 }
130
131 private function buildCrumbList(array $spec = array()) {
132
133 $spec = $spec + array(
134 'commit' => null,
135 'tags' => null,
136 'branches' => null,
137 'view' => null,
138 );
139
140 $crumb_list = array();
141
142 // On the home page, we don't have a DiffusionRequest.
143 if ($this->hasDiffusionRequest()) {
144 $drequest = $this->getDiffusionRequest();
145 $repository = $drequest->getRepository();
146 } else {
147 $drequest = null;
148 $repository = null;
149 }
150
151 if (!$repository) {
152 return $crumb_list;
153 }
154
155 $repository_name = $repository->getName();
156
157 if (!$spec['commit'] && !$spec['tags'] && !$spec['branches']) {
158 $branch_name = $drequest->getBranch();
159 if (phutil_nonempty_string($branch_name)) {
160 $repository_name .= ' ('.$branch_name.')';
161 }
162 }
163
164 $crumb = id(new PHUICrumbView())
165 ->setName($repository_name);
166 if (!$spec['view'] && !$spec['commit'] &&
167 !$spec['tags'] && !$spec['branches']) {
168 $crumb_list[] = $crumb;
169 return $crumb_list;
170 }
171 $crumb->setHref(
172 $drequest->generateURI(
173 array(
174 'action' => 'branch',
175 'path' => '/',
176 )));
177 $crumb_list[] = $crumb;
178
179 $stable_commit = $drequest->getStableCommit();
180 $commit_name = $repository->formatCommitName($stable_commit, $local = true);
181 $commit_uri = $repository->getCommitURI($stable_commit);
182
183 if ($spec['tags']) {
184 $crumb = new PHUICrumbView();
185 if ($spec['commit']) {
186 $crumb->setName(pht('Tags for %s', $commit_name));
187 $crumb->setHref($commit_uri);
188 } else {
189 $crumb->setName(pht('Tags'));
190 }
191 $crumb_list[] = $crumb;
192 return $crumb_list;
193 }
194
195 if ($spec['branches']) {
196 $crumb = id(new PHUICrumbView())
197 ->setName(pht('Branches'));
198 $crumb_list[] = $crumb;
199 return $crumb_list;
200 }
201
202 if ($spec['commit']) {
203 $crumb = id(new PHUICrumbView())
204 ->setName($commit_name);
205 $crumb_list[] = $crumb;
206 return $crumb_list;
207 }
208
209 $crumb = new PHUICrumbView();
210 $view = $spec['view'];
211
212 switch ($view) {
213 case 'history':
214 $view_name = pht('History');
215 break;
216 case 'browse':
217 $view_name = pht('Code');
218 break;
219 case 'lint':
220 $view_name = pht('Lint');
221 break;
222 case 'change':
223 $view_name = pht('Change');
224 break;
225 case 'compare':
226 $view_name = pht('Compare');
227 break;
228 }
229
230 $crumb = id(new PHUICrumbView())
231 ->setName($view_name);
232
233 $crumb_list[] = $crumb;
234 return $crumb_list;
235 }
236
237 protected function callConduitWithDiffusionRequest(
238 $method,
239 array $params = array()) {
240
241 $user = $this->getRequest()->getUser();
242 $drequest = $this->getDiffusionRequest();
243
244 return DiffusionQuery::callConduitWithDiffusionRequest(
245 $user,
246 $drequest,
247 $method,
248 $params);
249 }
250
251 protected function callConduitMethod($method, array $params = array()) {
252 $user = $this->getViewer();
253 $drequest = $this->getDiffusionRequest();
254
255 return DiffusionQuery::callConduitWithDiffusionRequest(
256 $user,
257 $drequest,
258 $method,
259 $params,
260 true);
261 }
262
263 protected function getRepositoryControllerURI(
264 PhabricatorRepository $repository,
265 $path) {
266 return $repository->getPathURI($path);
267 }
268
269 protected function renderPathLinks(DiffusionRequest $drequest, $action) {
270 $path = $drequest->getPath();
271 $path_parts = array_filter(explode('/', trim($path, '/')));
272
273 $divider = phutil_tag(
274 'span',
275 array(
276 'class' => 'phui-header-divider',
277 ),
278 '/');
279
280 $links = array();
281 if ($path_parts) {
282 $links[] = phutil_tag(
283 'a',
284 array(
285 'href' => $drequest->generateURI(
286 array(
287 'action' => $action,
288 'path' => '',
289 )),
290 ),
291 $drequest->getRepository()->getDisplayName());
292 $links[] = $divider;
293 $accum = '';
294 $last_key = last_key($path_parts);
295 foreach ($path_parts as $key => $part) {
296 $accum .= '/'.$part;
297 if ($key === $last_key) {
298 $links[] = $part;
299 } else {
300 $links[] = phutil_tag(
301 'a',
302 array(
303 'href' => $drequest->generateURI(
304 array(
305 'action' => $action,
306 'path' => $accum.'/',
307 )),
308 ),
309 $part);
310 $links[] = $divider;
311 }
312 }
313 } else {
314 $links[] = $drequest->getRepository()->getDisplayName();
315 $links[] = $divider;
316 }
317
318 return $links;
319 }
320
321 protected function renderStatusMessage($title, $body) {
322 return id(new PHUIInfoView())
323 ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
324 ->setTitle($title)
325 ->setFlush(true)
326 ->appendChild($body);
327 }
328
329 protected function renderCommitHashTag(DiffusionRequest $drequest) {
330 $stable_commit = $drequest->getStableCommit();
331 $commit = phutil_tag(
332 'a',
333 array(
334 'href' => $drequest->generateURI(
335 array(
336 'action' => 'commit',
337 'commit' => $stable_commit,
338 )),
339 ),
340 $drequest->getRepository()->formatCommitName($stable_commit, true));
341
342 $tag = id(new PHUITagView())
343 ->setName($commit)
344 ->setColor(PHUITagView::COLOR_INDIGO)
345 ->setBorder(PHUITagView::BORDER_NONE)
346 ->setType(PHUITagView::TYPE_SHADE);
347
348 return $tag;
349 }
350
351 protected function renderBranchTag(DiffusionRequest $drequest) {
352 $branch = $drequest->getBranch();
353 $branch = id(new PhutilUTF8StringTruncator())
354 ->setMaximumGlyphs(24)
355 ->truncateString($branch);
356
357 $tag = id(new PHUITagView())
358 ->setName($branch)
359 ->setColor(PHUITagView::COLOR_INDIGO)
360 ->setBorder(PHUITagView::BORDER_NONE)
361 ->setType(PHUITagView::TYPE_OUTLINE)
362 ->addClass('diffusion-header-branch-tag');
363
364 return $tag;
365 }
366
367 protected function renderSymbolicCommit(DiffusionRequest $drequest) {
368 $symbolic_tag = $drequest->getSymbolicCommit();
369 $symbolic_tag = id(new PhutilUTF8StringTruncator())
370 ->setMaximumGlyphs(24)
371 ->truncateString($symbolic_tag);
372
373 $tag = id(new PHUITagView())
374 ->setName($symbolic_tag)
375 ->setIcon('fa-tag')
376 ->setColor(PHUITagView::COLOR_INDIGO)
377 ->setBorder(PHUITagView::BORDER_NONE)
378 ->setType(PHUITagView::TYPE_SHADE);
379
380 return $tag;
381 }
382
383 protected function renderDirectoryReadme(DiffusionBrowseResultSet $browse) {
384 $readme_path = $browse->getReadmePath();
385 if ($readme_path === null) {
386 return null;
387 }
388
389 $drequest = $this->getDiffusionRequest();
390 $viewer = $this->getViewer();
391 $repository = $drequest->getRepository();
392 $repository_phid = $repository->getPHID();
393 $stable_commit = $drequest->getStableCommit();
394
395 $stable_commit_hash = PhabricatorHash::digestForIndex($stable_commit);
396 $readme_path_hash = PhabricatorHash::digestForIndex($readme_path);
397
398 $cache = PhabricatorCaches::getMutableStructureCache();
399 $cache_key = "diffusion".
400 ".repository({$repository_phid})".
401 ".commit({$stable_commit_hash})".
402 ".readme({$readme_path_hash})";
403
404 $readme_cache = $cache->getKey($cache_key);
405 if (!$readme_cache) {
406 try {
407 $result = $this->callConduitWithDiffusionRequest(
408 'diffusion.filecontentquery',
409 array(
410 'path' => $readme_path,
411 'commit' => $drequest->getStableCommit(),
412 ));
413 } catch (Exception $ex) {
414 return null;
415 }
416
417 $file_phid = $result['filePHID'];
418 if (!$file_phid) {
419 return null;
420 }
421
422 $file = id(new PhabricatorFileQuery())
423 ->setViewer($viewer)
424 ->withPHIDs(array($file_phid))
425 ->executeOne();
426 if (!$file) {
427 return null;
428 }
429
430 $corpus = $file->loadFileData();
431
432 $readme_cache = array(
433 'corpus' => $corpus,
434 );
435
436 $cache->setKey($cache_key, $readme_cache);
437 }
438
439 $readme_corpus = $readme_cache['corpus'];
440 if (!strlen($readme_corpus)) {
441 return null;
442 }
443
444 return id(new DiffusionReadmeView())
445 ->setUser($this->getViewer())
446 ->setPath($readme_path)
447 ->setContent($readme_corpus);
448 }
449
450 protected function renderSearchForm($path = '/') {
451 $drequest = $this->getDiffusionRequest();
452 $viewer = $this->getViewer();
453 switch ($drequest->getRepository()->getVersionControlSystem()) {
454 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
455 return null;
456 }
457
458 $search_term = $this->getRequest()->getStr('grep');
459 require_celerity_resource('diffusion-icons-css');
460 require_celerity_resource('diffusion-css');
461
462 $href = $drequest->generateURI(array(
463 'action' => 'browse',
464 'path' => $path,
465 ));
466
467 $bar = javelin_tag(
468 'input',
469 array(
470 'type' => 'text',
471 'id' => 'diffusion-search-input',
472 'name' => 'grep',
473 'class' => 'diffusion-search-input',
474 'sigil' => 'diffusion-search-input',
475 'placeholder' => pht('Pattern Search'),
476 'value' => $search_term,
477 ));
478
479 $form = phabricator_form(
480 $viewer,
481 array(
482 'method' => 'GET',
483 'action' => $href,
484 'sigil' => 'diffusion-search-form',
485 'class' => 'diffusion-search-form',
486 'id' => 'diffusion-search-form',
487 ),
488 array(
489 $bar,
490 ));
491
492 $form_view = phutil_tag(
493 'div',
494 array(
495 'class' => 'diffusion-search-form-view',
496 ),
497 $form);
498
499 return $form_view;
500 }
501
502 protected function buildTabsView($key) {
503 $drequest = $this->getDiffusionRequest();
504 $repository = $drequest->getRepository();
505
506 $view = new PHUIListView();
507
508 $view->addMenuItem(
509 id(new PHUIListItemView())
510 ->setKey('home')
511 ->setName(pht('Home'))
512 ->setIcon('fa-home')
513 ->setHref($drequest->generateURI(
514 array(
515 'action' => 'branch',
516 'path' => '',
517 )))
518 ->setSelected($key == 'home'));
519
520 $view->addMenuItem(
521 id(new PHUIListItemView())
522 ->setKey('code')
523 ->setName(pht('Code'))
524 ->setIcon('fa-code')
525 ->setHref($drequest->generateURI(
526 array(
527 'action' => 'browse',
528 )))
529 ->setSelected($key == 'code'));
530
531 if (!$repository->isSVN()) {
532 $view->addMenuItem(
533 id(new PHUIListItemView())
534 ->setKey('branch')
535 ->setName(pht('Branches'))
536 ->setIcon('fa-code-fork')
537 ->setHref($drequest->generateURI(
538 array(
539 'action' => 'branches',
540 )))
541 ->setSelected($key == 'branch'));
542 }
543
544 if (!$repository->isSVN()) {
545 $view->addMenuItem(
546 id(new PHUIListItemView())
547 ->setKey('tags')
548 ->setName(pht('Tags'))
549 ->setIcon('fa-tags')
550 ->setHref($drequest->generateURI(
551 array(
552 'action' => 'tags',
553 )))
554 ->setSelected($key == 'tags'));
555 }
556
557 $view->addMenuItem(
558 id(new PHUIListItemView())
559 ->setKey('history')
560 ->setName(pht('History'))
561 ->setIcon('fa-history')
562 ->setHref($drequest->generateURI(
563 array(
564 'action' => 'history',
565 )))
566 ->setSelected($key == 'history'));
567
568 return $view;
569
570 }
571
572 /**
573 * @return PHUIBoxView|null
574 */
575 protected function buildLocateFile() {
576 $request = $this->getRequest();
577 $viewer = $request->getUser();
578 $drequest = $this->getDiffusionRequest();
579 $repository = $drequest->getRepository();
580
581 $form_box = null;
582 if ($repository->canUsePathTree()) {
583 Javelin::initBehavior(
584 'diffusion-locate-file',
585 array(
586 'controlID' => 'locate-control',
587 'inputID' => 'locate-input',
588 'symbolicCommit' => $drequest->getSymbolicCommit(),
589 'browseBaseURI' => (string)$drequest->generateURI(
590 array(
591 'action' => 'browse',
592 'commit' => '',
593 'path' => '',
594 )),
595 'uri' => (string)$drequest->generateURI(
596 array(
597 'action' => 'pathtree',
598 )),
599 ));
600
601 $form = id(new AphrontFormView())
602 ->setUser($viewer)
603 ->appendChild(
604 id(new AphrontFormTypeaheadControl())
605 ->setHardpointID('locate-control')
606 ->setID('locate-input')
607 ->setPlaceholder(pht('Locate File')));
608 $form_box = id(new PHUIBoxView())
609 ->appendChild($form->buildLayoutView())
610 ->addClass('diffusion-profile-locate');
611 }
612 return $form_box;
613 }
614
615}