Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php
| <?php | <?php | ||||
| final class PHUIDiffTableOfContentsListView extends AphrontView { | final class PHUIDiffTableOfContentsListView extends AphrontView { | ||||
| private $items = array(); | private $items = array(); | ||||
| private $authorityPackages; | private $authorityPackages; | ||||
| private $header; | private $header; | ||||
| private $infoView; | private $infoView; | ||||
| private $background; | private $background; | ||||
| private $bare; | private $bare; | ||||
| private $components = array(); | |||||
| public function addItem(PHUIDiffTableOfContentsItemView $item) { | public function addItem(PHUIDiffTableOfContentsItemView $item) { | ||||
| $this->items[] = $item; | $this->items[] = $item; | ||||
| return $this; | return $this; | ||||
| } | } | ||||
| public function setAuthorityPackages(array $authority_packages) { | public function setAuthorityPackages(array $authority_packages) { | ||||
| assert_instances_of($authority_packages, 'PhabricatorOwnersPackage'); | assert_instances_of($authority_packages, 'PhabricatorOwnersPackage'); | ||||
| $this->authorityPackages = $authority_packages; | $this->authorityPackages = $authority_packages; | ||||
| Show All 36 Lines | public function render() { | ||||
| if ($this->getAuthorityPackages()) { | if ($this->getAuthorityPackages()) { | ||||
| $authority = mpull($this->getAuthorityPackages(), null, 'getPHID'); | $authority = mpull($this->getAuthorityPackages(), null, 'getPHID'); | ||||
| } else { | } else { | ||||
| $authority = array(); | $authority = array(); | ||||
| } | } | ||||
| $items = $this->items; | $items = $this->items; | ||||
| $viewer = $this->getViewer(); | |||||
| $item_map = array(); | |||||
| $vector_tree = new ArcanistDiffVectorTree(); | |||||
| foreach ($items as $item) { | |||||
| $item->setViewer($viewer); | |||||
| $changeset = $item->getChangeset(); | |||||
| $old_vector = $changeset->getOldStatePathVector(); | |||||
| $new_vector = $changeset->getNewStatePathVector(); | |||||
| $tree_vector = $this->newTreeVector($old_vector, $new_vector); | |||||
| $item_map[implode("\n", $tree_vector)] = $item; | |||||
| $vector_tree->addVector($tree_vector); | |||||
| } | |||||
| $node_list = $vector_tree->newDisplayList(); | |||||
| $node_map = array(); | |||||
| foreach ($node_list as $node) { | |||||
| $path_vector = $node->getVector(); | |||||
| $path_vector = implode("\n", $path_vector); | |||||
| $node_map[$path_vector] = $node; | |||||
| } | |||||
| // Mark all nodes which contain at least one path which exists in the new | |||||
| // state. Nodes we don't mark contain only deleted or moved files, so they | |||||
| // can be rendered with a less-prominent style. | |||||
| foreach ($node_map as $node_key => $node) { | |||||
| $item = idx($item_map, $node_key); | |||||
| if (!$item) { | |||||
| continue; | |||||
| } | |||||
| $changeset = $item->getChangeset(); | |||||
| if (!$changeset->getIsLowImportanceChangeset()) { | |||||
| $node->setAncestralAttribute('important', true); | |||||
| } | |||||
| } | |||||
| $any_packages = false; | |||||
| $any_coverage = false; | |||||
| $any_context = false; | |||||
| $rows = array(); | $rows = array(); | ||||
| $rowc = array(); | $rowc = array(); | ||||
| foreach ($items as $item) { | foreach ($node_map as $node_key => $node) { | ||||
| $item->setUser($this->getUser()); | $display_vector = $node->getDisplayVector(); | ||||
| $rows[] = $item->render(); | $item = idx($item_map, $node_key); | ||||
| if ($item) { | |||||
| $changeset = $item->getChangeset(); | |||||
| $icon = $changeset->newFileTreeIcon(); | |||||
| } else { | |||||
| $changeset = null; | |||||
| $icon = id(new PHUIIconView()) | |||||
| ->setIcon('fa-folder-open-o grey'); | |||||
| } | |||||
| if ($node->getChildren()) { | |||||
| $old_dir = true; | |||||
| $new_dir = true; | |||||
| } else { | |||||
| // TODO: When properties are set on a directory in SVN directly, this | |||||
| // might be incorrect. | |||||
| $old_dir = false; | |||||
| $new_dir = false; | |||||
| } | |||||
| $display_view = $this->newComponentView( | |||||
| $icon, | |||||
| $display_vector, | |||||
| $old_dir, | |||||
| $new_dir, | |||||
| $item); | |||||
| $depth = $node->getDisplayDepth(); | |||||
| $style = sprintf('padding-left: %dpx;', $depth * 16); | |||||
| if ($item) { | |||||
| $packages = $item->renderPackages(); | |||||
| } else { | |||||
| $packages = null; | |||||
| } | |||||
| if ($packages) { | |||||
| $any_packages = true; | |||||
| } | |||||
| if ($item) { | |||||
| if ($item->getCoverage()) { | |||||
| $any_coverage = true; | |||||
| } | |||||
| $coverage = $item->renderCoverage(); | |||||
| $modified_coverage = $item->renderModifiedCoverage(); | |||||
| } else { | |||||
| $coverage = null; | |||||
| $modified_coverage = null; | |||||
| } | |||||
| if ($item) { | |||||
| $context = $item->getContext(); | |||||
| if ($context) { | |||||
| $any_context = true; | |||||
| } | |||||
| } else { | |||||
| $context = null; | |||||
| } | |||||
| if ($item) { | |||||
| $lines = $item->renderChangesetLines(); | |||||
| } else { | |||||
| $lines = null; | |||||
| } | |||||
| $rows[] = array( | |||||
| $context, | |||||
| phutil_tag( | |||||
| 'div', | |||||
| array( | |||||
| 'style' => $style, | |||||
| ), | |||||
| $display_view), | |||||
| $lines, | |||||
| $coverage, | |||||
| $modified_coverage, | |||||
| $packages, | |||||
| ); | |||||
| $classes = array(); | |||||
| $have_authority = false; | $have_authority = false; | ||||
| if ($item) { | |||||
| $packages = $item->getPackages(); | $packages = $item->getPackages(); | ||||
| if ($packages) { | if ($packages) { | ||||
| if (array_intersect_key($packages, $authority)) { | if (array_intersect_key($packages, $authority)) { | ||||
| $have_authority = true; | $have_authority = true; | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| if ($have_authority) { | if ($have_authority) { | ||||
| $rowc[] = 'highlighted'; | $classes[] = 'highlighted'; | ||||
| } else { | |||||
| $rowc[] = null; | |||||
| } | |||||
| } | } | ||||
| // Check if any item has content in these columns. If no item does, we'll | if (!$node->getAttribute('important')) { | ||||
| // just hide them. | $classes[] = 'diff-toc-low-importance-row'; | ||||
| $any_coverage = false; | |||||
| $any_context = false; | |||||
| $any_packages = false; | |||||
| foreach ($items as $item) { | |||||
| if ($item->getContext() !== null) { | |||||
| $any_context = true; | |||||
| } | } | ||||
| if (strlen($item->getCoverage())) { | if ($changeset) { | ||||
| $any_coverage = true; | $classes[] = 'diff-toc-changeset-row'; | ||||
| } else { | |||||
| $classes[] = 'diff-toc-no-changeset-row'; | |||||
| } | } | ||||
| if ($item->getPackages() !== null) { | $rowc[] = implode(' ', $classes); | ||||
| $any_packages = true; | |||||
| } | |||||
| } | } | ||||
| $table = id(new AphrontTableView($rows)) | $table = id(new AphrontTableView($rows)) | ||||
| ->setRowClasses($rowc) | ->setRowClasses($rowc) | ||||
| ->setClassName('aphront-table-view-compact') | |||||
| ->setHeaders( | ->setHeaders( | ||||
| array( | array( | ||||
| null, | null, | ||||
| null, | |||||
| null, | |||||
| null, | |||||
| pht('Path'), | pht('Path'), | ||||
| pht('Size'), | |||||
| pht('Coverage (All)'), | pht('Coverage (All)'), | ||||
| pht('Coverage (Touched)'), | pht('Coverage (Touched)'), | ||||
| pht('Packages'), | pht('Packages'), | ||||
| )) | )) | ||||
| ->setColumnClasses( | ->setColumnClasses( | ||||
| array( | array( | ||||
| null, | null, | ||||
| 'differential-toc-char center', | 'diff-toc-path wide', | ||||
| 'differential-toc-prop center', | 'right', | ||||
| 'differential-toc-ftype center', | |||||
| 'differential-toc-file wide', | |||||
| 'differential-toc-cov', | 'differential-toc-cov', | ||||
| 'differential-toc-cov', | 'differential-toc-cov', | ||||
| null, | null, | ||||
| )) | )) | ||||
| ->setColumnVisibility( | ->setColumnVisibility( | ||||
| array( | array( | ||||
| $any_context, | $any_context, | ||||
| true, | true, | ||||
| true, | true, | ||||
| true, | |||||
| true, | |||||
| $any_coverage, | $any_coverage, | ||||
| $any_coverage, | $any_coverage, | ||||
| $any_packages, | $any_packages, | ||||
| )) | )) | ||||
| ->setDeviceVisibility( | ->setDeviceVisibility( | ||||
| array( | array( | ||||
| true, | true, | ||||
| true, | true, | ||||
| true, | false, | ||||
| true, | |||||
| true, | |||||
| false, | false, | ||||
| false, | false, | ||||
| true, | true, | ||||
| )); | )); | ||||
| $anchor = id(new PhabricatorAnchorView()) | $anchor = id(new PhabricatorAnchorView()) | ||||
| ->setAnchorName('toc') | ->setAnchorName('toc') | ||||
| ->setNavigationMarker(true); | ->setNavigationMarker(true); | ||||
| Show All 13 Lines | $box = id(new PHUIObjectBoxView()) | ||||
| ->setHeader($header) | ->setHeader($header) | ||||
| ->setBackground($this->background) | ->setBackground($this->background) | ||||
| ->setTable($table) | ->setTable($table) | ||||
| ->appendChild($anchor); | ->appendChild($anchor); | ||||
| if ($this->infoView) { | if ($this->infoView) { | ||||
| $box->setInfoView($this->infoView); | $box->setInfoView($this->infoView); | ||||
| } | } | ||||
| return $box; | return $box; | ||||
| } | } | ||||
| private function newTreeVector($old, $new) { | |||||
| if ($old === null && $new === null) { | |||||
| throw new Exception(pht('Changeset has no path vectors!')); | |||||
| } | |||||
| $vector = null; | |||||
| if ($old === null) { | |||||
| $vector = $new; | |||||
| } else if ($new === null) { | |||||
| $vector = $old; | |||||
| } else if ($old === $new) { | |||||
| $vector = $new; | |||||
| } | |||||
| if ($vector) { | |||||
| foreach ($vector as $k => $v) { | |||||
| $vector[$k] = $this->newScalarComponent($v); | |||||
| } | |||||
| return $vector; | |||||
| } | |||||
| $matrix = id(new PhutilEditDistanceMatrix()) | |||||
| ->setSequences($old, $new) | |||||
| ->setComputeString(true); | |||||
| $edits = $matrix->getEditString(); | |||||
| // If the edit sequence contains deletions followed by edits, move | |||||
| // the deletions to the end to left-align the new path. | |||||
| $edits = preg_replace('/(d+)(x+)/', '\2\1', $edits); | |||||
| $vector = array(); | |||||
| $length = strlen($edits); | |||||
| $old_cursor = 0; | |||||
| $new_cursor = 0; | |||||
| for ($ii = 0; $ii < strlen($edits); $ii++) { | |||||
| $c = $edits[$ii]; | |||||
| switch ($c) { | |||||
| case 'i': | |||||
| $vector[] = $this->newPairComponent(null, $new[$new_cursor]); | |||||
| $new_cursor++; | |||||
| break; | |||||
| case 'd': | |||||
| $vector[] = $this->newPairComponent($old[$old_cursor], null); | |||||
| $old_cursor++; | |||||
| break; | |||||
| case 's': | |||||
| case 'x': | |||||
| case 't': | |||||
| $vector[] = $this->newPairComponent( | |||||
| $old[$old_cursor], | |||||
| $new[$new_cursor]); | |||||
| $old_cursor++; | |||||
| $new_cursor++; | |||||
| break; | |||||
| default: | |||||
| throw new Exception(pht('Unknown edit string "%s"!', $c)); | |||||
| } | |||||
| } | |||||
| return $vector; | |||||
| } | |||||
| private function newScalarComponent($v) { | |||||
| $key = sprintf('path(%s)', $v); | |||||
| if (!isset($this->components[$key])) { | |||||
| $this->components[$key] = $v; | |||||
| } | |||||
| return $key; | |||||
| } | |||||
| private function newPairComponent($u, $v) { | |||||
| if ($u === $v) { | |||||
| return $this->newScalarComponent($u); | |||||
| } | |||||
| $key = sprintf('pair(%s > %s)', $u, $v); | |||||
| if (!isset($this->components[$key])) { | |||||
| $this->components[$key] = array($u, $v); | |||||
| } | |||||
| return $key; | |||||
| } | |||||
| private function newComponentView( | |||||
| $icon, | |||||
| array $keys, | |||||
| $old_dir, | |||||
| $new_dir, | |||||
| $item) { | |||||
| $is_simple = true; | |||||
| $items = array(); | |||||
| foreach ($keys as $key) { | |||||
| $component = $this->components[$key]; | |||||
| if (is_array($component)) { | |||||
| $is_simple = false; | |||||
| } else { | |||||
| $component = array( | |||||
| $component, | |||||
| $component, | |||||
| ); | |||||
| } | |||||
| $items[] = $component; | |||||
| } | |||||
| $move_icon = id(new PHUIIconView()) | |||||
| ->setIcon('fa-angle-double-right pink'); | |||||
| $old_row = array( | |||||
| phutil_tag('td', array(), $move_icon), | |||||
| ); | |||||
| $new_row = array( | |||||
| phutil_tag('td', array(), $icon), | |||||
| ); | |||||
| $last_old_key = null; | |||||
| $last_new_key = null; | |||||
| foreach ($items as $key => $component) { | |||||
| if (!is_array($component)) { | |||||
| $last_old_key = $key; | |||||
| $last_new_key = $key; | |||||
| } else { | |||||
| if ($component[0] !== null) { | |||||
| $last_old_key = $key; | |||||
| } | |||||
| if ($component[1] !== null) { | |||||
| $last_new_key = $key; | |||||
| } | |||||
| } | |||||
| } | |||||
| foreach ($items as $key => $component) { | |||||
| if (!is_array($component)) { | |||||
| $old = $component; | |||||
| $new = $component; | |||||
| } else { | |||||
| $old = $component[0]; | |||||
| $new = $component[1]; | |||||
| } | |||||
| $old_classes = array(); | |||||
| $new_classes = array(); | |||||
| if ($old === $new) { | |||||
| // Do nothing. | |||||
| } else if ($old === null) { | |||||
| $new_classes[] = 'diff-path-component-new'; | |||||
| } else if ($new === null) { | |||||
| $old_classes[] = 'diff-path-component-old'; | |||||
| } else { | |||||
| $old_classes[] = 'diff-path-component-old'; | |||||
| $new_classes[] = 'diff-path-component-new'; | |||||
| } | |||||
| if ($old !== null) { | |||||
| if (($key === $last_old_key) && !$old_dir) { | |||||
| // Do nothing. | |||||
| } else { | |||||
| $old = $old.'/'; | |||||
| } | |||||
| } | |||||
| if ($new !== null) { | |||||
| if (($key === $last_new_key) && $item) { | |||||
| $new = $item->newLink(); | |||||
| } else if (($key === $last_new_key) && !$new_dir) { | |||||
| // Do nothing. | |||||
| } else { | |||||
| $new = $new.'/'; | |||||
| } | |||||
| } | |||||
| $old_row[] = phutil_tag( | |||||
| 'td', | |||||
| array(), | |||||
| phutil_tag( | |||||
| 'div', | |||||
| array( | |||||
| 'class' => implode(' ', $old_classes), | |||||
| ), | |||||
| $old)); | |||||
| $new_row[] = phutil_tag( | |||||
| 'td', | |||||
| array(), | |||||
| phutil_tag( | |||||
| 'div', | |||||
| array( | |||||
| 'class' => implode(' ', $new_classes), | |||||
| ), | |||||
| $new)); | |||||
| } | |||||
| $old_row = phutil_tag( | |||||
| 'tr', | |||||
| array( | |||||
| 'class' => 'diff-path-old', | |||||
| ), | |||||
| $old_row); | |||||
| $new_row = phutil_tag( | |||||
| 'tr', | |||||
| array( | |||||
| 'class' => 'diff-path-new', | |||||
| ), | |||||
| $new_row); | |||||
| $rows = array(); | |||||
| $rows[] = $new_row; | |||||
| if (!$is_simple) { | |||||
| $rows[] = $old_row; | |||||
| } | |||||
| $body = phutil_tag('tbody', array(), $rows); | |||||
| $table = phutil_tag( | |||||
| 'table', | |||||
| array( | |||||
| ), | |||||
| $body); | |||||
| return $table; | |||||
| } | |||||
| } | } | ||||