All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jgit.revplot.PlotCommitList Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2008, Shawn O. Pearce ,
 * Copyright (C) 2010, Christian Halstrick 
 * Copyright (C) 2014, Konrad Kügler
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.revplot;

import java.text.MessageFormat;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeSet;

import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.revwalk.RevCommitList;
import org.eclipse.jgit.revwalk.RevWalk;

/**
 * An ordered list of {@link PlotCommit} subclasses.
 * 

* Commits are allocated into lanes as they enter the list, based upon their * connections between descendant (child) commits and ancestor (parent) commits. *

* The source of the list must be a {@link PlotWalk} and {@link #fillTo(int)} * must be used to populate the list. * * @param * type of lane used by the application. */ public class PlotCommitList extends RevCommitList> { static final int MAX_LENGTH = 25; private int positionsAllocated; private final TreeSet freePositions = new TreeSet(); private final HashSet activeLanes = new HashSet(32); /** number of (child) commits on a lane */ private final HashMap laneLength = new HashMap( 32); @Override public void clear() { super.clear(); positionsAllocated = 0; freePositions.clear(); activeLanes.clear(); laneLength.clear(); } @Override public void source(final RevWalk w) { if (!(w instanceof PlotWalk)) throw new ClassCastException(MessageFormat.format(JGitText.get().classCastNotA, PlotWalk.class.getName())); super.source(w); } /** * Find the set of lanes passing through a commit's row. *

* Lanes passing through a commit are lanes that the commit is not directly * on, but that need to travel through this commit to connect a descendant * (child) commit to an ancestor (parent) commit. Typically these lanes will * be drawn as lines in the passed commit's box, and the passed commit won't * appear to be connected to those lines. *

* This method modifies the passed collection by adding the lanes in any * order. * * @param currCommit * the commit the caller needs to get the lanes from. * @param result * collection to add the passing lanes into. */ public void findPassingThrough(final PlotCommit currCommit, final Collection result) { for (final PlotLane p : currCommit.passingLanes) result.add((L) p); } @Override protected void enter(final int index, final PlotCommit currCommit) { setupChildren(currCommit); final int nChildren = currCommit.getChildCount(); if (nChildren == 0) { currCommit.lane = nextFreeLane(); continueActiveLanes(currCommit); return; } if (nChildren == 1 && currCommit.children[0].getParentCount() < 2) { // Only one child, child has only us as their parent. // Stay in the same lane as the child. @SuppressWarnings("unchecked") final PlotCommit c = currCommit.children[0]; currCommit.lane = c.lane; Integer len = laneLength.get(currCommit.lane); len = Integer.valueOf(len.intValue() + 1); laneLength.put(currCommit.lane, len); } else { // More than one child, or our child is a merge. // We look for the child lane the current commit should continue. // Candidate lanes for this are those with children, that have the // current commit as their first parent. // There can be multiple candidate lanes. In that case the longest // lane is chosen, as this is usually the lane representing the // branch the commit actually was made on. // When there are no candidate lanes (i.e. the current commit has // only children whose non-first parent it is) we place the current // commit on a new lane. // The lane the current commit will be placed on: PlotLane reservedLane = null; PlotCommit childOnReservedLane = null; int lengthOfReservedLane = -1; for (int i = 0; i < nChildren; i++) { @SuppressWarnings("unchecked") final PlotCommit c = currCommit.children[i]; if (c.getParent(0) == currCommit) { Integer len = laneLength.get(c.lane); // we may be the first parent for multiple lines of // development, try to continue the longest one if (len.intValue() > lengthOfReservedLane) { reservedLane = c.lane; childOnReservedLane = c; lengthOfReservedLane = len.intValue(); } } } if (reservedLane != null) { currCommit.lane = reservedLane; laneLength.put(reservedLane, Integer.valueOf(lengthOfReservedLane + 1)); handleBlockedLanes(index, currCommit, childOnReservedLane); } else { currCommit.lane = nextFreeLane(); handleBlockedLanes(index, currCommit, null); } // close lanes of children, if there are no first parents that might // want to continue the child lanes for (int i = 0; i < nChildren; i++) { final PlotCommit c = currCommit.children[i]; PlotCommit firstParent = (PlotCommit) c.getParent(0); if (firstParent.lane != null && firstParent.lane != c.lane) closeLane(c.lane); } } continueActiveLanes(currCommit); } private void continueActiveLanes(final PlotCommit currCommit) { for (PlotLane lane : activeLanes) if (lane != currCommit.lane) currCommit.addPassingLane(lane); } /** * Sets up fork and merge information in the involved PlotCommits. * Recognizes and handles blockades that involve forking or merging arcs. * * @param index * the index of currCommit in the list * @param currCommit * @param childOnLane * the direct child on the same lane as currCommit, * may be null if currCommit is the first commit on * the lane */ private void handleBlockedLanes(final int index, final PlotCommit currCommit, final PlotCommit childOnLane) { for (PlotCommit child : currCommit.children) { if (child == childOnLane) continue; // simple continuations of lanes are handled by // continueActiveLanes() calls in enter() // Is the child a merge or is it forking off? boolean childIsMerge = child.getParent(0) != currCommit; if (childIsMerge) { PlotLane laneToUse = currCommit.lane; laneToUse = handleMerge(index, currCommit, childOnLane, child, laneToUse); child.addMergingLane(laneToUse); } else { // We want to draw a forking arc in the child's lane. // As an active lane, the child lane already continues // (unblocked) up to this commit, we only need to mark it as // forking off from the current commit. PlotLane laneToUse = child.lane; currCommit.addForkingOffLane(laneToUse); } } } // Handles the case where currCommit is a non-first parent of the child private PlotLane handleMerge(final int index, final PlotCommit currCommit, final PlotCommit childOnLane, PlotCommit child, PlotLane laneToUse) { // find all blocked positions between currCommit and this child int childIndex = index; // useless initialization, should // always be set in the loop below BitSet blockedPositions = new BitSet(); for (int r = index - 1; r >= 0; r--) { final PlotCommit rObj = get(r); if (rObj == child) { childIndex = r; break; } addBlockedPosition(blockedPositions, rObj); } // handle blockades if (blockedPositions.get(laneToUse.getPosition())) { // We want to draw a merging arc in our lane to the child, // which is on another lane, but our lane is blocked. // Check if childOnLane is beetween commit and the child we // are currently processing boolean needDetour = false; if (childOnLane != null) { for (int r = index - 1; r > childIndex; r--) { final PlotCommit rObj = get(r); if (rObj == childOnLane) { needDetour = true; break; } } } if (needDetour) { // It is childOnLane which is blocking us. Repositioning // our lane would not help, because this repositions the // child too, keeping the blockade. // Instead, we create a "detour lane" which gets us // around the blockade. That lane has no commits on it. laneToUse = nextFreeLane(blockedPositions); currCommit.addForkingOffLane(laneToUse); closeLane(laneToUse); } else { // The blockade is (only) due to other (already closed) // lanes at the current lane's position. In this case we // reposition the current lane. // We are the first commit on this lane, because // otherwise the child commit on this lane would have // kept other lanes from blocking us. Since we are the // first commit, we can freely reposition. int newPos = getFreePosition(blockedPositions); freePositions.add(Integer.valueOf(laneToUse .getPosition())); laneToUse.position = newPos; } } // Actually connect currCommit to the merge child drawLaneToChild(index, child, laneToUse); return laneToUse; } /** * Connects the commit at commitIndex to the child, using the given lane. * All blockades on the lane must be resolved before calling this method. * * @param commitIndex * @param child * @param laneToContinue */ private void drawLaneToChild(final int commitIndex, PlotCommit child, PlotLane laneToContinue) { for (int r = commitIndex - 1; r >= 0; r--) { final PlotCommit rObj = get(r); if (rObj == child) break; if (rObj != null) rObj.addPassingLane(laneToContinue); } } private static void addBlockedPosition(BitSet blockedPositions, final PlotCommit rObj) { if (rObj != null) { PlotLane lane = rObj.getLane(); // Positions may be blocked by a commit on a lane. if (lane != null) blockedPositions.set(lane.getPosition()); // Positions may also be blocked by forking off and merging lanes. // We don't consider passing lanes, because every passing lane forks // off and merges at it ends. for (PlotLane l : rObj.forkingOffLanes) blockedPositions.set(l.getPosition()); for (PlotLane l : rObj.mergingLanes) blockedPositions.set(l.getPosition()); } } private void closeLane(PlotLane lane) { if (activeLanes.remove(lane)) { recycleLane((L) lane); laneLength.remove(lane); freePositions.add(Integer.valueOf(lane.getPosition())); } } private void setupChildren(final PlotCommit currCommit) { final int nParents = currCommit.getParentCount(); for (int i = 0; i < nParents; i++) ((PlotCommit) currCommit.getParent(i)).addChild(currCommit); } private PlotLane nextFreeLane() { return nextFreeLane(null); } private PlotLane nextFreeLane(BitSet blockedPositions) { final PlotLane p = createLane(); p.position = getFreePosition(blockedPositions); activeLanes.add(p); laneLength.put(p, Integer.valueOf(1)); return p; } /** * @param blockedPositions * may be null * @return a free lane position */ private int getFreePosition(BitSet blockedPositions) { if (freePositions.isEmpty()) return positionsAllocated++; if (blockedPositions != null) { for (Integer pos : freePositions) if (!blockedPositions.get(pos.intValue())) { freePositions.remove(pos); return pos.intValue(); } return positionsAllocated++; } else { final Integer min = freePositions.first(); freePositions.remove(min); return min.intValue(); } } /** * @return a new Lane appropriate for this particular PlotList. */ protected L createLane() { return (L) new PlotLane(); } /** * Return colors and other reusable information to the plotter when a lane * is no longer needed. * * @param lane */ protected void recycleLane(final L lane) { // Nothing. } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy