Merged #66 "Query tags using RSS feeds"
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;

@@ -30,6 +33,20 @@
 

 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 {

 		List<FeedEntryModel> entries = new ArrayList<FeedEntryModel>();

@@ -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,