You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PlotCommitList.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>,
  3. * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  4. * Copyright (C) 2014, Konrad Kügler
  5. * and other copyright owners as documented in the project's IP log.
  6. *
  7. * This program and the accompanying materials are made available
  8. * under the terms of the Eclipse Distribution License v1.0 which
  9. * accompanies this distribution, is reproduced below, and is
  10. * available at http://www.eclipse.org/org/documents/edl-v10.php
  11. *
  12. * All rights reserved.
  13. *
  14. * Redistribution and use in source and binary forms, with or
  15. * without modification, are permitted provided that the following
  16. * conditions are met:
  17. *
  18. * - Redistributions of source code must retain the above copyright
  19. * notice, this list of conditions and the following disclaimer.
  20. *
  21. * - Redistributions in binary form must reproduce the above
  22. * copyright notice, this list of conditions and the following
  23. * disclaimer in the documentation and/or other materials provided
  24. * with the distribution.
  25. *
  26. * - Neither the name of the Eclipse Foundation, Inc. nor the
  27. * names of its contributors may be used to endorse or promote
  28. * products derived from this software without specific prior
  29. * written permission.
  30. *
  31. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  32. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  33. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  34. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  35. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  36. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  37. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  38. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  39. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  40. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  41. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  42. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  43. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. */
  45. package org.eclipse.jgit.revplot;
  46. import java.text.MessageFormat;
  47. import java.util.BitSet;
  48. import java.util.Collection;
  49. import java.util.HashMap;
  50. import java.util.HashSet;
  51. import java.util.TreeSet;
  52. import org.eclipse.jgit.internal.JGitText;
  53. import org.eclipse.jgit.revwalk.RevCommitList;
  54. import org.eclipse.jgit.revwalk.RevWalk;
  55. /**
  56. * An ordered list of {@link PlotCommit} subclasses.
  57. * <p>
  58. * Commits are allocated into lanes as they enter the list, based upon their
  59. * connections between descendant (child) commits and ancestor (parent) commits.
  60. * <p>
  61. * The source of the list must be a {@link PlotWalk} and {@link #fillTo(int)}
  62. * must be used to populate the list.
  63. *
  64. * @param <L>
  65. * type of lane used by the application.
  66. */
  67. public class PlotCommitList<L extends PlotLane> extends
  68. RevCommitList<PlotCommit<L>> {
  69. static final int MAX_LENGTH = 25;
  70. private int positionsAllocated;
  71. private final TreeSet<Integer> freePositions = new TreeSet<Integer>();
  72. private final HashSet<PlotLane> activeLanes = new HashSet<PlotLane>(32);
  73. /** number of (child) commits on a lane */
  74. private final HashMap<PlotLane, Integer> laneLength = new HashMap<PlotLane, Integer>(
  75. 32);
  76. @Override
  77. public void clear() {
  78. super.clear();
  79. positionsAllocated = 0;
  80. freePositions.clear();
  81. activeLanes.clear();
  82. laneLength.clear();
  83. }
  84. @Override
  85. public void source(final RevWalk w) {
  86. if (!(w instanceof PlotWalk))
  87. throw new ClassCastException(MessageFormat.format(JGitText.get().classCastNotA, PlotWalk.class.getName()));
  88. super.source(w);
  89. }
  90. /**
  91. * Find the set of lanes passing through a commit's row.
  92. * <p>
  93. * Lanes passing through a commit are lanes that the commit is not directly
  94. * on, but that need to travel through this commit to connect a descendant
  95. * (child) commit to an ancestor (parent) commit. Typically these lanes will
  96. * be drawn as lines in the passed commit's box, and the passed commit won't
  97. * appear to be connected to those lines.
  98. * <p>
  99. * This method modifies the passed collection by adding the lanes in any
  100. * order.
  101. *
  102. * @param currCommit
  103. * the commit the caller needs to get the lanes from.
  104. * @param result
  105. * collection to add the passing lanes into.
  106. */
  107. @SuppressWarnings("unchecked")
  108. public void findPassingThrough(final PlotCommit<L> currCommit,
  109. final Collection<L> result) {
  110. for (final PlotLane p : currCommit.passingLanes)
  111. result.add((L) p);
  112. }
  113. @Override
  114. protected void enter(final int index, final PlotCommit<L> currCommit) {
  115. setupChildren(currCommit);
  116. final int nChildren = currCommit.getChildCount();
  117. if (nChildren == 0) {
  118. currCommit.lane = nextFreeLane();
  119. } else if (nChildren == 1
  120. && currCommit.children[0].getParentCount() < 2) {
  121. // Only one child, child has only us as their parent.
  122. // Stay in the same lane as the child.
  123. @SuppressWarnings("unchecked")
  124. final PlotCommit<L> c = currCommit.children[0];
  125. currCommit.lane = c.lane;
  126. Integer len = laneLength.get(currCommit.lane);
  127. len = Integer.valueOf(len.intValue() + 1);
  128. laneLength.put(currCommit.lane, len);
  129. } else {
  130. // More than one child, or our child is a merge.
  131. // We look for the child lane the current commit should continue.
  132. // Candidate lanes for this are those with children, that have the
  133. // current commit as their first parent.
  134. // There can be multiple candidate lanes. In that case the longest
  135. // lane is chosen, as this is usually the lane representing the
  136. // branch the commit actually was made on.
  137. // When there are no candidate lanes (i.e. the current commit has
  138. // only children whose non-first parent it is) we place the current
  139. // commit on a new lane.
  140. // The lane the current commit will be placed on:
  141. PlotLane reservedLane = null;
  142. PlotCommit childOnReservedLane = null;
  143. int lengthOfReservedLane = -1;
  144. for (int i = 0; i < nChildren; i++) {
  145. @SuppressWarnings("unchecked")
  146. final PlotCommit<L> c = currCommit.children[i];
  147. if (c.getParent(0) == currCommit) {
  148. Integer len = laneLength.get(c.lane);
  149. // we may be the first parent for multiple lines of
  150. // development, try to continue the longest one
  151. if (len.intValue() > lengthOfReservedLane) {
  152. reservedLane = c.lane;
  153. childOnReservedLane = c;
  154. lengthOfReservedLane = len.intValue();
  155. }
  156. }
  157. }
  158. if (reservedLane != null) {
  159. currCommit.lane = reservedLane;
  160. laneLength.put(reservedLane,
  161. Integer.valueOf(lengthOfReservedLane + 1));
  162. handleBlockedLanes(index, currCommit, childOnReservedLane);
  163. } else {
  164. currCommit.lane = nextFreeLane();
  165. handleBlockedLanes(index, currCommit, null);
  166. }
  167. // close lanes of children, if there are no first parents that might
  168. // want to continue the child lanes
  169. for (int i = 0; i < nChildren; i++) {
  170. final PlotCommit c = currCommit.children[i];
  171. PlotCommit firstParent = (PlotCommit) c.getParent(0);
  172. if (firstParent.lane != null && firstParent.lane != c.lane)
  173. closeLane(c.lane);
  174. }
  175. }
  176. continueActiveLanes(currCommit);
  177. if (currCommit.getParentCount() == 0)
  178. closeLane(currCommit.lane);
  179. }
  180. private void continueActiveLanes(final PlotCommit currCommit) {
  181. for (PlotLane lane : activeLanes)
  182. if (lane != currCommit.lane)
  183. currCommit.addPassingLane(lane);
  184. }
  185. /**
  186. * Sets up fork and merge information in the involved PlotCommits.
  187. * Recognizes and handles blockades that involve forking or merging arcs.
  188. *
  189. * @param index
  190. * the index of <code>currCommit</code> in the list
  191. * @param currCommit
  192. * @param childOnLane
  193. * the direct child on the same lane as <code>currCommit</code>,
  194. * may be null if <code>currCommit</code> is the first commit on
  195. * the lane
  196. */
  197. private void handleBlockedLanes(final int index, final PlotCommit currCommit,
  198. final PlotCommit childOnLane) {
  199. for (PlotCommit child : currCommit.children) {
  200. if (child == childOnLane)
  201. continue; // simple continuations of lanes are handled by
  202. // continueActiveLanes() calls in enter()
  203. // Is the child a merge or is it forking off?
  204. boolean childIsMerge = child.getParent(0) != currCommit;
  205. if (childIsMerge) {
  206. PlotLane laneToUse = currCommit.lane;
  207. laneToUse = handleMerge(index, currCommit, childOnLane, child,
  208. laneToUse);
  209. child.addMergingLane(laneToUse);
  210. } else {
  211. // We want to draw a forking arc in the child's lane.
  212. // As an active lane, the child lane already continues
  213. // (unblocked) up to this commit, we only need to mark it as
  214. // forking off from the current commit.
  215. PlotLane laneToUse = child.lane;
  216. currCommit.addForkingOffLane(laneToUse);
  217. }
  218. }
  219. }
  220. // Handles the case where currCommit is a non-first parent of the child
  221. private PlotLane handleMerge(final int index, final PlotCommit currCommit,
  222. final PlotCommit childOnLane, PlotCommit child, PlotLane laneToUse) {
  223. // find all blocked positions between currCommit and this child
  224. int childIndex = index; // useless initialization, should
  225. // always be set in the loop below
  226. BitSet blockedPositions = new BitSet();
  227. for (int r = index - 1; r >= 0; r--) {
  228. final PlotCommit rObj = get(r);
  229. if (rObj == child) {
  230. childIndex = r;
  231. break;
  232. }
  233. addBlockedPosition(blockedPositions, rObj);
  234. }
  235. // handle blockades
  236. if (blockedPositions.get(laneToUse.getPosition())) {
  237. // We want to draw a merging arc in our lane to the child,
  238. // which is on another lane, but our lane is blocked.
  239. // Check if childOnLane is beetween commit and the child we
  240. // are currently processing
  241. boolean needDetour = false;
  242. if (childOnLane != null) {
  243. for (int r = index - 1; r > childIndex; r--) {
  244. final PlotCommit rObj = get(r);
  245. if (rObj == childOnLane) {
  246. needDetour = true;
  247. break;
  248. }
  249. }
  250. }
  251. if (needDetour) {
  252. // It is childOnLane which is blocking us. Repositioning
  253. // our lane would not help, because this repositions the
  254. // child too, keeping the blockade.
  255. // Instead, we create a "detour lane" which gets us
  256. // around the blockade. That lane has no commits on it.
  257. laneToUse = nextFreeLane(blockedPositions);
  258. currCommit.addForkingOffLane(laneToUse);
  259. closeLane(laneToUse);
  260. } else {
  261. // The blockade is (only) due to other (already closed)
  262. // lanes at the current lane's position. In this case we
  263. // reposition the current lane.
  264. // We are the first commit on this lane, because
  265. // otherwise the child commit on this lane would have
  266. // kept other lanes from blocking us. Since we are the
  267. // first commit, we can freely reposition.
  268. int newPos = getFreePosition(blockedPositions);
  269. freePositions.add(Integer.valueOf(laneToUse
  270. .getPosition()));
  271. laneToUse.position = newPos;
  272. }
  273. }
  274. // Actually connect currCommit to the merge child
  275. drawLaneToChild(index, child, laneToUse);
  276. return laneToUse;
  277. }
  278. /**
  279. * Connects the commit at commitIndex to the child, using the given lane.
  280. * All blockades on the lane must be resolved before calling this method.
  281. *
  282. * @param commitIndex
  283. * @param child
  284. * @param laneToContinue
  285. */
  286. private void drawLaneToChild(final int commitIndex, PlotCommit child,
  287. PlotLane laneToContinue) {
  288. for (int r = commitIndex - 1; r >= 0; r--) {
  289. final PlotCommit rObj = get(r);
  290. if (rObj == child)
  291. break;
  292. if (rObj != null)
  293. rObj.addPassingLane(laneToContinue);
  294. }
  295. }
  296. private static void addBlockedPosition(BitSet blockedPositions,
  297. final PlotCommit rObj) {
  298. if (rObj != null) {
  299. PlotLane lane = rObj.getLane();
  300. // Positions may be blocked by a commit on a lane.
  301. if (lane != null)
  302. blockedPositions.set(lane.getPosition());
  303. // Positions may also be blocked by forking off and merging lanes.
  304. // We don't consider passing lanes, because every passing lane forks
  305. // off and merges at it ends.
  306. for (PlotLane l : rObj.forkingOffLanes)
  307. blockedPositions.set(l.getPosition());
  308. for (PlotLane l : rObj.mergingLanes)
  309. blockedPositions.set(l.getPosition());
  310. }
  311. }
  312. @SuppressWarnings("unchecked")
  313. private void closeLane(PlotLane lane) {
  314. if (activeLanes.remove(lane)) {
  315. recycleLane((L) lane);
  316. laneLength.remove(lane);
  317. freePositions.add(Integer.valueOf(lane.getPosition()));
  318. }
  319. }
  320. private void setupChildren(final PlotCommit<L> currCommit) {
  321. final int nParents = currCommit.getParentCount();
  322. for (int i = 0; i < nParents; i++)
  323. ((PlotCommit) currCommit.getParent(i)).addChild(currCommit);
  324. }
  325. private PlotLane nextFreeLane() {
  326. return nextFreeLane(null);
  327. }
  328. private PlotLane nextFreeLane(BitSet blockedPositions) {
  329. final PlotLane p = createLane();
  330. p.position = getFreePosition(blockedPositions);
  331. activeLanes.add(p);
  332. laneLength.put(p, Integer.valueOf(1));
  333. return p;
  334. }
  335. /**
  336. * @param blockedPositions
  337. * may be null
  338. * @return a free lane position
  339. */
  340. private int getFreePosition(BitSet blockedPositions) {
  341. if (freePositions.isEmpty())
  342. return positionsAllocated++;
  343. if (blockedPositions != null) {
  344. for (Integer pos : freePositions)
  345. if (!blockedPositions.get(pos.intValue())) {
  346. freePositions.remove(pos);
  347. return pos.intValue();
  348. }
  349. return positionsAllocated++;
  350. } else {
  351. final Integer min = freePositions.first();
  352. freePositions.remove(min);
  353. return min.intValue();
  354. }
  355. }
  356. /**
  357. * @return a new Lane appropriate for this particular PlotList.
  358. */
  359. @SuppressWarnings("unchecked")
  360. protected L createLane() {
  361. return (L) new PlotLane();
  362. }
  363. /**
  364. * Return colors and other reusable information to the plotter when a lane
  365. * is no longer needed.
  366. *
  367. * @param lane
  368. */
  369. protected void recycleLane(final L lane) {
  370. // Nothing.
  371. }
  372. }