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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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. public void findPassingThrough(final PlotCommit<L> currCommit,
  108. final Collection<L> result) {
  109. for (final PlotLane p : currCommit.passingLanes)
  110. result.add((L) p);
  111. }
  112. @Override
  113. protected void enter(final int index, final PlotCommit<L> currCommit) {
  114. setupChildren(currCommit);
  115. final int nChildren = currCommit.getChildCount();
  116. if (nChildren == 0) {
  117. currCommit.lane = nextFreeLane();
  118. } else if (nChildren == 1
  119. && currCommit.children[0].getParentCount() < 2) {
  120. // Only one child, child has only us as their parent.
  121. // Stay in the same lane as the child.
  122. @SuppressWarnings("unchecked")
  123. final PlotCommit<L> c = currCommit.children[0];
  124. currCommit.lane = c.lane;
  125. Integer len = laneLength.get(currCommit.lane);
  126. len = Integer.valueOf(len.intValue() + 1);
  127. laneLength.put(currCommit.lane, len);
  128. } else {
  129. // More than one child, or our child is a merge.
  130. // We look for the child lane the current commit should continue.
  131. // Candidate lanes for this are those with children, that have the
  132. // current commit as their first parent.
  133. // There can be multiple candidate lanes. In that case the longest
  134. // lane is chosen, as this is usually the lane representing the
  135. // branch the commit actually was made on.
  136. // When there are no candidate lanes (i.e. the current commit has
  137. // only children whose non-first parent it is) we place the current
  138. // commit on a new lane.
  139. // The lane the current commit will be placed on:
  140. PlotLane reservedLane = null;
  141. PlotCommit childOnReservedLane = null;
  142. int lengthOfReservedLane = -1;
  143. for (int i = 0; i < nChildren; i++) {
  144. @SuppressWarnings("unchecked")
  145. final PlotCommit<L> c = currCommit.children[i];
  146. if (c.getParent(0) == currCommit) {
  147. Integer len = laneLength.get(c.lane);
  148. // we may be the first parent for multiple lines of
  149. // development, try to continue the longest one
  150. if (len.intValue() > lengthOfReservedLane) {
  151. reservedLane = c.lane;
  152. childOnReservedLane = c;
  153. lengthOfReservedLane = len.intValue();
  154. }
  155. }
  156. }
  157. if (reservedLane != null) {
  158. currCommit.lane = reservedLane;
  159. laneLength.put(reservedLane,
  160. Integer.valueOf(lengthOfReservedLane + 1));
  161. handleBlockedLanes(index, currCommit, childOnReservedLane);
  162. } else {
  163. currCommit.lane = nextFreeLane();
  164. handleBlockedLanes(index, currCommit, null);
  165. }
  166. // close lanes of children, if there are no first parents that might
  167. // want to continue the child lanes
  168. for (int i = 0; i < nChildren; i++) {
  169. final PlotCommit c = currCommit.children[i];
  170. PlotCommit firstParent = (PlotCommit) c.getParent(0);
  171. if (firstParent.lane != null && firstParent.lane != c.lane)
  172. closeLane(c.lane);
  173. }
  174. }
  175. continueActiveLanes(currCommit);
  176. if (currCommit.getParentCount() == 0)
  177. closeLane(currCommit.lane);
  178. }
  179. private void continueActiveLanes(final PlotCommit currCommit) {
  180. for (PlotLane lane : activeLanes)
  181. if (lane != currCommit.lane)
  182. currCommit.addPassingLane(lane);
  183. }
  184. /**
  185. * Sets up fork and merge information in the involved PlotCommits.
  186. * Recognizes and handles blockades that involve forking or merging arcs.
  187. *
  188. * @param index
  189. * the index of <code>currCommit</code> in the list
  190. * @param currCommit
  191. * @param childOnLane
  192. * the direct child on the same lane as <code>currCommit</code>,
  193. * may be null if <code>currCommit</code> is the first commit on
  194. * the lane
  195. */
  196. private void handleBlockedLanes(final int index, final PlotCommit currCommit,
  197. final PlotCommit childOnLane) {
  198. for (PlotCommit child : currCommit.children) {
  199. if (child == childOnLane)
  200. continue; // simple continuations of lanes are handled by
  201. // continueActiveLanes() calls in enter()
  202. // Is the child a merge or is it forking off?
  203. boolean childIsMerge = child.getParent(0) != currCommit;
  204. if (childIsMerge) {
  205. PlotLane laneToUse = currCommit.lane;
  206. laneToUse = handleMerge(index, currCommit, childOnLane, child,
  207. laneToUse);
  208. child.addMergingLane(laneToUse);
  209. } else {
  210. // We want to draw a forking arc in the child's lane.
  211. // As an active lane, the child lane already continues
  212. // (unblocked) up to this commit, we only need to mark it as
  213. // forking off from the current commit.
  214. PlotLane laneToUse = child.lane;
  215. currCommit.addForkingOffLane(laneToUse);
  216. }
  217. }
  218. }
  219. // Handles the case where currCommit is a non-first parent of the child
  220. private PlotLane handleMerge(final int index, final PlotCommit currCommit,
  221. final PlotCommit childOnLane, PlotCommit child, PlotLane laneToUse) {
  222. // find all blocked positions between currCommit and this child
  223. int childIndex = index; // useless initialization, should
  224. // always be set in the loop below
  225. BitSet blockedPositions = new BitSet();
  226. for (int r = index - 1; r >= 0; r--) {
  227. final PlotCommit rObj = get(r);
  228. if (rObj == child) {
  229. childIndex = r;
  230. break;
  231. }
  232. addBlockedPosition(blockedPositions, rObj);
  233. }
  234. // handle blockades
  235. if (blockedPositions.get(laneToUse.getPosition())) {
  236. // We want to draw a merging arc in our lane to the child,
  237. // which is on another lane, but our lane is blocked.
  238. // Check if childOnLane is beetween commit and the child we
  239. // are currently processing
  240. boolean needDetour = false;
  241. if (childOnLane != null) {
  242. for (int r = index - 1; r > childIndex; r--) {
  243. final PlotCommit rObj = get(r);
  244. if (rObj == childOnLane) {
  245. needDetour = true;
  246. break;
  247. }
  248. }
  249. }
  250. if (needDetour) {
  251. // It is childOnLane which is blocking us. Repositioning
  252. // our lane would not help, because this repositions the
  253. // child too, keeping the blockade.
  254. // Instead, we create a "detour lane" which gets us
  255. // around the blockade. That lane has no commits on it.
  256. laneToUse = nextFreeLane(blockedPositions);
  257. currCommit.addForkingOffLane(laneToUse);
  258. closeLane(laneToUse);
  259. } else {
  260. // The blockade is (only) due to other (already closed)
  261. // lanes at the current lane's position. In this case we
  262. // reposition the current lane.
  263. // We are the first commit on this lane, because
  264. // otherwise the child commit on this lane would have
  265. // kept other lanes from blocking us. Since we are the
  266. // first commit, we can freely reposition.
  267. int newPos = getFreePosition(blockedPositions);
  268. freePositions.add(Integer.valueOf(laneToUse
  269. .getPosition()));
  270. laneToUse.position = newPos;
  271. }
  272. }
  273. // Actually connect currCommit to the merge child
  274. drawLaneToChild(index, child, laneToUse);
  275. return laneToUse;
  276. }
  277. /**
  278. * Connects the commit at commitIndex to the child, using the given lane.
  279. * All blockades on the lane must be resolved before calling this method.
  280. *
  281. * @param commitIndex
  282. * @param child
  283. * @param laneToContinue
  284. */
  285. private void drawLaneToChild(final int commitIndex, PlotCommit child,
  286. PlotLane laneToContinue) {
  287. for (int r = commitIndex - 1; r >= 0; r--) {
  288. final PlotCommit rObj = get(r);
  289. if (rObj == child)
  290. break;
  291. if (rObj != null)
  292. rObj.addPassingLane(laneToContinue);
  293. }
  294. }
  295. private static void addBlockedPosition(BitSet blockedPositions,
  296. final PlotCommit rObj) {
  297. if (rObj != null) {
  298. PlotLane lane = rObj.getLane();
  299. // Positions may be blocked by a commit on a lane.
  300. if (lane != null)
  301. blockedPositions.set(lane.getPosition());
  302. // Positions may also be blocked by forking off and merging lanes.
  303. // We don't consider passing lanes, because every passing lane forks
  304. // off and merges at it ends.
  305. for (PlotLane l : rObj.forkingOffLanes)
  306. blockedPositions.set(l.getPosition());
  307. for (PlotLane l : rObj.mergingLanes)
  308. blockedPositions.set(l.getPosition());
  309. }
  310. }
  311. private void closeLane(PlotLane lane) {
  312. if (activeLanes.remove(lane)) {
  313. recycleLane((L) lane);
  314. laneLength.remove(lane);
  315. freePositions.add(Integer.valueOf(lane.getPosition()));
  316. }
  317. }
  318. private void setupChildren(final PlotCommit<L> currCommit) {
  319. final int nParents = currCommit.getParentCount();
  320. for (int i = 0; i < nParents; i++)
  321. ((PlotCommit) currCommit.getParent(i)).addChild(currCommit);
  322. }
  323. private PlotLane nextFreeLane() {
  324. return nextFreeLane(null);
  325. }
  326. private PlotLane nextFreeLane(BitSet blockedPositions) {
  327. final PlotLane p = createLane();
  328. p.position = getFreePosition(blockedPositions);
  329. activeLanes.add(p);
  330. laneLength.put(p, Integer.valueOf(1));
  331. return p;
  332. }
  333. /**
  334. * @param blockedPositions
  335. * may be null
  336. * @return a free lane position
  337. */
  338. private int getFreePosition(BitSet blockedPositions) {
  339. if (freePositions.isEmpty())
  340. return positionsAllocated++;
  341. if (blockedPositions != null) {
  342. for (Integer pos : freePositions)
  343. if (!blockedPositions.get(pos.intValue())) {
  344. freePositions.remove(pos);
  345. return pos.intValue();
  346. }
  347. return positionsAllocated++;
  348. } else {
  349. final Integer min = freePositions.first();
  350. freePositions.remove(min);
  351. return min.intValue();
  352. }
  353. }
  354. /**
  355. * @return a new Lane appropriate for this particular PlotList.
  356. */
  357. protected L createLane() {
  358. return (L) new PlotLane();
  359. }
  360. /**
  361. * Return colors and other reusable information to the plotter when a lane
  362. * is no longer needed.
  363. *
  364. * @param lane
  365. */
  366. protected void recycleLane(final L lane) {
  367. // Nothing.
  368. }
  369. }