James Moger
2013-10-01 4360b3af73e5e9e575adee9f8d8b462a20445553
commit | author | age
f084f4 1 /*
JM 2  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3  * Copyright 2013 gitblit.com.
4  * and other copyright owners as documented in the project's IP log.
5  *
6  * This program and the accompanying materials are made available
7  * under the terms of the Eclipse Distribution License v1.0 which
8  * accompanies this distribution, is reproduced below, and is
9  * available at http://www.eclipse.org/org/documents/edl-v10.php
10  *
11  * All rights reserved.
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package com.gitblit;
20
21 import java.awt.BasicStroke;
22 import java.awt.Color;
23 import java.awt.Graphics;
24 import java.awt.Graphics2D;
25 import java.awt.RenderingHints;
26 import java.awt.Stroke;
27 import java.awt.image.BufferedImage;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.io.Serializable;
32 import java.util.ArrayList;
33 import java.util.LinkedList;
34 import java.util.List;
6de953 35 import java.util.Set;
JM 36 import java.util.TreeSet;
f084f4 37
JM 38 import javax.imageio.ImageIO;
39 import javax.servlet.ServletException;
40 import javax.servlet.http.HttpServlet;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43
44 import org.eclipse.jgit.lib.Ref;
45 import org.eclipse.jgit.lib.Repository;
46 import org.eclipse.jgit.revplot.AbstractPlotRenderer;
47 import org.eclipse.jgit.revplot.PlotCommit;
48 import org.eclipse.jgit.revplot.PlotCommitList;
49 import org.eclipse.jgit.revplot.PlotLane;
50 import org.eclipse.jgit.revplot.PlotWalk;
51 import org.eclipse.jgit.revwalk.RevCommit;
52
53 import com.gitblit.utils.JGitUtils;
54 import com.gitblit.utils.StringUtils;
55
56 /**
57  * Handles requests for branch graphs
699e71 58  *
f084f4 59  * @author James Moger
699e71 60  *
f084f4 61  */
JM 62 public class BranchGraphServlet extends HttpServlet {
63
64     private static final long serialVersionUID = 1L;
65
66     private static final int LANE_WIDTH = 14;
67
68     // must match tr.commit css height
69     private static final int ROW_HEIGHT = 24;
70
71     private static final int RIGHT_PAD = 2;
72
73     private final Stroke[] strokeCache;
74
75     public BranchGraphServlet() {
76         super();
77
78         strokeCache = new Stroke[4];
79         for (int i = 1; i < strokeCache.length; i++)
80             strokeCache[i] = new BasicStroke(i);
81     }
82
83     /**
84      * Returns an url to this servlet for the specified parameters.
699e71 85      *
f084f4 86      * @param baseURL
JM 87      * @param repository
88      * @param objectId
89      * @param numberCommits
90      * @return an url
91      */
92     public static String asLink(String baseURL, String repository, String objectId, int numberCommits) {
93         if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
94             baseURL = baseURL.substring(0, baseURL.length() - 1);
95         }
96         return baseURL + Constants.BRANCH_GRAPH_PATH + "?r=" + repository
97                 + (objectId == null ? "" : ("&h=" + objectId))
98                 + (numberCommits > 0 ? ("&l=" + numberCommits) : "");
99     }
100
101     @Override
102     protected long getLastModified(HttpServletRequest req) {
103         String repository = req.getParameter("r");
104         String objectId = req.getParameter("h");
105         Repository r = null;
106         try {
107             r = GitBlit.self().getRepository(repository);
108             if (StringUtils.isEmpty(objectId)) {
109                 objectId = JGitUtils.getHEADRef(r);
110             }
111             RevCommit commit = JGitUtils.getCommit(r, objectId);
112             return JGitUtils.getCommitDate(commit).getTime();
113         } finally {
114             if (r != null) {
115                 r.close();
116             }
117         }
118     }
119
120     @Override
121     protected void doGet(HttpServletRequest request, HttpServletResponse response)
122             throws ServletException, IOException {
123         InputStream is = null;
124         Repository r = null;
125         PlotWalk rw = null;
126         try {
127             String repository = request.getParameter("r");
128             String objectId = request.getParameter("h");
129             String length = request.getParameter("l");
130
131             r = GitBlit.self().getRepository(repository);
132
133             rw = new PlotWalk(r);
134             if (StringUtils.isEmpty(objectId)) {
135                 objectId = JGitUtils.getHEADRef(r);
136             }
137
138             rw.markStart(rw.lookupCommit(r.resolve(objectId)));
139
140             // default to the items-per-page setting, unless specified
141             int maxCommits = GitBlit.getInteger(Keys.web.itemsPerPage, 50);
8a47de 142             int requestedCommits = maxCommits;
f084f4 143             if (!StringUtils.isEmpty(length)) {
JM 144                 int l = Integer.parseInt(length);
145                 if (l > 0) {
8a47de 146                     requestedCommits = l;
f084f4 147                 }
JM 148             }
149
150             // fetch the requested commits plus some extra so that the last
699e71 151             // commit displayed *likely* has correct lane assignments
f084f4 152             CommitList commitList = new CommitList();
JM 153             commitList.source(rw);
8a47de 154             commitList.fillTo(2*Math.max(requestedCommits, maxCommits));
f084f4 155
JM 156             // determine the appropriate width for the image
b384a9 157             int numLanes = 1;
6de953 158             int numCommits = Math.min(requestedCommits, commitList.size());
b384a9 159             if (numCommits > 1) {
JM 160                 // determine graph width
161                 Set<String> parents = new TreeSet<String>();
162                 for (int i = 0; i < commitList.size(); i++) {
163                     PlotCommit<Lane> commit = commitList.get(i);
164                     boolean checkLane = false;
165
166                     if (i < numCommits) {
167                         // commit in visible list
168                         checkLane = true;
169
170                         // remember parents
171                         for (RevCommit p : commit.getParents()) {
172                             parents.add(p.getName());
173                         }
174                     } else if (parents.contains(commit.getName())) {
175                         // commit outside visible list, but it is a parent of a
176                         // commit in the visible list so we need to know it's lane
177                         // assignment
178                         checkLane = true;
6de953 179                     }
b384a9 180
JM 181                     if (checkLane) {
182                         int pos = commit.getLane().getPosition();
183                         numLanes = Math.max(numLanes, pos + 1);
184                     }
6de953 185                 }
f084f4 186             }
JM 187
188             int graphWidth = numLanes * LANE_WIDTH + RIGHT_PAD;
189             int rowHeight = ROW_HEIGHT;
190
191             // create an image buffer and render the lanes
192             BufferedImage image = new BufferedImage(graphWidth, rowHeight*numCommits, BufferedImage.TYPE_INT_ARGB);
699e71 193
f084f4 194             Graphics2D g = null;
JM 195             try {
196                 g = image.createGraphics();
197                 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
198                 LanesRenderer renderer = new LanesRenderer();
6de953 199                 for (int i = 0; i < commitList.size(); i++) {
f084f4 200                     PlotCommit<Lane> commit = commitList.get(i);
JM 201                     Graphics row = g.create(0, i*rowHeight, graphWidth, rowHeight);
202                     try {
203                         renderer.paint(row, commit, rowHeight, graphWidth);
204                     } finally {
205                         row.dispose();
206                         row = null;
207                     }
208                 }
209             } finally {
210                 if (g != null) {
211                     g.dispose();
212                     g = null;
213                 }
214             }
215
216             // write the image buffer to the client
217             response.setContentType("image/png");
b384a9 218             if (numCommits > 1) {
f084f4 219                 response.setHeader("Cache-Control", "public, max-age=60, must-revalidate");
JM 220                 response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commitList.get(0)).getTime());
221             }
222             OutputStream os = response.getOutputStream();
223             ImageIO.write(image, "png", os);
224             os.flush();
225             image.flush();
226             image = null;
227         } catch (Exception e) {
228             e.printStackTrace();
229         } finally {
230             if (is != null) {
231                 is.close();
232                 is = null;
233             }
234             if (rw != null) {
235                 rw.dispose();
236                 rw = null;
237             }
238             if (r != null) {
239                 r.close();
240                 r = null;
241             }
242         }
243     }
244
245     private Stroke stroke(final int width) {
246         if (width < strokeCache.length)
247             return strokeCache[width];
248         return new BasicStroke(width);
249     }
250
251     static class CommitList extends PlotCommitList<Lane> {
252         final List<Color> laneColors;
253         final LinkedList<Color> colors;
254
255         CommitList() {
256             laneColors = new ArrayList<Color>();
257             laneColors.add(new Color(133, 166, 214));
258             laneColors.add(new Color(221, 205, 93));
259             laneColors.add(new Color(199, 134, 57));
260             laneColors.add(new Color(131, 150, 98));
261             laneColors.add(new Color(197, 123, 127));
262             laneColors.add(new Color(139, 136, 140));
263             laneColors.add(new Color(48, 135, 144));
264             laneColors.add(new Color(190, 93, 66));
265             laneColors.add(new Color(143, 163, 54));
266             laneColors.add(new Color(180, 148, 74));
267             laneColors.add(new Color(101, 101, 217));
268             laneColors.add(new Color(72, 153, 119));
269             laneColors.add(new Color(23, 101, 160));
270             laneColors.add(new Color(132, 164, 118));
271             laneColors.add(new Color(255, 230, 59));
272             laneColors.add(new Color(136, 176, 70));
273             laneColors.add(new Color(255, 138, 1));
274             laneColors.add(new Color(123, 187, 95));
275             laneColors.add(new Color(233, 88, 98));
276             laneColors.add(new Color(93, 158, 254));
277             laneColors.add(new Color(175, 215, 0));
278             laneColors.add(new Color(140, 134, 142));
279             laneColors.add(new Color(232, 168, 21));
280             laneColors.add(new Color(0, 172, 191));
281             laneColors.add(new Color(251, 58, 4));
282             laneColors.add(new Color(63, 64, 255));
283             laneColors.add(new Color(27, 194, 130));
284             laneColors.add(new Color(0, 104, 183));
285
286             colors = new LinkedList<Color>();
287             repackColors();
288         }
289
290         private void repackColors() {
291             colors.addAll(laneColors);
292         }
293
294         @Override
295         protected Lane createLane() {
296             final Lane lane = new Lane();
297             if (colors.isEmpty())
298                 repackColors();
299             lane.color = colors.removeFirst();
300             return lane;
301         }
302
303         @Override
304         protected void recycleLane(final Lane lane) {
305             colors.add(lane.color);
306         }
307     }
308
309     static class Lane extends PlotLane {
310
311         private static final long serialVersionUID = 1L;
312
313         Color color;
314
315         @Override
316         public boolean equals(Object o) {
317             return super.equals(o) && color.equals(((Lane)o).color);
318         }
319
320         @Override
321         public int hashCode() {
322             return super.hashCode() ^ color.hashCode();
323         }
324     }
325
326     class LanesRenderer extends AbstractPlotRenderer<Lane, Color> implements Serializable {
327
328         private static final long serialVersionUID = 1L;
329
330         final Color commitDotFill = new Color(220, 220, 220);
331
332         final Color commitDotOutline = new Color(110, 110, 110);
333
334         transient Graphics2D g;
335
336         void paint(Graphics in, PlotCommit<Lane> commit, int h, int w) {
337             g = (Graphics2D) in.create();
338             try {
339                 if (commit != null)
340                     paintCommit(commit, h);
341             } finally {
342                 g.dispose();
343                 g = null;
344             }
345         }
346
347         @Override
348         protected void drawLine(Color color, int x1, int y1, int x2, int y2, int width) {
349             if (y1 == y2) {
350                 x1 -= width / 2;
351                 x2 -= width / 2;
352             } else if (x1 == x2) {
353                 y1 -= width / 2;
354                 y2 -= width / 2;
355             }
356
357             g.setColor(color);
358             g.setStroke(stroke(width));
359             g.drawLine(x1, y1, x2, y2);
360         }
361
362         @Override
363         protected void drawCommitDot(int x, int y, int w, int h) {
364             g.setColor(commitDotFill);
365             g.setStroke(strokeCache[2]);
366             g.fillOval(x + 2, y + 1, w - 2, h - 2);
367             g.setColor(commitDotOutline);
368             g.drawOval(x + 2, y + 1, w - 2, h - 2);
369         }
370
371         @Override
372         protected void drawBoundaryDot(int x, int y, int w, int h) {
373             drawCommitDot(x, y, w, h);
374         }
375
376         @Override
377         protected void drawText(String msg, int x, int y) {
378         }
379
380         @Override
381         protected Color laneColor(Lane myLane) {
382             return myLane != null ? myLane.color : Color.black;
383         }
384
385         @Override
386         protected int drawLabel(int x, int y, Ref ref) {
387             return 0;
388         }
389     }
390 }