From 69007029f122c3f77db044e879188cc12be3c2f6 Mon Sep 17 00:00:00 2001
From: Florian Zschocke <florian.zschocke@cycos.com>
Date: Mon, 26 Aug 2013 06:39:57 -0400
Subject: [PATCH] Add method JGitUtils.createRepository(folder, name, shared) to create a new repository as if it was created with the --shared command line switch of git.

---
 src/main/java/com/gitblit/utils/JGitUtils.java |  280 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 262 insertions(+), 18 deletions(-)

diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index 1f2ae94..345375a 100644
--- a/src/main/java/com/gitblit/utils/JGitUtils.java
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -19,12 +19,14 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.text.DecimalFormat;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -33,6 +35,7 @@
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.FetchCommand;
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.TagCommand;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -55,6 +58,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;
@@ -64,7 +68,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
 import org.eclipse.jgit.revwalk.filter.RevFilter;
-import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.FetchResult;
 import org.eclipse.jgit.transport.RefSpec;
@@ -85,6 +89,8 @@
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
 import com.gitblit.models.SubmoduleModel;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
 
 /**
  * Collection of static methods for retrieving information from a repository.
@@ -195,7 +201,7 @@
 		File folder = new File(repositoriesFolder, name);
 		if (folder.exists()) {
 			File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
-			FileRepository repository = new FileRepository(gitDir);
+			Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
 			result.fetchResult = fetchRepository(credentialsProvider, repository);
 			repository.close();
 		} else {
@@ -264,6 +270,105 @@
 		}
 	}
 
+    /**
+     * 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 {
+            Repository repo = createRepository(repositoriesFolder, name);
+
+            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 (! System.getProperty("os.name").toLowerCase().startsWith("windows")) {
+                    final CLibrary libc = (CLibrary) Native.loadLibrary("c", CLibrary.class);
+
+                    //libc.chmod("/path/to/file", 0755);
+                }
+            }
+
+            return repo;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    interface CLibrary extends Library {
+        public int chmod(String path, int mode);
+    }
+    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;
+        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 ) return Integer.toOctalString(intValue);
+            return enumValue.getConfigValue();
+        }
+
+        int getPerm()
+        {
+            if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue;
+            return enumValue.getPerm();
+        }
+
+        boolean isShared()
+        {
+            return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx;
+        }
+    }
+
+
 	/**
 	 * Returns a list of repository names in the specified folder.
 	 * 
@@ -294,6 +399,7 @@
 		list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
 				onlyBare, searchSubfolders, depth, patterns));
 		StringUtils.sortRepositorynames(list);
+		list.remove(".git"); // issue-256
 		return list;
 	}
 
@@ -435,39 +541,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());
 	}
 
 	/**
@@ -768,6 +891,51 @@
 	}
 
 	/**
+	 * Returns the list of files changed in a specified commit. If the
+	 * repository does not exist or is empty, an empty list is returned.
+	 * 
+	 * @param repository
+	 * @param startCommit
+	 *            earliest commit
+	 * @param endCommit
+	 *            most recent commit. if null, HEAD is assumed.
+	 * @return list of files changed in a commit range
+	 */
+	public static List<PathChangeModel> getFilesInRange(Repository repository, RevCommit startCommit, RevCommit endCommit) {
+		List<PathChangeModel> list = new ArrayList<PathChangeModel>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		try {
+			DiffFormatter df = new DiffFormatter(null);
+			df.setRepository(repository);
+			df.setDiffComparator(RawTextComparator.DEFAULT);
+			df.setDetectRenames(true);
+
+			List<DiffEntry> diffEntries = df.scan(startCommit.getTree(), endCommit.getTree());
+			for (DiffEntry diff : diffEntries) {
+				
+				if (diff.getChangeType().equals(ChangeType.DELETE)) {
+					list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
+							.getNewMode().getBits(), diff.getOldId().name(), null, diff
+							.getChangeType()));
+				} else if (diff.getChangeType().equals(ChangeType.RENAME)) {
+					list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
+							.getNewMode().getBits(), diff.getNewId().name(), null, diff
+							.getChangeType()));
+				} else {
+					list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
+							.getNewMode().getBits(), diff.getNewId().name(), null, diff
+							.getChangeType()));
+				}
+			}			
+			Collections.sort(list);
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
+		}
+		return list;
+	}
+	/**
 	 * Returns the list of files in the repository on the default branch that
 	 * match one of the specified extensions. This is a CASE-SENSITIVE search.
 	 * If the repository does not exist or is empty, an empty list is returned.
@@ -978,18 +1146,30 @@
 		}
 		try {
 			// resolve branch
-			ObjectId branchObject;
+			ObjectId startRange = null;
+			ObjectId endRange;
 			if (StringUtils.isEmpty(objectId)) {
-				branchObject = getDefaultBranch(repository);
+				endRange = getDefaultBranch(repository);
 			} else {
-				branchObject = repository.resolve(objectId);
+				if( objectId.contains("..") ) {
+					// range expression
+					String[] parts = objectId.split("\\.\\.");
+					startRange = repository.resolve(parts[0]);
+					endRange = repository.resolve(parts[1]);
+				} else {
+					// objectid
+					endRange= repository.resolve(objectId);
+				}
 			}
-			if (branchObject == null) {
+			if (endRange == null) {
 				return list;
 			}
 
 			RevWalk rw = new RevWalk(repository);
-			rw.markStart(rw.parseCommit(branchObject));
+			rw.markStart(rw.parseCommit(endRange));
+			if (startRange != null) {
+				rw.markUninteresting(rw.parseCommit(startRange));	
+			}
 			if (!StringUtils.isEmpty(path)) {
 				TreeFilter filter = AndTreeFilter.create(
 						PathFilterGroup.createFromStrings(Collections.singleton(path)),
@@ -1689,6 +1869,70 @@
 	}
 
 	/**
+	 * this method creates an incremental revision number as a tag according to
+	 * the amount of already existing tags, which start with a defined prefix.
+	 * 
+	 * @param repository
+	 * @param objectId
+	 * @param tagger
+	 * @param prefix
+	 * @param intPattern
+	 * @param message
+	 * @return true if operation was successful, otherwise false
+	 */
+	public static boolean createIncrementalRevisionTag(Repository repository,
+			String objectId, PersonIdent tagger, String prefix, String intPattern, String message) {
+		boolean result = false;
+		Iterator<Entry<String, Ref>> iterator = repository.getTags().entrySet().iterator();
+		long lastRev = 0;
+		while (iterator.hasNext()) {
+			Entry<String, Ref> entry = iterator.next();
+			if (entry.getKey().startsWith(prefix)) {
+				try {
+					long val = Long.parseLong(entry.getKey().substring(prefix.length()));
+					if (val > lastRev) {
+						lastRev = val;
+					}
+				} catch (Exception e) {
+					// this tag is NOT an incremental revision tag
+				}
+			}
+		}
+		DecimalFormat df = new DecimalFormat(intPattern);
+		result = createTag(repository, objectId, tagger, prefix + df.format((lastRev + 1)), message);
+		return result;
+	}
+
+	/**
+	 * creates a tag in a repository
+	 * 
+	 * @param repository
+	 * @param objectId, the ref the tag points towards
+	 * @param tagger, the person tagging the object
+	 * @param tag, the string label
+	 * @param message, the string message
+	 * @return boolean, true if operation was successful, otherwise false
+	 */
+	public static boolean createTag(Repository repository, String objectId, PersonIdent tagger, String tag, String message) {
+		try {			
+			Git gitClient = Git.open(repository.getDirectory());
+			TagCommand tagCommand = gitClient.tag();
+			tagCommand.setTagger(tagger);
+			tagCommand.setMessage(message);
+			if (objectId != null) {
+				RevObject revObj = getCommit(repository, objectId);
+				tagCommand.setObjectId(revObj);
+			}
+			tagCommand.setName(tag);
+			Ref call = tagCommand.call();			
+			return call != null ? true : false;
+		} catch (Exception e) {
+			error(e, repository, "Failed to create tag {1} in repository {0}", objectId, tag);
+		}
+		return false;
+	}
+	
+	/**
 	 * Create an orphaned branch in a repository.
 	 * 
 	 * @param repository

--
Gitblit v1.9.1