From 4ab184198bd7eac67eb767cf2e19423f618a70ae Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 03 Jun 2011 17:44:19 -0400
Subject: [PATCH] GitNotes.  Unit testing.  More correct refs.

---
 src/com/gitblit/models/RefModel.java                 |   79 ++++-
 src/com/gitblit/wicket/panels/TagsPanel.html         |    9 
 src/com/gitblit/wicket/pages/TagPage.html            |    6 
 src/com/gitblit/wicket/panels/RefsPanel.java         |   53 ++-
 src/com/gitblit/wicket/pages/CommitPage.html         |   12 
 tests/com/gitblit/tests/GitBlitSuite.java            |   40 ++
 src/com/gitblit/wicket/pages/MarkdownPage.java       |    2 
 src/com/gitblit/wicket/resources/gitblit.css         |   27 +
 src/com/gitblit/wicket/panels/TagsPanel.java         |   97 ++++-
 src/com/gitblit/wicket/GitBlitWebApp.properties      |    3 
 src/com/gitblit/wicket/pages/CommitPage.java         |   14 
 src/com/gitblit/wicket/panels/HistoryPanel.java      |    3 
 src/com/gitblit/wicket/pages/RawPage.java            |   92 +++--
 tests/com/gitblit/tests/JGitUtilsTest.java           |   69 +++-
 src/com/gitblit/utils/TicgitUtils.java               |   11 
 src/com/gitblit/wicket/panels/SearchPanel.java       |    3 
 src/com/gitblit/wicket/panels/CommitHeaderPanel.java |    8 
 src/com/gitblit/wicket/panels/LogPanel.java          |    3 
 src/com/gitblit/utils/MetricUtils.java               |    2 
 src/com/gitblit/wicket/pages/BlobPage.java           |  161 ++++++----
 src/com/gitblit/wicket/pages/SummaryPage.java        |    2 
 src/com/gitblit/wicket/pages/TagPage.java            |   58 ++-
 src/com/gitblit/utils/JGitUtils.java                 |   98 ++++--
 23 files changed, 570 insertions(+), 282 deletions(-)

diff --git a/src/com/gitblit/models/RefModel.java b/src/com/gitblit/models/RefModel.java
index b33c8ba..39bf19c 100644
--- a/src/com/gitblit/models/RefModel.java
+++ b/src/com/gitblit/models/RefModel.java
@@ -19,50 +19,99 @@
 import java.util.Date;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
 
 public class RefModel implements Serializable, Comparable<RefModel> {
 
 	private static final long serialVersionUID = 1L;
 	public final String displayName;
-	public final RevCommit commit;
-	public transient Ref ref;
+	public final RevObject referencedObject;
+	public transient Ref reference;
 
-	public RefModel(String displayName, Ref ref, RevCommit commit) {
+	public RefModel(String displayName, Ref ref, RevObject refObject) {
 		this.displayName = displayName;
-		this.ref = ref;
-		this.commit = commit;
+		this.reference = ref;
+		this.referencedObject = refObject;
 	}
 
 	public Date getDate() {
-		return new Date(commit.getCommitTime() * 1000L);
+		Date date = new Date(0);
+		if (referencedObject != null) {
+			if (referencedObject instanceof RevTag) {
+				date = ((RevTag) referencedObject).getTaggerIdent().getWhen();
+			} else if (referencedObject instanceof RevCommit) {
+				date = ((RevCommit) referencedObject).getCommitterIdent().getWhen();
+			}
+		}
+		return date;
 	}
 
 	public String getName() {
-		return ref.getName();
+		return reference.getName();
 	}
 
-	public ObjectId getCommitId() {
-		return commit.getId();
+	public int getReferencedObjectType() {
+		int type = referencedObject.getType();
+		if (referencedObject instanceof RevTag) {
+			type = ((RevTag) referencedObject).getObject().getType();
+		}
+		return type;
 	}
 
-	public String getShortLog() {
-		return commit.getShortMessage();
+	public ObjectId getReferencedObjectId() {
+		if (referencedObject instanceof RevTag) {
+			return ((RevTag) referencedObject).getObject().getId();
+		}
+		return referencedObject.getId();
+	}
+
+	public String getShortMessage() {
+		String message = "";
+		if (referencedObject instanceof RevTag) {
+			message = ((RevTag) referencedObject).getShortMessage();
+		} else if (referencedObject instanceof RevCommit) {
+			message = ((RevCommit) referencedObject).getShortMessage();
+		}
+		return message;
+	}
+
+	public String getFullMessage() {
+		String message = "";
+		if (referencedObject instanceof RevTag) {
+			message = ((RevTag) referencedObject).getFullMessage();
+		} else if (referencedObject instanceof RevCommit) {
+			message = ((RevCommit) referencedObject).getFullMessage();
+		}
+		return message;
+	}
+
+	public PersonIdent getAuthorIdent() {
+		if (referencedObject instanceof RevTag) {
+			return ((RevTag) referencedObject).getTaggerIdent();
+		} else if (referencedObject instanceof RevCommit) {
+			return ((RevCommit) referencedObject).getAuthorIdent();
+		}
+		return null;
 	}
 
 	public ObjectId getObjectId() {
-		return ref.getObjectId();
+		return reference.getObjectId();
 	}
 
 	public boolean isAnnotatedTag() {
-		// ref.isPeeled() ??
-		return !getCommitId().equals(getObjectId());
+		if (referencedObject instanceof RevTag) {
+			return !getReferencedObjectId().equals(getObjectId());
+		}
+		return reference.getPeeledObjectId() != null;
 	}
 
 	@Override
 	public int hashCode() {
-		return getCommitId().hashCode() + getName().hashCode();
+		return getReferencedObjectId().hashCode() + getName().hashCode();
 	}
 
 	@Override
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java
index fd2eaf3..2590a30 100644
--- a/src/com/gitblit/utils/JGitUtils.java
+++ b/src/com/gitblit/utils/JGitUtils.java
@@ -29,7 +29,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Set;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -42,7 +41,6 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.StopWalkException;
-import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -212,41 +210,31 @@
 		return commit;
 	}
 
-	public static Map<ObjectId, List<String>> getAllRefs(Repository r) {
-		Map<ObjectId, List<String>> refs = new HashMap<ObjectId, List<String>>();
-		Map<AnyObjectId, Set<Ref>> allRefs = r.getAllRefsByPeeledObjectId();
-		for (Entry<AnyObjectId, Set<Ref>> setRefs : allRefs.entrySet()) {
-			List<String> list = new ArrayList<String>();
-			for (Ref setRef : setRefs.getValue()) {
-				String name = setRef.getName();
-				list.add(name);
+	public static Map<ObjectId, List<RefModel>> getAllRefs(Repository r) {
+		List<RefModel> list = getRefs(r, org.eclipse.jgit.lib.RefDatabase.ALL, -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.put(setRefs.getKey().toObjectId(), list);
-		}
+			refs.get(objectid).add(ref);
+		}		
 		return refs;
 	}
 
-	/**
-	 * Lookup an entry stored in a tree, failing if not present.
-	 * 
-	 * @param tree
-	 *            the tree to search.
-	 * @param path
-	 *            the path to find the entry of.
-	 * @return the parsed object entry at this path
-	 * @throws Exception
-	 */
-	public static byte[] getRawContent(Repository r, RevCommit commit, final String path) {
+	public static byte[] getByteContent(Repository r, RevTree tree, final String path) {
 		RevWalk rw = new RevWalk(r);
 		TreeWalk tw = new TreeWalk(r);
 		tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
 		byte[] content = null;
 		try {
-			if (commit == null) {
+			if (tree == null) {
 				ObjectId object = r.resolve(Constants.HEAD);
-				commit = rw.parseCommit(object);
+				RevCommit commit = rw.parseCommit(object);
+				tree = commit.getTree();
 			}
-			tw.reset(commit.getTree());
+			tw.reset(tree);
 			while (tw.next()) {
 				if (tw.isSubtree() && !path.equals(tw.getPathString())) {
 					tw.enterSubtree();
@@ -268,7 +256,7 @@
 				content = os.toByteArray();
 			}
 		} catch (Throwable t) {
-			LOGGER.error("Can't find " + path + " in tree " + commit.getTree().name(), t);
+			LOGGER.error("Can't find " + path + " in tree " + tree.name(), t);
 		} finally {
 			rw.dispose();
 			tw.release();
@@ -276,8 +264,40 @@
 		return content;
 	}
 
-	public static String getRawContentAsString(Repository r, RevCommit commit, String blobPath) {
-		byte[] content = getRawContent(r, commit, blobPath);
+	public static String getStringContent(Repository r, RevTree tree, String blobPath) {
+		byte[] content = getByteContent(r, 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);
+		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);
+			byte[] tmp = new byte[4096];
+			InputStream in = ldr.openStream();
+			int n;
+			while ((n = in.read(tmp)) > 0) {
+				os.write(tmp, 0, n);
+			}
+			in.close();
+			content = os.toByteArray();
+		} catch (Throwable t) {
+			LOGGER.error("Can't find blob " + objectId, t);
+		} finally {
+			rw.dispose();
+		}
+		return content;
+	}
+
+	public static String getStringContent(Repository r, String objectId) {
+		byte[] content = getByteContent(r, objectId);
 		if (content == null) {
 			return null;
 		}
@@ -605,7 +625,7 @@
 		return getRefs(r, Constants.R_REMOTES, maxCount);
 	}
 
-	public static List<RefModel> getNotes(Repository r, int maxCount) {
+	public static List<RefModel> getNotesRefs(Repository r, int maxCount) {
 		return getRefs(r, Constants.R_NOTES, maxCount);
 	}
 
@@ -613,11 +633,13 @@
 		List<RefModel> list = new ArrayList<RefModel>();
 		try {
 			Map<String, Ref> map = r.getRefDatabase().getRefs(refs);
+			RevWalk rw = new RevWalk(r);
 			for (Entry<String, Ref> entry : map.entrySet()) {
 				Ref ref = entry.getValue();
-				RevCommit commit = getCommit(r, ref.getObjectId().getName());
-				list.add(new RefModel(entry.getKey(), ref, commit));
+				RevObject object = rw.parseAny(ref.getObjectId());
+				list.add(new RefModel(entry.getKey(), ref, object));
 			}
+			rw.dispose();
 			Collections.sort(list);
 			Collections.reverse(list);
 			if (maxCount > 0 && list.size() > maxCount) {
@@ -631,14 +653,18 @@
 
 	public static List<GitNote> getNotesOnCommit(Repository repository, RevCommit commit) {
 		List<GitNote> list = new ArrayList<GitNote>();
-		List<RefModel> notesRefs = getNotes(repository, -1);
+		List<RefModel> notesRefs = getNotesRefs(repository, -1);
 		for (RefModel notesRef : notesRefs) {
-			RevCommit notes = JGitUtils.getCommit(repository, notesRef.getName());
+			RevTree notesTree = JGitUtils.getCommit(repository, notesRef.getName()).getTree();
 			StringBuilder sb = new StringBuilder(commit.getName());
 			sb.insert(2, '/');
-			String text = getRawContentAsString(repository, notes, sb.toString());
+			String notePath = sb.toString();
+			String text = getStringContent(repository, notesTree, notePath);
 			if (!StringUtils.isEmpty(text)) {
-				GitNote gitNote = new GitNote(notesRef, text);
+				List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
+				RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
+						.size() - 1));
+				GitNote gitNote = new GitNote(noteRef, text);
 				list.add(gitNote);
 			}
 		}
diff --git a/src/com/gitblit/utils/MetricUtils.java b/src/com/gitblit/utils/MetricUtils.java
index 4ca9f36..d963bf5 100644
--- a/src/com/gitblit/utils/MetricUtils.java
+++ b/src/com/gitblit/utils/MetricUtils.java
@@ -47,7 +47,7 @@
 			final List<RefModel> tags = JGitUtils.getTags(r, -1);
 			final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>();
 			for (RefModel tag : tags) {
-				tagMap.put(tag.getCommitId(), tag);
+				tagMap.put(tag.getReferencedObjectId(), tag);
 			}
 			try {
 				RevWalk walk = new RevWalk(r);
diff --git a/src/com/gitblit/utils/TicgitUtils.java b/src/com/gitblit/utils/TicgitUtils.java
index 576de34..8224d1c 100644
--- a/src/com/gitblit/utils/TicgitUtils.java
+++ b/src/com/gitblit/utils/TicgitUtils.java
@@ -21,6 +21,7 @@
 import java.util.List;
 
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -64,7 +65,8 @@
 		if (ticgitBranch == null) {
 			return null;
 		}
-		List<PathModel> paths = JGitUtils.getFilesInPath(r, null, ticgitBranch.commit);
+		RevCommit commit = (RevCommit) ticgitBranch.referencedObject;
+		List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);
 		List<TicketModel> tickets = new ArrayList<TicketModel>();
 		for (PathModel ticketFolder : paths) {
 			if (ticketFolder.isTree()) {
@@ -97,11 +99,10 @@
 	}
 
 	private static void readTicketContents(Repository r, RefModel ticketsBranch, TicketModel ticket) {
-		List<PathModel> ticketFiles = JGitUtils
-				.getFilesInPath(r, ticket.name, ticketsBranch.commit);
+		RevCommit commit = (RevCommit) ticketsBranch.referencedObject;
+		List<PathModel> ticketFiles = JGitUtils.getFilesInPath(r, ticket.name, commit);
 		for (PathModel file : ticketFiles) {
-			String content = JGitUtils.getRawContentAsString(r, ticketsBranch.commit, file.path)
-					.trim();
+			String content = JGitUtils.getStringContent(r, commit.getTree(), file.path).trim();
 			if (file.name.equals("TICKET_ID")) {
 				ticket.id = content;
 			} else if (file.name.equals("TITLE")) {
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 2ba03e6..f64f1e5 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -93,4 +93,5 @@
 gb.showReadme = show readme
 gb.showReadmeDescription = show a \"readme\" markdown file on the summary page
 gb.nameDescription = use '/' to group repositories.  e.g. libraries/mycoollib.git
-gb.ownerDescription = the owner may edit repository settings
\ No newline at end of file
+gb.ownerDescription = the owner may edit repository settings
+gb.blob = blob
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/BlobPage.java b/src/com/gitblit/wicket/pages/BlobPage.java
index 4601ac7..3c5c8e0 100644
--- a/src/com/gitblit/wicket/pages/BlobPage.java
+++ b/src/com/gitblit/wicket/pages/BlobPage.java
@@ -29,6 +29,7 @@
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
@@ -38,82 +39,100 @@
 	public BlobPage(PageParameters params) {
 		super(params);
 
+		Repository r = getRepository();
 		final String blobPath = WicketUtils.getPath(params);
 
-		String extension = null;
-		if (blobPath.lastIndexOf('.') > -1) {
-			extension = blobPath.substring(blobPath.lastIndexOf('.') + 1).toLowerCase();
-		}
+		if (StringUtils.isEmpty(blobPath)) {
+			// blob by objectid
 
-		// see if we should redirect to the markdown page
-		for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
-			if (ext.equals(extension)) {
-				setResponsePage(MarkdownPage.class, params);
-				return;
-			}
-		}
-
-		// standard blob view
-		Repository r = getRepository();
-		RevCommit commit = getCommit();
-
-		// blob page links
-		add(new Label("blameLink", getString("gb.blame")));
-		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-		add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
-				repositoryName, objectId, blobPath)));
-		add(new BookmarkablePageLink<Void>("headLink", BlobPage.class,
-				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, blobPath)));
-
-		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
-
-		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
-
-		// Map the extensions to types
-		Map<String, Integer> map = new HashMap<String, Integer>();
-		for (String ext : GitBlit.getStrings(Keys.web.prettyPrintExtensions)) {
-			map.put(ext.toLowerCase(), 1);
-		}
-		for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
-			map.put(ext.toLowerCase(), 2);
-		}
-		for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
-			map.put(ext.toLowerCase(), 3);
-		}
-
-		if (extension != null) {
-			int type = 0;
-			if (map.containsKey(extension)) {
-				type = map.get(extension);
-			}
-			Component c = null;
-			switch (type) {
-			case 1:
-				// PrettyPrint blob text
-				c = new Label("blobText", JGitUtils.getRawContentAsString(r, commit, blobPath));
-				WicketUtils.setCssClass(c, "prettyprint linenums");
-				break;
-			case 2:
-				// TODO image blobs
-				c = new Label("blobText", "Image File");
-				break;
-			case 3:
-				// TODO binary blobs
-				c = new Label("blobText", "Binary File");
-				break;
-			default:
-				// plain text
-				c = new Label("blobText", JGitUtils.getRawContentAsString(r, commit, blobPath));
-				WicketUtils.setCssClass(c, "plainprint");
-			}
+			add(new Label("blameLink", getString("gb.blame")).setEnabled(false));
+			add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class).setEnabled(false));
+			add(new BookmarkablePageLink<Void>("rawLink", RawPage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+			add(new BookmarkablePageLink<Void>("headLink", BlobPage.class).setEnabled(false));
+			add(new CommitHeaderPanel("commitHeader", objectId));
+			add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
+			Component c = new Label("blobText", JGitUtils.getStringContent(r, objectId));
+			WicketUtils.setCssClass(c, "plainprint");
 			add(c);
 		} else {
-			// plain text
-			Label blobLabel = new Label("blobText", JGitUtils.getRawContentAsString(r, commit,
-					blobPath));
-			WicketUtils.setCssClass(blobLabel, "plainprint");
-			add(blobLabel);
+			// standard blob view
+			String extension = null;
+			if (blobPath.lastIndexOf('.') > -1) {
+				extension = blobPath.substring(blobPath.lastIndexOf('.') + 1).toLowerCase();
+			}
+
+			// see if we should redirect to the markdown page
+			for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
+				if (ext.equals(extension)) {
+					setResponsePage(MarkdownPage.class, params);
+					return;
+				}
+			}
+
+			// manually get commit because it can be null
+			RevCommit commit = JGitUtils.getCommit(r, objectId);
+
+			// blob page links
+			add(new Label("blameLink", getString("gb.blame")));
+			add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+			add(new BookmarkablePageLink<Void>("rawLink", RawPage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+			add(new BookmarkablePageLink<Void>("headLink", BlobPage.class,
+					WicketUtils.newPathParameter(repositoryName, Constants.HEAD, blobPath)));
+
+			add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+			add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
+
+			// Map the extensions to types
+			Map<String, Integer> map = new HashMap<String, Integer>();
+			for (String ext : GitBlit.getStrings(Keys.web.prettyPrintExtensions)) {
+				map.put(ext.toLowerCase(), 1);
+			}
+			for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
+				map.put(ext.toLowerCase(), 2);
+			}
+			for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
+				map.put(ext.toLowerCase(), 3);
+			}
+
+			if (extension != null) {
+				int type = 0;
+				if (map.containsKey(extension)) {
+					type = map.get(extension);
+				}
+				Component c = null;
+				switch (type) {
+				case 1:
+					// PrettyPrint blob text
+					c = new Label("blobText", JGitUtils.getStringContent(r, commit.getTree(),
+							blobPath));
+					WicketUtils.setCssClass(c, "prettyprint linenums");
+					break;
+				case 2:
+					// TODO image blobs
+					c = new Label("blobText", "Image File");
+					break;
+				case 3:
+					// TODO binary blobs
+					c = new Label("blobText", "Binary File");
+					break;
+				default:
+					// plain text
+					c = new Label("blobText", JGitUtils.getStringContent(r, commit.getTree(),
+							blobPath));
+					WicketUtils.setCssClass(c, "plainprint");
+				}
+				add(c);
+			} else {
+				// plain text
+				Label blobLabel = new Label("blobText", JGitUtils.getStringContent(r,
+						commit.getTree(), blobPath));
+				WicketUtils.setCssClass(blobLabel, "plainprint");
+				add(blobLabel);
+			}
 		}
 	}
 
diff --git a/src/com/gitblit/wicket/pages/CommitPage.html b/src/com/gitblit/wicket/pages/CommitPage.html
index bd317b7..f2d328e 100644
--- a/src/com/gitblit/wicket/pages/CommitPage.html
+++ b/src/com/gitblit/wicket/pages/CommitPage.html
@@ -45,10 +45,16 @@
 	<div class="commit_message" wicket:id="fullMessage">[commit message]</div>
 
 	<!--  git notes -->
-	<table style="padding-bottom:5px;">
+	<table class="gitnotes">
 		<tr wicket:id="notes">
-			<td style="vertical-align:top;"><span class="headRef" wicket:id="refName"></span><br/><span wicket:id="authorName"></span><br/><span wicket:id="authorDate"></span></td>
-			<td><span wicket:id="noteContent"></span></td>
+			<td class="info">
+				<table>
+					<tr><td><span wicket:id="refName"></span></td></tr>
+					<tr><td><span class="sha1" wicket:id="authorName"></span></td></tr>
+					<tr><td><span class="sha1" wicket:id="authorDate"></span></td></tr>
+				</table>
+			</td>
+			<td class="message"><span class="sha1" wicket:id="noteContent"></span></td>
 		</tr>
 	</table>
 	
diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java
index 3af9cf1..7b5fdbe 100644
--- a/src/com/gitblit/wicket/pages/CommitPage.java
+++ b/src/com/gitblit/wicket/pages/CommitPage.java
@@ -16,9 +16,9 @@
 package com.gitblit.wicket.pages;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-import org.apache.wicket.Component;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
@@ -37,12 +37,13 @@
 import com.gitblit.models.GitNote;
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.JGitUtils.SearchType;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.CommitLegendPanel;
 import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.RefsPanel;
 
 public class CommitPage extends RepositoryPage {
 
@@ -123,13 +124,10 @@
 
 			public void populateItem(final Item<GitNote> item) {
 				GitNote entry = item.getModelObject();
-				Component c = new LinkPanel("refName", null, entry.notesRef.displayName,
-						CommitPage.class, newCommitParameter(entry.notesRef.commit.getName()));
-				WicketUtils.setCssClass(c, "headRef");
-				item.add(c);
-				item.add(createPersonPanel("authorName", entry.notesRef.commit.getAuthorIdent(), SearchType.AUTHOR));
+				item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
+				item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(), SearchType.AUTHOR));
 				item.add(WicketUtils.createTimestampLabel("authorDate",
-						entry.notesRef.commit.getAuthorIdent().getWhen(), getTimeZone()));
+						entry.notesRef.getAuthorIdent().getWhen(), getTimeZone()));
 				item.add(new Label("noteContent", StringUtils.breakLinesForHtml(entry.content)).setEscapeModelStrings(false));
 			}
 		};
diff --git a/src/com/gitblit/wicket/pages/MarkdownPage.java b/src/com/gitblit/wicket/pages/MarkdownPage.java
index fc8b7cb..9202fb8 100644
--- a/src/com/gitblit/wicket/pages/MarkdownPage.java
+++ b/src/com/gitblit/wicket/pages/MarkdownPage.java
@@ -48,7 +48,7 @@
 				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, markdownPath)));
 
 		// Read raw markdown content and transform it to html
-		String markdownText = JGitUtils.getRawContentAsString(r, commit, markdownPath);
+		String markdownText = JGitUtils.getStringContent(r, commit.getTree(), markdownPath);
 		String htmlText;
 		try {
 			htmlText = MarkdownUtils.transformMarkdown(markdownText);
diff --git a/src/com/gitblit/wicket/pages/RawPage.java b/src/com/gitblit/wicket/pages/RawPage.java
index acda03a..c1ebea5 100644
--- a/src/com/gitblit/wicket/pages/RawPage.java
+++ b/src/com/gitblit/wicket/pages/RawPage.java
@@ -28,6 +28,7 @@
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.WicketUtils;
 
 public class RawPage extends WebPage {
@@ -50,49 +51,58 @@
 			return;
 		}
 
-		RevCommit commit = JGitUtils.getCommit(r, objectId);
-
-		String extension = null;
-		if (blobPath.lastIndexOf('.') > -1) {
-			extension = blobPath.substring(blobPath.lastIndexOf('.') + 1);
-		}
-
-		// Map the extensions to types
-		Map<String, Integer> map = new HashMap<String, Integer>();
-		for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
-			map.put(ext.toLowerCase(), 2);
-		}
-		for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
-			map.put(ext.toLowerCase(), 3);
-		}
-
-		if (extension != null) {
-			int type = 0;
-			if (map.containsKey(extension)) {
-				type = map.get(extension);
-			}
-			Component c = null;
-			switch (type) {
-			case 2:
-				// TODO image blobs
-				c = new Label("rawText", "Image File");
-				break;
-			case 3:
-				// TODO binary blobs
-				c = new Label("rawText", "Binary File");
-				break;
-			default:
-				// plain text
-				c = new Label("rawText", JGitUtils.getRawContentAsString(r, commit, blobPath));
-				WicketUtils.setCssClass(c, "plainprint");
-			}
-			add(c);
-		} else {
-			// plain text
-			Label blobLabel = new Label("rawText", JGitUtils.getRawContentAsString(r, commit,
-					blobPath));
+		if (StringUtils.isEmpty(blobPath)) {
+			// objectid referenced raw view
+			Label blobLabel = new Label("rawText", JGitUtils.getStringContent(r, objectId));
 			WicketUtils.setCssClass(blobLabel, "plainprint");
 			add(blobLabel);
+		} else {
+			// standard raw blob view
+			RevCommit commit = JGitUtils.getCommit(r, objectId);
+
+			String extension = null;
+			if (blobPath.lastIndexOf('.') > -1) {
+				extension = blobPath.substring(blobPath.lastIndexOf('.') + 1);
+			}
+
+			// Map the extensions to types
+			Map<String, Integer> map = new HashMap<String, Integer>();
+			for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
+				map.put(ext.toLowerCase(), 2);
+			}
+			for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
+				map.put(ext.toLowerCase(), 3);
+			}
+
+			if (extension != null) {
+				int type = 0;
+				if (map.containsKey(extension)) {
+					type = map.get(extension);
+				}
+				Component c = null;
+				switch (type) {
+				case 2:
+					// TODO image blobs
+					c = new Label("rawText", "Image File");
+					break;
+				case 3:
+					// TODO binary blobs
+					c = new Label("rawText", "Binary File");
+					break;
+				default:
+					// plain text
+					c = new Label("rawText", JGitUtils.getStringContent(r, commit.getTree(),
+							blobPath));
+					WicketUtils.setCssClass(c, "plainprint");
+				}
+				add(c);
+			} else {
+				// plain text
+				Label blobLabel = new Label("rawText", JGitUtils.getStringContent(r,
+						commit.getTree(), blobPath));
+				WicketUtils.setCssClass(blobLabel, "plainprint");
+				add(blobLabel);
+			}
 		}
 		r.close();
 	}
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java
index a2d36d2..fd21ed6 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/com/gitblit/wicket/pages/SummaryPage.java
@@ -167,7 +167,7 @@
 					}
 				}
 				if (!StringUtils.isEmpty(readme)) {
-					String markdownText = JGitUtils.getRawContentAsString(r, head, readme);
+					String markdownText = JGitUtils.getStringContent(r, head.getTree(), readme);
 					htmlText = MarkdownUtils.transformMarkdown(markdownText);
 				}
 			} catch (ParseException p) {
diff --git a/src/com/gitblit/wicket/pages/TagPage.html b/src/com/gitblit/wicket/pages/TagPage.html
index 839375b..f1a42c1 100644
--- a/src/com/gitblit/wicket/pages/TagPage.html
+++ b/src/com/gitblit/wicket/pages/TagPage.html
@@ -12,8 +12,10 @@
 	
 	<!-- commit info -->
 	<table class="plain">
-		<tr><th><wicket:message key="gb.object">[object]</wicket:message></th><td><span class="sha1" wicket:id="tagId">[tag id]</span></td></tr>
-		<tr><th><wicket:message key="gb.tagger">[tagger]</wicket:message></th><td><span class="sha1" wicket:id="tagAuthor">[tag author]</span></td></tr>
+		<tr><th><wicket:message key="gb.name">[name]</wicket:message></th><td><span class="tagRef" wicket:id="tagName">[tag name]</span></td></tr>
+		<tr><th><wicket:message key="gb.tag">[tag]</wicket:message></th><td><span class="sha1" wicket:id="tagId">[tag id]</span></td></tr>
+		<tr><th><wicket:message key="gb.object">[object]</wicket:message></th><td><span class="sha1" wicket:id="taggedObject">[tagged object]</span> <span class="link" wicket:id="taggedObjectType"></span></td></tr>
+		<tr><th><wicket:message key="gb.tagger">[tagger]</wicket:message></th><td><span class="sha1" wicket:id="tagger">[tagger]</span></td></tr>
 		<tr><th></th><td><span class="sha1" wicket:id="tagDate">[tag date]</span></td></tr>
 	</table>
 	
diff --git a/src/com/gitblit/wicket/pages/TagPage.java b/src/com/gitblit/wicket/pages/TagPage.java
index 7945231..71a8645 100644
--- a/src/com/gitblit/wicket/pages/TagPage.java
+++ b/src/com/gitblit/wicket/pages/TagPage.java
@@ -15,17 +15,21 @@
  */
 package com.gitblit.wicket.pages;
 
+import java.text.MessageFormat;
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
 
 import com.gitblit.models.RefModel;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JGitUtils.SearchType;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.RefsPanel;
 
 public class TagPage extends RepositoryPage {
 
@@ -33,11 +37,10 @@
 		super(params);
 
 		Repository r = getRepository();
-		RevCommit c = getCommit();
-		List<RefModel> tags = JGitUtils.getTags(r, -1);
 
+		// Find tag in repository
+		List<RefModel> tags = JGitUtils.getTags(r, -1);
 		RefModel tagRef = null;
-		// determine tag
 		for (RefModel tag : tags) {
 			if (tag.getName().equals(objectId) || tag.getObjectId().getName().equals(objectId)) {
 				tagRef = tag;
@@ -45,25 +48,42 @@
 			}
 		}
 
+		// Failed to find tag!
 		if (tagRef == null) {
-			// point to commit
-			add(new LinkPanel("commit", "title", c.getShortMessage(), CommitPage.class,
-					newCommitParameter()));
-			add(new LinkPanel("tagId", "list", c.getName(), CommitPage.class,
-					newCommitParameter(c.getName())));
-		} else {
-			// TODO commit or tree or blob?
-			add(new LinkPanel("commit", "title", tagRef.displayName, CommitPage.class,
-					newCommitParameter()));
-			add(new LinkPanel("tagId", "list", c.getName(), CommitPage.class,
-					newCommitParameter(c.getName())));
+			error(MessageFormat.format("Could not find tag {0}", objectId), true);
 		}
 
-		add(createPersonPanel("tagAuthor", c.getAuthorIdent(), SearchType.AUTHOR));
-		add(WicketUtils
-				.createTimestampLabel("tagDate", c.getAuthorIdent().getWhen(), getTimeZone()));
+		// Display tag.
+		Class<? extends RepositoryPage> linkClass;
+		PageParameters linkParameters = newCommitParameter(tagRef.getReferencedObjectId().getName());
+		String typeKey;
+		switch (tagRef.getReferencedObjectType()) {
+		case Constants.OBJ_BLOB:
+			typeKey = "gb.blob";
+			linkClass = BlobPage.class;
+			break;
+		case Constants.OBJ_TREE:
+			typeKey = "gb.tree";
+			linkClass = TreePage.class;
+			break;
+		case Constants.OBJ_COMMIT:
+		default:
+			typeKey = "gb.commit";
+			linkClass = CommitPage.class;
+			break;
+		}
+		add(new LinkPanel("commit", "title", tagRef.displayName, linkClass, linkParameters));
+		add(new RefsPanel("tagName", repositoryName, Arrays.asList(tagRef)));
+		add(new Label("tagId", tagRef.getObjectId().getName()));
+		add(new LinkPanel("taggedObject", "list", tagRef.getReferencedObjectId().getName(),
+				linkClass, linkParameters));
+		add(new Label("taggedObjectType", getString(typeKey)));
 
-		addFullText("fullMessage", c.getFullMessage(), true);
+		add(createPersonPanel("tagger", tagRef.getAuthorIdent(), SearchType.AUTHOR));
+		add(WicketUtils.createTimestampLabel("tagDate", tagRef.getAuthorIdent().getWhen(),
+				getTimeZone()));
+
+		addFullText("fullMessage", tagRef.getFullMessage(), true);
 	}
 
 	@Override
diff --git a/src/com/gitblit/wicket/panels/CommitHeaderPanel.java b/src/com/gitblit/wicket/panels/CommitHeaderPanel.java
index 1a6580d..7d0ad0f 100644
--- a/src/com/gitblit/wicket/panels/CommitHeaderPanel.java
+++ b/src/com/gitblit/wicket/panels/CommitHeaderPanel.java
@@ -25,6 +25,14 @@
 
 	private static final long serialVersionUID = 1L;
 
+	public CommitHeaderPanel(String id, String title) {
+		super(id);
+		add(new Label("shortmessage", title));
+		add(new Label("commitid"));
+		add(new Label("author"));
+		add(new Label("date"));
+	}
+	
 	public CommitHeaderPanel(String id, String repositoryName, RevCommit c) {
 		super(id);
 		add(new LinkPanel("shortmessage", "title", c.getShortMessage(), CommitPage.class,
diff --git a/src/com/gitblit/wicket/panels/HistoryPanel.java b/src/com/gitblit/wicket/panels/HistoryPanel.java
index 180a248..9ad407c 100644
--- a/src/com/gitblit/wicket/panels/HistoryPanel.java
+++ b/src/com/gitblit/wicket/panels/HistoryPanel.java
@@ -34,6 +34,7 @@
 import com.gitblit.Keys;
 import com.gitblit.models.PathModel;
 import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.RefModel;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JGitUtils.SearchType;
 import com.gitblit.utils.StringUtils;
@@ -73,7 +74,7 @@
 		}
 		final boolean isTree = matchingPath == null ? true : matchingPath.isTree();
 
-		final Map<ObjectId, List<String>> allRefs = JGitUtils.getAllRefs(r);
+		final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r);
 		List<RevCommit> commits;
 		if (pageResults) {
 			// Paging result set
diff --git a/src/com/gitblit/wicket/panels/LogPanel.java b/src/com/gitblit/wicket/panels/LogPanel.java
index 436c24f..873d7d9 100644
--- a/src/com/gitblit/wicket/panels/LogPanel.java
+++ b/src/com/gitblit/wicket/panels/LogPanel.java
@@ -31,6 +31,7 @@
 
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
+import com.gitblit.models.RefModel;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JGitUtils.SearchType;
 import com.gitblit.utils.StringUtils;
@@ -57,7 +58,7 @@
 			itemsPerPage = 50;
 		}
 
-		final Map<ObjectId, List<String>> allRefs = JGitUtils.getAllRefs(r);
+		final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r);
 		List<RevCommit> commits;
 		if (pageResults) {
 			// Paging result set
diff --git a/src/com/gitblit/wicket/panels/RefsPanel.java b/src/com/gitblit/wicket/panels/RefsPanel.java
index 33b1884..266a49b 100644
--- a/src/com/gitblit/wicket/panels/RefsPanel.java
+++ b/src/com/gitblit/wicket/panels/RefsPanel.java
@@ -29,6 +29,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+import com.gitblit.models.RefModel;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.pages.CommitPage;
 import com.gitblit.wicket.pages.LogPage;
@@ -39,45 +40,55 @@
 	private static final long serialVersionUID = 1L;
 
 	public RefsPanel(String id, final String repositoryName, RevCommit c,
-			Map<ObjectId, List<String>> refs) {
+			Map<ObjectId, List<RefModel>> refs) {
+		this(id, repositoryName, refs.get(c.getId()));
+	}
+
+	public RefsPanel(String id, final String repositoryName, List<RefModel> refs) {
 		super(id);
-		List<String> refNames = refs.get(c.getId());
-		if (refNames == null) {
-			refNames = new ArrayList<String>();
+		if (refs == null) {
+			refs = new ArrayList<RefModel>();
 		}
-		Collections.sort(refNames);
+		Collections.sort(refs);
 		// refNames.remove(Constants.HEAD);
 
-		ListDataProvider<String> refsDp = new ListDataProvider<String>(refNames);
-		DataView<String> refsView = new DataView<String>("ref", refsDp) {
+		ListDataProvider<RefModel> refsDp = new ListDataProvider<RefModel>(refs);
+		DataView<RefModel> refsView = new DataView<RefModel>("ref", refsDp) {
 			private static final long serialVersionUID = 1L;
 
-			public void populateItem(final Item<String> item) {
-				String entry = item.getModelObject();
+			public void populateItem(final Item<RefModel> item) {
+				RefModel entry = item.getModelObject();
+				String name = entry.displayName;
+				String objectid = entry.getReferencedObjectId().getName();
 				Component c = null;
-				if (entry.startsWith(Constants.R_HEADS)) {
+				if (name.startsWith(Constants.R_HEADS)) {
 					// local head
-					c = new LinkPanel("refName", null, entry.substring(Constants.R_HEADS.length()),
-							LogPage.class, WicketUtils.newObjectParameter(repositoryName, entry));
+					c = new LinkPanel("refName", null, name.substring(Constants.R_HEADS.length()),
+							LogPage.class, WicketUtils.newObjectParameter(repositoryName, objectid));
 					WicketUtils.setCssClass(c, "headRef");
-				} else if (entry.startsWith(Constants.R_REMOTES)) {
+				} else if (name.equals(Constants.HEAD)) {
+					// local head
+					c = new LinkPanel("refName", null, name, LogPage.class,
+							WicketUtils.newObjectParameter(repositoryName, objectid));
+					WicketUtils.setCssClass(c, "headRef");
+				} else if (name.startsWith(Constants.R_REMOTES)) {
 					// remote head
 					c = new LinkPanel("refName", null,
-							entry.substring(Constants.R_REMOTES.length()), LogPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry));
+							name.substring(Constants.R_REMOTES.length()), LogPage.class,
+							WicketUtils.newObjectParameter(repositoryName, objectid));
 					WicketUtils.setCssClass(c, "remoteRef");
-				} else if (entry.startsWith(Constants.R_TAGS)) {
+				} else if (name.startsWith(Constants.R_TAGS)) {
 					// tag
-					c = new LinkPanel("refName", null, entry.substring(Constants.R_TAGS.length()),
-							TagPage.class, WicketUtils.newObjectParameter(repositoryName, entry));
+					c = new LinkPanel("refName", null, name.substring(Constants.R_TAGS.length()),
+							TagPage.class, WicketUtils.newObjectParameter(repositoryName, objectid));
 					WicketUtils.setCssClass(c, "tagRef");
 				} else {
 					// other
-					c = new LinkPanel("refName", null, entry, CommitPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry));
+					c = new LinkPanel("refName", null, name, CommitPage.class,
+							WicketUtils.newObjectParameter(repositoryName, objectid));
 					WicketUtils.setCssClass(c, "otherRef");
 				}
-				WicketUtils.setHtmlTooltip(c, entry);
+				WicketUtils.setHtmlTooltip(c, name);
 				item.add(c);
 			}
 		};
diff --git a/src/com/gitblit/wicket/panels/SearchPanel.java b/src/com/gitblit/wicket/panels/SearchPanel.java
index f91e0e8..5f82a42 100644
--- a/src/com/gitblit/wicket/panels/SearchPanel.java
+++ b/src/com/gitblit/wicket/panels/SearchPanel.java
@@ -29,6 +29,7 @@
 
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
+import com.gitblit.models.RefModel;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JGitUtils.SearchType;
 import com.gitblit.utils.StringUtils;
@@ -55,7 +56,7 @@
 
 		RevCommit commit = JGitUtils.getCommit(r, objectId);
 
-		final Map<ObjectId, List<String>> allRefs = JGitUtils.getAllRefs(r);
+		final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r);
 		List<RevCommit> commits;
 		if (pageResults) {
 			// Paging result set
diff --git a/src/com/gitblit/wicket/panels/TagsPanel.html b/src/com/gitblit/wicket/panels/TagsPanel.html
index 9f98504..481d8e8 100644
--- a/src/com/gitblit/wicket/panels/TagsPanel.html
+++ b/src/com/gitblit/wicket/panels/TagsPanel.html
@@ -28,7 +28,7 @@
 	<!--  annotated tag links -->
 	<wicket:fragment wicket:id="annotatedLinks">
 		<span class="link">
-			<a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="commit"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
+			<a wicket:id="tag"><wicket:message key="gb.tag"></wicket:message></a> | <a wicket:id="commit"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
 		</span>
 	</wicket:fragment>
 	
@@ -38,6 +38,13 @@
 			<a wicket:id="commit"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
 		</span>
 	</wicket:fragment>
+
+	<!-- blob tag links -->
+	<wicket:fragment wicket:id="blobLinks">
+		<span class="link">
+			<a wicket:id="tag"><wicket:message key="gb.tag"></wicket:message></a> | <a wicket:id="blob"><wicket:message key="gb.blob"></wicket:message></a> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a>
+		</span>
+	</wicket:fragment>
 	
 </wicket:panel>
 </body>
diff --git a/src/com/gitblit/wicket/panels/TagsPanel.java b/src/com/gitblit/wicket/panels/TagsPanel.java
index de8112a..259af31 100644
--- a/src/com/gitblit/wicket/panels/TagsPanel.java
+++ b/src/com/gitblit/wicket/panels/TagsPanel.java
@@ -24,17 +24,22 @@
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
 import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
 
 import com.gitblit.models.RefModel;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.BlobPage;
 import com.gitblit.wicket.pages.CommitPage;
 import com.gitblit.wicket.pages.LogPage;
+import com.gitblit.wicket.pages.RawPage;
+import com.gitblit.wicket.pages.RepositoryPage;
 import com.gitblit.wicket.pages.SummaryPage;
 import com.gitblit.wicket.pages.TagPage;
 import com.gitblit.wicket.pages.TagsPage;
+import com.gitblit.wicket.pages.TreePage;
 
 public class TagsPanel extends BasePanel {
 
@@ -67,47 +72,83 @@
 
 				item.add(WicketUtils.createDateLabel("tagDate", entry.getDate(), getTimeZone()));
 
-				// tag icon
-				if (entry.isAnnotatedTag()) {
-					item.add(WicketUtils.newImage("tagIcon", "tag_16x16.png"));
-				} else {
-					item.add(WicketUtils.newBlankImage("tagIcon"));
+				Class<? extends RepositoryPage> linkClass;
+				switch (entry.getReferencedObjectType()) {
+				case Constants.OBJ_BLOB:
+					linkClass = BlobPage.class;
+					break;
+				case Constants.OBJ_TREE:
+					linkClass = TreePage.class;
+					break;
+				case Constants.OBJ_COMMIT:
+				default:
+					linkClass = CommitPage.class;
+					break;
 				}
-
-				item.add(new LinkPanel("tagName", "list name", entry.displayName, CommitPage.class,
-						WicketUtils.newObjectParameter(repositoryName, entry.getCommitId()
-								.getName())));
+				item.add(new LinkPanel("tagName", "list name", entry.displayName, linkClass,
+						WicketUtils.newObjectParameter(repositoryName, entry
+								.getReferencedObjectId().getName())));
 				String message;
 				if (maxCount > 0) {
-					message = StringUtils.trimString(entry.getShortLog(), 40);
+					message = StringUtils.trimString(entry.getShortMessage(), 40);
 				} else {
-					message = entry.getShortLog();
+					// workaround for RevTag returning a lengthy shortlog. :(
+					message = StringUtils.trimShortLog(entry.getShortMessage());
 				}
-				if (entry.isAnnotatedTag()) {
+				if (linkClass.equals(BlobPage.class)) {
+					// Blob Tag Object
+					item.add(WicketUtils.newImage("tagIcon", "file_16x16.png"));
 					item.add(new LinkPanel("tagDescription", "list", message, TagPage.class,
 							WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
 									.getName())));
-					Fragment fragment = new Fragment("tagLinks", "annotatedLinks", this);
-					fragment.add(new BookmarkablePageLink<Void>("view", TagPage.class, WicketUtils
+
+					Fragment fragment = new Fragment("tagLinks", "blobLinks", this);
+					fragment.add(new BookmarkablePageLink<Void>("tag", TagPage.class, WicketUtils
 							.newObjectParameter(repositoryName, entry.getObjectId().getName()))
 							.setEnabled(entry.isAnnotatedTag()));
-					fragment.add(new BookmarkablePageLink<Void>("commit", CommitPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry.getCommitId()
+
+					fragment.add(new BookmarkablePageLink<Void>("blob", linkClass, WicketUtils
+							.newObjectParameter(repositoryName, entry.getReferencedObjectId()
 									.getName())));
-					fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
-							.newObjectParameter(repositoryName, entry.getName())));
+
+					fragment.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
+							.newObjectParameter(repositoryName, entry.getReferencedObjectId()
+									.getName())));
 					item.add(fragment);
 				} else {
-					item.add(new LinkPanel("tagDescription", "list", message, CommitPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
-									.getName())));
-					Fragment fragment = new Fragment("tagLinks", "lightweightLinks", this);
-					fragment.add(new BookmarkablePageLink<Void>("commit", CommitPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry.getCommitId()
-									.getName())));
-					fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
-							.newObjectParameter(repositoryName, entry.getName())));
-					item.add(fragment);
+					// TODO Tree Tag Object
+					// Standard Tag Object
+					if (entry.isAnnotatedTag()) {
+						item.add(WicketUtils.newImage("tagIcon", "tag_16x16.png"));
+						item.add(new LinkPanel("tagDescription", "list", message, TagPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
+										.getName())));
+
+						Fragment fragment = new Fragment("tagLinks", "annotatedLinks", this);
+						fragment.add(new BookmarkablePageLink<Void>("tag", TagPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
+										.getName())).setEnabled(entry.isAnnotatedTag()));
+
+						fragment.add(new BookmarkablePageLink<Void>("commit", linkClass,
+								WicketUtils.newObjectParameter(repositoryName, entry
+										.getReferencedObjectId().getName())));
+
+						fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+						item.add(fragment);
+					} else {
+						item.add(WicketUtils.newBlankImage("tagIcon"));
+						item.add(new LinkPanel("tagDescription", "list", message, CommitPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
+										.getName())));
+						Fragment fragment = new Fragment("tagLinks", "lightweightLinks", this);
+						fragment.add(new BookmarkablePageLink<Void>("commit", CommitPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry
+										.getReferencedObjectId().getName())));
+						fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+						item.add(fragment);
+					}
 				}
 
 				WicketUtils.setAlternatingBackground(item, counter);
diff --git a/src/com/gitblit/wicket/resources/gitblit.css b/src/com/gitblit/wicket/resources/gitblit.css
index c9356c3..5dd0f16 100644
--- a/src/com/gitblit/wicket/resources/gitblit.css
+++ b/src/com/gitblit/wicket/resources/gitblit.css
@@ -243,6 +243,7 @@
 }
 
 div.commit_message {
+	font-family: monospace;
 	padding: 8px;
 	border: solid #bbb;
 	border-width: 1px 0px 0px;
@@ -508,7 +509,7 @@
 	border-right: 1px solid #bbb;	
 }
 
-table.pretty, table.comments, table.repositories {
+table.pretty, table.comments, table.repositories, table.gitnotes {
 	width:100%;
 }
 
@@ -608,7 +609,29 @@
 }
 table.palette td.pane {
 	padding: 0px;	
-} 
+}
+
+table.gitnotes {	
+	padding-bottom: 5px;
+}
+table.gitnotes td {
+	border-top: 1px solid #ccc;
+	padding-top: 3px;
+	vertical-align:top;
+}
+
+table.gitnotes td table td {
+	border: none;
+	padding-top: 0px;
+}
+
+table.gitnotes td.info {
+}
+
+table.gitnotes td.message {
+	width: 65%;
+	border-left: 1px solid #ccc;
+}
 
 tr th a { padding-right: 15px; background-position: right; background-repeat:no-repeat; }
 tr th.wicket_orderDown a {background-image: url(arrow_down.png); }
diff --git a/tests/com/gitblit/tests/GitBlitSuite.java b/tests/com/gitblit/tests/GitBlitSuite.java
index 97e46c9..fe201b8 100644
--- a/tests/com/gitblit/tests/GitBlitSuite.java
+++ b/tests/com/gitblit/tests/GitBlitSuite.java
@@ -16,15 +16,19 @@
 package com.gitblit.tests;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 import junit.extensions.TestSetup;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
 import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.FetchCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.transport.RefSpec;
 
 import com.gitblit.FileSettings;
 import com.gitblit.GitBlit;
@@ -61,6 +65,14 @@
 		return new FileRepository(new File(REPOSITORIES, "ticgit.git"));
 	}
 
+	public static Repository getJGitRepository() throws Exception {
+		return new FileRepository(new File(REPOSITORIES, "nested/jgit.git"));
+	}
+
+	public static Repository getBluezGnomeRepository() throws Exception {
+		return new FileRepository(new File(REPOSITORIES, "nested/bluez-gnome.git"));
+	}
+
 	@Override
 	protected void setUp() throws Exception {
 		FileSettings settings = new FileSettings("distrib/gitblit.properties");
@@ -71,12 +83,15 @@
 
 		if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {
 			cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git", true);
-			cloneOrFetch("nested/helloworld.git", "https://github.com/git/hello-world.git", true);
 			cloneOrFetch("ticgit.git", "https://github.com/jeffWelling/ticgit.git", true);
+			cloneOrFetch("nested/bluez-gnome.git", "https://git.kernel.org/pub/scm/bluetooth/bluez-gnome.git", true);
+			cloneOrFetch("nested/jgit.git", "https://github.com/eclipse/jgit.git", true);
+			cloneOrFetch("nested/helloworld.git", "https://github.com/git/hello-world.git", true);
 
 			enableTickets("ticgit.git");
 			enableDocs("ticgit.git");
 			showRemoteBranches("ticgit.git");
+			showRemoteBranches("nested/jgit.git");
 		}
 	}
 
@@ -84,22 +99,35 @@
 		File folder = new File(REPOSITORIES, toFolder + (bare ? "" : "/.git"));
 		if (folder.exists()) {
 			System.out.print("Updating " + (bare ? "bare " : " ") + toFolder + "... ");
-			FileRepository repository = new FileRepository(new File(REPOSITORIES, toFolder));
-			Git git = new Git(repository);
-			git.fetch().call();
-			repository.close();
+			fetch(toFolder);
 			System.out.println("done.");
 		} else {
 			System.out.println("Cloning " + (bare ? "bare " : " ") + toFolder + "... ");
 			CloneCommand clone = new CloneCommand();
 			clone.setBare(bare);
-			clone.setCloneAllBranches(true);
+			clone.setCloneAllBranches(true);			
 			clone.setURI(fromUrl);
 			clone.setDirectory(folder);
 			clone.call();
+			// Now we have to fetch because CloneCommand doesn't fetch
+			// Notes nor does it allow manual RefSpec.
+			fetch(toFolder);
 			System.out.println("done.");
 		}
 	}
+	
+	private void fetch(String toFolder) throws Exception {
+		FileRepository repository = new FileRepository(new File(REPOSITORIES, toFolder));
+		Git git = new Git(repository);
+		FetchCommand fetch = git.fetch();
+		List<RefSpec> specs = new ArrayList<RefSpec>();
+		specs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+		specs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
+		specs.add(new RefSpec("+refs/notes/*:refs/notes/*"));
+		fetch.setRefSpecs(specs);
+		fetch.call();
+		repository.close();
+	}
 
 	private void enableTickets(String repositoryName) {
 		try {
diff --git a/tests/com/gitblit/tests/JGitUtilsTest.java b/tests/com/gitblit/tests/JGitUtilsTest.java
index 6afa38b..7196afd 100644
--- a/tests/com/gitblit/tests/JGitUtilsTest.java
+++ b/tests/com/gitblit/tests/JGitUtilsTest.java
@@ -34,11 +34,13 @@
 
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
+import com.gitblit.models.GitNote;
 import com.gitblit.models.PathModel;
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JGitUtils.SearchType;
+import com.gitblit.utils.StringUtils;
 
 public class JGitUtilsTest extends TestCase {
 
@@ -115,10 +117,24 @@
 	}
 
 	public void testRefs() throws Exception {
-		Repository repository = GitBlitSuite.getTicgitRepository();
-		Map<ObjectId, List<String>> map = JGitUtils.getAllRefs(repository);
+		Repository repository = GitBlitSuite.getJGitRepository();
+		Map<ObjectId, List<RefModel>> map = JGitUtils.getAllRefs(repository);
 		repository.close();
 		assertTrue(map.size() > 0);
+		for (Map.Entry<ObjectId, List<RefModel>> entry : map.entrySet()) {
+			List<RefModel> list = entry.getValue();
+			for (RefModel ref : list) {
+				if (ref.displayName.equals("refs/tags/spearce-gpg-pub")) {
+					assertTrue(ref.getObjectId().getName().equals("8bbde7aacf771a9afb6992434f1ae413e010c6d8"));
+					assertTrue(ref.getAuthorIdent().getEmailAddress().equals("spearce@spearce.org"));
+					assertTrue(ref.getShortMessage().startsWith("GPG key"));
+					assertTrue(ref.getFullMessage().startsWith("GPG key"));					
+					assertTrue(ref.getReferencedObjectType() == Constants.OBJ_BLOB);
+				} else if (ref.displayName.equals("refs/tags/v0.12.1")) {
+					assertTrue(ref.isAnnotatedTag());
+				}
+			}
+		}
 	}
 
 	public void testBranches() throws Exception {
@@ -127,17 +143,17 @@
 			assertTrue(model.getName().startsWith(Constants.R_HEADS));
 			assertTrue(model.equals(model));
 			assertFalse(model.equals(""));
-			assertTrue(model.hashCode() == model.getCommitId().hashCode()
+			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
 					+ model.getName().hashCode());
-			assertTrue(model.getShortLog().equals(model.commit.getShortMessage()));
+			assertTrue(model.getShortMessage().equals(model.getShortMessage()));
 		}
 		for (RefModel model : JGitUtils.getRemoteBranches(repository, -1)) {
 			assertTrue(model.getName().startsWith(Constants.R_REMOTES));
 			assertTrue(model.equals(model));
 			assertFalse(model.equals(""));
-			assertTrue(model.hashCode() == model.getCommitId().hashCode()
+			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
 					+ model.getName().hashCode());
-			assertTrue(model.getShortLog().equals(model.commit.getShortMessage()));
+			assertTrue(model.getShortMessage().equals(model.getShortMessage()));
 		}
 		assertTrue(JGitUtils.getRemoteBranches(repository, 10).size() == 10);
 		repository.close();
@@ -152,33 +168,52 @@
 			assertTrue(model.getName().startsWith(Constants.R_TAGS));
 			assertTrue(model.equals(model));
 			assertFalse(model.equals(""));
-			assertTrue(model.hashCode() == model.getCommitId().hashCode()
+			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
 					+ model.getName().hashCode());
-			assertTrue(model.getShortLog().equals(model.commit.getShortMessage()));
+		}
+		repository.close();
+		
+		repository = GitBlitSuite.getBluezGnomeRepository();
+		for (RefModel model : JGitUtils.getTags(repository, -1)) {
+			if (model.getObjectId().getName().equals("728643ec0c438c77e182898c2f2967dbfdc231c8")) {
+				assertFalse(model.isAnnotatedTag());
+				assertTrue(model.getAuthorIdent().getEmailAddress().equals("marcel@holtmann.org"));
+				assertTrue(model.getFullMessage().equals("Update changelog and bump version number\n"));
+			}
 		}
 		repository.close();
 	}
 
 	public void testCommitNotes() throws Exception {
-//		Repository repository = new FileRepository(new File("c:/projects/git/jgit.git/.git"));
-//		RevCommit commit = JGitUtils.getCommit(repository,
-//				"ada903085d1b4ef8c79e3e2d91f49fee7e188f53");
-//		List<GitNote> list = JGitUtils.getNotesOnCommit(repository, commit);
-//		repository.close();
-//		assertTrue(list.size() > 0);
+		Repository repository = GitBlitSuite.getJGitRepository();
+		RevCommit commit = JGitUtils.getCommit(repository,
+				"690c268c793bfc218982130fbfc25870f292295e");
+		List<GitNote> list = JGitUtils.getNotesOnCommit(repository, commit);
+		repository.close();
+		assertTrue(list.size() > 0);
+		assertTrue(list.get(0).notesRef.getReferencedObjectId().getName()
+				.equals("183474d554e6f68478a02d9d7888b67a9338cdff"));
 	}
 
 	public void testStringContent() throws Exception {
 		Repository repository = GitBlitSuite.getHelloworldRepository();
-		String contentA = JGitUtils.getRawContentAsString(repository, null, "java.java");
+		String contentA = JGitUtils.getStringContent(repository, null, "java.java");
 		RevCommit commit = JGitUtils.getCommit(repository, Constants.HEAD);
-		String contentB = JGitUtils.getRawContentAsString(repository, commit, "java.java");
-		String contentC = JGitUtils.getRawContentAsString(repository, commit, "missing.txt");
+		String contentB = JGitUtils.getStringContent(repository, commit.getTree(), "java.java");
+		String contentC = JGitUtils.getStringContent(repository, commit.getTree(), "missing.txt");
+
+		// manually construct a blob, calculate the hash, lookup the hash in git
+		StringBuilder sb = new StringBuilder();
+		sb.append("blob ").append(contentA.length()).append('\0');
+		sb.append(contentA);
+		String sha1 = StringUtils.getSHA1(sb.toString());
+		String contentD = JGitUtils.getStringContent(repository, sha1);
 		repository.close();
 		assertTrue("ContentA is null!", contentA != null && contentA.length() > 0);
 		assertTrue("ContentB is null!", contentB != null && contentB.length() > 0);
 		assertTrue(contentA.equals(contentB));
 		assertTrue(contentC == null);
+		assertTrue(contentA.equals(contentD));
 	}
 
 	public void testFilesInCommit() throws Exception {

--
Gitblit v1.9.1