From 557a1fa25a0a1d60bdb71ea3ad0e4e9a9cd9cc61 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 17 Sep 2013 17:39:10 -0400
Subject: [PATCH] Ignore the contents of a default .git/description file

---
 src/main/java/com/gitblit/utils/JGitUtils.java |  223 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 209 insertions(+), 14 deletions(-)

diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index 9dabebe..cf6ec26 100644
--- a/src/main/java/com/gitblit/utils/JGitUtils.java
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -32,6 +32,7 @@
 import java.util.Map.Entry;
 import java.util.regex.Pattern;
 
+import org.apache.commons.io.filefilter.TrueFileFilter;
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.FetchCommand;
 import org.eclipse.jgit.api.Git;
@@ -58,6 +59,7 @@
 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;
@@ -259,13 +261,187 @@
 	 * @return Repository
 	 */
 	public static Repository createRepository(File repositoriesFolder, String name) {
+		return createRepository(repositoriesFolder, name, "FALSE");
+	}
+
+	/**
+	 * Creates a bare, shared repository.
+	 *
+	 * @param repositoriesFolder
+	 * @param name
+	 * @param shared
+	 *          the setting for the --shared option of "git init".
+	 * @return Repository
+	 */
+	public static Repository createRepository(File repositoriesFolder, String name, String shared) {
 		try {
-			Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
-			return git.getRepository();
-		} catch (GitAPIException e) {
+			Repository repo = null;
+			try {
+				Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
+				repo = git.getRepository();
+			} catch (GitAPIException e) {
+				throw new RuntimeException(e);
+			}
+
+			GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared);
+			if (sharedRepository.isShared()) {
+				StoredConfig config = repo.getConfig();
+				config.setString("core", null, "sharedRepository", sharedRepository.getValue());
+				config.setBoolean("receive", null, "denyNonFastforwards", true);
+				config.save();
+
+				if (! JnaUtils.isWindows()) {
+					Iterator<File> iter = org.apache.commons.io.FileUtils.iterateFilesAndDirs(repo.getDirectory(),
+							TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
+					// Adjust permissions on file/directory
+					while (iter.hasNext()) {
+						adjustSharedPerm(iter.next(), sharedRepository);
+					}
+				}
+			}
+
+			return repo;
+		} catch (IOException e) {
 			throw new RuntimeException(e);
 		}
 	}
+
+	private enum GitConfigSharedRepositoryValue
+	{
+		UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0),
+		GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660),
+		ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664),
+		Oxxx(null, -1);
+
+		private String configValue;
+		private int permValue;
+		private GitConfigSharedRepositoryValue(String config, int perm) { configValue = config; permValue = perm; };
+
+		public String getConfigValue() { return configValue; };
+		public int getPerm() { return permValue; };
+
+	}
+
+	private static class GitConfigSharedRepository
+	{
+		private int intValue;
+		private GitConfigSharedRepositoryValue enumValue;
+
+		GitConfigSharedRepository(String s) {
+			if ( s == null || s.trim().isEmpty() ) {
+				enumValue = GitConfigSharedRepositoryValue.GROUP;
+			}
+			else {
+				try {
+					// Try one of the string values
+					enumValue = GitConfigSharedRepositoryValue.valueOf(s.trim().toUpperCase());
+				} catch (IllegalArgumentException  iae) {
+					try {
+						// Try if this is an octal number
+						int i = Integer.parseInt(s, 8);
+						if ( (i & 0600) != 0600 ) {
+							String msg = String.format("Problem with core.sharedRepository filemode value (0%03o).\nThe owner of files must always have read and write permissions.", i);
+							throw new IllegalArgumentException(msg);
+						}
+						intValue = i & 0666;
+						enumValue = GitConfigSharedRepositoryValue.Oxxx;
+					} catch (NumberFormatException nfe) {
+						throw new IllegalArgumentException("Bad configuration value for 'shared': '" + s + "'");
+					}
+				}
+			}
+		}
+
+		String getValue() {
+			if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) {
+				if (intValue == 0) return "0";
+				return String.format("0%o", intValue);
+			}
+			return enumValue.getConfigValue();
+		}
+
+		int getPerm() {
+			if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue;
+			return enumValue.getPerm();
+		}
+
+		boolean isCustom() {
+			return enumValue == GitConfigSharedRepositoryValue.Oxxx;
+		}
+
+		boolean isShared() {
+			return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx;
+		}
+	}
+
+
+	/**
+	 * Adjust file permissions of a file/directory for shared repositories
+	 *
+	 * @param path
+	 * 			File that should get its permissions changed.
+	 * @param configShared
+	 * 			Configuration string value for the shared mode.
+	 * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned.
+	 */
+	public static int adjustSharedPerm(File path, String configShared) {
+		return adjustSharedPerm(path, new GitConfigSharedRepository(configShared));
+	}
+
+
+	/**
+	 * Adjust file permissions of a file/directory for shared repositories
+	 *
+	 * @param path
+	 * 			File that should get its permissions changed.
+	 * @param configShared
+	 * 			Configuration setting for the shared mode.
+	 * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned.
+	 */
+	public static int adjustSharedPerm(File path, GitConfigSharedRepository configShared) {
+		if (! configShared.isShared()) return 0;
+		if (! path.exists()) return -1;
+
+		int perm = configShared.getPerm();
+		JnaUtils.Filestat stat = JnaUtils.getFilestat(path);
+		if (stat == null) return -1;
+		int mode = stat.mode;
+		if (mode < 0) return -1;
+
+		// Now, here is the kicker: Under Linux, chmod'ing a sgid file whose guid is different from the process'
+		// effective guid will reset the sgid flag of the file. Since there is no way to get the sgid flag back in
+		// that case, we decide to rather not touch is and getting the right permissions will have to be achieved
+		// in a different way, e.g. by using an appropriate umask for the Gitblit process.
+		if (System.getProperty("os.name").toLowerCase().startsWith("linux")) {
+			if ( ((mode & (JnaUtils.S_ISGID | JnaUtils.S_ISUID)) != 0)
+				&& stat.gid != JnaUtils.getegid() ) {
+				LOGGER.debug("Not adjusting permissions to prevent clearing suid/sgid bits for '" + path + "'" );
+				return 0;
+			}
+		}
+
+		// If the owner has no write access, delete it from group and other, too.
+		if ((mode & JnaUtils.S_IWUSR) == 0) perm &= ~0222;
+		// If the owner has execute access, set it for all blocks that have read access.
+		if ((mode & JnaUtils.S_IXUSR) == JnaUtils.S_IXUSR) perm |= (perm & 0444) >> 2;
+
+		if (configShared.isCustom()) {
+			// Use the custom value for access permissions.
+			mode = (mode & ~0777) | perm;
+		}
+		else {
+			// Just add necessary bits to existing permissions.
+			mode |= perm;
+		}
+
+		if (path.isDirectory()) {
+			mode |= (mode & 0444) >> 2;
+			mode |= JnaUtils.S_ISGID;
+		}
+
+		return JnaUtils.setFilemode(path, mode);
+	}
+
 
 	/**
 	 * Returns a list of repository names in the specified folder.
@@ -439,39 +615,56 @@
 		}
 		return false;
 	}
+	
+	/**
+	 * Encapsulates the result of cloning or pulling from a repository.
+	 */
+	public static class LastChange {
+		public Date when;
+		public String who;
+		
+		LastChange() {
+			when = new Date(0);			
+		}
+		
+		LastChange(long lastModified) {
+			this.when = new Date(lastModified);
+		}
+	}
 
 	/**
-	 * Returns the date of the most recent commit on a branch. If the repository
-	 * does not exist Date(0) is returned. If it does exist but is empty, the
-	 * last modified date of the repository folder is returned.
+	 * Returns the date and author of the most recent commit on a branch. If the
+	 * repository does not exist Date(0) is returned. If it does exist but is
+	 * empty, the last modified date of the repository folder is returned.
 	 * 
 	 * @param repository
-	 * @return
+	 * @return a LastChange object
 	 */
-	public static Date getLastChange(Repository repository) {
+	public static LastChange getLastChange(Repository repository) {
 		if (!hasCommits(repository)) {
 			// null repository
 			if (repository == null) {
-				return new Date(0);
+				return new LastChange();
 			}
 			// fresh repository
-			return new Date(repository.getDirectory().lastModified());
+			return new LastChange(repository.getDirectory().lastModified());
 		}
 
 		List<RefModel> branchModels = getLocalBranches(repository, true, -1);
 		if (branchModels.size() > 0) {
 			// find most recent branch update
-			Date lastChange = new Date(0);
+			LastChange lastChange = new LastChange();			
 			for (RefModel branchModel : branchModels) {
-				if (branchModel.getDate().after(lastChange)) {
-					lastChange = branchModel.getDate();
+				if (branchModel.getDate().after(lastChange.when)) {
+					lastChange.when = branchModel.getDate();
+					lastChange.who = branchModel.getAuthorIdent().getName();
 				}
 			}
 			return lastChange;
 		}
 		
 		// default to the repository folder modification date
-		return new Date(repository.getDirectory().lastModified());
+		return new LastChange(repository.getDirectory().lastModified());
 	}
 
 	/**
@@ -549,6 +742,8 @@
 		try {
 			if (tree == null) {
 				ObjectId object = getDefaultBranch(repository);
+				if (object == null)
+					return null;
 				RevCommit commit = rw.parseCommit(object);
 				tree = commit.getTree();
 			}

--
Gitblit v1.9.1