From c75304637f5535e634e57d4ed933c0fdb594d890 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 28 Oct 2011 08:08:00 -0400
Subject: [PATCH] Added a list branches rpc

---
 src/com/gitblit/utils/JGitUtils.java |  288 ++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 240 insertions(+), 48 deletions(-)

diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java
index 03a5d00..80147d4 100644
--- a/src/com/gitblit/utils/JGitUtils.java
+++ b/src/com/gitblit/utils/JGitUtils.java
@@ -21,6 +21,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.Charset;
+import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -36,6 +37,8 @@
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.FetchCommand;
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.diff.DiffFormatter;
@@ -61,6 +64,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.revwalk.filter.RevFilter;
 import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.FetchResult;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -91,6 +95,29 @@
 	static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
 
 	/**
+	 * Log an error message and exception.
+	 * 
+	 * @param t
+	 * @param repository
+	 *            if repository is not null it MUST be the {0} parameter in the
+	 *            pattern.
+	 * @param pattern
+	 * @param objects
+	 */
+	private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
+		List<Object> parameters = new ArrayList<Object>();
+		if (objects != null && objects.length > 0) {
+			for (Object o : objects) {
+				parameters.add(o);
+			}
+		}
+		if (repository != null) {
+			parameters.add(0, repository.getDirectory().getAbsolutePath());
+		}
+		LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
+	}
+
+	/**
 	 * Returns the displayable name of the person in the form "Real Name <email
 	 * address>".  If the email address is empty, just "Real Name" is returned.
 	 * 
@@ -110,6 +137,15 @@
 	}
 
 	/**
+	 * Encapsulates the result of cloning or pulling from a repository.
+	 */
+	public static class CloneResult {
+		public String name;
+		public FetchResult fetchResult;
+		public boolean createdRepository;
+	}
+
+	/**
 	 * Clone or Fetch a repository. If the local repository does not exist,
 	 * clone is called. If the repository does exist, fetch is called. By
 	 * default the clone/fetch retrieves the remote heads, tags, and notes.
@@ -117,33 +153,65 @@
 	 * @param repositoriesFolder
 	 * @param name
 	 * @param fromUrl
-	 * @return FetchResult
+	 * @return CloneResult
 	 * @throws Exception
 	 */
-	public static FetchResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
+	public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
 			throws Exception {
-		FetchResult result = null;
-		if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
-			name += Constants.DOT_GIT_EXT;
+		return cloneRepository(repositoriesFolder, name, fromUrl, true, null);
+	}
+
+	/**
+	 * Clone or Fetch a repository. If the local repository does not exist,
+	 * clone is called. If the repository does exist, fetch is called. By
+	 * default the clone/fetch retrieves the remote heads, tags, and notes.
+	 * 
+	 * @param repositoriesFolder
+	 * @param name
+	 * @param fromUrl
+	 * @param bare
+	 * @param credentialsProvider
+	 * @return CloneResult
+	 * @throws Exception
+	 */
+	public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl,
+			boolean bare, CredentialsProvider credentialsProvider) throws Exception {
+		CloneResult result = new CloneResult();
+		if (bare) {
+			// bare repository, ensure .git suffix
+			if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
+				name += Constants.DOT_GIT_EXT;
+			}
+		} else {
+			// normal repository, strip .git suffix
+			if (name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
+				name = name.substring(0, name.indexOf(Constants.DOT_GIT_EXT));
+			}
 		}
+		result.name = name;
+
 		File folder = new File(repositoriesFolder, name);
 		if (folder.exists()) {
 			File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
 			FileRepository repository = new FileRepository(gitDir);
-			result = fetchRepository(repository);
+			result.fetchResult = fetchRepository(credentialsProvider, repository);
 			repository.close();
 		} else {
 			CloneCommand clone = new CloneCommand();
-			clone.setBare(true);
+			clone.setBare(bare);
 			clone.setCloneAllBranches(true);
 			clone.setURI(fromUrl);
 			clone.setDirectory(folder);
+			if (credentialsProvider != null) {
+				clone.setCredentialsProvider(credentialsProvider);
+			}
 			clone.call();
 			// 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 = fetchRepository(repository);
+			result.createdRepository = true;
+			result.fetchResult = fetchRepository(credentialsProvider, repository);
 			repository.close();
 		}
 		return result;
@@ -153,13 +221,14 @@
 	 * Fetch updates from the remote repository. If refSpecs is unspecifed,
 	 * remote heads, tags, and notes are retrieved.
 	 * 
+	 * @param credentialsProvider
 	 * @param repository
 	 * @param refSpecs
 	 * @return FetchResult
 	 * @throws Exception
 	 */
-	public static FetchResult fetchRepository(Repository repository, RefSpec... refSpecs)
-			throws Exception {
+	public static FetchResult fetchRepository(CredentialsProvider credentialsProvider,
+			Repository repository, RefSpec... refSpecs) throws Exception {
 		Git git = new Git(repository);
 		FetchCommand fetch = git.fetch();
 		List<RefSpec> specs = new ArrayList<RefSpec>();
@@ -170,8 +239,32 @@
 		} else {
 			specs.addAll(Arrays.asList(refSpecs));
 		}
+		if (credentialsProvider != null) {
+			fetch.setCredentialsProvider(credentialsProvider);
+		}
 		fetch.setRefSpecs(specs);
-		FetchResult result = fetch.call();
+		FetchResult fetchRes = fetch.call();
+		return fetchRes;
+	}
+
+	/**
+	 * Reset HEAD to the latest remote tracking commit.
+	 * 
+	 * @param repository
+	 * @param remoteRef
+	 *            the remote tracking reference (e.g. origin/master)
+	 * @return Ref
+	 * @throws Exception
+	 */
+	public static Ref resetHEAD(Repository repository, String remoteRef) throws Exception {
+		if (!remoteRef.startsWith(Constants.R_REMOTES)) {
+			remoteRef = Constants.R_REMOTES + remoteRef;
+		}
+		Git git = new Git(repository);
+		ResetCommand reset = git.reset();
+		reset.setMode(ResetType.SOFT);
+		reset.setRef(remoteRef);
+		Ref result = reset.call();
 		return result;
 	}
 
@@ -206,7 +299,7 @@
 		}
 		list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
 				exportAll, searchSubfolders));
-		Collections.sort(list);
+		StringUtils.sortRepositorynames(list);
 		return list;
 	}
 
@@ -263,19 +356,24 @@
 		if (!hasCommits(repository)) {
 			return null;
 		}
-		if (StringUtils.isEmpty(branch)) {
-			branch = Constants.HEAD;
-		}
 		RevCommit commit = null;
 		try {
+			// resolve branch
+			ObjectId branchObject;
+			if (StringUtils.isEmpty(branch)) {
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(branch);
+			}
+
 			RevWalk walk = new RevWalk(repository);
 			walk.sort(RevSort.REVERSE);
-			RevCommit head = walk.parseCommit(repository.resolve(branch));
+			RevCommit head = walk.parseCommit(branchObject);
 			walk.markStart(head);
 			commit = walk.next();
 			walk.dispose();
 		} catch (Throwable t) {
-			LOGGER.error("Failed to determine first commit", t);
+			error(t, repository, "{0} failed to determine first commit");
 		}
 		return commit;
 	}
@@ -310,7 +408,7 @@
 	 * @return true if the repository has commits
 	 */
 	public static boolean hasCommits(Repository repository) {
-		if (repository != null && repository.getDirectory().exists()) {			
+		if (repository != null && repository.getDirectory().exists()) {
 			return (new File(repository.getDirectory(), "objects").list().length > 2)
 					|| (new File(repository.getDirectory(), "objects/pack").list().length > 0);
 		}
@@ -324,7 +422,7 @@
 	 * 
 	 * @param repository
 	 * @param branch
-	 *            if unspecified, HEAD is assumed.
+	 *            if unspecified, all branches are checked.
 	 * @return
 	 */
 	public static Date getLastChange(Repository repository, String branch) {
@@ -337,8 +435,23 @@
 			return new Date(repository.getDirectory().lastModified());
 		}
 		if (StringUtils.isEmpty(branch)) {
-			branch = Constants.HEAD;
+			List<RefModel> branchModels = getLocalBranches(repository, true, -1);
+			if (branchModels.size() > 0) {
+				// find most recent branch update
+				Date lastChange = new Date(0);
+				for (RefModel branchModel : branchModels) {
+					if (branchModel.getDate().after(lastChange)) {
+						lastChange = branchModel.getDate();
+					}
+				}
+				return lastChange;
+			} else {
+				// try to find head
+				branch = Constants.HEAD;
+			}
 		}
+
+		// lookup specified branch
 		RevCommit commit = getCommit(repository, branch);
 		return getCommitDate(commit);
 	}
@@ -347,9 +460,12 @@
 	 * Retrieves a Java Date from a Git commit.
 	 * 
 	 * @param commit
-	 * @return date of the commit
+	 * @return date of the commit or Date(0) if the commit is null
 	 */
 	public static Date getCommitDate(RevCommit commit) {
+		if (commit == null) {
+			return new Date(0);
+		}
 		return new Date(commit.getCommitTime() * 1000L);
 	}
 
@@ -368,16 +484,19 @@
 		}
 		RevCommit commit = null;
 		try {
+			// resolve object id
+			ObjectId branchObject;
 			if (StringUtils.isEmpty(objectId)) {
-				objectId = Constants.HEAD;
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(objectId);
 			}
-			ObjectId object = repository.resolve(objectId);
 			RevWalk walk = new RevWalk(repository);
-			RevCommit rev = walk.parseCommit(object);
+			RevCommit rev = walk.parseCommit(branchObject);
 			commit = rev;
 			walk.dispose();
 		} catch (Throwable t) {
-			LOGGER.error("Failed to get commit " + objectId, t);
+			error(t, repository, "{0} failed to get commit {1}", objectId);
 		}
 		return commit;
 	}
@@ -398,7 +517,7 @@
 		byte[] content = null;
 		try {
 			if (tree == null) {
-				ObjectId object = repository.resolve(Constants.HEAD);
+				ObjectId object = getDefaultBranch(repository);
 				RevCommit commit = rw.parseCommit(object);
 				tree = commit.getTree();
 			}
@@ -424,7 +543,7 @@
 				content = os.toByteArray();
 			}
 		} catch (Throwable t) {
-			LOGGER.error("Can't find " + path + " in tree " + tree.name(), t);
+			error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
 		} finally {
 			rw.dispose();
 			tw.release();
@@ -473,7 +592,7 @@
 			in.close();
 			content = os.toByteArray();
 		} catch (Throwable t) {
-			LOGGER.error("Can't find blob " + objectId, t);
+			error(t, repository, "{0} can't find blob {1}", objectId);
 		} finally {
 			rw.dispose();
 		}
@@ -514,7 +633,7 @@
 			return list;
 		}
 		if (commit == null) {
-			commit = getCommit(repository, Constants.HEAD);
+			commit = getCommit(repository, null);
 		}
 		final TreeWalk tw = new TreeWalk(repository);
 		try {
@@ -543,7 +662,7 @@
 				}
 			}
 		} catch (IOException e) {
-			LOGGER.error("Failed to get files for commit " + commit.getName(), e);
+			error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
 		} finally {
 			tw.release();
 		}
@@ -568,7 +687,7 @@
 		RevWalk rw = new RevWalk(repository);
 		try {
 			if (commit == null) {
-				ObjectId object = repository.resolve(Constants.HEAD);
+				ObjectId object = getDefaultBranch(repository);
 				commit = rw.parseCommit(object);
 			}
 
@@ -602,7 +721,7 @@
 				}
 			}
 		} catch (Throwable t) {
-			LOGGER.error("failed to determine files in commit!", t);
+			error(t, repository, "{0} failed to determine files in commit!");
 		} finally {
 			rw.dispose();
 		}
@@ -623,7 +742,7 @@
 		if (!hasCommits(repository)) {
 			return list;
 		}
-		RevCommit commit = getCommit(repository, Constants.HEAD);
+		RevCommit commit = getCommit(repository, null);
 		final TreeWalk tw = new TreeWalk(repository);
 		try {
 			tw.addTree(commit.getTree());
@@ -645,7 +764,7 @@
 				list.add(getPathModel(tw, null, commit));
 			}
 		} catch (IOException e) {
-			LOGGER.error("Failed to get documents for commit " + commit.getName(), e);
+			error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
 		} finally {
 			tw.release();
 		}
@@ -674,7 +793,7 @@
 				size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
 			}
 		} catch (Throwable t) {
-			LOGGER.error("Failed to retrieve blob size", 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());
@@ -713,7 +832,7 @@
 	 * @return list of commits
 	 */
 	public static List<RevCommit> getRevLog(Repository repository, int maxCount) {
-		return getRevLog(repository, Constants.HEAD, 0, maxCount);
+		return getRevLog(repository, null, 0, maxCount);
 	}
 
 	/**
@@ -762,12 +881,16 @@
 			return list;
 		}
 		try {
+			// resolve branch
+			ObjectId branchObject;
 			if (StringUtils.isEmpty(objectId)) {
-				objectId = Constants.HEAD;
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(objectId);
 			}
+
 			RevWalk rw = new RevWalk(repository);
-			ObjectId object = repository.resolve(objectId);
-			rw.markStart(rw.parseCommit(object));
+			rw.markStart(rw.parseCommit(branchObject));
 			if (!StringUtils.isEmpty(path)) {
 				TreeFilter filter = AndTreeFilter.create(
 						PathFilterGroup.createFromStrings(Collections.singleton(path)),
@@ -796,7 +919,7 @@
 			}
 			rw.dispose();
 		} catch (Throwable t) {
-			LOGGER.error("Failed to get revlog", t);
+			error(t, repository, "{0} failed to get {1} revlog for path {2}", objectId, path);
 		}
 		return list;
 	}
@@ -850,9 +973,14 @@
 			return list;
 		}
 		try {
+			// resolve branch
+			ObjectId branchObject;
 			if (StringUtils.isEmpty(objectId)) {
-				objectId = Constants.HEAD;
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(objectId);
 			}
+
 			RevWalk rw = new RevWalk(repository);
 			rw.setRevFilter(new RevFilter() {
 
@@ -887,8 +1015,7 @@
 				}
 
 			});
-			ObjectId object = repository.resolve(objectId);
-			rw.markStart(rw.parseCommit(object));
+			rw.markStart(rw.parseCommit(branchObject));
 			Iterable<RevCommit> revlog = rw;
 			if (offset > 0) {
 				int count = 0;
@@ -911,9 +1038,40 @@
 			}
 			rw.dispose();
 		} catch (Throwable t) {
-			LOGGER.error("Failed to search revlogs", t);
+			error(t, repository, "{0} failed to {1} search revlogs for {2}", type.name(), value);
 		}
 		return list;
+	}
+
+	/**
+	 * Returns the default branch to use for a repository. Normally returns
+	 * whatever branch HEAD points to, but if HEAD points to nothing it returns
+	 * the most recently updated branch.
+	 * 
+	 * @param repository
+	 * @return the objectid of a branch
+	 * @throws Exception
+	 */
+	public static ObjectId getDefaultBranch(Repository repository) throws Exception {
+		ObjectId object = repository.resolve(Constants.HEAD);
+		if (object == null) {
+			// no HEAD
+			// perhaps non-standard repository, try local branches
+			List<RefModel> branchModels = getLocalBranches(repository, true, -1);
+			if (branchModels.size() > 0) {
+				// use most recently updated branch
+				RefModel branch = null;
+				Date lastDate = new Date(0);
+				for (RefModel branchModel : branchModels) {
+					if (branchModel.getDate().after(lastDate)) {
+						branch = branchModel;
+						lastDate = branch.getDate();
+					}
+				}
+				object = branch.getReferencedObjectId();
+			}
+		}
+		return object;
 	}
 
 	/**
@@ -1044,7 +1202,7 @@
 				list = new ArrayList<RefModel>(list.subList(0, maxCount));
 			}
 		} catch (IOException e) {
-			LOGGER.error("Failed to retrieve " + refs, e);
+			error(e, repository, "{0} failed to retrieve {1}", refs);
 		}
 		return list;
 	}
@@ -1082,6 +1240,40 @@
 	}
 
 	/**
+	 * Create an orphaned branch in a repository. This code does not work.
+	 * 
+	 * @param repository
+	 * @param name
+	 * @return
+	 */
+	public static boolean createOrphanBranch(Repository repository, String name) {
+		return true;
+		// boolean success = false;
+		// try {
+		// ObjectId prev = repository.resolve(Constants.HEAD + "^1");
+		// // create the orphan branch
+		// RefUpdate orphanRef = repository.updateRef(Constants.R_HEADS + name);
+		// orphanRef.setNewObjectId(prev);
+		// orphanRef.setExpectedOldObjectId(ObjectId.zeroId());
+		// Result updateResult = orphanRef.update();
+		//
+		// switch (updateResult) {
+		// case NEW:
+		// success = true;
+		// break;
+		// case NO_CHANGE:
+		// default:
+		// break;
+		// }
+		//
+		// } catch (Throwable t) {
+		// error(t, repository, "{0} failed to create orphaned branch {1}",
+		// name);
+		// }
+		// return success;
+	}
+
+	/**
 	 * Returns a StoredConfig object for the repository.
 	 * 
 	 * @param repository
@@ -1092,9 +1284,9 @@
 		try {
 			c.load();
 		} catch (ConfigInvalidException cex) {
-			LOGGER.error("Repository configuration is invalid!", cex);
+			error(cex, repository, "{0} configuration is invalid!");
 		} catch (IOException cex) {
-			LOGGER.error("Could not open repository configuration!", cex);
+			error(cex, repository, "Could not open configuration for {0}!");
 		}
 		return c;
 	}
@@ -1154,7 +1346,7 @@
 			zos.finish();
 			success = true;
 		} catch (IOException e) {
-			LOGGER.error("Failed to zip files from commit " + commit.getName(), e);
+			error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
 		} finally {
 			tw.release();
 			rw.dispose();

--
Gitblit v1.9.1