From f8bb95d50ad925ab16a0a167bc553f036434a2d7 Mon Sep 17 00:00:00 2001 From: James Moger Date: Sat, 5 Jan 2013 10:23:42 -0500 Subject: [PATCH] Draft mechanism to record a push log as a hidden orphan branch --- src/com/gitblit/Constants.java | 2 + src/com/gitblit/GitServlet.java | 55 ++- src/com/gitblit/models/Activity.java | 83 ----- src/com/gitblit/models/PushLogEntry.java | 166 +++++++++ src/com/gitblit/models/RepositoryCommit.java | 104 ++++++ src/com/gitblit/utils/ActivityUtils.java | 2 +- src/com/gitblit/utils/IssueUtils.java | 16 +- src/com/gitblit/utils/JGitUtils.java | 14 + src/com/gitblit/utils/PushLogUtils.java | 344 ++++++++++++++++++ .../gitblit/wicket/panels/ActivityPanel.java | 2 +- src/com/gitblit/wicket/panels/RefsPanel.java | 6 + tests/com/gitblit/tests/GitServletTest.java | 15 + tests/com/gitblit/tests/PushLogTest.java | 37 ++ 13 files changed, 751 insertions(+), 95 deletions(-) create mode 100644 src/com/gitblit/models/PushLogEntry.java create mode 100644 src/com/gitblit/models/RepositoryCommit.java create mode 100644 src/com/gitblit/utils/PushLogUtils.java create mode 100644 tests/com/gitblit/tests/PushLogTest.java diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index ca332690..4dd14712 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -88,6 +88,8 @@ public class Constants { public static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ"; + public static final String R_GITBLIT = "refs/gitblit/"; + public static String getGitBlitVersion() { return NAME + " v" + VERSION; } diff --git a/src/com/gitblit/GitServlet.java b/src/com/gitblit/GitServlet.java index 94a51be0..05f38b9d 100644 --- a/src/com/gitblit/GitServlet.java +++ b/src/com/gitblit/GitServlet.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import javax.servlet.ServletConfig; @@ -37,7 +38,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory; +import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PostReceiveHook; @@ -45,6 +48,8 @@ import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.ReceivePack; +import org.eclipse.jgit.transport.RefFilter; +import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.slf4j.Logger; @@ -55,7 +60,9 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ClientLogger; import com.gitblit.utils.HttpUtils; +import com.gitblit.utils.IssueUtils; import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.PushLogUtils; import com.gitblit.utils.StringUtils; /** @@ -131,6 +138,35 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { return rp; } }); + + // override the default upload pack to exclude gitblit refs + setUploadPackFactory(new DefaultUploadPackFactory() { + @Override + public UploadPack create(final HttpServletRequest req, final Repository db) + throws ServiceNotEnabledException, ServiceNotAuthorizedException { + UploadPack up = super.create(req, db); + RefFilter refFilter = new RefFilter() { + @Override + public Map filter(Map refs) { + // admin accounts can access all refs + UserModel user = GitBlit.self().authenticate(req); + if (user == null) { + user = UserModel.ANONYMOUS; + } + if (user.canAdmin()) { + return refs; + } + + // normal users can not clone gitblit refs + refs.remove(IssueUtils.GB_ISSUES); + refs.remove(PushLogUtils.GB_PUSHES); + return refs; + } + }; + up.setRefFilter(refFilter); + return up; + } + }); super.init(new GitblitServletConfig(config)); } @@ -264,12 +300,11 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { logger.info("skipping post-receive hooks, no refs created, updated, or removed"); return; } - RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName); - Set scripts = new LinkedHashSet(); - scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository)); - scripts.addAll(repository.postReceiveScripts); + UserModel user = getUserModel(rp); - runGroovy(repository, user, commands, rp, scripts); + RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName); + + // log ref changes for (ReceiveCommand cmd : commands) { if (Result.OK.equals(cmd.getResult())) { // add some logging for important ref changes @@ -288,6 +323,16 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { } } } + + // update push log + PushLogUtils.updatePushLog(user, rp.getRepository(), commands); + logger.info(MessageFormat.format("{0} push log updated", repository.name)); + + // run Groovy hook scripts + Set scripts = new LinkedHashSet(); + scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository)); + scripts.addAll(repository.postReceiveScripts); + runGroovy(repository, user, commands, rp, scripts); // Experimental // runNativeScript(rp, "hooks/post-receive", commands); diff --git a/src/com/gitblit/models/Activity.java b/src/com/gitblit/models/Activity.java index 7e0cb4b3..59405c7f 100644 --- a/src/com/gitblit/models/Activity.java +++ b/src/com/gitblit/models/Activity.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.revwalk.RevCommit; import com.gitblit.utils.StringUtils; @@ -127,86 +126,4 @@ public class Activity implements Serializable, Comparable { // reverse chronological order return o.startDate.compareTo(startDate); } - - /** - * Model class to represent a RevCommit, it's source repository, and the - * branch. This class is used by the activity page. - * - * @author James Moger - */ - public static class RepositoryCommit implements Serializable, Comparable { - - private static final long serialVersionUID = 1L; - - public final String repository; - - public final String branch; - - private final RevCommit commit; - - private List refs; - - public RepositoryCommit(String repository, String branch, RevCommit commit) { - this.repository = repository; - this.branch = branch; - this.commit = commit; - } - - public void setRefs(List refs) { - this.refs = refs; - } - - public List getRefs() { - return refs; - } - - public String getName() { - return commit.getName(); - } - - public String getShortName() { - return commit.getName().substring(0, 8); - } - - public String getShortMessage() { - return commit.getShortMessage(); - } - - public int getParentCount() { - return commit.getParentCount(); - } - - public PersonIdent getAuthorIdent() { - return commit.getAuthorIdent(); - } - - public PersonIdent getCommitterIdent() { - return commit.getCommitterIdent(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof RepositoryCommit) { - RepositoryCommit commit = (RepositoryCommit) o; - return repository.equals(commit.repository) && getName().equals(commit.getName()); - } - return false; - } - - @Override - public int hashCode() { - return (repository + commit).hashCode(); - } - - @Override - public int compareTo(RepositoryCommit o) { - // reverse-chronological order - if (commit.getCommitTime() > o.commit.getCommitTime()) { - return -1; - } else if (commit.getCommitTime() < o.commit.getCommitTime()) { - return 1; - } - return 0; - } - } } diff --git a/src/com/gitblit/models/PushLogEntry.java b/src/com/gitblit/models/PushLogEntry.java new file mode 100644 index 00000000..32a7d007 --- /dev/null +++ b/src/com/gitblit/models/PushLogEntry.java @@ -0,0 +1,166 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.models; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.revwalk.RevCommit; + +/** + * Model class to represent a push into a repository. + * + * @author James Moger + */ +public class PushLogEntry implements Serializable, Comparable { + + private static final long serialVersionUID = 1L; + + public final String repository; + + public final Date date; + + public final UserModel user; + + private final Set commits; + + /** + * Constructor for specified duration of push from start date. + * + * @param repository + * the repository that received the push + * @param date + * the date of the push + * @param user + * the user who pushed + */ + public PushLogEntry(String repository, Date date, UserModel user) { + this.repository = repository; + this.date = date; + this.user = user; + this.commits = new LinkedHashSet(); + } + + /** + * Adds a commit to the push entry object as long as the commit is not a + * duplicate. + * + * @param branch + * @param commit + * @return a RepositoryCommit, if one was added. Null if this is duplicate + * commit + */ + public RepositoryCommit addCommit(String branch, RevCommit commit) { + RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit); + if (commits.add(commitModel)) { + return commitModel; + } + return null; + } + + /** + * Returns the list of branches changed by the push. + * + * @return a list of branches + */ + public List getChangedBranches() { + return getChangedRefs(Constants.R_HEADS); + } + + /** + * Returns the list of tags changed by the push. + * + * @return a list of tags + */ + public List getChangedTags() { + return getChangedRefs(Constants.R_TAGS); + } + + /** + * Gets the changed refs in the push. + * + * @param baseRef + * @return the changed refs + */ + protected List getChangedRefs(String baseRef) { + Set refs = new HashSet(); + for (RepositoryCommit commit : commits) { + if (baseRef == null || commit.branch.startsWith(baseRef)) { + refs.add(commit.branch); + } + } + List list = new ArrayList(refs); + Collections.sort(list); + return list; + } + + /** + * The total number of commits in the push. + * + * @return the number of commits in the push + */ + public int getCommitCount() { + return commits.size(); + } + + /** + * Returns all commits in the push. + * + * @return a list of commits + */ + public List getCommits() { + List list = new ArrayList(commits); + Collections.sort(list); + return list; + } + + /** + * Returns all commits that belong to a particular ref + * + * @param ref + * @return a list of commits + */ + public List getCommits(String ref) { + List list = new ArrayList(); + for (RepositoryCommit commit : commits) { + if (commit.branch.equals(ref)) { + list.add(commit); + } + } + Collections.sort(list); + return list; + } + + @Override + public int compareTo(PushLogEntry o) { + // reverse chronological order + return o.date.compareTo(date); + } + + @Override + public String toString() { + return MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1} pushed {2,number,0} commit{3} to {4} ", + date, user.getDisplayName(), commits.size(), commits.size() == 1 ? "":"s", repository); + } +} diff --git a/src/com/gitblit/models/RepositoryCommit.java b/src/com/gitblit/models/RepositoryCommit.java new file mode 100644 index 00000000..3a98f61f --- /dev/null +++ b/src/com/gitblit/models/RepositoryCommit.java @@ -0,0 +1,104 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.models; + +import java.io.Serializable; +import java.util.List; + +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; + +/** + * Model class to represent a RevCommit, it's source repository, and the branch. + * This class is used by the activity page. + * + * @author James Moger + */ +public class RepositoryCommit implements Serializable, Comparable { + + private static final long serialVersionUID = 1L; + + public final String repository; + + public final String branch; + + private final RevCommit commit; + + private List refs; + + public RepositoryCommit(String repository, String branch, RevCommit commit) { + this.repository = repository; + this.branch = branch; + this.commit = commit; + } + + public void setRefs(List refs) { + this.refs = refs; + } + + public List getRefs() { + return refs; + } + + public String getName() { + return commit.getName(); + } + + public String getShortName() { + return commit.getName().substring(0, 8); + } + + public String getShortMessage() { + return commit.getShortMessage(); + } + + public int getParentCount() { + return commit.getParentCount(); + } + + public PersonIdent getAuthorIdent() { + return commit.getAuthorIdent(); + } + + public PersonIdent getCommitterIdent() { + return commit.getCommitterIdent(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof RepositoryCommit) { + RepositoryCommit commit = (RepositoryCommit) o; + return repository.equals(commit.repository) && getName().equals(commit.getName()); + } + return false; + } + + @Override + public int hashCode() { + return (repository + commit).hashCode(); + } + + @Override + public int compareTo(RepositoryCommit o) { + // reverse-chronological order + if (commit.getCommitTime() > o.commit.getCommitTime()) { + return -1; + } else if (commit.getCommitTime() < o.commit.getCommitTime()) { + return 1; + } + return 0; + } +} \ No newline at end of file diff --git a/src/com/gitblit/utils/ActivityUtils.java b/src/com/gitblit/utils/ActivityUtils.java index ef3a55e7..732fdeb1 100644 --- a/src/com/gitblit/utils/ActivityUtils.java +++ b/src/com/gitblit/utils/ActivityUtils.java @@ -36,9 +36,9 @@ import org.eclipse.jgit.revwalk.RevCommit; import com.gitblit.GitBlit; import com.gitblit.models.Activity; -import com.gitblit.models.Activity.RepositoryCommit; import com.gitblit.models.GravatarProfile; import com.gitblit.models.RefModel; +import com.gitblit.models.RepositoryCommit; import com.gitblit.models.RepositoryModel; import com.google.gson.reflect.TypeToken; diff --git a/src/com/gitblit/utils/IssueUtils.java b/src/com/gitblit/utils/IssueUtils.java index 7b24ccf7..1b90c7d6 100644 --- a/src/com/gitblit/utils/IssueUtils.java +++ b/src/com/gitblit/utils/IssueUtils.java @@ -76,9 +76,9 @@ public class IssueUtils { public abstract boolean accept(IssueModel issue); } - public static final String GB_ISSUES = "refs/heads/gb-issues"; + public static final String GB_ISSUES = "refs/gitblit/issues"; - static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class); + static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class); /** * Log an error message and exception. @@ -111,7 +111,13 @@ public class IssueUtils { * @return a refmodel for the gb-issues branch or null */ public static RefModel getIssuesBranch(Repository repository) { - return JGitUtils.getBranch(repository, "gb-issues"); + List refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT); + for (RefModel ref : refs) { + if (ref.reference.getName().equals(GB_ISSUES)) { + return ref; + } + } + return null; } /** @@ -394,7 +400,7 @@ public class IssueUtils { public static IssueModel createIssue(Repository repository, Change change) { RefModel issuesBranch = getIssuesBranch(repository); if (issuesBranch == null) { - JGitUtils.createOrphanBranch(repository, "gb-issues", null); + JGitUtils.createOrphanBranch(repository, GB_ISSUES, null); } if (StringUtils.isEmpty(change.author)) { @@ -471,7 +477,7 @@ public class IssueUtils { RefModel issuesBranch = getIssuesBranch(repository); if (issuesBranch == null) { - throw new RuntimeException("gb-issues branch does not exist!"); + throw new RuntimeException(GB_ISSUES + " does not exist!"); } if (StringUtils.isEmpty(issueId)) { diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java index beaa27da..099036e5 100644 --- a/src/com/gitblit/utils/JGitUtils.java +++ b/src/com/gitblit/utils/JGitUtils.java @@ -1457,6 +1457,20 @@ public class JGitUtils { int maxCount) { return getRefs(repository, Constants.R_NOTES, fullName, maxCount); } + + /** + * Returns the list of refs in the specified base ref. If repository does + * not exist or is empty, an empty list is returned. + * + * @param repository + * @param fullName + * if true, /refs/yadayadayada is returned. If false, + * yadayadayada is returned. + * @return list of refs + */ + public static List getRefs(Repository repository, String baseRef) { + return getRefs(repository, baseRef, true, -1); + } /** * Returns a list of references in the repository matching "refs". If the diff --git a/src/com/gitblit/utils/PushLogUtils.java b/src/com/gitblit/utils/PushLogUtils.java new file mode 100644 index 00000000..a3b1d66b --- /dev/null +++ b/src/com/gitblit/utils/PushLogUtils.java @@ -0,0 +1,344 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.utils; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.models.PathModel.PathChangeModel; +import com.gitblit.models.PushLogEntry; +import com.gitblit.models.RefModel; +import com.gitblit.models.UserModel; + +/** + * Utility class for maintaining a pushlog within a git repository on an + * orphan branch. + * + * @author James Moger + * + */ +public class PushLogUtils { + + public static final String GB_PUSHES = "refs/gitblit/pushes"; + + static final Logger LOGGER = LoggerFactory.getLogger(PushLogUtils.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 parameters = new ArrayList(); + 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 a RefModel for the gb-pushes branch in the repository. If the + * branch can not be found, null is returned. + * + * @param repository + * @return a refmodel for the gb-pushes branch or null + */ + public static RefModel getPushLogBranch(Repository repository) { + List refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT); + for (RefModel ref : refs) { + if (ref.reference.getName().equals(GB_PUSHES)) { + return ref; + } + } + return null; + } + + /** + * Updates a push log. + * + * @param user + * @param repository + * @param commands + * @return true, if the update was successful + */ + public static boolean updatePushLog(UserModel user, Repository repository, + Collection commands) { + RefModel pushlogBranch = getPushLogBranch(repository); + if (pushlogBranch == null) { + JGitUtils.createOrphanBranch(repository, GB_PUSHES, null); + } + + boolean success = false; + String message = "push"; + + try { + ObjectId headId = repository.resolve(GB_PUSHES + "^{commit}"); + ObjectInserter odi = repository.newObjectInserter(); + try { + // Create the in-memory index of the push log entry + DirCache index = createIndex(repository, headId, commands); + ObjectId indexTreeId = index.writeTree(odi); + + PersonIdent ident = new PersonIdent(user.getDisplayName(), + user.emailAddress == null ? user.username:user.emailAddress); + + // Create a commit object + CommitBuilder commit = new CommitBuilder(); + commit.setAuthor(ident); + commit.setCommitter(ident); + commit.setEncoding(Constants.CHARACTER_ENCODING); + commit.setMessage(message); + commit.setParentId(headId); + commit.setTreeId(indexTreeId); + + // Insert the commit into the repository + ObjectId commitId = odi.insert(commit); + odi.flush(); + + RevWalk revWalk = new RevWalk(repository); + try { + RevCommit revCommit = revWalk.parseCommit(commitId); + RefUpdate ru = repository.updateRef(GB_PUSHES); + ru.setNewObjectId(commitId); + ru.setExpectedOldObjectId(headId); + ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false); + Result rc = ru.forceUpdate(); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + success = true; + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, + ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, GB_PUSHES, commitId.toString(), + rc)); + } + } finally { + revWalk.release(); + } + } finally { + odi.release(); + } + } catch (Throwable t) { + error(t, repository, "Failed to commit pushlog entry to {0}"); + } + return success; + } + + /** + * Creates an in-memory index of the push log entry. + * + * @param repo + * @param headId + * @param commands + * @return an in-memory index + * @throws IOException + */ + private static DirCache createIndex(Repository repo, ObjectId headId, + Collection commands) throws IOException { + + DirCache inCoreIndex = DirCache.newInCore(); + DirCacheBuilder dcBuilder = inCoreIndex.builder(); + ObjectInserter inserter = repo.newObjectInserter(); + + long now = System.currentTimeMillis(); + Set ignorePaths = new TreeSet(); + try { + // add receive commands to the temporary index + for (ReceiveCommand command : commands) { + // use the ref names as the path names + String path = command.getRefName(); + ignorePaths.add(path); + + StringBuilder change = new StringBuilder(); + change.append(command.getType().name()).append(' '); + switch (command.getType()) { + case CREATE: + change.append(ObjectId.zeroId().getName()); + change.append(' '); + change.append(command.getNewId().getName()); + break; + case UPDATE: + case UPDATE_NONFASTFORWARD: + change.append(command.getOldId().getName()); + change.append(' '); + change.append(command.getNewId().getName()); + break; + case DELETE: + change = null; + break; + } + if (change == null) { + // ref deleted + continue; + } + String content = change.toString(); + + // create an index entry for this attachment + final DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setLength(content.length()); + dcEntry.setLastModified(now); + dcEntry.setFileMode(FileMode.REGULAR_FILE); + + // insert object + dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))); + + // add to temporary in-core index + dcBuilder.add(dcEntry); + } + + // Traverse HEAD to add all other paths + TreeWalk treeWalk = new TreeWalk(repo); + int hIdx = -1; + if (headId != null) + hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId)); + treeWalk.setRecursive(true); + + while (treeWalk.next()) { + String path = treeWalk.getPathString(); + CanonicalTreeParser hTree = null; + if (hIdx != -1) + hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); + if (!ignorePaths.contains(path)) { + // add entries from HEAD for all other paths + if (hTree != null) { + // create a new DirCacheEntry with data retrieved from + // HEAD + final DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setObjectId(hTree.getEntryObjectId()); + dcEntry.setFileMode(hTree.getEntryFileMode()); + + // add to temporary in-core index + dcBuilder.add(dcEntry); + } + } + } + + // release the treewalk + treeWalk.release(); + + // finish temporary in-core index used for this commit + dcBuilder.finish(); + } finally { + inserter.release(); + } + return inCoreIndex; + } + + public static List getPushLog(String repositoryName, Repository repository) { + return getPushLog(repositoryName, repository, null, -1); + } + + public static List getPushLog(String repositoryName, Repository repository, int maxCount) { + return getPushLog(repositoryName, repository, null, maxCount); + } + + public static List getPushLog(String repositoryName, Repository repository, Date minimumDate) { + return getPushLog(repositoryName, repository, minimumDate, -1); + } + + public static List getPushLog(String repositoryName, Repository repository, Date minimumDate, int maxCount) { + List list = new ArrayList(); + RefModel ref = getPushLogBranch(repository); + if (ref == null) { + return list; + } + List pushes; + if (minimumDate == null) { + pushes = JGitUtils.getRevLog(repository, GB_PUSHES, 0, maxCount); + } else { + pushes = JGitUtils.getRevLog(repository, GB_PUSHES, minimumDate); + } + for (RevCommit push : pushes) { + if (push.getAuthorIdent().getName().equalsIgnoreCase("gitblit")) { + // skip gitblit/internal commits + continue; + } + Date date = push.getAuthorIdent().getWhen(); + UserModel user = new UserModel(push.getAuthorIdent().getEmailAddress()); + user.displayName = push.getAuthorIdent().getName(); + PushLogEntry log = new PushLogEntry(repositoryName, date, user); + list.add(log); + List changedRefs = JGitUtils.getFilesInCommit(repository, push); + for (PathChangeModel change : changedRefs) { + switch (change.changeType) { + case DELETE: + break; + case ADD: + case MODIFY: + String content = JGitUtils.getStringContent(repository, push.getTree(), change.path); + String [] fields = content.split(" "); + String oldId = fields[1]; + String newId = fields[2]; + List pushedCommits = JGitUtils.getRevLog(repository, oldId, newId); + for (RevCommit pushedCommit : pushedCommits) { + log.addCommit(change.path, pushedCommit); + } + break; + default: + break; + } + } + } + Collections.sort(list); + return list; + } +} diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.java b/src/com/gitblit/wicket/panels/ActivityPanel.java index 6caee3e7..669c36be 100644 --- a/src/com/gitblit/wicket/panels/ActivityPanel.java +++ b/src/com/gitblit/wicket/panels/ActivityPanel.java @@ -27,7 +27,7 @@ import com.gitblit.Constants; import com.gitblit.GitBlit; import com.gitblit.Keys; import com.gitblit.models.Activity; -import com.gitblit.models.Activity.RepositoryCommit; +import com.gitblit.models.RepositoryCommit; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.WicketUtils; import com.gitblit.wicket.pages.CommitDiffPage; diff --git a/src/com/gitblit/wicket/panels/RefsPanel.java b/src/com/gitblit/wicket/panels/RefsPanel.java index b4676427..3ba22c0b 100644 --- a/src/com/gitblit/wicket/panels/RefsPanel.java +++ b/src/com/gitblit/wicket/panels/RefsPanel.java @@ -129,8 +129,14 @@ public class RefsPanel extends Panel { name = name.substring(Constants.R_TAGS.length()); cssClass = "tagRef"; } else if (name.startsWith(Constants.R_NOTES)) { + // codereview refs linkClass = CommitPage.class; cssClass = "otherRef"; + } else if (name.startsWith(com.gitblit.Constants.R_GITBLIT)) { + // gitblit refs + linkClass = LogPage.class; + cssClass = "otherRef"; + name = name.substring(com.gitblit.Constants.R_GITBLIT.length()); } Component c = new LinkPanel("refName", null, name, linkClass, diff --git a/tests/com/gitblit/tests/GitServletTest.java b/tests/com/gitblit/tests/GitServletTest.java index 7dce07d8..771c4b9a 100644 --- a/tests/com/gitblit/tests/GitServletTest.java +++ b/tests/com/gitblit/tests/GitServletTest.java @@ -7,9 +7,11 @@ import static org.junit.Assert.assertTrue; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.OutputStreamWriter; import java.text.MessageFormat; import java.util.Date; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jgit.api.CloneCommand; @@ -18,6 +20,7 @@ import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefSpec; @@ -34,9 +37,11 @@ import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AuthorizationControl; import com.gitblit.GitBlit; import com.gitblit.Keys; +import com.gitblit.models.PushLogEntry; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.PushLogUtils; public class GitServletTest { @@ -756,4 +761,14 @@ public class GitServletTest { GitBlitSuite.close(git); GitBlit.self().deleteUser(user.username); } + + @Test + public void testPushLog() throws IOException { + String name = "refchecks/ticgit.git"; + File refChecks = new File(GitBlitSuite.REPOSITORIES, name); + FileRepository repository = new FileRepository(refChecks); + List pushes = PushLogUtils.getPushLog(name, repository); + GitBlitSuite.close(repository); + assertTrue("Repository has an empty push log!", pushes.size() > 0); + } } diff --git a/tests/com/gitblit/tests/PushLogTest.java b/tests/com/gitblit/tests/PushLogTest.java new file mode 100644 index 00000000..aa4cf418 --- /dev/null +++ b/tests/com/gitblit/tests/PushLogTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.tests; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.eclipse.jgit.storage.file.FileRepository; +import org.junit.Test; + +import com.gitblit.models.PushLogEntry; +import com.gitblit.utils.PushLogUtils; + +public class PushLogTest { + + @Test + public void testPushLog() throws IOException { + String name = "~james/helloworld.git"; + FileRepository repository = new FileRepository(new File(GitBlitSuite.REPOSITORIES, name)); + List pushes = PushLogUtils.getPushLog(name, repository); + GitBlitSuite.close(repository); + } +} \ No newline at end of file -- 2.39.5