Java tutorial
// 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.gitiles.RevisionParser.PATH_SPLITTER; import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.Files; import com.google.gitiles.PathServlet.FileType; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.StringTokenizer; /** Soy data converter for git trees. */ public class TreeSoyData { /** * Number of characters to display for a symlink target. Targets longer than * this are abbreviated for display in a tree listing. */ private static final int MAX_SYMLINK_TARGET_LENGTH = 72; /** * Maximum number of bytes to load from a blob that claims to be a symlink. If * the blob is larger than this byte limit it will be displayed as a binary * file instead of as a symlink. */ static final int MAX_SYMLINK_SIZE = 16 << 10; static String resolveTargetUrl(GitilesView view, String target) { if (target.startsWith("/")) { return null; } // simplifyPath() normalizes "a/../../" to "a", so manually check whether // the path leads above the git root. int depth = new StringTokenizer(view.getTreePath(), "/").countTokens(); for (String part : PATH_SPLITTER.split(target)) { if (part.equals("..")) { depth--; if (depth < 0) { return null; } } else if (!part.isEmpty() && !part.equals(".")) { depth++; } } String path = Files.simplifyPath(view.getTreePath() + "/" + target); return GitilesView.path().copyFrom(view).setTreePath(!path.equals(".") ? path : "").toUrl(); } @VisibleForTesting static String getTargetDisplayName(String target) { if (target.length() <= MAX_SYMLINK_TARGET_LENGTH) { return target; } else { int lastSlash = target.lastIndexOf('/'); // TODO(dborowitz): Doesn't abbreviate a long last path component. return lastSlash >= 0 ? "..." + target.substring(lastSlash) : target; } } private final RevWalk rw; private final GitilesView view; public TreeSoyData(RevWalk rw, GitilesView view) { this.rw = rw; this.view = view; } public Map<String, Object> toSoyData(ObjectId treeId, TreeWalk tw) throws MissingObjectException, IOException { List<Object> entries = Lists.newArrayList(); GitilesView.Builder urlBuilder = GitilesView.path().copyFrom(view); while (tw.next()) { FileType type = FileType.forEntry(tw); String name = tw.getNameString(); switch (view.getType()) { case PATH: urlBuilder.setTreePath(view.getTreePath() + "/" + name); break; case REVISION: // Got here from a tag pointing at a tree. urlBuilder.setTreePath(name); break; default: throw new IllegalStateException( String.format("Cannot render TreeSoyData from %s view", view.getType())); } String url = urlBuilder.toUrl(); if (type == FileType.TREE) { name += "/"; url += "/"; } Map<String, String> entry = Maps.newHashMapWithExpectedSize(4); entry.put("type", type.toString()); entry.put("name", name); entry.put("url", url); if (type == FileType.SYMLINK) { String target = new String(rw.getObjectReader().open(tw.getObjectId(0)).getCachedBytes(), Charsets.UTF_8); // TODO(dborowitz): Merge Shawn's changes before copying these methods // in. entry.put("targetName", getTargetDisplayName(target)); String targetUrl = resolveTargetUrl(view, target); if (targetUrl != null) { entry.put("targetUrl", targetUrl); } } entries.add(entry); } Map<String, Object> data = Maps.newHashMapWithExpectedSize(3); data.put("sha", treeId.name()); data.put("entries", entries); if (view.getType() == GitilesView.Type.PATH && view.getRevision().getPeeledType() == OBJ_COMMIT) { data.put("logUrl", GitilesView.log().copyFrom(view).toUrl()); } return data; } public Map<String, Object> toSoyData(ObjectId treeId) throws MissingObjectException, IOException { TreeWalk tw = new TreeWalk(rw.getObjectReader()); tw.addTree(treeId); tw.setRecursive(false); return toSoyData(treeId, tw); } }