From 1d2d5efe6d5dafbd2236776bc4667521921e5c8c Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 08 May 2014 13:23:55 -0400
Subject: [PATCH] Merged #66 "Query tags using RSS feeds"

---
 src/main/java/com/gitblit/utils/SyndicationUtils.java     |   57 +++++++++++
 src/main/java/com/gitblit/servlet/SyndicationServlet.java |  133 ++++++++++++++++++--------
 src/main/java/com/gitblit/utils/JGitUtils.java            |   48 +++++++++
 src/main/java/com/gitblit/Constants.java                  |   21 ++++
 src/site/rpc.mkd                                          |    1 
 releases.moxie                                            |    2 
 src/test/java/com/gitblit/tests/SyndicationUtilsTest.java |   36 +++++++
 7 files changed, 253 insertions(+), 45 deletions(-)

diff --git a/releases.moxie b/releases.moxie
index 228fc7e..96a0ae9 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -13,9 +13,11 @@
     changes: ~
     additions:
     - Add FORK_REPOSITORY RPC request type (issue-371, pr-161, ticket-65)
+    - Add object type (ot) parameter for RSS queries to retrieve tag details (pr-165, ticket-66)
     dependencyChanges: ~
     contributors:
     - Manisha Gayathri
+    - Gerard Smyth
 }
 
 #
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
index c8ce83c..95eb944 100644
--- a/src/main/java/com/gitblit/Constants.java
+++ b/src/main/java/com/gitblit/Constants.java
@@ -403,6 +403,27 @@
 	}
 
 	/**
+	 * Enumeration of the feed content object types.
+	 */
+	public static enum FeedObjectType {
+		COMMIT, TAG;
+
+		public static FeedObjectType forName(String name) {
+			for (FeedObjectType type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return COMMIT;
+		}
+
+		@Override
+		public String toString() {
+			return name().toLowerCase();
+		}
+	}
+
+	/**
 	 * The types of objects that can be indexed and queried.
 	 */
 	public static enum SearchObjectType {
diff --git a/src/main/java/com/gitblit/servlet/SyndicationServlet.java b/src/main/java/com/gitblit/servlet/SyndicationServlet.java
index 24def99..631df78 100644
--- a/src/main/java/com/gitblit/servlet/SyndicationServlet.java
+++ b/src/main/java/com/gitblit/servlet/SyndicationServlet.java
@@ -163,6 +163,15 @@
 				searchType = type;
 			}
 		}
+
+		Constants.FeedObjectType objectType = Constants.FeedObjectType.COMMIT;
+		if (!StringUtils.isEmpty(request.getParameter("ot"))) {
+			Constants.FeedObjectType type = Constants.FeedObjectType.forName(request.getParameter("ot"));
+			if (type != null) {
+				objectType = type;
+			}
+		}
+
 		int length = settings.getInteger(Keys.web.syndicationEntries, 25);
 		if (StringUtils.isEmpty(objectId)) {
 			objectId = org.eclipse.jgit.lib.Constants.HEAD;
@@ -214,14 +223,7 @@
 
 
 		boolean mountParameters = settings.getBoolean(Keys.web.mountParameters, true);
-		String urlPattern;
-		if (mountParameters) {
-			// mounted parameters
-			urlPattern = "{0}/commit/{1}/{2}";
-		} else {
-			// parameterized parameters
-			urlPattern = "{0}/commit/?r={1}&h={2}";
-		}
+
 		String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
 		if (StringUtils.isEmpty(gitblitUrl)) {
 			gitblitUrl = HttpUtils.getGitblitURL(request);
@@ -247,47 +249,92 @@
 				feedDescription = model.description;
 			}
 
-			List<RevCommit> commits;
-			if (StringUtils.isEmpty(searchString)) {
-				// standard log/history lookup
-				commits = JGitUtils.getRevLog(repository, objectId, offset, length);
+			if (objectType == Constants.FeedObjectType.TAG) {
+
+				String urlPattern;
+				if (mountParameters) {
+					// mounted parameters
+					urlPattern = "{0}/tag/{1}/{2}";
+				} else {
+					// parameterized parameters
+					urlPattern = "{0}/tag/?r={1}&h={2}";
+				}
+
+				List<RefModel> tags = JGitUtils.getTags(repository, false, length, offset);
+
+				for (RefModel tag : tags) {
+					FeedEntryModel entry = new FeedEntryModel();
+					entry.title = tag.getName();
+					entry.author = tag.getAuthorIdent().getName();
+					entry.link = MessageFormat.format(urlPattern, gitblitUrl,
+							StringUtils.encodeURL(model.name.replace('/', fsc)), tag.getObjectId().getName());
+					entry.published = tag.getDate();
+					entry.contentType = "text/html";
+					entry.content = tag.getFullMessage();
+					entry.repository = model.name;
+					entry.branch = objectId;
+
+					entry.tags = new ArrayList<String>();
+
+					// add tag id and referenced commit id
+					entry.tags.add("tag:" + tag.getObjectId().getName());
+					entry.tags.add("commit:" + tag.getReferencedObjectId().getName());
+
+					entries.add(entry);
+				}
 			} else {
-				// repository search
-				commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType,
-						offset, length);
-			}
-			Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, model.showRemoteBranches);
-			BugtraqProcessor processor = new BugtraqProcessor(settings);
 
-			// convert RevCommit to SyndicatedEntryModel
-			for (RevCommit commit : commits) {
-				FeedEntryModel entry = new FeedEntryModel();
-				entry.title = commit.getShortMessage();
-				entry.author = commit.getAuthorIdent().getName();
-				entry.link = MessageFormat.format(urlPattern, gitblitUrl,
-						StringUtils.encodeURL(model.name.replace('/', fsc)), commit.getName());
-				entry.published = commit.getCommitterIdent().getWhen();
-				entry.contentType = "text/html";
-				String message = processor.processCommitMessage(repository, model, commit.getFullMessage());
-				entry.content = message;
-				entry.repository = model.name;
-				entry.branch = objectId;
-				entry.tags = new ArrayList<String>();
-
-				// add commit id and parent commit ids
-				entry.tags.add("commit:" + commit.getName());
-				for (RevCommit parent : commit.getParents()) {
-					entry.tags.add("parent:" + parent.getName());
+				String urlPattern;
+				if (mountParameters) {
+					// mounted parameters
+					urlPattern = "{0}/commit/{1}/{2}";
+				} else {
+					// parameterized parameters
+					urlPattern = "{0}/commit/?r={1}&h={2}";
 				}
 
-				// add refs to tabs list
-				List<RefModel> refs = allRefs.get(commit.getId());
-				if (refs != null && refs.size() > 0) {
-					for (RefModel ref : refs) {
-						entry.tags.add("ref:" + ref.getName());
+				List<RevCommit> commits;
+				if (StringUtils.isEmpty(searchString)) {
+					// standard log/history lookup
+					commits = JGitUtils.getRevLog(repository, objectId, offset, length);
+				} else {
+					// repository search
+					commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType,
+							offset, length);
+				}
+				Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, model.showRemoteBranches);
+				BugtraqProcessor processor = new BugtraqProcessor(settings);
+
+				// convert RevCommit to SyndicatedEntryModel
+				for (RevCommit commit : commits) {
+					FeedEntryModel entry = new FeedEntryModel();
+					entry.title = commit.getShortMessage();
+					entry.author = commit.getAuthorIdent().getName();
+					entry.link = MessageFormat.format(urlPattern, gitblitUrl,
+							StringUtils.encodeURL(model.name.replace('/', fsc)), commit.getName());
+					entry.published = commit.getCommitterIdent().getWhen();
+					entry.contentType = "text/html";
+					String message = processor.processCommitMessage(repository, model, commit.getFullMessage());
+					entry.content = message;
+					entry.repository = model.name;
+					entry.branch = objectId;
+					entry.tags = new ArrayList<String>();
+
+					// add commit id and parent commit ids
+					entry.tags.add("commit:" + commit.getName());
+					for (RevCommit parent : commit.getParents()) {
+						entry.tags.add("parent:" + parent.getName());
 					}
+
+					// add refs to tabs list
+					List<RefModel> refs = allRefs.get(commit.getId());
+					if (refs != null && refs.size() > 0) {
+						for (RefModel ref : refs) {
+							entry.tags.add("ref:" + ref.getName());
+						}
+					}
+					entries.add(entry);
 				}
-				entries.add(entry);
 			}
 		}
 
diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index 190872a..da51ea9 100644
--- a/src/main/java/com/gitblit/utils/JGitUtils.java
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -1668,6 +1668,24 @@
 	}
 
 	/**
+	 * Returns the list of tags in the repository. If repository does not exist
+	 * or is empty, an empty list is returned.
+	 *
+	 * @param repository
+	 * @param fullName
+	 *            if true, /refs/tags/yadayadayada is returned. If false,
+	 *            yadayadayada is returned.
+	 * @param maxCount
+	 *            if < 0, all tags are returned
+	 * @param offset
+	 *            if maxCount provided sets the starting point of the records to return
+	 * @return list of tags
+	 */
+	public static List<RefModel> getTags(Repository repository, boolean fullName, int maxCount, int offset) {
+		return getRefs(repository, Constants.R_TAGS, fullName, maxCount, offset);
+	}
+
+	/**
 	 * Returns the list of local branches in the repository. If repository does
 	 * not exist or is empty, an empty list is returned.
 	 *
@@ -1748,6 +1766,27 @@
 	 */
 	private static List<RefModel> getRefs(Repository repository, String refs, boolean fullName,
 			int maxCount) {
+		return getRefs(repository, refs, fullName, maxCount, 0);
+	}
+
+	/**
+	 * Returns a list of references in the repository matching "refs". If the
+	 * repository is null or empty, an empty list is returned.
+	 *
+	 * @param repository
+	 * @param refs
+	 *            if unspecified, all refs are returned
+	 * @param fullName
+	 *            if true, /refs/something/yadayadayada is returned. If false,
+	 *            yadayadayada is returned.
+	 * @param maxCount
+	 *            if < 0, all references are returned
+	 * @param offset
+	 *            if maxCount provided sets the starting point of the records to return
+	 * @return list of references
+	 */
+	private static List<RefModel> getRefs(Repository repository, String refs, boolean fullName,
+			int maxCount, int offset) {
 		List<RefModel> list = new ArrayList<RefModel>();
 		if (maxCount == 0) {
 			return list;
@@ -1771,7 +1810,14 @@
 			Collections.sort(list);
 			Collections.reverse(list);
 			if (maxCount > 0 && list.size() > maxCount) {
-				list = new ArrayList<RefModel>(list.subList(0, maxCount));
+				if (offset < 0) {
+					offset = 0;
+				}
+				int endIndex = offset + maxCount;
+				if (endIndex > list.size()) {
+					endIndex = list.size();
+				}
+				list = new ArrayList<RefModel>(list.subList(offset, endIndex));
 			}
 		} catch (IOException e) {
 			error(e, repository, "{0} failed to retrieve {1}", refs);
diff --git a/src/main/java/com/gitblit/utils/SyndicationUtils.java b/src/main/java/com/gitblit/utils/SyndicationUtils.java
index 2ee1cf6..93e9321 100644
--- a/src/main/java/com/gitblit/utils/SyndicationUtils.java
+++ b/src/main/java/com/gitblit/utils/SyndicationUtils.java
@@ -25,6 +25,7 @@
 import java.util.List;
 
 import com.gitblit.Constants;
+import com.gitblit.Constants.FeedObjectType;
 import com.gitblit.GitBlitException;
 import com.gitblit.models.FeedEntryModel;
 import com.sun.syndication.feed.synd.SyndCategory;
@@ -137,6 +138,59 @@
 	 */
 	public static List<FeedEntryModel> readFeed(String url, String repository, String branch,
 			int numberOfEntries, int page, String username, char[] password) throws IOException {
+		return readFeed(url, repository, branch, FeedObjectType.COMMIT, numberOfEntries,
+				page, username, password);
+	}
+
+	/**
+	 * Reads tags from the specified repository.
+	 *
+	 * @param url
+	 *            the url of the Gitblit server
+	 * @param repository
+	 *            the repository name
+	 * @param branch
+	 *            the branch name (optional)
+	 * @param numberOfEntries
+	 *            the number of entries to retrieve. if <= 0 the server default
+	 *            is used.
+	 * @param page
+	 *            0-indexed. used to paginate the results.
+	 * @param username
+	 * @param password
+	 * @return a list of SyndicationModel entries
+	 * @throws {@link IOException}
+	 */
+	public static List<FeedEntryModel> readTags(String url, String repository,
+			int numberOfEntries, int page, String username, char[] password) throws IOException {
+		return readFeed(url, repository, null, FeedObjectType.TAG, numberOfEntries,
+				page, username, password);
+	}
+
+	/**
+	 * Reads a Gitblit RSS feed.
+	 *
+	 * @param url
+	 *            the url of the Gitblit server
+	 * @param repository
+	 *            the repository name
+	 * @param branch
+	 *            the branch name (optional)
+	 * @param objectType
+	 *            the object type to return (optional, COMMIT assummed)
+	 * @param numberOfEntries
+	 *            the number of entries to retrieve. if <= 0 the server default
+	 *            is used.
+	 * @param page
+	 *            0-indexed. used to paginate the results.
+	 * @param username
+	 * @param password
+	 * @return a list of SyndicationModel entries
+	 * @throws {@link IOException}
+	 */
+	private static List<FeedEntryModel> readFeed(String url, String repository, String branch,
+			FeedObjectType objectType, int numberOfEntries, int page, String username,
+			char[] password) throws IOException {
 		// build feed url
 		List<String> parameters = new ArrayList<String>();
 		if (numberOfEntries > 0) {
@@ -148,6 +202,9 @@
 		if (!StringUtils.isEmpty(branch)) {
 			parameters.add("h=" + branch);
 		}
+		if (objectType != null) {
+			parameters.add("ot=" + objectType.name());
+		}
 		return readFeed(url, parameters, repository, branch, username, password);
 	}
 
diff --git a/src/site/rpc.mkd b/src/site/rpc.mkd
index 2e502e2..302084f 100644
--- a/src/site/rpc.mkd
+++ b/src/site/rpc.mkd
@@ -32,6 +32,7 @@
 <tr><th>url parameter</th><th>default</th><th>description</th></tr>
 <tr><td colspan='3'><b>standard query</b></td></tr>
 <tr><td><em>repository</em></td><td><em>required</em></td><td>repository name is part of the url (see examples below)</td></tr>
+<tr><td>ot=</td><td><em>optional</em><br/>default: COMMIT</td><td>object type to return in results. COMMIT or TAG</td></tr>
 <tr><td>h=</td><td><em>optional</em><br/>default: HEAD</td><td>starting branch, ref, or commit id</td></tr>
 <tr><td>l=</td><td><em>optional</em><br/>default: web.syndicationEntries</td><td>maximum return count</td></tr>
 <tr><td>pg=</td><td><em>optional</em><br/>default: 0</td><td>page number for paging<br/>(offset into history = pagenumber*maximum return count)</td></tr>
diff --git a/src/test/java/com/gitblit/tests/SyndicationUtilsTest.java b/src/test/java/com/gitblit/tests/SyndicationUtilsTest.java
index d206bbd..b4bb044 100644
--- a/src/test/java/com/gitblit/tests/SyndicationUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/SyndicationUtilsTest.java
@@ -21,7 +21,10 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import com.gitblit.Constants.SearchType;
@@ -29,6 +32,20 @@
 import com.gitblit.utils.SyndicationUtils;
 
 public class SyndicationUtilsTest extends GitblitUnitTest {
+
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+		}
+	}
 
 	@Test
 	public void testSyndication() throws Exception {
@@ -60,7 +77,7 @@
 	}
 
 	@Test
-	public void testFeedRead() throws Exception {
+	public void testFeedReadCommits() throws Exception {
 		Set<String> links = new HashSet<String>();
 		for (int i = 0; i < 2; i++) {
 			List<FeedEntryModel> feed = SyndicationUtils.readFeed(GitBlitSuite.url, "ticgit.git",
@@ -77,6 +94,23 @@
 	}
 
 	@Test
+	public void testFeedReadTags() throws Exception {
+		Set<String> links = new HashSet<String>();
+		for (int i = 0; i < 2; i++) {
+			List<FeedEntryModel> feed = SyndicationUtils.readTags(GitBlitSuite.url, "test/gitective.git",
+					5, i, GitBlitSuite.account, GitBlitSuite.password.toCharArray());
+			assertTrue(feed != null);
+			assertTrue(feed.size() > 0);
+			assertEquals(5, feed.size());
+			for (FeedEntryModel entry : feed) {
+				links.add(entry.link);
+			}
+		}
+		// confirm we have 10 unique tags
+		assertEquals("Feed pagination failed", 10, links.size());
+	}
+
+	@Test
 	public void testSearchFeedRead() throws Exception {
 		List<FeedEntryModel> feed = SyndicationUtils
 				.readSearchFeed(GitBlitSuite.url, "ticgit.git", null, "test", null, 5, 0,

--
Gitblit v1.9.1