From 9197d340db81a245193dff1ecb44889b8e0dfe31 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 24 May 2011 17:39:38 -0400
Subject: [PATCH] Download zip feature.

---
 src/com/gitblit/wicket/GitBlitWebApp.properties |    3 
 src/com/gitblit/wicket/pages/CommitPage.java    |    6 +
 src/com/gitblit/DownloadZipServlet.java         |  100 +++++++++++++++++++++++++
 src/com/gitblit/GitBlitServer.java              |    7 +
 src/com/gitblit/wicket/pages/TreePage.java      |    6 +
 src/com/gitblit/wicket/pages/TreePage.html      |    4 
 distrib/gitblit.properties                      |    3 
 src/com/gitblit/wicket/pages/CommitPage.html    |    7 +
 src/com/gitblit/tests/JGitUtilsTest.java        |   23 +++++
 src/com/gitblit/wicket/resources/gitblit.css    |    8 +
 src/com/gitblit/Constants.java                  |    4 +
 src/com/gitblit/utils/JGitUtils.java            |   52 ++++++++++++
 12 files changed, 214 insertions(+), 9 deletions(-)

diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index b263f32..e2ddce6 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -55,6 +55,9 @@
 # If web.authenticate=false, any user can execute the aforementioned functions.  
 web.allowAdministration = true
 
+# Allow dyanamic zip downloads.   
+web.allowZipDownloads = true
+
 # This is the message display above the repositories table.
 # This can point to a file with Markdown content.
 # Specifying "gitblit" uses the internal welcome message.
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index 46f3208..b84ab7d 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -17,6 +17,10 @@
 	public final static String ADMIN_ROLE = "#admin";
 
 	public final static String PROPERTIES_FILE = "gitblit.properties";
+	
+	public final static String GIT_SERVLET_PATH = "/git/";
+	
+	public final static String ZIP_SERVLET_PATH = "/zip/";
 
 	public static enum AccessRestrictionType {
 		NONE, PUSH, CLONE, VIEW;
diff --git a/src/com/gitblit/DownloadZipServlet.java b/src/com/gitblit/DownloadZipServlet.java
new file mode 100644
index 0000000..87fda90
--- /dev/null
+++ b/src/com/gitblit/DownloadZipServlet.java
@@ -0,0 +1,100 @@
+package com.gitblit;
+
+import java.util.Date;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.models.RepositoryModel;
+
+public class DownloadZipServlet extends HttpServlet {
+
+	public static String asLink(String baseURL, String repository, String objectId, String path) {
+		return baseURL + (baseURL.endsWith("/") ? "" : "/") + "zip?r=" + repository + (path == null ? "" : ("&p=" + path)) + (objectId == null ? "" : ("&h=" + objectId));
+	}
+
+	private static final long serialVersionUID = 1L;
+
+	private final static Logger logger = LoggerFactory.getLogger(DownloadZipServlet.class);
+
+	public DownloadZipServlet() {
+		super();
+	}
+
+	@Override
+	protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException {
+		processRequest(request, response);
+	}
+
+	@Override
+	protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException {
+		processRequest(request, response);
+	}
+
+	private void processRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException {
+		if (!GitBlit.self().settings().getBoolean(Keys.web.allowZipDownloads, true)) {
+			logger.warn("Zip downloads are disabled");
+			response.sendError(HttpServletResponse.SC_FORBIDDEN);
+			return;
+
+		}
+		String repository = request.getParameter("r");
+		String basePath = request.getParameter("p");
+		String objectId = request.getParameter("h");
+
+		try {
+			String name = repository;
+			if (name.indexOf('/') > -1) {
+				name = name.substring(name.lastIndexOf('/') + 1);
+			}
+
+			// check roles first
+			boolean authorized = request.isUserInRole(Constants.ADMIN_ROLE);
+			authorized |= request.isUserInRole(repository);
+
+			if (!authorized) {
+				RepositoryModel model = GitBlit.self().getRepositoryModel(repository);
+				if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
+					logger.warn("Unauthorized access via zip servlet for " + model.name);
+					response.sendError(HttpServletResponse.SC_FORBIDDEN);
+					return;
+				}
+			}
+			if (!StringUtils.isEmpty(basePath)) {
+				name += "-" + basePath.replace('/', '_');
+			}
+			if (!StringUtils.isEmpty(objectId)) {
+				name += "-" + objectId;
+			}
+
+			Repository r = GitBlit.self().getRepository(repository);
+			RevCommit commit = JGitUtils.getCommit(r, objectId);
+			Date date = JGitUtils.getCommitDate(commit);
+			String contentType = "application/octet-stream";
+			response.setContentType(contentType + "; charset=" + response.getCharacterEncoding());
+			// response.setContentLength(attachment.getFileSize());
+			response.setHeader("Content-Disposition", "attachment; filename=\"" + name + ".zip" + "\"");
+			response.setDateHeader("Last-Modified", date.getTime());
+			response.setHeader("Cache-Control", "no-cache");
+			response.setHeader("Pragma", "no-cache");
+			response.setDateHeader("Expires", 0);
+
+			try {
+				JGitUtils.zip(r, basePath, objectId, response.getOutputStream());
+				response.flushBuffer();
+			} catch (Throwable t) {
+				logger.error("Failed to write attachment to client", t);
+			}
+		} catch (Throwable t) {
+			logger.error("Failed to write attachment to client", t);
+		}
+	}
+}
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java
index e9e4463..a7b1538 100644
--- a/src/com/gitblit/GitBlitServer.java
+++ b/src/com/gitblit/GitBlitServer.java
@@ -211,10 +211,13 @@
 		wicketFilter.setInitParameter(WicketFilter.FILTER_MAPPING_PARAM, wicketPathSpec);
 		wicketFilter.setInitParameter(WicketFilter.IGNORE_PATHS_PARAM, "git/");
 		rootContext.addFilter(wicketFilter, wicketPathSpec, FilterMapping.DEFAULT);
-
+		
+		// Zip Servlet
+		rootContext.addServlet(DownloadZipServlet.class, Constants.ZIP_SERVLET_PATH + "*");
+		
 		// Git Servlet
 		ServletHolder gitServlet = null;
-		String gitServletPathSpec = "/git/*";
+		String gitServletPathSpec = Constants.GIT_SERVLET_PATH + "*";
 		if (fileSettings.getBoolean(Keys.git.enableGitServlet, true)) {
 			gitServlet = rootContext.addServlet(GitBlitServlet.class, gitServletPathSpec);
 			gitServlet.setInitParameter("base-path", params.repositoriesFolder);
diff --git a/src/com/gitblit/tests/JGitUtilsTest.java b/src/com/gitblit/tests/JGitUtilsTest.java
index 196058c..11b7712 100644
--- a/src/com/gitblit/tests/JGitUtilsTest.java
+++ b/src/com/gitblit/tests/JGitUtilsTest.java
@@ -1,6 +1,7 @@
 package com.gitblit.tests;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.util.Date;
 import java.util.List;
 
@@ -103,5 +104,27 @@
 		r.close();
 		System.out.println(diff);
 	}
+	
+	public void testZip() throws Exception {
+		Repository r = new FileRepository(new File(repositoriesFolder, "gitblit.git/" + Constants.DOT_GIT));
+		FileOutputStream fos = null;
+		try {
+			File zipFile = new File("c:/output.zip");
+			zipFile.delete();
+			fos = new FileOutputStream(zipFile);
+			if (JGitUtils.zip(r, "src", Constants.HEAD, fos)) {
+				System.out.println("zip = " + zipFile.length() + " bytes");
+			} else {
+				System.err.println("failed to generate zip file?!");
+			}
+		} finally {
+			if (fos != null) {
+				try {
+					fos.close();
+				} catch (Throwable t) {
+				}
+			}
+		}
+	}
 
 }
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java
index c9c13c5..b153c0c 100644
--- a/src/com/gitblit/utils/JGitUtils.java
+++ b/src/com/gitblit/utils/JGitUtils.java
@@ -4,6 +4,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.charset.Charset;
 import java.text.DateFormat;
 import java.text.ParseException;
@@ -17,6 +18,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.diff.DiffEntry;
@@ -808,10 +811,57 @@
 		return null;
 	}
 
+	public static boolean zip(Repository r, String basePath, String objectId, OutputStream os) throws Exception {
+		RevCommit commit = getCommit(r, objectId);
+		if (commit == null) {
+			return false;
+		}
+		final RevWalk rw = new RevWalk(r);
+		final TreeWalk walk = new TreeWalk(r);
+		try {
+			walk.addTree(commit.getTree());
+			ZipOutputStream zos = new ZipOutputStream(os);
+			zos.setComment("Generated by Git:Blit");
+			if (basePath != null && basePath.length() > 0) {
+				PathFilter f = PathFilter.create(basePath);
+				walk.setFilter(f);
+			}
+			walk.setRecursive(true);
+			while (walk.next()) {
+				ZipEntry entry = new ZipEntry(walk.getPathString());
+				entry.setSize(walk.getObjectReader().getObjectSize(walk.getObjectId(0), Constants.OBJ_BLOB));
+				entry.setComment(commit.getName());
+				zos.putNextEntry(entry);
+
+				ObjectId entid = walk.getObjectId(0);
+				FileMode entmode = walk.getFileMode(0);
+				RevBlob blob = (RevBlob) rw.lookupAny(entid, entmode.getObjectType());
+				rw.parseBody(blob);
+
+				ObjectLoader ldr = r.open(blob.getId(), Constants.OBJ_BLOB);
+				byte[] tmp = new byte[4096];
+				InputStream in = ldr.openStream();
+				int n;
+				while ((n = in.read(tmp)) > 0) {
+					zos.write(tmp, 0, n);
+				}
+				in.close();
+			}
+			zos.finish();
+			return true;
+		} catch (IOException e) {
+			LOGGER.error("Failed to zip files from commit " + commit.getName(), e);
+		} finally {
+			walk.release();
+			rw.dispose();
+		}
+		return false;
+	}
+
 	public static List<Metric> getDateMetrics(Repository r) {
 		Metric total = new Metric("TOTAL");
 		final Map<String, Metric> metricMap = new HashMap<String, Metric>();
-		
+
 		if (hasCommits(r)) {
 			final List<RefModel> tags = getTags(r, -1);
 			final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>();
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index b6dbc11..0c4c350 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -92,4 +92,5 @@
 gb.canAdminDescription = can administer Git:Blit server
 gb.permittedUsers = permitted users
 gb.isFrozen = is frozen
-gb.isFrozenDescription = deny push operations
\ No newline at end of file
+gb.isFrozenDescription = deny push operations
+gb.zip = zip
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/CommitPage.html b/src/com/gitblit/wicket/pages/CommitPage.html
index 059d77e..1b5fffe 100644
--- a/src/com/gitblit/wicket/pages/CommitPage.html
+++ b/src/com/gitblit/wicket/pages/CommitPage.html
@@ -23,7 +23,12 @@
 		<tr><th><wicket:message key="gb.committer">committer</wicket:message></th><td><span class="sha1" wicket:id="commitCommitter">[committer]</span></td></tr>
 		<tr><th></th><td><span class="sha1" wicket:id="commitCommitterDate">[commit date]</span></td></tr>
 		<tr><th><wicket:message key="gb.commit">commit</wicket:message></th><td><span class="sha1" wicket:id="commitId">[commit id]</span></td></tr>
-		<tr><th><wicket:message key="gb.tree">tree</wicket:message></th><td><span class="sha1" wicket:id="commitTree">[commit tree]</span></td></tr>
+		<tr><th><wicket:message key="gb.tree">tree</wicket:message></th>
+			<td><span class="sha1" wicket:id="commitTree">[commit tree]</span>
+				<span class="link">
+					<a wicket:id="treeLink"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="zipLink"><wicket:message key="gb.zip"></wicket:message></a>
+				</span>
+			</td></tr>
 		<tr><th valign="top"><wicket:message key="gb.parent">parent</wicket:message></th>
 			<td>
 				<span wicket:id="commitParents">
diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java
index 5396e82..c3c3fa9 100644
--- a/src/com/gitblit/wicket/pages/CommitPage.java
+++ b/src/com/gitblit/wicket/pages/CommitPage.java
@@ -6,6 +6,7 @@
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
@@ -13,6 +14,9 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+import com.gitblit.DownloadZipServlet;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JGitUtils.SearchType;
 import com.gitblit.wicket.LinkPanel;
@@ -62,6 +66,8 @@
 		add(new Label("commitId", c.getName()));
 
 		add(new LinkPanel("commitTree", "list", c.getTree().getName(), TreePage.class, newCommitParameter()));
+		add(new BookmarkablePageLink<Void>("treeLink", TreePage.class, newCommitParameter()));
+		add(new ExternalLink("zipLink", DownloadZipServlet.asLink(getRequest().getRelativePathPrefixToContextRoot(), repositoryName, objectId, null)).setVisible(GitBlit.self().settings().getBoolean(Keys.web.allowZipDownloads, true)));
 
 		// Parent Commits
 		ListDataProvider<String> parentsDp = new ListDataProvider<String>(parents);
diff --git a/src/com/gitblit/wicket/pages/TreePage.html b/src/com/gitblit/wicket/pages/TreePage.html
index 35eac29..8d706ee 100644
--- a/src/com/gitblit/wicket/pages/TreePage.html
+++ b/src/com/gitblit/wicket/pages/TreePage.html
@@ -9,7 +9,7 @@
 
 	<!-- blob nav links -->	
 	<div class="page_nav2">
-		<a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a>
+		<a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a> | <a wicket:id="zipLink"><wicket:message key="gb.zip"></wicket:message></a>
 	</div>	
 	
 	<!-- commit header -->
@@ -32,7 +32,7 @@
 	<!--  tree links -->
 	<wicket:fragment wicket:id="treeLinks">
 		<span class="link">
-			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
+			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="zip"><wicket:message key="gb.zip"></wicket:message></a>
 		</span>
 	</wicket:fragment>
 	
diff --git a/src/com/gitblit/wicket/pages/TreePage.java b/src/com/gitblit/wicket/pages/TreePage.java
index ea5bd53..e385fab 100644
--- a/src/com/gitblit/wicket/pages/TreePage.java
+++ b/src/com/gitblit/wicket/pages/TreePage.java
@@ -5,6 +5,7 @@
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
 import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
@@ -13,6 +14,9 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+import com.gitblit.DownloadZipServlet;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
 import com.gitblit.utils.ByteFormat;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.wicket.LinkPanel;
@@ -36,6 +40,7 @@
 		// tree page links
 		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class, WicketUtils.newPathParameter(repositoryName, objectId, path)));
 		add(new BookmarkablePageLink<Void>("headLink", TreePage.class, WicketUtils.newPathParameter(repositoryName, Constants.HEAD, path)));
+		add(new ExternalLink("zipLink", DownloadZipServlet.asLink(getRequest().getRelativePathPrefixToContextRoot(), repositoryName, objectId, path)).setVisible(GitBlit.self().settings().getBoolean(Keys.web.allowZipDownloads, true)));
 
 		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
 
@@ -73,6 +78,7 @@
 						Fragment links = new Fragment("pathLinks", "treeLinks", this);
 						links.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils.newPathParameter(repositoryName, entry.commitId, entry.path)));
 						links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils.newPathParameter(repositoryName, entry.commitId, entry.path)));
+						links.add(new ExternalLink("zip", DownloadZipServlet.asLink(getRequest().getRelativePathPrefixToContextRoot(), repositoryName, objectId, entry.path)).setVisible(GitBlit.self().settings().getBoolean(Keys.web.allowZipDownloads, true)));
 						item.add(links);
 					} else {
 						// blob link
diff --git a/src/com/gitblit/wicket/resources/gitblit.css b/src/com/gitblit/wicket/resources/gitblit.css
index 4a971a6..64484d2 100644
--- a/src/com/gitblit/wicket/resources/gitblit.css
+++ b/src/com/gitblit/wicket/resources/gitblit.css
@@ -114,15 +114,19 @@
 	color: #008000;
 }
 
+span.link {
+	color: #888;
+}
+
 span.link, span.link a {
 	font-family: sans-serif;
-	font-size: 11px;
+	font-size: 11px;	
 }
 
 span.link em, div.link span em {
 	font-style: normal;
 	font-family: sans-serif;
-	font-size: 11px;
+	font-size: 11px;	
 }
 
 div.page_header {

--
Gitblit v1.9.1