@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.

Support rendering arbitrary sections in the middle of a Harbormaster build log so links to line 3500 work

Summary:
Depends on D19162. Ref T13088. When a user links to `$1234`, we need to render a default view of the log with a piece at the head, a piece at the end, and a piece in the middle.

We also need to figure out the offset for line 1234, or multiple offsets for "1234-2345".

Since the logic views/reads mostly anticipated this it isn't too much of a mess, although there are a couple of bugs this exposes with view specifications that use combinations of parameters which were previously impossible.

Test Plan: Viewed a large log with no line marker. Viewed `$1`. Viewed `$end`. Viewed `$35-40`, etc. Expanded context around logs.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13088

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

+202 -33
+5 -5
resources/celerity/map.php
··· 416 416 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 417 417 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 418 418 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 419 - 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '796a8803', 419 + 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'ab1173d1', 420 420 'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e', 421 421 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 422 422 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', ··· 636 636 'javelin-behavior-event-all-day' => 'b41537c9', 637 637 'javelin-behavior-fancy-datepicker' => 'ecf4e799', 638 638 'javelin-behavior-global-drag-and-drop' => '960f6a39', 639 - 'javelin-behavior-harbormaster-log' => '796a8803', 639 + 'javelin-behavior-harbormaster-log' => 'ab1173d1', 640 640 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 641 641 'javelin-behavior-high-security-warning' => 'a464fe03', 642 642 'javelin-behavior-history-install' => '7ee2b591', ··· 1526 1526 'javelin-behavior', 1527 1527 'javelin-quicksand', 1528 1528 ), 1529 - '796a8803' => array( 1530 - 'javelin-behavior', 1531 - ), 1532 1529 '7a68dda3' => array( 1533 1530 'owners-path-editor', 1534 1531 'javelin-behavior', ··· 1767 1764 'javelin-dom', 1768 1765 'javelin-util', 1769 1766 'phabricator-prefab', 1767 + ), 1768 + 'ab1173d1' => array( 1769 + 'javelin-behavior', 1770 1770 ), 1771 1771 'ab2f381b' => array( 1772 1772 'javelin-request',
+164 -26
src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
··· 16 16 return new Aphront404Response(); 17 17 } 18 18 19 + $highlight_range = $request->getURILineRange('lines', 1000); 20 + 19 21 $log_size = $this->getTotalByteLength($log); 20 22 21 23 $head_lines = $request->getInt('head'); ··· 65 67 ); 66 68 } 67 69 70 + if ($highlight_range) { 71 + $highlight_views = $this->getHighlightViews( 72 + $log, 73 + $highlight_range, 74 + $log_size); 75 + foreach ($highlight_views as $highlight_view) { 76 + $views[] = $highlight_view; 77 + } 78 + } 79 + 68 80 if ($tail_lines > 0) { 69 81 $views[] = array( 70 82 'offset' => $tail_offset, ··· 86 98 87 99 $direction = $read['direction']; 88 100 if ($direction < 0) { 89 - $offset -= $read_length; 90 - if ($offset < 0) { 101 + if ($offset > $read_length) { 102 + $offset -= $read_length; 103 + } else { 104 + $read_length = $offset; 91 105 $offset = 0; 92 - $read_length = $log_size; 93 106 } 94 107 } 95 108 ··· 215 228 } 216 229 217 230 $limit = $view['limit']; 218 - if ($limit < ($view_offset + $view_length)) { 219 - $view_length = ($limit - $view_offset); 231 + if ($limit !== null) { 232 + if ($limit < ($view_offset + $view_length)) { 233 + $view_length = ($limit - $view_offset); 234 + } 220 235 } 221 236 } else { 222 237 $view_offset = $data_offset; ··· 226 241 } 227 242 228 243 $limit = $view['limit']; 229 - if ($limit > $view_offset) { 230 - $view_length -= ($limit - $view_offset); 231 - $view_offset = $limit; 244 + if ($limit !== null) { 245 + if ($limit > $view_offset) { 246 + $view_length -= ($limit - $view_offset); 247 + $view_offset = $limit; 248 + } 232 249 } 233 250 } 234 251 ··· 325 342 } 326 343 327 344 $uri = $log->getURI(); 328 - $highlight_range = $request->getURIData('lines'); 329 345 330 346 $rows = array(); 331 347 foreach ($render as $range) { ··· 339 355 $display_line = ($line['line'] + 1); 340 356 $display_text = ($line['data']); 341 357 358 + $cell_attr = array(); 359 + if ($highlight_range) { 360 + if (($display_line >= $highlight_range[0]) && 361 + ($display_line <= $highlight_range[1])) { 362 + $cell_attr = array( 363 + 'class' => 'phabricator-source-highlight', 364 + ); 365 + } 366 + } 367 + 342 368 $display_line = phutil_tag( 343 369 'a', 344 370 array( ··· 347 373 $display_line); 348 374 349 375 $line_cell = phutil_tag('th', array(), $display_line); 350 - $text_cell = phutil_tag('td', array(), $display_text); 376 + $text_cell = phutil_tag('td', $cell_attr, $display_text); 351 377 352 378 $rows[] = phutil_tag( 353 379 'tr', ··· 557 583 $vs = $vview['viewOffset']; 558 584 $ve = $vs + $vview['viewLength']; 559 585 586 + // Don't merge if one of the slices starts at a byte offset 587 + // significantly after the other ends. 588 + if (($vs > $ue + $body_bytes) || ($us > $ve + $body_bytes)) { 589 + continue; 590 + } 591 + 560 592 $uss = $uview['sliceOffset']; 561 593 $use = $uss + $uview['sliceLength']; 562 594 563 595 $vss = $vview['sliceOffset']; 564 596 $vse = $vss + $vview['sliceLength']; 565 597 566 - if ($ue <= $vs) { 567 - if (($ue + $body_bytes) >= $vs) { 568 - if (($use + $body_lines) >= $vss) { 569 - $views[$ukey] = array( 570 - 'sliceLength' => ($vse - $uss), 571 - 'viewLength' => ($ve - $us), 572 - ) + $views[$ukey]; 598 + // Don't merge if one of the slices starts at a line offset 599 + // significantly after the other ends. 600 + if ($uss > ($vse + $body_lines) || $vss > ($use + $body_lines)) { 601 + continue; 602 + } 603 + 604 + // These views are overlapping or nearly overlapping, so we merge 605 + // them. We merge views even if they aren't exactly adjacent since 606 + // it's silly to render an "expand more" which only expands a couple 607 + // of lines. 608 + 609 + $offset = min($us, $vs); 610 + $length = max($ue, $ve) - $offset; 611 + 612 + $slice_offset = min($uss, $vss); 613 + $slice_length = max($use, $vse) - $slice_offset; 614 + 615 + $views[$ukey] = array( 616 + 'viewOffset' => $offset, 617 + 'viewLength' => $length, 618 + 'sliceOffset' => $slice_offset, 619 + 'sliceLength' => $slice_length, 620 + ) + $views[$ukey]; 573 621 574 - unset($views[$vkey]); 575 - continue; 576 - } 577 - } 578 - } 622 + unset($views[$vkey]); 579 623 } 580 624 } 581 625 ··· 603 647 'meta' => array( 604 648 'headOffset' => $range['head'], 605 649 'tailOffset' => $range['tail'], 606 - 'head' => 4, 650 + 'head' => 128, 607 651 'tail' => 0, 608 652 ), 609 653 ), ··· 620 664 'meta' => array( 621 665 'headOffset' => $range['head'], 622 666 'tailOffset' => $range['tail'], 623 - 'head' => 2, 624 - 'tail' => 2, 667 + 'head' => 128, 668 + 'tail' => 128, 625 669 ), 626 670 ), 627 671 $mid_text); ··· 640 684 'headOffset' => $range['head'], 641 685 'tailOffset' => $range['tail'], 642 686 'head' => 0, 643 - 'tail' => 4, 687 + 'tail' => 128, 644 688 ), 645 689 ), 646 690 $down_text); ··· 725 769 ); 726 770 727 771 return phutil_tag('tr', array(), $format_cells); 772 + } 773 + 774 + private function getHighlightViews( 775 + HarbormasterBuildLog $log, 776 + array $range, 777 + $log_size) { 778 + // If we're highlighting a line range in the file, we first need to figure 779 + // out the offsets for the lines we care about. 780 + list($range_min, $range_max) = $range; 781 + 782 + // Read the markers to find a range we can load which includes both lines. 783 + $read_range = $log->getLineSpanningRange($range_min, $range_max); 784 + list($min_pos, $max_pos, $min_line) = $read_range; 785 + 786 + $length = ($max_pos - $min_pos); 787 + 788 + // Reject to do the read if it requires us to examine a huge amount of 789 + // data. For example, the user may request lines "$1-1000" of a file where 790 + // each line has 100MB of text. 791 + $limit = (1024 * 1024 * 16); 792 + if ($length > $limit) { 793 + return array(); 794 + } 795 + 796 + $data = $log->loadData($min_pos, $length); 797 + 798 + $offset = $min_pos; 799 + $min_offset = null; 800 + $max_offset = null; 801 + 802 + $lines = $this->getLines($data); 803 + $number = ($min_line + 1); 804 + 805 + foreach ($lines as $line) { 806 + if ($min_offset === null) { 807 + if ($number === $range_min) { 808 + $min_offset = $offset; 809 + } 810 + } 811 + 812 + $offset += strlen($line); 813 + 814 + if ($max_offset === null) { 815 + if ($number === $range_max) { 816 + $max_offset = $offset; 817 + break; 818 + } 819 + } 820 + 821 + $number += 1; 822 + } 823 + 824 + $context_lines = 8; 825 + 826 + // Build views around the beginning and ends of the respective lines. We 827 + // expect these views to overlap significantly in normal circumstances 828 + // and be merged later. 829 + $views = array(); 830 + 831 + if ($min_offset !== null) { 832 + $views[] = array( 833 + 'offset' => $min_offset, 834 + 'lines' => $context_lines + ($range_max - $range_min) - 1, 835 + 'direction' => 1, 836 + 'limit' => null, 837 + ); 838 + if ($min_offset > 0) { 839 + $views[] = array( 840 + 'offset' => $min_offset, 841 + 'lines' => $context_lines, 842 + 'direction' => -1, 843 + 'limit' => null, 844 + ); 845 + } 846 + } 847 + 848 + if ($max_offset !== null) { 849 + $views[] = array( 850 + 'offset' => $max_offset, 851 + 'lines' => $context_lines + ($range_max - $range_min), 852 + 'direction' => -1, 853 + 'limit' => null, 854 + ); 855 + if ($max_offset < $log_size) { 856 + $views[] = array( 857 + 'offset' => $max_offset, 858 + 'lines' => $context_lines, 859 + 'direction' => 1, 860 + 'limit' => null, 861 + ); 862 + } 863 + } 864 + 865 + return $views; 728 866 } 729 867 730 868 }
+30
src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
··· 212 212 return $parts; 213 213 } 214 214 215 + public function getLineSpanningRange($min_line, $max_line) { 216 + $map = $this->getLineMap(); 217 + if (!$map) { 218 + throw new Exception(pht('No line map.')); 219 + } 220 + 221 + $min_pos = 0; 222 + $min_line = 0; 223 + $max_pos = $this->getByteLength(); 224 + list($map) = $map; 225 + foreach ($map as $marker) { 226 + list($offset, $count) = $marker; 227 + 228 + if ($count < $min_line) { 229 + if ($offset > $min_pos) { 230 + $min_pos = $offset; 231 + $min_line = $count; 232 + } 233 + } 234 + 235 + if ($count > $max_line) { 236 + $max_pos = min($max_pos, $offset); 237 + break; 238 + } 239 + } 240 + 241 + return array($min_pos, $max_pos, $min_line); 242 + } 243 + 244 + 215 245 public function getReadPosition($read_offset) { 216 246 $position = array(0, 0); 217 247
+2 -1
src/applications/harbormaster/view/HarbormasterBuildLogView.php
··· 67 67 'harbormaster-log', 68 68 array( 69 69 'contentNodeID' => $content_id, 70 - 'renderURI' => $log->getRenderURI($this->getHighlightedLineRange()), 70 + 'initialURI' => $log->getRenderURI($this->getHighlightedLineRange()), 71 + 'renderURI' => $log->getRenderURI(null), 71 72 )); 72 73 73 74 $box_view->appendChild($content_div);
+1 -1
webroot/rsrc/js/application/harbormaster/behavior-harbormaster-log.js
··· 79 79 JX.DOM.setContent(contentNode, JX.$H(r.markup)); 80 80 } 81 81 82 - var uri = new JX.URI(config.renderURI); 82 + var uri = new JX.URI(config.initialURI); 83 83 84 84 new JX.Request(uri, onresponse) 85 85 .send();