--- /dev/null
+/*\r
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>\r
+ * Copyright 2013 gitblit.com.\r
+ * and other copyright owners as documented in the project's IP log.\r
+ *\r
+ * This program and the accompanying materials are made available\r
+ * under the terms of the Eclipse Distribution License v1.0 which\r
+ * accompanies this distribution, is reproduced below, and is\r
+ * available at http://www.eclipse.org/org/documents/edl-v10.php\r
+ *\r
+ * All rights reserved.\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.RenderingHints;\r
+import java.awt.Stroke;\r
+import java.awt.image.BufferedImage;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+\r
+import javax.imageio.ImageIO;\r
+import javax.servlet.ServletException;\r
+import javax.servlet.http.HttpServlet;\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+\r
+import org.eclipse.jgit.lib.Ref;\r
+import org.eclipse.jgit.lib.Repository;\r
+import org.eclipse.jgit.revplot.AbstractPlotRenderer;\r
+import org.eclipse.jgit.revplot.PlotCommit;\r
+import org.eclipse.jgit.revplot.PlotCommitList;\r
+import org.eclipse.jgit.revplot.PlotLane;\r
+import org.eclipse.jgit.revplot.PlotWalk;\r
+import org.eclipse.jgit.revwalk.RevCommit;\r
+\r
+import com.gitblit.utils.JGitUtils;\r
+import com.gitblit.utils.StringUtils;\r
+\r
+/**\r
+ * Handles requests for branch graphs\r
+ * \r
+ * @author James Moger\r
+ * \r
+ */\r
+public class BranchGraphServlet extends HttpServlet {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ private static final int LANE_WIDTH = 14;\r
+\r
+ // must match tr.commit css height\r
+ private static final int ROW_HEIGHT = 24;\r
+\r
+ private static final int RIGHT_PAD = 2;\r
+\r
+ private final Stroke[] strokeCache;\r
+\r
+ public BranchGraphServlet() {\r
+ super();\r
+\r
+ strokeCache = new Stroke[4];\r
+ for (int i = 1; i < strokeCache.length; i++)\r
+ strokeCache[i] = new BasicStroke(i);\r
+ }\r
+\r
+ /**\r
+ * Returns an url to this servlet for the specified parameters.\r
+ * \r
+ * @param baseURL\r
+ * @param repository\r
+ * @param objectId\r
+ * @param numberCommits\r
+ * @return an url\r
+ */\r
+ public static String asLink(String baseURL, String repository, String objectId, int numberCommits) {\r
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {\r
+ baseURL = baseURL.substring(0, baseURL.length() - 1);\r
+ }\r
+ return baseURL + Constants.BRANCH_GRAPH_PATH + "?r=" + repository\r
+ + (objectId == null ? "" : ("&h=" + objectId))\r
+ + (numberCommits > 0 ? ("&l=" + numberCommits) : "");\r
+ }\r
+\r
+ @Override\r
+ protected long getLastModified(HttpServletRequest req) {\r
+ String repository = req.getParameter("r");\r
+ String objectId = req.getParameter("h");\r
+ Repository r = null;\r
+ try {\r
+ r = GitBlit.self().getRepository(repository);\r
+ if (StringUtils.isEmpty(objectId)) {\r
+ objectId = JGitUtils.getHEADRef(r);\r
+ }\r
+ RevCommit commit = JGitUtils.getCommit(r, objectId);\r
+ return JGitUtils.getCommitDate(commit).getTime();\r
+ } finally {\r
+ if (r != null) {\r
+ r.close();\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)\r
+ throws ServletException, IOException {\r
+ InputStream is = null;\r
+ Repository r = null;\r
+ PlotWalk rw = null;\r
+ try {\r
+ String repository = request.getParameter("r");\r
+ String objectId = request.getParameter("h");\r
+ String length = request.getParameter("l");\r
+\r
+ r = GitBlit.self().getRepository(repository);\r
+\r
+ rw = new PlotWalk(r);\r
+ if (StringUtils.isEmpty(objectId)) {\r
+ objectId = JGitUtils.getHEADRef(r);\r
+ }\r
+\r
+ rw.markStart(rw.lookupCommit(r.resolve(objectId)));\r
+\r
+ // default to the items-per-page setting, unless specified\r
+ int maxCommits = GitBlit.getInteger(Keys.web.itemsPerPage, 50);\r
+ if (!StringUtils.isEmpty(length)) {\r
+ int l = Integer.parseInt(length);\r
+ if (l > 0) {\r
+ maxCommits = l;\r
+ }\r
+ }\r
+\r
+ // fetch the requested commits plus some extra so that the last\r
+ // commit displayed *likely* has correct lane assignments \r
+ CommitList commitList = new CommitList();\r
+ commitList.source(rw);\r
+ commitList.fillTo(2*maxCommits);\r
+\r
+ // determine the appropriate width for the image\r
+ int numLanes = 0;\r
+ int numCommits = Math.min(maxCommits, commitList.size()); \r
+ for (int i = 0; i < numCommits; i++) {\r
+ PlotCommit<Lane> commit = commitList.get(i);\r
+ int pos = commit.getLane().getPosition();\r
+ numLanes = Math.max(numLanes, pos + 1);\r
+ }\r
+\r
+ int graphWidth = numLanes * LANE_WIDTH + RIGHT_PAD;\r
+ int rowHeight = ROW_HEIGHT;\r
+\r
+ // create an image buffer and render the lanes\r
+ BufferedImage image = new BufferedImage(graphWidth, rowHeight*numCommits, BufferedImage.TYPE_INT_ARGB);\r
+ Graphics2D g = null;\r
+ try {\r
+ g = image.createGraphics();\r
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
+ LanesRenderer renderer = new LanesRenderer();\r
+ for (int i = 0; i < numCommits; i++) {\r
+ PlotCommit<Lane> commit = commitList.get(i);\r
+ Graphics row = g.create(0, i*rowHeight, graphWidth, rowHeight);\r
+ try {\r
+ renderer.paint(row, commit, rowHeight, graphWidth);\r
+ } finally {\r
+ row.dispose();\r
+ row = null;\r
+ }\r
+ }\r
+ } finally {\r
+ if (g != null) {\r
+ g.dispose();\r
+ g = null;\r
+ }\r
+ }\r
+\r
+ // write the image buffer to the client\r
+ response.setContentType("image/png");\r
+ if (numCommits > 0) {\r
+ response.setHeader("Cache-Control", "public, max-age=60, must-revalidate");\r
+ response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commitList.get(0)).getTime());\r
+ }\r
+ OutputStream os = response.getOutputStream();\r
+ ImageIO.write(image, "png", os);\r
+ os.flush();\r
+ image.flush();\r
+ image = null;\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ } finally {\r
+ if (is != null) {\r
+ is.close();\r
+ is = null;\r
+ }\r
+ if (rw != null) {\r
+ rw.dispose();\r
+ rw = null;\r
+ }\r
+ if (r != null) {\r
+ r.close();\r
+ r = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ private Stroke stroke(final int width) {\r
+ if (width < strokeCache.length)\r
+ return strokeCache[width];\r
+ return new BasicStroke(width);\r
+ }\r
+\r
+ static class CommitList extends PlotCommitList<Lane> {\r
+ final List<Color> laneColors;\r
+ final LinkedList<Color> colors;\r
+\r
+ CommitList() {\r
+ laneColors = new ArrayList<Color>();\r
+ laneColors.add(new Color(133, 166, 214));\r
+ laneColors.add(new Color(221, 205, 93));\r
+ laneColors.add(new Color(199, 134, 57));\r
+ laneColors.add(new Color(131, 150, 98));\r
+ laneColors.add(new Color(197, 123, 127));\r
+ laneColors.add(new Color(139, 136, 140));\r
+ laneColors.add(new Color(48, 135, 144));\r
+ laneColors.add(new Color(190, 93, 66));\r
+ laneColors.add(new Color(143, 163, 54));\r
+ laneColors.add(new Color(180, 148, 74));\r
+ laneColors.add(new Color(101, 101, 217));\r
+ laneColors.add(new Color(72, 153, 119));\r
+ laneColors.add(new Color(23, 101, 160));\r
+ laneColors.add(new Color(132, 164, 118));\r
+ laneColors.add(new Color(255, 230, 59));\r
+ laneColors.add(new Color(136, 176, 70));\r
+ laneColors.add(new Color(255, 138, 1));\r
+ laneColors.add(new Color(123, 187, 95));\r
+ laneColors.add(new Color(233, 88, 98));\r
+ laneColors.add(new Color(93, 158, 254));\r
+ laneColors.add(new Color(175, 215, 0));\r
+ laneColors.add(new Color(140, 134, 142));\r
+ laneColors.add(new Color(232, 168, 21));\r
+ laneColors.add(new Color(0, 172, 191));\r
+ laneColors.add(new Color(251, 58, 4));\r
+ laneColors.add(new Color(63, 64, 255));\r
+ laneColors.add(new Color(27, 194, 130));\r
+ laneColors.add(new Color(0, 104, 183));\r
+\r
+ colors = new LinkedList<Color>();\r
+ repackColors();\r
+ }\r
+\r
+ private void repackColors() {\r
+ colors.addAll(laneColors);\r
+ }\r
+\r
+ @Override\r
+ protected Lane createLane() {\r
+ final Lane lane = new Lane();\r
+ if (colors.isEmpty())\r
+ repackColors();\r
+ lane.color = colors.removeFirst();\r
+ return lane;\r
+ }\r
+\r
+ @Override\r
+ protected void recycleLane(final Lane lane) {\r
+ colors.add(lane.color);\r
+ }\r
+ }\r
+\r
+ static class Lane extends PlotLane {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ Color color;\r
+\r
+ @Override\r
+ public boolean equals(Object o) {\r
+ return super.equals(o) && color.equals(((Lane)o).color);\r
+ }\r
+\r
+ @Override\r
+ public int hashCode() {\r
+ return super.hashCode() ^ color.hashCode();\r
+ }\r
+ }\r
+\r
+ class LanesRenderer extends AbstractPlotRenderer<Lane, Color> implements Serializable {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ final Color commitDotFill = new Color(220, 220, 220);\r
+\r
+ final Color commitDotOutline = new Color(110, 110, 110);\r
+\r
+ transient Graphics2D g;\r
+\r
+ void paint(Graphics in, PlotCommit<Lane> commit, int h, int w) {\r
+ g = (Graphics2D) in.create();\r
+ try {\r
+ if (commit != null)\r
+ paintCommit(commit, h);\r
+ } finally {\r
+ g.dispose();\r
+ g = null;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected void drawLine(Color color, int x1, int y1, int x2, int y2, int width) {\r
+ if (y1 == y2) {\r
+ x1 -= width / 2;\r
+ x2 -= width / 2;\r
+ } else if (x1 == x2) {\r
+ y1 -= width / 2;\r
+ y2 -= width / 2;\r
+ }\r
+\r
+ g.setColor(color);\r
+ g.setStroke(stroke(width));\r
+ g.drawLine(x1, y1, x2, y2);\r
+ }\r
+\r
+ @Override\r
+ protected void drawCommitDot(int x, int y, int w, int h) {\r
+ g.setColor(commitDotFill);\r
+ g.setStroke(strokeCache[2]);\r
+ g.fillOval(x + 2, y + 1, w - 2, h - 2);\r
+ g.setColor(commitDotOutline);\r
+ g.drawOval(x + 2, y + 1, w - 2, h - 2);\r
+ }\r
+\r
+ @Override\r
+ protected void drawBoundaryDot(int x, int y, int w, int h) {\r
+ drawCommitDot(x, y, w, h);\r
+ }\r
+\r
+ @Override\r
+ protected void drawText(String msg, int x, int y) {\r
+ }\r
+\r
+ @Override\r
+ protected Color laneColor(Lane myLane) {\r
+ return myLane != null ? myLane.color : Color.black;\r
+ }\r
+\r
+ @Override\r
+ protected int drawLabel(int x, int y, Ref ref) {\r
+ return 0;\r
+ }\r
+ }\r
+}\r