From 13417cf9c6eec555b51da49742e47939d2f5715b Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 19 Oct 2012 22:47:33 -0400
Subject: [PATCH] Exclude submodules from zip downloads (issue 151)

---
 src/com/gitblit/utils/JGitUtils.java |  187 ++++++++++++++++++++++++++++++++++------------
 1 files changed, 138 insertions(+), 49 deletions(-)

diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java
index ff701b3..bc44f00 100644
--- a/src/com/gitblit/utils/JGitUtils.java
+++ b/src/com/gitblit/utils/JGitUtils.java
@@ -29,6 +29,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -44,6 +45,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.lib.BlobBasedConfig;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
@@ -56,7 +58,6 @@
 import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.lib.TreeFormatter;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -86,6 +87,7 @@
 import com.gitblit.models.PathModel;
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
+import com.gitblit.models.SubmoduleModel;
 
 /**
  * Collection of static methods for retrieving information from a repository.
@@ -208,11 +210,10 @@
 			if (credentialsProvider != null) {
 				clone.setCredentialsProvider(credentialsProvider);
 			}
-			clone.call();
+			Repository repository = clone.call().getRepository();
+			
 			// Now we have to fetch because CloneCommand doesn't fetch
 			// refs/notes nor does it allow manual RefSpec.
-			File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
-			FileRepository repository = new FileRepository(gitDir);
 			result.createdRepository = true;
 			result.fetchResult = fetchRepository(credentialsProvider, repository);
 			repository.close();
@@ -277,16 +278,24 @@
 	 *            recurse into subfolders to find grouped repositories
 	 * @param depth
 	 *            optional recursion depth, -1 = infinite recursion
+	 * @param exclusions
+	 *            list of regex exclusions for matching to folder names
 	 * @return list of repository names
 	 */
 	public static List<String> getRepositoryList(File repositoriesFolder, boolean onlyBare,
-			boolean searchSubfolders, int depth) {
+			boolean searchSubfolders, int depth, List<String> exclusions) {
 		List<String> list = new ArrayList<String>();
 		if (repositoriesFolder == null || !repositoriesFolder.exists()) {
 			return list;
 		}
+		List<Pattern> patterns = new ArrayList<Pattern>();
+		if (!ArrayUtils.isEmpty(exclusions)) {
+			for (String regex : exclusions) {
+				patterns.add(Pattern.compile(regex));
+			}
+		}
 		list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
-				onlyBare, searchSubfolders, depth));
+				onlyBare, searchSubfolders, depth, patterns));
 		StringUtils.sortRepositorynames(list);
 		return list;
 	}
@@ -305,18 +314,35 @@
 	 *            recurse into subfolders to find grouped repositories
 	 * @param depth
 	 *            recursion depth, -1 = infinite recursion
+	 * @param patterns
+	 *            list of regex patterns for matching to folder names
 	 * @return
 	 */
 	private static List<String> getRepositoryList(String basePath, File searchFolder,
-			boolean onlyBare, boolean searchSubfolders, int depth) {
+			boolean onlyBare, boolean searchSubfolders, int depth, List<Pattern> patterns) {
 		File baseFile = new File(basePath);
 		List<String> list = new ArrayList<String>();
 		if (depth == 0) {
 			return list;
 		}
+		
 		int nextDepth = (depth == -1) ? -1 : depth - 1;
 		for (File file : searchFolder.listFiles()) {
 			if (file.isDirectory()) {
+				boolean exclude = false;
+				for (Pattern pattern : patterns) {
+					String path = FileUtils.getRelativePath(baseFile, file).replace('\\',  '/');
+					if (pattern.matcher(path).matches()) {
+						LOGGER.debug(MessageFormat.format("excluding {0} because of rule {1}", path, pattern.pattern()));
+						exclude = true;
+						break;
+					}
+				}
+				if (exclude) {
+					// skip to next file
+					continue;
+				}
+
 				File gitDir = FileKey.resolve(new File(searchFolder, file.getName()), FS.DETECTED);
 				if (gitDir != null) {
 					if (onlyBare && gitDir.getName().equals(".git")) {
@@ -328,11 +354,13 @@
 						list.add(repository);
 					} else if (searchSubfolders && file.canRead()) {
 						// look for repositories in subfolders
-						list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders, nextDepth));
+						list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
+								nextDepth, patterns));
 					}
 				} else if (searchSubfolders && file.canRead()) {
 					// look for repositories in subfolders
-					list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders, nextDepth));
+					list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
+							nextDepth, patterns));
 				}
 			}
 		}
@@ -531,18 +559,20 @@
 				}
 				ObjectId entid = tw.getObjectId(0);
 				FileMode entmode = tw.getFileMode(0);
-				RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
-				rw.parseBody(ro);
-				ByteArrayOutputStream os = new ByteArrayOutputStream();
-				ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
-				byte[] tmp = new byte[4096];
-				InputStream in = ldr.openStream();
-				int n;
-				while ((n = in.read(tmp)) > 0) {
-					os.write(tmp, 0, n);
+				if (entmode != FileMode.GITLINK) {
+					RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
+					rw.parseBody(ro);
+					ByteArrayOutputStream os = new ByteArrayOutputStream();
+					ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
+					byte[] tmp = new byte[4096];
+					InputStream in = ldr.openStream();
+					int n;
+					while ((n = in.read(tmp)) > 0) {
+						os.write(tmp, 0, n);
+					}
+					in.close();
+					content = os.toByteArray();
 				}
-				in.close();
-				content = os.toByteArray();
 			}
 		} catch (Throwable t) {
 			error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
@@ -702,7 +732,8 @@
 				tw.addTree(commit.getTree());
 				while (tw.next()) {
 					list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
-							.getRawMode(0), commit.getId().getName(), ChangeType.ADD));
+							.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
+							ChangeType.ADD));
 				}
 				tw.release();
 			} else {
@@ -713,17 +744,22 @@
 				df.setDetectRenames(true);
 				List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
 				for (DiffEntry diff : diffs) {
+					String objectId = null;
+					if (FileMode.GITLINK.equals(diff.getNewMode())) {
+						objectId = diff.getNewId().name();
+					}
+
 					if (diff.getChangeType().equals(ChangeType.DELETE)) {
 						list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
-								.getNewMode().getBits(), commit.getId().getName(), diff
+								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
 								.getChangeType()));
 					} else if (diff.getChangeType().equals(ChangeType.RENAME)) {
 						list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
-								.getNewMode().getBits(), commit.getId().getName(), diff
+								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
 								.getChangeType()));
 					} else {
 						list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
-								.getNewMode().getBits(), commit.getId().getName(), diff
+								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
 								.getChangeType()));
 					}
 				}
@@ -816,15 +852,16 @@
 		} else {
 			name = tw.getPathString().substring(basePath.length() + 1);
 		}
+		ObjectId objectId = tw.getObjectId(0);
 		try {
-			if (!tw.isSubtree()) {
-				size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
+			if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
+				size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);
 			}
 		} catch (Throwable t) {
 			error(t, null, "failed to retrieve blob size for " + tw.getPathString());
 		}
 		return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
-				commit.getName());
+				objectId.getName(), commit.getName());
 	}
 
 	/**
@@ -841,13 +878,10 @@
 		} else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
 			return "-rwxr-xr-x";
 		} else if (FileMode.SYMLINK.equals(mode)) {
-			// FIXME symlink permissions
 			return "symlink";
 		} else if (FileMode.GITLINK.equals(mode)) {
-			// FIXME gitlink permissions
-			return "gitlink";
+			return "submodule";
 		}
-		// FIXME missing permissions
 		return "missing";
 	}
 
@@ -1334,9 +1368,23 @@
 	 * @return all refs grouped by their referenced object id
 	 */
 	public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository) {
+		return getAllRefs(repository, true);
+	}
+	
+	/**
+	 * Returns all refs grouped by their associated object id.
+	 * 
+	 * @param repository
+	 * @param includeRemoteRefs
+	 * @return all refs grouped by their referenced object id
+	 */
+	public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository, boolean includeRemoteRefs) {
 		List<RefModel> list = getRefs(repository, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1);
 		Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>();
 		for (RefModel ref : list) {
+			if (!includeRemoteRefs && ref.getName().startsWith(Constants.R_REMOTES)) {
+				continue;
+			}
 			ObjectId objectid = ref.getReferencedObjectId();
 			if (!refs.containsKey(objectid)) {
 				refs.put(objectid, new ArrayList<RefModel>());
@@ -1503,6 +1551,62 @@
 		}
 		return branch;
 	}
+		
+	/**
+	 * Returns the list of submodules for this repository.
+	 * 
+	 * @param repository
+	 * @param commit
+	 * @return list of submodules
+	 */
+	public static List<SubmoduleModel> getSubmodules(Repository repository, String commitId) {
+		RevCommit commit = getCommit(repository, commitId);
+		return getSubmodules(repository, commit.getTree());
+	}
+	
+	/**
+	 * Returns the list of submodules for this repository.
+	 * 
+	 * @param repository
+	 * @param commit
+	 * @return list of submodules
+	 */
+	public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {
+		List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();
+		byte [] blob = getByteContent(repository, tree, ".gitmodules");
+		if (blob == null) {
+			return list;
+		}
+		try {
+			BlobBasedConfig config = new BlobBasedConfig(repository.getConfig(), blob);
+			for (String module : config.getSubsections("submodule")) {
+				String path = config.getString("submodule", module, "path");
+				String url = config.getString("submodule", module, "url");
+				list.add(new SubmoduleModel(module, path, url));
+			}
+		} catch (ConfigInvalidException e) {
+			LOGGER.error("Failed to load .gitmodules file for " + repository.getDirectory(), e);
+		}
+		return list;
+	}
+	
+	/**
+	 * Returns the submodule definition for the specified path at the specified
+	 * commit.  If no module is defined for the path, null is returned.
+	 * 
+	 * @param repository
+	 * @param commit
+	 * @param path
+	 * @return a submodule definition or null if there is no submodule
+	 */
+	public static SubmoduleModel getSubmoduleModel(Repository repository, String commitId, String path) {
+		for (SubmoduleModel model : getSubmodules(repository, commitId)) {
+			if (model.path.equals(path)) {
+				return model;
+			}
+		}
+		return null;
+	}
 
 	/**
 	 * Returns the list of notes entered about the commit from the refs/notes
@@ -1621,24 +1725,6 @@
 	}
 
 	/**
-	 * Returns a StoredConfig object for the repository.
-	 * 
-	 * @param repository
-	 * @return the StoredConfig of the repository
-	 */
-	public static StoredConfig readConfig(Repository repository) {
-		StoredConfig c = repository.getConfig();
-		try {
-			c.load();
-		} catch (ConfigInvalidException cex) {
-			error(cex, repository, "{0} configuration is invalid!");
-		} catch (IOException cex) {
-			error(cex, repository, "Could not open configuration for {0}!");
-		}
-		return c;
-	}
-
-	/**
 	 * Zips the contents of the tree at the (optionally) specified revision and
 	 * the (optionally) specified basepath to the supplied outputstream.
 	 * 
@@ -1670,6 +1756,9 @@
 			}
 			tw.setRecursive(true);
 			while (tw.next()) {
+				if (tw.getFileMode(0) == FileMode.GITLINK) {
+					continue;
+				}
 				ZipEntry entry = new ZipEntry(tw.getPathString());
 				entry.setSize(tw.getObjectReader().getObjectSize(tw.getObjectId(0),
 						Constants.OBJ_BLOB));

--
Gitblit v1.9.1