@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
3final class DiffusionRepositoryController extends DiffusionController {
4
5 private $browseFuture;
6 private $branchButton = null;
7 private $branchFuture;
8
9 public function shouldAllowPublic() {
10 return true;
11 }
12
13 public function handleRequest(AphrontRequest $request) {
14 $response = $this->loadDiffusionContext();
15 if ($response) {
16 return $response;
17 }
18
19 require_celerity_resource('diffusion-css');
20
21 $viewer = $this->getViewer();
22 $drequest = $this->getDiffusionRequest();
23 $repository = $drequest->getRepository();
24
25 $crumbs = $this->buildCrumbs();
26 $crumbs->setBorder(true);
27
28 $header = $this->buildHeaderView($repository);
29 $actions = $this->buildActionList($repository);
30 $description = $this->buildDescriptionView($repository);
31 $locate_file = $this->buildLocateFile();
32
33 // Before we do any work, make sure we're looking at a some content: we're
34 // on a valid branch, and the repository is not empty.
35 $page_has_content = false;
36 $empty_title = null;
37 $empty_message = null;
38
39 // If this VCS supports branches, check that the selected branch actually
40 // exists.
41 if ($drequest->supportsBranches()) {
42 // NOTE: Mercurial may have multiple branch heads with the same name.
43 $ref_cursors = id(new PhabricatorRepositoryRefCursorQuery())
44 ->setViewer($viewer)
45 ->withRepositoryPHIDs(array($repository->getPHID()))
46 ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH))
47 ->withRefNames(array($drequest->getBranch()))
48 ->needPositions(true)
49 ->execute();
50
51 // It's possible that this branch previously existed, but has been
52 // deleted. Make sure we have valid cursor positions, not just cursors.
53 $any_positions = false;
54 foreach ($ref_cursors as $ref_cursor) {
55 if ($ref_cursor->getPositions()) {
56 $any_positions = true;
57 break;
58 }
59 }
60
61 if ($any_positions) {
62 // This is a valid branch, so we necessarily have some content.
63 $page_has_content = true;
64 } else {
65 $default = $repository->getDefaultBranch();
66 if ($default != $drequest->getBranch()) {
67 $empty_title = pht('No Such Branch');
68 $empty_message = pht(
69 'There is no branch named "%s" in this repository.',
70 $drequest->getBranch());
71 } else {
72 $empty_title = pht('No Default Branch');
73 $empty_message = pht(
74 'This repository is configured with default branch "%s" but '.
75 'there is no branch with that name in this repository.',
76 $default);
77 $branch_setup_uri = $this->getApplicationURI(
78 'edit/'.$repository->getID().'/page/branches/');
79 phlog('Repository "'.$repository->getName().'" is configured with '.
80 'default branch "'.$default.'" but there is no such branch in '.
81 'this repository. Correct the settings at '.$branch_setup_uri);
82 }
83 }
84 }
85
86 // If we didn't find any branches, check if there are any commits at all.
87 // This can tailor the message for empty repositories.
88 $any_commit = null;
89 if (!$page_has_content) {
90 $any_commit = id(new DiffusionCommitQuery())
91 ->setViewer($viewer)
92 ->withRepository($repository)
93 ->setLimit(1)
94 ->execute();
95 if ($any_commit) {
96 if (!$drequest->supportsBranches()) {
97 $page_has_content = true;
98 }
99 } else {
100 $empty_title = pht('Empty Repository');
101 $empty_message = pht('This repository does not have any commits yet.');
102 }
103 }
104
105 if ($page_has_content) {
106 $content = $this->buildNormalContent($drequest);
107 } else {
108 // If we have a commit somewhere, find branches.
109 // TODO: Evan will replace
110 // $this->buildNormalContent($drequest);
111 $content = id(new PHUIInfoView())
112 ->setTitle($empty_title)
113 ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
114 ->setErrors(array($empty_message));
115 }
116
117 $tabs = $this->buildTabsView('home');
118
119 $clone_uri = $drequest->generateURI(
120 array(
121 'action' => 'clone',
122 ));
123
124 if ($repository->isSVN()) {
125 $clone_text = pht('Checkout');
126 } else {
127 $clone_text = pht('Clone');
128 }
129
130 $actions_button = id(new PHUIButtonView())
131 ->setTag('a')
132 ->setText(pht('Actions'))
133 ->setIcon('fa-bars')
134 ->addClass('mmr')
135 ->setColor(PHUIButtonView::GREY)
136 ->setDropdown(true)
137 ->setDropdownMenu($actions);
138
139 $clone_button = id(new PHUIButtonView())
140 ->setTag('a')
141 ->setText($clone_text)
142 ->setColor(PHUIButtonView::GREEN)
143 ->setIcon('fa-download')
144 ->setWorkflow(true)
145 ->setHref($clone_uri);
146
147 $bar = id(new PHUILeftRightView())
148 ->setLeft($locate_file)
149 ->setRight(array($this->branchButton, $actions_button, $clone_button))
150 ->addClass('diffusion-action-bar');
151
152 $status_view = null;
153 if ($repository->isReadOnly()) {
154 $status_view = id(new PHUIInfoView())
155 ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
156 ->setErrors(
157 array(
158 phutil_escape_html_newlines(
159 $repository->getReadOnlyMessageForDisplay()),
160 ));
161 }
162
163 $view = id(new PHUITwoColumnView())
164 ->setHeader($header)
165 ->setFooter(
166 array(
167 $status_view,
168 $bar,
169 $description,
170 $content,
171 ));
172
173 if ($page_has_content) {
174 $view->setTabs($tabs);
175 }
176
177 return $this->newPage()
178 ->setTitle(
179 array(
180 $repository->getName(),
181 $repository->getDisplayName(),
182 ))
183 ->setCrumbs($crumbs)
184 ->appendChild(array(
185 $view,
186 ));
187 }
188
189
190 private function buildNormalContent(DiffusionRequest $drequest) {
191 $request = $this->getRequest();
192 $repository = $drequest->getRepository();
193
194 $commit = $drequest->getCommit();
195 $path = $drequest->getPath();
196
197 $futures = array();
198
199 $browse_pager = id(new PHUIPagerView())
200 ->readFromRequest($request);
201
202 $this->browseFuture = $this->callConduitMethod(
203 'diffusion.browsequery',
204 array(
205 'commit' => $commit,
206 'path' => $path,
207 'limit' => $browse_pager->getPageSize() + 1,
208 ));
209 $futures[] = $this->browseFuture;
210
211 if ($this->needBranchFuture()) {
212 $branch_limit = $this->getBranchLimit();
213 $this->branchFuture = $this->callConduitMethod(
214 'diffusion.branchquery',
215 array(
216 'closed' => false,
217 'limit' => $branch_limit + 1,
218 ));
219 $futures[] = $this->branchFuture;
220 }
221
222 $futures = array_filter($futures);
223 $futures = new FutureIterator($futures);
224 foreach ($futures as $future) {
225 // Just resolve all the futures before continuing.
226 }
227
228 $content = array();
229
230 try {
231 $browse_results = $this->browseFuture->resolve();
232 $browse_results = DiffusionBrowseResultSet::newFromConduit(
233 $browse_results);
234
235 $browse_paths = $browse_results->getPaths();
236 $browse_paths = $browse_pager->sliceResults($browse_paths);
237
238 $browse_exception = null;
239 } catch (Exception $ex) {
240 $browse_results = null;
241 $browse_paths = null;
242 $browse_exception = $ex;
243 }
244
245 if ($browse_results) {
246 $readme = $this->renderDirectoryReadme($browse_results);
247 } else {
248 $readme = null;
249 }
250
251 if ($readme) {
252 $content[] = $readme;
253 }
254
255 $content[] = $this->buildBrowseTable(
256 $browse_results,
257 $browse_paths,
258 $browse_exception,
259 $browse_pager);
260
261 try {
262 $branch_button = $this->buildBranchList($drequest);
263 $this->branchButton = $branch_button;
264 } catch (Exception $ex) {
265 if (!$repository->isImporting()) {
266 $content[] = $this->renderStatusMessage(
267 pht('Unable to Load Branches'),
268 $ex->getMessage());
269 }
270 }
271
272 return $content;
273 }
274
275 private function buildHeaderView(PhabricatorRepository $repository) {
276 $viewer = $this->getViewer();
277 $drequest = $this->getDiffusionRequest();
278 $search = $this->renderSearchForm();
279
280 $header = id(new PHUIHeaderView())
281 ->setHeader($repository->getName())
282 ->setViewer($viewer)
283 ->setPolicyObject($repository)
284 ->setProfileHeader(true)
285 ->setImage($repository->getProfileImageURI())
286 ->setImageEditURL('/diffusion/picture/'.$repository->getID().'/')
287 ->addActionItem($search)
288 ->addClass('diffusion-profile-header');
289
290 if (!$repository->isTracked()) {
291 $header->setStatus('fa-ban', 'dark', pht('Inactive'));
292 } else if ($repository->isReadOnly()) {
293 $header->setStatus('fa-wrench', 'indigo', pht('Under Maintenance'));
294 } else if ($repository->isImporting()) {
295 $ratio = $repository->loadImportProgress();
296 $percentage = sprintf('%.2f%%', 100 * $ratio);
297 $header->setStatus(
298 'fa-clock-o',
299 'indigo',
300 pht('Importing (%s)...', $percentage));
301 } else if ($repository->isPublishingDisabled()) {
302 $header->setStatus('fa-minus', 'bluegrey', pht('Publishing Disabled'));
303 } else {
304 $header->setStatus('fa-check', 'bluegrey', pht('Active'));
305 }
306
307 if (!$repository->isSVN()) {
308 $default = $repository->getDefaultBranch();
309 if ($default != $drequest->getBranch()) {
310 $branch_tag = $this->renderBranchTag($drequest);
311 $header->addTag($branch_tag);
312 }
313 }
314
315 // Display associated projects as tags in repository browsing header
316 $project_phids = $repository->getProjectPHIDs();
317 if ($project_phids) {
318 $handles = id(new PhabricatorHandleQuery())
319 ->setViewer($viewer)
320 ->withPHIDs($project_phids)
321 ->execute();
322 $project_handles = array_select_keys(
323 $handles,
324 $project_phids);
325 foreach ($project_handles as $handle) {
326 $header->addTag($handle->renderTag());
327 }
328 }
329
330 return $header;
331 }
332
333 private function buildActionList(PhabricatorRepository $repository) {
334 $viewer = $this->getViewer();
335
336 $edit_uri = $repository->getPathURI('manage/');
337 $action_view = id(new PhabricatorActionListView())
338 ->setViewer($viewer)
339 ->setObject($repository);
340
341 $action_view->addAction(
342 id(new PhabricatorActionView())
343 ->setName(pht('Manage Repository'))
344 ->setIcon('fa-cogs')
345 ->setHref($edit_uri));
346
347 if ($repository->isHosted()) {
348 $push_uri = $this->getApplicationURI(
349 'pushlog/?repositories='.$repository->getPHID());
350
351 $action_view->addAction(
352 id(new PhabricatorActionView())
353 ->setName(pht('View Push Logs'))
354 ->setIcon('fa-upload')
355 ->setHref($push_uri));
356
357 $pull_uri = $this->getApplicationURI(
358 'synclog/?repositories='.$repository->getPHID());
359
360 $action_view->addAction(
361 id(new PhabricatorActionView())
362 ->setName(pht('View Sync Logs'))
363 ->setIcon('fa-exchange')
364 ->setHref($pull_uri));
365 }
366
367 $pull_uri = $this->getApplicationURI(
368 'pulllog/?repositories='.$repository->getPHID());
369
370 $action_view->addAction(
371 id(new PhabricatorActionView())
372 ->setName(pht('View Pull Logs'))
373 ->setIcon('fa-download')
374 ->setHref($pull_uri));
375
376 return $action_view;
377 }
378
379 private function buildDescriptionView(PhabricatorRepository $repository) {
380 $viewer = $this->getViewer();
381 $view = id(new PHUIPropertyListView())
382 ->setViewer($viewer);
383
384 $description = $repository->getDetail('description');
385 if (strlen($description)) {
386 $description = new PHUIRemarkupView($viewer, $description);
387 $view->addTextContent($description);
388 return id(new PHUIObjectBoxView())
389 ->appendChild($view)
390 ->addClass('diffusion-profile-description');
391 }
392 return null;
393 }
394
395 private function buildBranchList(DiffusionRequest $drequest) {
396 $viewer = $this->getViewer();
397
398 if (!$this->needBranchFuture()) {
399 return null;
400 }
401
402 $branches = $this->branchFuture->resolve();
403 if (!$branches) {
404 return null;
405 }
406
407 $limit = $this->getBranchLimit();
408 $more_branches = (count($branches) > $limit);
409 $branches = array_slice($branches, 0, $limit);
410
411 $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches);
412
413 $actions = id(new PhabricatorActionListView())
414 ->setViewer($viewer);
415
416 foreach ($branches as $branch) {
417 $branch_uri = $drequest->generateURI(
418 array(
419 'action' => 'branch',
420 'branch' => $branch->getShortname(),
421 ));
422 $actions->addAction(
423 id(new PhabricatorActionView())
424 ->setName($branch->getShortname())
425 ->setIcon('fa-code-fork')
426 ->setHref($branch_uri));
427 }
428
429 if ($more_branches) {
430 $more_uri = $drequest->generateURI(
431 array(
432 'action' => 'branches',
433 ));
434 $actions->addAction(
435 id(new PhabricatorActionView())
436 ->setType(PhabricatorActionView::TYPE_DIVIDER));
437 $actions->addAction(
438 id(new PhabricatorActionView())
439 ->setName(pht('See More Branches'))
440 ->setIcon('fa-external-link')
441 ->setHref($more_uri));
442 }
443
444 $button = id(new PHUIButtonView())
445 ->setText(pht('Branch: %s', $drequest->getBranch()))
446 ->setTag('a')
447 ->addClass('mmr')
448 ->setIcon('fa-code-fork')
449 ->setColor(PHUIButtonView::GREY)
450 ->setDropdown(true)
451 ->setDropdownMenu($actions);
452
453 return $button;
454 }
455
456 private function buildBrowseTable(
457 $browse_results,
458 $browse_paths,
459 $browse_exception,
460 PHUIPagerView $pager) {
461
462 require_celerity_resource('diffusion-icons-css');
463
464 $request = $this->getRequest();
465 $viewer = $request->getUser();
466 $drequest = $this->getDiffusionRequest();
467 $repository = $drequest->getRepository();
468
469 if ($browse_exception) {
470 if ($repository->isImporting()) {
471 // The history table renders a useful message.
472 return null;
473 } else {
474 return $this->renderStatusMessage(
475 pht('Unable to Retrieve Paths'),
476 $browse_exception->getMessage());
477 }
478 }
479
480 $browse_table = id(new DiffusionBrowseTableView())
481 ->setUser($viewer)
482 ->setDiffusionRequest($drequest);
483 if ($browse_paths) {
484 $browse_table->setPaths($browse_paths);
485 } else {
486 $browse_table->setPaths(array());
487 }
488
489 $browse_uri = $drequest->generateURI(array('action' => 'browse'));
490 $pager->setURI($browse_uri, 'offset');
491
492 $repository_name = $repository->getName();
493 $branch_name = $drequest->getBranch();
494 if (phutil_nonempty_string($branch_name)) {
495 $repository_name .= ' ('.$branch_name.')';
496 }
497
498 $header = phutil_tag(
499 'a',
500 array(
501 'href' => $browse_uri,
502 'class' => 'diffusion-view-browse-header',
503 ),
504 $repository_name);
505
506 return id(new PHUIObjectBoxView())
507 ->setHeaderText($header)
508 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
509 ->setTable($browse_table)
510 ->addClass('diffusion-mobile-view')
511 ->setPager($pager);
512 }
513
514 private function needBranchFuture() {
515 $drequest = $this->getDiffusionRequest();
516
517 if ($drequest->getBranch() === null) {
518 return false;
519 }
520
521 return true;
522 }
523
524 private function getBranchLimit() {
525 return 15;
526 }
527
528}