Put a shortlog on the repository index page The repo index page now shows an abbreviated list of branches and tags along the left-hand side, with the main content area taken up by a shortlog of HEAD. Since the ref list is now abbreviated, provide a new /+refs page that shows the unabbreviated ref list. (This unabbreviated list is still a bit ugly but ends up looking a lot like the old repo index page.) Change-Id: Ie72690d5e9b8162f68818782bfdaa568ad319983
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java index f463445..73bc426 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
@@ -224,6 +224,8 @@ return new HostIndexServlet(renderer, urls, accessFactory); case REPOSITORY_INDEX: return new RepositoryIndexServlet(renderer, accessFactory, timeCache); + case REFS: + return new RefServlet(renderer, timeCache); case REVISION: return new RevisionServlet(renderer, linkifier()); case PATH:
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java index e84bb2a..d5ac93b 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java
@@ -51,6 +51,7 @@ public static enum Type { HOST_INDEX, REPOSITORY_INDEX, + REFS, REVISION, PATH, DIFF, @@ -88,6 +89,7 @@ case REVISION: revision = other.revision; // Fallthrough. + case REFS: case REPOSITORY_INDEX: repositoryName = other.repositoryName; // Fallthrough. @@ -139,6 +141,7 @@ switch (type) { case HOST_INDEX: case REPOSITORY_INDEX: + case REFS: throw new IllegalStateException(String.format("cannot set revision on %s view", type)); default: this.revision = checkNotNull(revision); @@ -244,6 +247,9 @@ case REPOSITORY_INDEX: checkRepositoryIndex(); break; + case REFS: + checkRefs(); + break; case REVISION: checkRevision(); break; @@ -275,6 +281,10 @@ checkHostIndex(); } + private void checkRefs() { + checkRepositoryIndex(); + } + private void checkRevision() { checkState(revision != Revision.NULL, "missing revision on %s view", type); checkRepositoryIndex(); @@ -302,6 +312,10 @@ return new Builder(Type.REPOSITORY_INDEX); } + public static Builder refs() { + return new Builder(Type.REFS); + } + public static Builder revision() { return new Builder(Type.REVISION); } @@ -426,6 +440,9 @@ case REPOSITORY_INDEX: url.append(repositoryName).append('/'); break; + case REFS: + url.append(repositoryName).append("/+refs"); + break; case REVISION: url.append(repositoryName).append("/+/").append(revision.getName()); break;
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java b/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java index dbeb2e9..77bd36a 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java
@@ -14,13 +14,9 @@ package com.google.gitiles; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.gitiles.CommitSoyData.KeySet; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -29,9 +25,13 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.gitiles.CommitSoyData.KeySet; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; public class LogSoyData { private final HttpServletRequest req;
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/RefServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/RefServlet.java new file mode 100644 index 0000000..82c5b46 --- /dev/null +++ b/gitiles-servlet/src/main/java/com/google/gitiles/RefServlet.java
@@ -0,0 +1,105 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// 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.google.gitiles; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; +import com.google.common.util.concurrent.UncheckedExecutionException; + +import org.eclipse.jgit.http.server.ServletUtils; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.revwalk.RevWalk; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** Serves an HTML page with all the refs in a repository. */ +public class RefServlet extends BaseServlet { + private static final long serialVersionUID = 1L; + + private final TimeCache timeCache; + + protected RefServlet(Renderer renderer, TimeCache timeCache) { + super(renderer); + this.timeCache = checkNotNull(timeCache, "timeCache"); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { + RevWalk walk = new RevWalk(ServletUtils.getRepository(req)); + List<Map<String, String>> tags; + try { + tags = getTags(req, timeCache, walk, 0); + } finally { + walk.release(); + } + render(req, res, "gitiles.refsDetail", ImmutableMap.of( + "branches", getBranches(req, 0), + "tags", tags)); + } + + static List<Map<String, String>> getBranches(HttpServletRequest req, int limit) + throws IOException { + return getRefs(req, Constants.R_HEADS, Ordering.from(RefComparator.INSTANCE), limit); + } + + static List<Map<String, String>> getTags(HttpServletRequest req, TimeCache timeCache, + RevWalk walk, int limit) throws IOException { + return getRefs(req, Constants.R_TAGS, tagComparator(timeCache, walk), limit); + } + + private static Ordering<Ref> tagComparator(final TimeCache timeCache, final RevWalk walk) { + return Ordering.natural().onResultOf(new Function<Ref, Long>() { + @Override + public Long apply(Ref ref) { + try { + return timeCache.getTime(walk, ref.getObjectId()); + } catch (IOException e) { + throw new UncheckedExecutionException(e); + } + } + }).reverse().compound(RefComparator.INSTANCE); + } + + private static List<Map<String, String>> getRefs(HttpServletRequest req, String prefix, + Ordering<Ref> ordering, int limit) throws IOException { + RefDatabase refdb = ServletUtils.getRepository(req).getRefDatabase(); + Collection<Ref> refs = refdb.getRefs(prefix).values(); + refs = ordering.leastOf(refs, limit > 0 ? limit + 1 : refs.size()); + List<Map<String, String>> result = Lists.newArrayListWithCapacity(refs.size()); + + for (Ref ref : refs) { + String name = ref.getName().substring(prefix.length()); + boolean needPrefix = !ref.getName().equals(refdb.getRef(name).getName()); + result.add(ImmutableMap.of( + "url", GitilesView.revision().copyFrom(req).setRevision( + Revision.unpeeled(needPrefix ? ref.getName() : name, ref.getObjectId())).toUrl(), + "name", name)); + } + return result; + } +}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java b/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java index dc4f680..aa12869 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java
@@ -42,6 +42,7 @@ "LogDetail.soy", "ObjectDetail.soy", "PathDetail.soy", + "RefList.soy", "RevisionDetail.soy", "RepositoryIndex.soy");
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java index 11f0e9e..a0fea72 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java
@@ -17,22 +17,18 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import com.google.common.collect.Ordering; -import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.common.collect.Maps; import org.eclipse.jgit.http.server.ServletUtils; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefComparator; -import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import java.io.IOException; -import java.util.Collection; import java.util.List; import java.util.Map; @@ -43,6 +39,9 @@ public class RepositoryIndexServlet extends BaseServlet { private static final long serialVersionUID = 1L; + static final int REF_LIMIT = 10; + private static final int LOG_LIMIT = 20; + private final GitilesAccess.Factory accessFactory; private final TimeCache timeCache; @@ -60,49 +59,48 @@ @VisibleForTesting Map<String, ?> buildData(HttpServletRequest req) throws IOException { + GitilesView view = ViewFilter.getView(req); + Repository repo = ServletUtils.getRepository(req); RepositoryDescription desc = accessFactory.forRequest(req).getRepositoryDescription(); - RevWalk walk = new RevWalk(ServletUtils.getRepository(req)); + RevWalk walk = new RevWalk(repo); List<Map<String, String>> tags; + Map<String, Object> data; try { - tags = getRefs(req, Constants.R_TAGS, tagComparator(walk)); + tags = RefServlet.getTags(req, timeCache, walk, REF_LIMIT); + ObjectId headId = repo.resolve(Constants.HEAD); + if (headId != null) { + RevObject head = walk.parseAny(repo.resolve(Constants.HEAD)); + if (head.getType() == Constants.OBJ_COMMIT) { + walk.reset(); + walk.markStart((RevCommit) head); + data = new LogSoyData(req, repo, view).toSoyData(walk, LOG_LIMIT, "HEAD", null); + } else { + // TODO(dborowitz): Handle non-commit or missing HEAD? + data = Maps.newHashMapWithExpectedSize(6); + } + } else { + data = Maps.newHashMapWithExpectedSize(6); + } } finally { walk.release(); } - return ImmutableMap.of("cloneUrl", desc.cloneUrl, - "mirroredFromUrl", Strings.nullToEmpty(desc.mirroredFromUrl), - "description", Strings.nullToEmpty(desc.description), - "branches", getRefs(req, Constants.R_HEADS, Ordering.from(RefComparator.INSTANCE)), - "tags", tags); - } + List<Map<String, String>> branches = RefServlet.getBranches(req, REF_LIMIT); - private List<Map<String, String>> getRefs(HttpServletRequest req, String prefix, - Ordering<Ref> ordering) throws IOException { - RefDatabase refdb = ServletUtils.getRepository(req).getRefDatabase(); - Collection<Ref> refs = ordering.sortedCopy(refdb.getRefs(prefix).values()); - List<Map<String, String>> result = Lists.newArrayListWithCapacity(refs.size()); - - for (Ref ref : refs) { - String name = ref.getName().substring(prefix.length()); - boolean needPrefix = !ref.getName().equals(refdb.getRef(name).getName()); - result.add(ImmutableMap.of( - "url", GitilesView.revision().copyFrom(req).setRevision( - Revision.unpeeled(needPrefix ? ref.getName() : name, ref.getObjectId())).toUrl(), - "name", name)); + data.put("cloneUrl", desc.cloneUrl); + data.put("mirroredFromUrl", Strings.nullToEmpty(desc.mirroredFromUrl)); + data.put("description", Strings.nullToEmpty(desc.description)); + data.put("branches", trim(branches)); + if (branches.size() > REF_LIMIT) { + data.put("moreBranchesUrl", GitilesView.refs().copyFrom(view).toUrl()); } - - return result; + data.put("tags", trim(tags)); + if (tags.size() > REF_LIMIT) { + data.put("moreTagsUrl", GitilesView.refs().copyFrom(view).toUrl()); + } + return data; } - private Ordering<Ref> tagComparator(final RevWalk walk) { - return Ordering.natural().onResultOf(new Function<Ref, Long>() { - @Override - public Long apply(Ref ref) { - try { - return timeCache.getTime(walk, ref.getObjectId()); - } catch (IOException e) { - throw new UncheckedExecutionException(e); - } - } - }).reverse().compound(RefComparator.INSTANCE); + private static <T> List<T> trim(List<T> list) { + return list.size() > REF_LIMIT ? list.subList(0, REF_LIMIT) : list; } }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java index e7532ab..90cd94a 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java
@@ -18,6 +18,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import org.eclipse.jgit.http.server.ServletUtils; +import org.eclipse.jgit.http.server.glue.WrappedRequest; + import java.io.IOException; import java.util.Map; @@ -26,9 +29,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jgit.http.server.ServletUtils; -import org.eclipse.jgit.http.server.glue.WrappedRequest; - /** Filter to parse URLs and convert them to {@link GitilesView}s. */ public class ViewFilter extends AbstractHttpFilter { // TODO(dborowitz): Make this public in JGit (or implement getRegexGroup @@ -41,6 +41,7 @@ private static final String CMD_AUTO = "+"; private static final String CMD_DIFF = "+diff"; private static final String CMD_LOG = "+log"; + private static final String CMD_REFS = "+refs"; private static final String CMD_SHOW = "+show"; public static GitilesView getView(HttpServletRequest req) { @@ -101,10 +102,12 @@ // Non-path cases. if (repoName.isEmpty()) { return GitilesView.hostIndex(); + } else if (command.equals(CMD_REFS) && path.isEmpty()) { + return GitilesView.refs().setRepositoryName(repoName); } else if (command.isEmpty()) { return GitilesView.repositoryIndex().setRepositoryName(repoName); } else if (path.isEmpty()) { - return null; // Command but no path. + return null; // Command that requires a path, but no path. } path = trimLeadingSlash(path); @@ -128,6 +131,8 @@ } } else if (CMD_DIFF.equals(command)) { view = GitilesView.diff().setTreePath(path); + } else if (CMD_REFS.equals(command)) { + view = GitilesView.repositoryIndex(); } else { return null; // Bad command. }