[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/diffusion/view/ -> DiffusionHistoryTableView.php (source)

   1  <?php
   2  
   3  final class DiffusionHistoryTableView extends DiffusionView {
   4  
   5    private $history;
   6    private $revisions = array();
   7    private $handles = array();
   8    private $isHead;
   9    private $parents;
  10    private $buildCache;
  11  
  12    public function setHistory(array $history) {
  13      assert_instances_of($history, 'DiffusionPathChange');
  14      $this->history = $history;
  15      $this->buildCache = null;
  16      return $this;
  17    }
  18  
  19    public function loadRevisions() {
  20      $commit_phids = array();
  21      foreach ($this->history as $item) {
  22        if ($item->getCommit()) {
  23          $commit_phids[] = $item->getCommit()->getPHID();
  24        }
  25      }
  26  
  27      // TODO: Get rid of this.
  28      $this->revisions = id(new DifferentialRevision())
  29        ->loadIDsByCommitPHIDs($commit_phids);
  30      return $this;
  31    }
  32  
  33    public function setHandles(array $handles) {
  34      assert_instances_of($handles, 'PhabricatorObjectHandle');
  35      $this->handles = $handles;
  36      return $this;
  37    }
  38  
  39    public function getRequiredHandlePHIDs() {
  40      $phids = array();
  41      foreach ($this->history as $item) {
  42        $data = $item->getCommitData();
  43        if ($data) {
  44          if ($data->getCommitDetail('authorPHID')) {
  45            $phids[$data->getCommitDetail('authorPHID')] = true;
  46          }
  47          if ($data->getCommitDetail('committerPHID')) {
  48            $phids[$data->getCommitDetail('committerPHID')] = true;
  49          }
  50        }
  51      }
  52      return array_keys($phids);
  53    }
  54  
  55    public function setParents(array $parents) {
  56      $this->parents = $parents;
  57      return $this;
  58    }
  59  
  60    public function setIsHead($is_head) {
  61      $this->isHead = $is_head;
  62      return $this;
  63    }
  64  
  65    public function loadBuildablesOnDemand() {
  66      if ($this->buildCache !== null) {
  67        return $this->buildCache;
  68      }
  69  
  70      $commits_to_builds = array();
  71  
  72      $commits = mpull($this->history, 'getCommit');
  73  
  74      $commit_phids = mpull($commits, 'getPHID');
  75  
  76      $buildables = id(new HarbormasterBuildableQuery())
  77        ->setViewer($this->getUser())
  78        ->withBuildablePHIDs($commit_phids)
  79        ->withManualBuildables(false)
  80        ->execute();
  81  
  82      $this->buildCache = mpull($buildables, null, 'getBuildablePHID');
  83  
  84      return $this->buildCache;
  85    }
  86  
  87    public function render() {
  88      $drequest = $this->getDiffusionRequest();
  89  
  90      $handles = $this->handles;
  91  
  92      $graph = null;
  93      if ($this->parents) {
  94        $graph = $this->renderGraph();
  95      }
  96  
  97      $show_builds = PhabricatorApplication::isClassInstalledForViewer(
  98        'PhabricatorHarbormasterApplication',
  99        $this->getUser());
 100  
 101      $rows = array();
 102      $ii = 0;
 103      foreach ($this->history as $history) {
 104        $epoch = $history->getEpoch();
 105  
 106        if ($epoch) {
 107          $date = phabricator_date($epoch, $this->user);
 108          $time = phabricator_time($epoch, $this->user);
 109        } else {
 110          $date = null;
 111          $time = null;
 112        }
 113  
 114        $data = $history->getCommitData();
 115        $author_phid = $committer = $committer_phid = null;
 116        if ($data) {
 117          $author_phid = $data->getCommitDetail('authorPHID');
 118          $committer_phid = $data->getCommitDetail('committerPHID');
 119          $committer = $data->getCommitDetail('committer');
 120        }
 121  
 122        if ($author_phid && isset($handles[$author_phid])) {
 123          $author = $handles[$author_phid]->renderLink();
 124        } else {
 125          $author = self::renderName($history->getAuthorName());
 126        }
 127  
 128        $different_committer = false;
 129        if ($committer_phid) {
 130          $different_committer = ($committer_phid != $author_phid);
 131        } else if ($committer != '') {
 132          $different_committer = ($committer != $history->getAuthorName());
 133        }
 134        if ($different_committer) {
 135          if ($committer_phid && isset($handles[$committer_phid])) {
 136            $committer = $handles[$committer_phid]->renderLink();
 137          } else {
 138            $committer = self::renderName($committer);
 139          }
 140          $author = hsprintf('%s/%s', $author, $committer);
 141        }
 142  
 143        // We can show details once the message and change have been imported.
 144        $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
 145                          PhabricatorRepositoryCommit::IMPORTED_CHANGE;
 146  
 147        $commit = $history->getCommit();
 148        if ($commit && $commit->isPartiallyImported($partial_import) && $data) {
 149          $summary = AphrontTableView::renderSingleDisplayLine(
 150            $history->getSummary());
 151        } else {
 152          $summary = phutil_tag('em', array(), "Importing\xE2\x80\xA6");
 153        }
 154  
 155        $build = null;
 156        if ($show_builds) {
 157          $buildable_lookup = $this->loadBuildablesOnDemand();
 158          $buildable = idx($buildable_lookup, $commit->getPHID());
 159          if ($buildable !== null) {
 160            $icon = HarbormasterBuildable::getBuildableStatusIcon(
 161              $buildable->getBuildableStatus());
 162            $color = HarbormasterBuildable::getBuildableStatusColor(
 163              $buildable->getBuildableStatus());
 164            $name = HarbormasterBuildable::getBuildableStatusName(
 165              $buildable->getBuildableStatus());
 166  
 167            $icon_view = id(new PHUIIconView())
 168              ->setIconFont($icon.' '.$color);
 169  
 170            $tooltip_view = javelin_tag(
 171              'span',
 172              array(
 173                'sigil' => 'has-tooltip',
 174                'meta' => array('tip' => $name),
 175              ),
 176              $icon_view);
 177  
 178            Javelin::initBehavior('phabricator-tooltips');
 179  
 180            $href_view = phutil_tag(
 181              'a',
 182              array('href' => '/'.$buildable->getMonogram()),
 183              $tooltip_view);
 184  
 185            $build = $href_view;
 186  
 187            $has_any_build = true;
 188          }
 189        }
 190  
 191        $rows[] = array(
 192          $graph ? $graph[$ii++] : null,
 193          self::linkCommit(
 194            $drequest->getRepository(),
 195            $history->getCommitIdentifier()),
 196          $build,
 197          ($commit ?
 198            self::linkRevision(idx($this->revisions, $commit->getPHID())) :
 199            null),
 200          $author,
 201          $summary,
 202          $date,
 203          $time,
 204        );
 205      }
 206  
 207      $view = new AphrontTableView($rows);
 208      $view->setHeaders(
 209        array(
 210          '',
 211          pht('Commit'),
 212          '',
 213          pht('Revision'),
 214          pht('Author/Committer'),
 215          pht('Details'),
 216          pht('Date'),
 217          pht('Time'),
 218        ));
 219      $view->setColumnClasses(
 220        array(
 221          'threads',
 222          'n',
 223          'icon',
 224          'n',
 225          '',
 226          'wide',
 227          '',
 228          'right',
 229        ));
 230      $view->setColumnVisibility(
 231        array(
 232          $graph ? true : false,
 233        ));
 234      $view->setDeviceVisibility(
 235        array(
 236          $graph ? true : false,
 237          true,
 238          true,
 239          true,
 240          false,
 241          true,
 242          false,
 243          false,
 244        ));
 245      return $view->render();
 246    }
 247  
 248    /**
 249     * Draw a merge/branch graph from the parent revision data. We're basically
 250     * building up a bunch of strings like this:
 251     *
 252     *  ^
 253     *  |^
 254     *  o|
 255     *  |o
 256     *  o
 257     *
 258     * ...which form an ASCII representation of the graph we eventually want to
 259     * draw.
 260     *
 261     * NOTE: The actual implementation is black magic.
 262     */
 263    private function renderGraph() {
 264      // This keeps our accumulated information about each line of the
 265      // merge/branch graph.
 266      $graph = array();
 267  
 268      // This holds the next commit we're looking for in each column of the
 269      // graph.
 270      $threads = array();
 271  
 272      // This is the largest number of columns any row has, i.e. the width of
 273      // the graph.
 274      $count = 0;
 275  
 276      foreach ($this->history as $key => $history) {
 277        $joins = array();
 278        $splits = array();
 279  
 280        $parent_list = $this->parents[$history->getCommitIdentifier()];
 281  
 282        // Look for some thread which has this commit as the next commit. If
 283        // we find one, this commit goes on that thread. Otherwise, this commit
 284        // goes on a new thread.
 285  
 286        $line = '';
 287        $found = false;
 288        $pos = count($threads);
 289        for ($n = 0; $n < $count; $n++) {
 290          if (empty($threads[$n])) {
 291            $line .= ' ';
 292            continue;
 293          }
 294  
 295          if ($threads[$n] == $history->getCommitIdentifier()) {
 296            if ($found) {
 297              $line .= ' ';
 298              $joins[] = $n;
 299              unset($threads[$n]);
 300            } else {
 301              $line .= 'o';
 302              $found = true;
 303              $pos = $n;
 304            }
 305          } else {
 306  
 307            // We render a "|" for any threads which have a commit that we haven't
 308            // seen yet, this is later drawn as a vertical line.
 309            $line .= '|';
 310          }
 311        }
 312  
 313        // If we didn't find the thread this commit goes on, start a new thread.
 314        // We use "o" to mark the commit for the rendering engine, or "^" to
 315        // indicate that there's nothing after it so the line from the commit
 316        // upward should not be drawn.
 317  
 318        if (!$found) {
 319          if ($this->isHead) {
 320            $line .= '^';
 321          } else {
 322            $line .= 'o';
 323            foreach ($graph as $k => $meta) {
 324              // Go back across all the lines we've already drawn and add a
 325              // "|" to the end, since this is connected to some future commit
 326              // we don't know about.
 327              for ($jj = strlen($meta['line']); $jj <= $count; $jj++) {
 328                $graph[$k]['line'] .= '|';
 329              }
 330            }
 331          }
 332        }
 333  
 334        // Update the next commit on this thread to the commit's first parent.
 335        // This might have the effect of making a new thread.
 336        $threads[$pos] = head($parent_list);
 337  
 338        // If we made a new thread, increase the thread count.
 339        $count = max($pos + 1, $count);
 340  
 341        // Now, deal with splits (merges). I picked this terms opposite to the
 342        // underlying repository term to confuse you.
 343        foreach (array_slice($parent_list, 1) as $parent) {
 344          $found = false;
 345  
 346          // Try to find the other parent(s) in our existing threads. If we find
 347          // them, split to that thread.
 348  
 349          foreach ($threads as $idx => $thread_commit) {
 350            if ($thread_commit == $parent) {
 351              $found = true;
 352              $splits[] = $idx;
 353            }
 354          }
 355  
 356          // If we didn't find the parent, we don't know about it yet. Find the
 357          // first free thread and add it as the "next" commit in that thread.
 358          // This might create a new thread.
 359  
 360          if (!$found) {
 361            for ($n = 0; $n < $count; $n++) {
 362              if (empty($threads[$n])) {
 363                break;
 364              }
 365            }
 366            $threads[$n] = $parent;
 367            $splits[] = $n;
 368            $count = max($n + 1, $count);
 369          }
 370        }
 371  
 372        $graph[] = array(
 373          'line' => $line,
 374          'split' => $splits,
 375          'join' => $joins,
 376        );
 377      }
 378  
 379      // Render into tags for the behavior.
 380  
 381      foreach ($graph as $k => $meta) {
 382        $graph[$k] = javelin_tag(
 383          'div',
 384          array(
 385            'sigil' => 'commit-graph',
 386            'meta' => $meta,
 387          ),
 388          '');
 389      }
 390  
 391      Javelin::initBehavior(
 392        'diffusion-commit-graph',
 393        array(
 394          'count' => $count,
 395        ));
 396  
 397      return $graph;
 398    }
 399  
 400  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1