From cb285cbfddfc0b633d6b8cdb4dc0d2bd2b8b51ef Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Thu, 05 Jan 2012 17:34:05 -0500 Subject: [PATCH] Fixed bug in receive hook for repositories in subfolders --- src/com/gitblit/utils/JGitUtils.java | 963 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 files changed, 805 insertions(+), 158 deletions(-) diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java index 5656efb..d694ee2 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; @@ -59,8 +62,10 @@ import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevTree; 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.transport.CredentialsProvider; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.treewalk.TreeWalk; @@ -80,10 +85,46 @@ import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.RefModel; +/** + * Collection of static methods for retrieving information from a repository. + * + * @author James Moger + * + */ public class JGitUtils { 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. + * + * @param person + * @return "Real Name <email address>" or "Real Name" + */ public static String getDisplayName(PersonIdent person) { if (StringUtils.isEmpty(person.getEmailAddress())) { return person.getName(); @@ -96,37 +137,99 @@ return r.toString().trim(); } - public static FetchResult cloneRepository(File repositoriesFolder, String name, String fromUrl) + /** + * 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. + * + * @param repositoriesFolder + * @param name + * @param fromUrl + * @return CloneResult + * @throws Exception + */ + 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; } - public static FetchResult fetchRepository(Repository repository, RefSpec... refSpecs) - throws Exception { + /** + * 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(CredentialsProvider credentialsProvider, + Repository repository, RefSpec... refSpecs) throws Exception { Git git = new Git(repository); FetchCommand fetch = git.fetch(); List<RefSpec> specs = new ArrayList<RefSpec>(); @@ -137,17 +240,58 @@ } else { specs.addAll(Arrays.asList(refSpecs)); } + if (credentialsProvider != null) { + fetch.setCredentialsProvider(credentialsProvider); + } fetch.setRefSpecs(specs); - FetchResult result = fetch.call(); - repository.close(); + 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; } + /** + * Creates a bare repository. + * + * @param repositoriesFolder + * @param name + * @return Repository + */ public static Repository createRepository(File repositoriesFolder, String name) { Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call(); return git.getRepository(); } + /** + * Returns a list of repository names in the specified folder. + * + * @param repositoriesFolder + * @param exportAll + * if true, all repositories are listed. If false only the + * repositories with a "git-daemon-export-ok" file are included + * @param searchSubfolders + * recurse into subfolders to find grouped repositories + * @return list of repository names + */ public static List<String> getRepositoryList(File repositoriesFolder, boolean exportAll, boolean searchSubfolders) { List<String> list = new ArrayList<String>(); @@ -156,10 +300,24 @@ } list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder, exportAll, searchSubfolders)); - Collections.sort(list); + StringUtils.sortRepositorynames(list); return list; } + /** + * Recursive function to find git repositories. + * + * @param basePath + * basePath is stripped from the repository name as repositories + * are relative to this path + * @param searchFolder + * @param exportAll + * if true all repositories are listed. If false only the + * repositories with a "git-daemon-export-ok" file are included + * @param searchSubfolders + * recurse into subfolders to find grouped repositories + * @return + */ private static List<String> getRepositoryList(String basePath, File searchFolder, boolean exportAll, boolean searchSubfolders) { List<String> list = new ArrayList<String>(); @@ -186,104 +344,194 @@ return list; } - public static RevCommit getFirstCommit(Repository r, String branch) { - if (!hasCommits(r)) { + /** + * Returns the first commit on a branch. If the repository does not exist or + * is empty, null is returned. + * + * @param repository + * @param branch + * if unspecified, HEAD is assumed. + * @return RevCommit + */ + public static RevCommit getFirstCommit(Repository repository, String branch) { + if (!hasCommits(repository)) { return null; - } - if (StringUtils.isEmpty(branch)) { - branch = Constants.HEAD; } RevCommit commit = null; try { - RevWalk walk = new RevWalk(r); + // 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(r.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; } - public static Date getFirstChange(Repository r, String branch) { - RevCommit commit = getFirstCommit(r, branch); + /** + * Returns the date of the first commit on a branch. If the repository does + * not exist, Date(0) is returned. If the repository does exist bit is + * empty, the last modified date of the repository folder is returned. + * + * @param repository + * @param branch + * if unspecified, HEAD is assumed. + * @return Date of the first commit on a branch + */ + public static Date getFirstChange(Repository repository, String branch) { + RevCommit commit = getFirstCommit(repository, branch); if (commit == null) { - if (r == null || !r.getDirectory().exists()) { + if (repository == null || !repository.getDirectory().exists()) { return new Date(0); } // fresh repository - return new Date(r.getDirectory().lastModified()); + return new Date(repository.getDirectory().lastModified()); } return getCommitDate(commit); } - public static boolean hasCommits(Repository r) { - if (r != null && r.getDirectory().exists()) { - return new File(r.getDirectory(), Constants.R_HEADS).list().length > 0; + /** + * Determine if a repository has any commits. This is determined by checking + * the for loose and packed objects. + * + * @param repository + * @return true if the repository has commits + */ + public static boolean hasCommits(Repository repository) { + if (repository != null && repository.getDirectory().exists()) { + return (new File(repository.getDirectory(), "objects").list().length > 2) + || (new File(repository.getDirectory(), "objects/pack").list().length > 0); } return false; } - public static Date getLastChange(Repository r) { - if (!hasCommits(r)) { + /** + * 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. + * + * @param repository + * @param branch + * if unspecified, all branches are checked. + * @return + */ + public static Date getLastChange(Repository repository, String branch) { + if (!hasCommits(repository)) { // null repository - if (r == null) { + if (repository == null) { return new Date(0); } // fresh repository - return new Date(r.getDirectory().lastModified()); + return new Date(repository.getDirectory().lastModified()); } - RevCommit commit = getCommit(r, Constants.HEAD); + if (StringUtils.isEmpty(branch)) { + 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); } + /** + * Retrieves a Java Date from a Git commit. + * + * @param 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); } - public static RevCommit getCommit(Repository r, String objectId) { - if (!hasCommits(r)) { + /** + * Retrieves a Java Date from a Git commit. + * + * @param commit + * @return date of the commit or Date(0) if the commit is null + */ + public static Date getAuthorDate(RevCommit commit) { + if (commit == null) { + return new Date(0); + } + return commit.getAuthorIdent().getWhen(); + } + + /** + * Returns the specified commit from the repository. If the repository does + * not exist or is empty, null is returned. + * + * @param repository + * @param objectId + * if unspecified, HEAD is assumed. + * @return RevCommit + */ + public static RevCommit getCommit(Repository repository, String objectId) { + if (!hasCommits(repository)) { return null; } 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 = r.resolve(objectId); - RevWalk walk = new RevWalk(r); - RevCommit rev = walk.parseCommit(object); + RevWalk walk = new RevWalk(repository); + 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; } - public static Map<ObjectId, List<RefModel>> getAllRefs(Repository r) { - List<RefModel> list = getRefs(r, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1); - Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>(); - for (RefModel ref : list) { - ObjectId objectid = ref.getReferencedObjectId(); - if (!refs.containsKey(objectid)) { - refs.put(objectid, new ArrayList<RefModel>()); - } - refs.get(objectid).add(ref); - } - return refs; - } - - public static byte[] getByteContent(Repository r, RevTree tree, final String path) { - RevWalk rw = new RevWalk(r); - TreeWalk tw = new TreeWalk(r); + /** + * Retrieves the raw byte content of a file in the specified tree. + * + * @param repository + * @param tree + * if null, the RevTree from HEAD is assumed. + * @param path + * @return content as a byte [] + */ + public static byte[] getByteContent(Repository repository, RevTree tree, final String path) { + RevWalk rw = new RevWalk(repository); + TreeWalk tw = new TreeWalk(repository); tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path))); byte[] content = null; try { if (tree == null) { - ObjectId object = r.resolve(Constants.HEAD); + ObjectId object = getDefaultBranch(repository); RevCommit commit = rw.parseCommit(object); tree = commit.getTree(); } @@ -298,7 +546,7 @@ RevObject ro = rw.lookupAny(entid, entmode.getObjectType()); rw.parseBody(ro); ByteArrayOutputStream os = new ByteArrayOutputStream(); - ObjectLoader ldr = r.open(ro.getId(), Constants.OBJ_BLOB); + ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB); byte[] tmp = new byte[4096]; InputStream in = ldr.openStream(); int n; @@ -309,7 +557,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(); @@ -317,22 +565,38 @@ return content; } - public static String getStringContent(Repository r, RevTree tree, String blobPath) { - byte[] content = getByteContent(r, tree, blobPath); + /** + * Returns the UTF-8 string content of a file in the specified tree. + * + * @param repository + * @param tree + * if null, the RevTree from HEAD is assumed. + * @param blobPath + * @return UTF-8 string content + */ + public static String getStringContent(Repository repository, RevTree tree, String blobPath) { + byte[] content = getByteContent(repository, tree, blobPath); if (content == null) { return null; } return new String(content, Charset.forName(Constants.CHARACTER_ENCODING)); } - public static byte[] getByteContent(Repository r, String objectId) { - RevWalk rw = new RevWalk(r); + /** + * Gets the raw byte content of the specified blob object. + * + * @param repository + * @param objectId + * @return byte [] blob content + */ + public static byte[] getByteContent(Repository repository, String objectId) { + RevWalk rw = new RevWalk(repository); byte[] content = null; try { RevBlob blob = rw.lookupBlob(ObjectId.fromString(objectId)); rw.parseBody(blob); ByteArrayOutputStream os = new ByteArrayOutputStream(); - ObjectLoader ldr = r.open(blob.getId(), Constants.OBJ_BLOB); + ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB); byte[] tmp = new byte[4096]; InputStream in = ldr.openStream(); int n; @@ -342,34 +606,54 @@ 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(); } return content; } - public static String getStringContent(Repository r, String objectId) { - byte[] content = getByteContent(r, objectId); + /** + * Gets the UTF-8 string content of the blob specified by objectId. + * + * @param repository + * @param objectId + * @return UTF-8 string content + */ + public static String getStringContent(Repository repository, String objectId) { + byte[] content = getByteContent(repository, objectId); if (content == null) { return null; } return new String(content, Charset.forName(Constants.CHARACTER_ENCODING)); } - public static List<PathModel> getFilesInPath(Repository r, String basePath, RevCommit commit) { + /** + * Returns the list of files in the specified folder at the specified + * commit. If the repository does not exist or is empty, an empty list is + * returned. + * + * @param repository + * @param path + * if unspecified, root folder is assumed. + * @param commit + * if null, HEAD is assumed. + * @return list of files in specified path + */ + public static List<PathModel> getFilesInPath(Repository repository, String path, + RevCommit commit) { List<PathModel> list = new ArrayList<PathModel>(); - if (!hasCommits(r)) { + if (!hasCommits(repository)) { return list; } if (commit == null) { - commit = getCommit(r, Constants.HEAD); + commit = getCommit(repository, null); } - final TreeWalk tw = new TreeWalk(r); + final TreeWalk tw = new TreeWalk(repository); try { tw.addTree(commit.getTree()); - if (!StringUtils.isEmpty(basePath)) { - PathFilter f = PathFilter.create(basePath); + if (!StringUtils.isEmpty(path)) { + PathFilter f = PathFilter.create(path); tw.setFilter(f); tw.setRecursive(false); boolean foundFolder = false; @@ -377,12 +661,12 @@ if (!foundFolder && tw.isSubtree()) { tw.enterSubtree(); } - if (tw.getPathString().equals(basePath)) { + if (tw.getPathString().equals(path)) { foundFolder = true; continue; } if (foundFolder) { - list.add(getPathModel(tw, basePath, commit)); + list.add(getPathModel(tw, path, commit)); } } } else { @@ -392,7 +676,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(); } @@ -400,17 +684,29 @@ return list; } - public static List<PathChangeModel> getFilesInCommit(Repository r, RevCommit commit) { + /** + * 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 commit + * if null, HEAD is assumed. + * @return list of files changed in a commit + */ + public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit) { List<PathChangeModel> list = new ArrayList<PathChangeModel>(); - RevWalk rw = new RevWalk(r); + if (!hasCommits(repository)) { + return list; + } + RevWalk rw = new RevWalk(repository); try { if (commit == null) { - ObjectId object = r.resolve(Constants.HEAD); + ObjectId object = getDefaultBranch(repository); commit = rw.parseCommit(object); - } + } if (commit.getParentCount() == 0) { - TreeWalk tw = new TreeWalk(r); + TreeWalk tw = new TreeWalk(repository); tw.reset(); tw.setRecursive(true); tw.addTree(commit.getTree()); @@ -422,7 +718,7 @@ } else { RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE); - df.setRepository(r); + df.setRepository(repository); df.setDiffComparator(RawTextComparator.DEFAULT); df.setDetectRenames(true); List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree()); @@ -439,17 +735,29 @@ } } } 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(); + rw.dispose(); } return list; } - public static List<PathModel> getDocuments(Repository r, List<String> extensions) { + /** + * Returns the list of files in the repository 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. + * + * @param repository + * @param extensions + * @return list of files in repository with a matching extension + */ + public static List<PathModel> getDocuments(Repository repository, List<String> extensions) { List<PathModel> list = new ArrayList<PathModel>(); - RevCommit commit = getCommit(r, Constants.HEAD); - final TreeWalk tw = new TreeWalk(r); + if (!hasCommits(repository)) { + return list; + } + RevCommit commit = getCommit(repository, null); + final TreeWalk tw = new TreeWalk(repository); try { tw.addTree(commit.getTree()); if (extensions != null && extensions.size() > 0) { @@ -470,7 +778,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(); } @@ -478,6 +786,14 @@ return list; } + /** + * Returns a path model of the current file in the treewalk. + * + * @param tw + * @param basePath + * @param commit + * @return a path model of the current file in the treewalk + */ private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) { String name; long size = 0; @@ -491,12 +807,18 @@ 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()); } + /** + * Returns a permissions representation of the mode bits. + * + * @param mode + * @return string representation of the mode bits + */ public static String getPermissionsFromMode(int mode) { if (FileMode.TREE.equals(mode)) { return "drwxr-xr-x"; @@ -515,27 +837,113 @@ return "missing"; } - public static List<RevCommit> getRevLog(Repository r, int maxCount) { - return getRevLog(r, Constants.HEAD, 0, maxCount); - } - - public static List<RevCommit> getRevLog(Repository r, String objectId, int offset, int maxCount) { - return getRevLog(r, objectId, null, offset, maxCount); - } - - public static List<RevCommit> getRevLog(Repository r, String objectId, String path, int offset, - int maxCount) { + /** + * Returns a list of commits since the minimum date starting from the + * specified object id. + * + * @param repository + * @param objectId + * if unspecified, HEAD is assumed. + * @param minimumDate + * @return list of commits + */ + public static List<RevCommit> getRevLog(Repository repository, String objectId, Date minimumDate) { List<RevCommit> list = new ArrayList<RevCommit>(); - if (!hasCommits(r)) { + if (!hasCommits(repository)) { 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(r); - ObjectId object = r.resolve(objectId); - rw.markStart(rw.parseCommit(object)); + + RevWalk rw = new RevWalk(repository); + rw.markStart(rw.parseCommit(branchObject)); + rw.setRevFilter(CommitTimeRevFilter.after(minimumDate)); + Iterable<RevCommit> revlog = rw; + for (RevCommit rev : revlog) { + list.add(rev); + } + rw.dispose(); + } catch (Throwable t) { + error(t, repository, "{0} failed to get {1} revlog for minimum date {2}", objectId, + minimumDate); + } + return list; + } + + /** + * Returns a list of commits starting from HEAD and working backwards. + * + * @param repository + * @param maxCount + * if < 0, all commits for the repository are returned. + * @return list of commits + */ + public static List<RevCommit> getRevLog(Repository repository, int maxCount) { + return getRevLog(repository, null, 0, maxCount); + } + + /** + * Returns a list of commits starting from the specified objectId using an + * offset and maxCount for paging. This is similar to LIMIT n OFFSET p in + * SQL. If the repository does not exist or is empty, an empty list is + * returned. + * + * @param repository + * @param objectId + * if unspecified, HEAD is assumed. + * @param offset + * @param maxCount + * if < 0, all commits are returned. + * @return a paged list of commits + */ + public static List<RevCommit> getRevLog(Repository repository, String objectId, int offset, + int maxCount) { + return getRevLog(repository, objectId, null, offset, maxCount); + } + + /** + * Returns a list of commits for the repository or a path within the + * repository. Caller may specify ending revision with objectId. Caller may + * specify offset and maxCount to achieve pagination of results. If the + * repository does not exist or is empty, an empty list is returned. + * + * @param repository + * @param objectId + * if unspecified, HEAD is assumed. + * @param path + * if unspecified, commits for repository are returned. If + * specified, commits for the path are returned. + * @param offset + * @param maxCount + * if < 0, all commits are returned. + * @return a paged list of commits + */ + public static List<RevCommit> getRevLog(Repository repository, String objectId, String path, + int offset, int maxCount) { + List<RevCommit> list = new ArrayList<RevCommit>(); + if (maxCount == 0) { + return list; + } + if (!hasCommits(repository)) { + return list; + } + try { + // resolve branch + ObjectId branchObject; + if (StringUtils.isEmpty(objectId)) { + branchObject = getDefaultBranch(repository); + } else { + branchObject = repository.resolve(objectId); + } + + RevWalk rw = new RevWalk(repository); + rw.markStart(rw.parseCommit(branchObject)); if (!StringUtils.isEmpty(path)) { TreeFilter filter = AndTreeFilter.create( PathFilterGroup.createFromStrings(Collections.singleton(path)), @@ -564,45 +972,98 @@ } 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; } - public static enum SearchType { - AUTHOR, COMMITTER, COMMIT; - - public static SearchType forName(String name) { - for (SearchType type : values()) { - if (type.name().equalsIgnoreCase(name)) { - return type; - } - } - return COMMIT; - } - - @Override - public String toString() { - return name().toLowerCase(); - } - } - - public static List<RevCommit> searchRevlogs(Repository r, String objectId, String value, - final SearchType type, int offset, int maxCount) { - final String lcValue = value.toLowerCase(); + /** + * Returns a list of commits for the repository within the range specified + * by startRangeId and endRangeId. If the repository does not exist or is + * empty, an empty list is returned. + * + * @param repository + * @param startRangeId + * the first commit (not included in results) + * @param endRangeId + * the end commit (included in results) + * @return a list of commits + */ + public static List<RevCommit> getRevLog(Repository repository, String startRangeId, + String endRangeId) { List<RevCommit> list = new ArrayList<RevCommit>(); - if (!hasCommits(r)) { + if (!hasCommits(repository)) { return list; } try { - if (StringUtils.isEmpty(objectId)) { - objectId = Constants.HEAD; + ObjectId endRange = repository.resolve(endRangeId); + ObjectId startRange = repository.resolve(startRangeId); + + RevWalk rw = new RevWalk(repository); + rw.markStart(rw.parseCommit(endRange)); + if (startRange.equals(ObjectId.zeroId())) { + // maybe this is a tag or an orphan branch + list.add(rw.parseCommit(endRange)); + rw.dispose(); + return list; + } else { + rw.markUninteresting(rw.parseCommit(startRange)); } - RevWalk rw = new RevWalk(r); + + Iterable<RevCommit> revlog = rw; + for (RevCommit rev : revlog) { + list.add(rev); + } + rw.dispose(); + } catch (Throwable t) { + error(t, repository, "{0} failed to get revlog for {1}..{2}", startRangeId, endRangeId); + } + return list; + } + + /** + * Search the commit history for a case-insensitive match to the value. + * Search results require a specified SearchType of AUTHOR, COMMITTER, or + * COMMIT. Results may be paginated using offset and maxCount. If the + * repository does not exist or is empty, an empty list is returned. + * + * @param repository + * @param objectId + * if unspecified, HEAD is assumed. + * @param value + * @param type + * AUTHOR, COMMITTER, COMMIT + * @param offset + * @param maxCount + * if < 0, all matches are returned + * @return matching list of commits + */ + public static List<RevCommit> searchRevlogs(Repository repository, String objectId, + String value, final com.gitblit.Constants.SearchType type, int offset, int maxCount) { + final String lcValue = value.toLowerCase(); + List<RevCommit> list = new ArrayList<RevCommit>(); + if (maxCount == 0) { + return list; + } + if (!hasCommits(repository)) { + return list; + } + try { + // resolve branch + ObjectId branchObject; + if (StringUtils.isEmpty(objectId)) { + branchObject = getDefaultBranch(repository); + } else { + branchObject = repository.resolve(objectId); + } + + RevWalk rw = new RevWalk(repository); rw.setRevFilter(new RevFilter() { @Override public RevFilter clone() { + // FindBugs complains about this method name. + // This is part of JGit design and unrelated to Cloneable. return this; } @@ -630,8 +1091,7 @@ } }); - ObjectId object = r.resolve(objectId); - rw.markStart(rw.parseCommit(object)); + rw.markStart(rw.parseCommit(branchObject)); Iterable<RevCommit> revlog = rw; if (offset > 0) { int count = 0; @@ -654,32 +1114,154 @@ } 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; } - public static List<RefModel> getTags(Repository r, boolean fullName, int maxCount) { - return getRefs(r, Constants.R_TAGS, fullName, maxCount); + /** + * 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; } - public static List<RefModel> getLocalBranches(Repository r, boolean fullName, int maxCount) { - return getRefs(r, Constants.R_HEADS, fullName, maxCount); + /** + * Returns all refs grouped by their associated object id. + * + * @param repository + * @return all refs grouped by their referenced object id + */ + public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository) { + 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) { + ObjectId objectid = ref.getReferencedObjectId(); + if (!refs.containsKey(objectid)) { + refs.put(objectid, new ArrayList<RefModel>()); + } + refs.get(objectid).add(ref); + } + return refs; } - public static List<RefModel> getRemoteBranches(Repository r, boolean fullName, int maxCount) { - return getRefs(r, Constants.R_REMOTES, fullName, maxCount); + /** + * Returns the list of tags in the repository. If repository does not exist + * or is empty, an empty list is returned. + * + * @param repository + * @param fullName + * if true, /refs/tags/yadayadayada is returned. If false, + * yadayadayada is returned. + * @param maxCount + * if < 0, all tags are returned + * @return list of tags + */ + public static List<RefModel> getTags(Repository repository, boolean fullName, int maxCount) { + return getRefs(repository, Constants.R_TAGS, fullName, maxCount); } - public static List<RefModel> getNotesRefs(Repository r, boolean fullName, int maxCount) { - return getRefs(r, Constants.R_NOTES, fullName, maxCount); + /** + * Returns the list of local branches in the repository. If repository does + * not exist or is empty, an empty list is returned. + * + * @param repository + * @param fullName + * if true, /refs/heads/yadayadayada is returned. If false, + * yadayadayada is returned. + * @param maxCount + * if < 0, all local branches are returned + * @return list of local branches + */ + public static List<RefModel> getLocalBranches(Repository repository, boolean fullName, + int maxCount) { + return getRefs(repository, Constants.R_HEADS, fullName, maxCount); } - private static List<RefModel> getRefs(Repository r, String refs, boolean fullName, int maxCount) { + /** + * Returns the list of remote branches in the repository. If repository does + * not exist or is empty, an empty list is returned. + * + * @param repository + * @param fullName + * if true, /refs/remotes/yadayadayada is returned. If false, + * yadayadayada is returned. + * @param maxCount + * if < 0, all remote branches are returned + * @return list of remote branches + */ + public static List<RefModel> getRemoteBranches(Repository repository, boolean fullName, + int maxCount) { + return getRefs(repository, Constants.R_REMOTES, fullName, maxCount); + } + + /** + * Returns the list of note branches. If repository does not exist or is + * empty, an empty list is returned. + * + * @param repository + * @param fullName + * if true, /refs/notes/yadayadayada is returned. If false, + * yadayadayada is returned. + * @param maxCount + * if < 0, all note branches are returned + * @return list of note branches + */ + public static List<RefModel> getNoteBranches(Repository repository, boolean fullName, + int maxCount) { + return getRefs(repository, Constants.R_NOTES, fullName, maxCount); + } + + /** + * Returns a list of references in the repository matching "refs". If the + * repository is null or empty, an empty list is returned. + * + * @param repository + * @param refs + * if unspecified, all refs are returned + * @param fullName + * if true, /refs/something/yadayadayada is returned. If false, + * yadayadayada is returned. + * @param maxCount + * if < 0, all references are returned + * @return list of references + */ + private static List<RefModel> getRefs(Repository repository, String refs, boolean fullName, + int maxCount) { List<RefModel> list = new ArrayList<RefModel>(); + if (maxCount == 0) { + return list; + } + if (!hasCommits(repository)) { + return list; + } try { - Map<String, Ref> map = r.getRefDatabase().getRefs(refs); - RevWalk rw = new RevWalk(r); + Map<String, Ref> map = repository.getRefDatabase().getRefs(refs); + RevWalk rw = new RevWalk(repository); for (Entry<String, Ref> entry : map.entrySet()) { Ref ref = entry.getValue(); RevObject object = rw.parseAny(ref.getObjectId()); @@ -696,15 +1278,27 @@ 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; } + /** + * Returns the list of notes entered about the commit from the refs/notes + * namespace. If the repository does not exist or is empty, an empty list is + * returned. + * + * @param repository + * @param commit + * @return list of notes + */ public static List<GitNote> getNotesOnCommit(Repository repository, RevCommit commit) { List<GitNote> list = new ArrayList<GitNote>(); - List<RefModel> notesRefs = getNotesRefs(repository, true, -1); - for (RefModel notesRef : notesRefs) { + if (!hasCommits(repository)) { + return list; + } + List<RefModel> noteBranches = getNoteBranches(repository, true, -1); + for (RefModel notesRef : noteBranches) { RevTree notesTree = JGitUtils.getCommit(repository, notesRef.getName()).getTree(); StringBuilder sb = new StringBuilder(commit.getName()); sb.insert(2, '/'); @@ -721,31 +1315,84 @@ return list; } - public static StoredConfig readConfig(Repository r) { - StoredConfig c = r.getConfig(); + /** + * 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 + * @return the StoredConfig of the repository + */ + public static StoredConfig readConfig(Repository repository) { + StoredConfig c = repository.getConfig(); 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; } - public static boolean zip(Repository r, String basePath, String objectId, OutputStream os) - throws Exception { - RevCommit commit = getCommit(r, objectId); + /** + * Zips the contents of the tree at the (optionally) specified revision and + * the (optionally) specified basepath to the supplied outputstream. + * + * @param repository + * @param basePath + * if unspecified, entire repository is assumed. + * @param objectId + * if unspecified, HEAD is assumed. + * @param os + * @return true if repository was successfully zipped to supplied output + * stream + */ + public static boolean zip(Repository repository, String basePath, String objectId, + OutputStream os) { + RevCommit commit = getCommit(repository, objectId); if (commit == null) { return false; } boolean success = false; - RevWalk rw = new RevWalk(r); - TreeWalk tw = new TreeWalk(r); + RevWalk rw = new RevWalk(repository); + TreeWalk tw = new TreeWalk(repository); try { tw.addTree(commit.getTree()); ZipOutputStream zos = new ZipOutputStream(os); - zos.setComment("Generated by Git:Blit"); + zos.setComment("Generated by Gitblit"); if (!StringUtils.isEmpty(basePath)) { PathFilter f = PathFilter.create(basePath); tw.setFilter(f); @@ -763,7 +1410,7 @@ RevBlob blob = (RevBlob) rw.lookupAny(entid, entmode.getObjectType()); rw.parseBody(blob); - ObjectLoader ldr = r.open(blob.getId(), Constants.OBJ_BLOB); + ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB); byte[] tmp = new byte[4096]; InputStream in = ldr.openStream(); int n; @@ -775,7 +1422,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