From 08934df7370f071d08ec98ba846ae5c5e70baf65 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 16 Mar 2019 12:26:33 +0100 Subject: [PATCH] Adapt to Gerrit 2.16 Build against Gerrit API 2.16.7. Adapt plugin sources to Gerrit API changes. Adapt Gitblit 1.7.1 sources to JGit 5.1.6 and to Lucene 6.6.5 used in Gerrit 2.16.6. Fix the links in the web UI to also work in the new PolyGerrit UI. Remove the "Browse" menu on a "Project" page; it doesn't work anymore in the PolyGerrit UI. (Appeared on the wrong pages in the wrong place, and was non-functional.) Gerrit has removed the pegdown parser for markdown files; they use flexmark now. Gitblit uses pegdown, but re-including them from Gitblit caused crashes when trying to parse markdown files. Rewrite two Markdown classes in Gitblit to use flexmark instead. --- docu/index.md | 43 +- pom.xml | 10 +- .../com/gitblit/service/LuceneService.java | 64 +-- .../java/com/gitblit/servlet/RawServlet.java | 2 + .../com/gitblit/tickets/TicketIndexer.java | 41 +- .../java/com/gitblit/utils/MarkdownUtils.java | 215 +++++++++ .../java/com/gitblit/utils/RefLogUtils.java | 30 +- .../com/gitblit/wicket/MarkupProcessor.java | 448 ++++++++++++++++++ .../com/gitblit/wicket/pages/RootPage.java | 9 +- .../gitblit/wicket/panels/BranchesPanel.java | 236 +++++++++ .../plugins/gitblit/GitBlitInitStep.java | 18 +- .../gerrit/plugins/gitblit/GitBlitModule.java | 14 + .../plugins/gitblit/GitBlitTopMenu.java | 9 +- .../plugins/gitblit/GitBlitWebUrls.java | 104 ++++ .../gerrit/plugins/gitblit/HttpUtils.java | 47 ++ .../gitblit/app/GerritGitBlitWebApp.java | 5 +- .../GerritGitBlitAuthenticationManager.java | 11 +- .../auth/GerritGitBlitUserManager.java | 2 +- .../gitblit/auth/GerritGitBlitUserModel.java | 12 +- .../com/syntevo/bugtraq/BugtraqConfig.java | 2 +- src/main/resources/Documentation/index.md | 39 +- 21 files changed, 1193 insertions(+), 168 deletions(-) create mode 100644 src/main/java/com/gitblit/utils/MarkdownUtils.java create mode 100644 src/main/java/com/gitblit/wicket/MarkupProcessor.java create mode 100644 src/main/java/com/gitblit/wicket/panels/BranchesPanel.java create mode 100644 src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitWebUrls.java create mode 100644 src/main/java/com/googlesource/gerrit/plugins/gitblit/HttpUtils.java diff --git a/docu/index.md b/docu/index.md index 641d8767..8885c768 100644 --- a/docu/index.md +++ b/docu/index.md @@ -5,7 +5,7 @@ with full SSO through Gerrit. * License: [Apache Public License 2.0](http://www.apache.org/licenses/LICENSE-2.0) * [Home page](https://github.com/tomaswolf/gerrit-gitblit-plugin) -* Installed plugin version: 2.15.171.0-SNAPSHOT +* Installed plugin version: 2.16.171.1-SNAPSHOT For a list of contributors, see at [GitHub](https://github.com/tomaswolf/gerrit-gitblit-plugin/graphs/contributors). @@ -15,30 +15,11 @@ This is a privately maintained fork of the official Gerrit-Gitblit plugin. Pleas # Configuration -There are two different configurations: one for Gerrit so it knows how to generate links that will be processed by the plugin, and -an optional GitBlit configuration for the plugin itself. +The plugin in configured in Gerrit's global configuration file `gerrit.config`. +As of v2.16.171.0, this plugin does _not_ use the `[gitweb]` section anymore. +Configuration is done in the `[plugin "gitblit"]` section. -## Gerrit configuration - -In Gerrit's `gerrit.config`, define the `[gitweb]` section as follows: - - [gitweb] - type = custom - url = plugins/gitblit/ - linkname = browse - project = summary/?r=${project} - revision = commit/?r=${project}&h=${commit} - branch = log/?r=${project}&h=${branch} - filehistory = history/?f=${file}&r=${project}&h=${branch} - file = blob/?r=${project}&h=${commit}&f=${file} - roottree = tree/?r=${project}&h=${commit} - -This is normally done automatically if you add the plugin and run through `java -jar gerrit.war init -d site_path`, but you can also -add this manually to Gerrit's config file. The `linkname` can be adapted to your taste. - -### Configuring the top menu - -This plugin adds a "GitBlit" top menu to Gerrit, and also a new sub-menu item to the "Projects" top menu. Since v2.11.162.2 of this plugin, the link +This plugin adds a "GitBlit" top menu to Gerrit. Since v2.11.162.2 of this plugin, the link texts for all sub-menu items can be configured to your taste in a `[plugin "gitblit"]` section in your `gerrit.config`. If the section is not present, or some values in that section are not defined, the plugin uses built-in default texts. The default configuration would correspond to @@ -47,14 +28,14 @@ or some values in that section are not defined, the plugin uses built-in default activity = Activity documentation = Documentation search = - browse = Browse + linkname = Gitblit -The first four are sub-menu items of the "GitBlit" top menu, the last one is a new "browse" sub-menu item in Gerrit's "Projects" menu that is shown -for Gerrit's "current" project (since v2.11.162.2). +The first four are sub-menu items of the "GitBlit" top menu, the last one is the link text shown on links to Gitblit for individual files +or in the project list. The "search" sub-menu item is by default not set and will thus not be shown. Setting it makes only sense if you enable GitBlit indexing on some of your projects. See the [GitBlit documentation](http://gitblit.com/setup_lucene.html) for more information on that. - + ## GitBlit configuration The plugin includes a minimal default configuration to make GitBlit act only as a repository viewer. You can augment that with further @@ -66,7 +47,7 @@ To see the built-in configuration, access it at [`gitblit.properties`](../src/ma By default, the built-in configuration does allow anonymous browsing, subject to the repository and ref-level access restrictions defined in Gerrit. If you want to lock the GitBlit plugin to allow only logged-in users to browse, set in `$GERRIT_SITE/etc/gitblit.properties` the key -`web.authenticateViewPages = true`. This is the only key of the built-in configuration that you _can_ override. +`web.authenticateViewPages = true`. This is the only key of the built-in configuration that you _can_ override. GitBlit's ticket service, fan-out service, and its plugin mechanism are disabled in this plugin, as is ssh access through GitBlit since Gerrit already provides that. Also disabled is Gitblit's LFS implementation. @@ -95,7 +76,7 @@ The GitBlit `${baseFolder}` is the plugin's data directory provided by Gerrit at

- + # Issue tracking @@ -111,6 +92,6 @@ Report bugs or make feature requests at the [GitHub issue tracker](https://githu
-GitBlit plugin 2.15.171.0-SNAPSHOT +GitBlit plugin 2.16.171.1-SNAPSHOT
diff --git a/pom.xml b/pom.xml index 12a298ac..391e38de 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ limitations under the License. gitblit-plugin GitBlit for Gerrit integrated as a plugin Gerrit - GitBlit Plugin - 2.15.171.0-SNAPSHOT + 2.16.171.1-SNAPSHOT Apache License 2.0 @@ -29,7 +29,7 @@ limitations under the License. plugin - 2.15 + 2.16.7 1.7.1 1.4.22 restart @@ -232,6 +232,8 @@ limitations under the License. com/gitblit/models/RefModel$*.class com/gitblit/models/RepositoryCommit.class com/gitblit/models/RepositoryCommit$*.class + com/gitblit/wicket/MarkupProcessor.class + com/gitblit/wicket/MarkupProcessor$*.class com/gitblit/wicket/SessionlessForm.class com/gitblit/wicket/SessionlessForm$*.class com/gitblit/wicket/pages/BasePage.class @@ -258,6 +260,8 @@ limitations under the License. com/gitblit/wicket/panels/LogPanel$*.class com/gitblit/wicket/panels/SearchPanel.class com/gitblit/wicket/panels/SearchPanel$*.class + com/gitblit/utils/MarkdownUtils.class + com/gitblit/utils/MarkdownUtils$*.class com/gitblit/utils/MetricUtils.class com/gitblit/utils/MetricUtils$*.class com/gitblit/utils/GitBlitDiffFormatter.class @@ -277,6 +281,8 @@ limitations under the License. com/gitblit/utils/RefLogUtils$*.class com/gitblit/wicket/pages/NewRepositoryPage.class com/gitblit/wicket/pages/NewRepositoryPage$*.class + com/gitblit/wicket/panels/BranchesPanel.class + com/gitblit/wicket/panels/BranchesPanel$*.class com/gitblit/wicket/panels/HistoryPanel.class com/gitblit/wicket/panels/HistoryPanel$*.class com/gitblit/tickets/BranchTicketService.class diff --git a/src/main/java/com/gitblit/service/LuceneService.java b/src/main/java/com/gitblit/service/LuceneService.java index 708a3d29..b99912d1 100644 --- a/src/main/java/com/gitblit/service/LuceneService.java +++ b/src/main/java/com/gitblit/service/LuceneService.java @@ -97,9 +97,9 @@ /** * The Lucene service handles indexing and searching repositories. - * + * * @author James Moger - * + * */ public class LuceneService implements Runnable { @@ -185,7 +185,7 @@ public void run() { /** * Synchronously indexes a repository. This may build a complete index of a repository or it may update an existing index. - * + * * @param displayName * the name of the repository * @param repository @@ -226,7 +226,7 @@ private void index(RepositoryModel model, Repository repository) { /** * Close the writer/searcher objects for a repository. - * + * * @param repositoryName */ public synchronized void close(String repositoryName) { @@ -251,7 +251,7 @@ public synchronized void close(String repositoryName) { /** * Close all Lucene indexers. - * + * */ public synchronized void close() { // close all writers @@ -277,7 +277,7 @@ public synchronized void close() { /** * Deletes the Lucene index for the specified repository. - * + * * @param repositoryName * @return true, if successful */ @@ -305,7 +305,7 @@ public boolean deleteIndex(String repositoryName) { /** * Returns the author for the commit, if this information is available. - * + * * @param commit * @return an author or unknown */ @@ -323,7 +323,7 @@ private String getAuthor(RevCommit commit) { /** * Returns the committer for the commit, if this information is available. - * + * * @param commit * @return an committer or unknown */ @@ -341,7 +341,7 @@ private String getCommitter(RevCommit commit) { /** * Get the tree associated with the given commit. - * + * * @param walk * @param commit * @return tree @@ -358,7 +358,7 @@ private RevTree getTree(final RevWalk walk, final RevCommit commit) throws IOExc /** * Construct a keyname from the branch. - * + * * @param branchName * @return a keyname appropriate for the Git config file format */ @@ -368,7 +368,7 @@ private String getBranchKey(String branchName) { /** * Returns the Lucene configuration for the specified repository. - * + * * @param repository * @return a config object */ @@ -381,7 +381,7 @@ private FileBasedConfig getConfig(Repository repository) { /** * Reads the Lucene config file for the repository to check the index version. If the index version is different, then rebuild the repository * index. - * + * * @param repository * @return true of the on-disk index format is different than INDEX_VERSION */ @@ -399,7 +399,7 @@ private boolean shouldReindex(Repository repository) { /** * This completely indexes the repository and will destroy any existing index. - * + * * @param repositoryName * @param repository * @return IndexResult @@ -613,7 +613,7 @@ public int compare(RefModel ref1, RefModel ref2) { /** * Incrementally update the index with the specified commit for the repository. - * + * * @param repositoryName * @param repository * @param branch @@ -688,7 +688,7 @@ private IndexResult index(String repositoryName, Repository repository, String b /** * Delete a blob from the specified branch of the repository index. - * + * * @param repositoryName * @param branch * @param path @@ -699,10 +699,9 @@ public boolean deleteBlob(String repositoryName, String branch, String path) thr String pattern = MessageFormat.format("{0}:'{'0} AND {1}:\"'{'1'}'\" AND {2}:\"'{'2'}'\"", FIELD_OBJECT_TYPE, FIELD_BRANCH, FIELD_PATH); String q = MessageFormat.format(pattern, SearchObjectType.blob.name(), branch, path); - BooleanQuery query = new BooleanQuery(); StandardAnalyzer analyzer = new StandardAnalyzer(); QueryParser qp = new QueryParser(FIELD_SUMMARY, analyzer); - query.add(qp.parse(q), Occur.MUST); + BooleanQuery query = (new BooleanQuery.Builder()).add(qp.parse(q), Occur.MUST).build(); IndexWriter writer = getIndexWriter(repositoryName); int numDocsBefore = writer.numDocs(); @@ -720,7 +719,7 @@ public boolean deleteBlob(String repositoryName, String branch, String path) thr /** * Updates a repository index incrementally from the last indexed commits. - * + * * @param model * @param repository * @return IndexResult @@ -849,7 +848,7 @@ public int compare(RefModel ref1, RefModel ref2) { /** * Creates a Lucene document for a commit - * + * * @param commit * @param tags * @return a Lucene document @@ -871,7 +870,7 @@ private Document createDocument(RevCommit commit, List tags) { /** * Incrementally index an object for the repository. - * + * * @param repositoryName * @param doc * @return true, if successful @@ -917,7 +916,7 @@ private synchronized void resetIndexSearcher(String repository) throws IOExcepti /** * Gets an index searcher for the repository. - * + * * @param repository * @return * @throws IOException @@ -926,7 +925,7 @@ private IndexSearcher getIndexSearcher(String repository) throws IOException { IndexSearcher searcher = searchers.get(repository); if (searcher == null) { IndexWriter writer = getIndexWriter(repository); - searcher = new IndexSearcher(DirectoryReader.open(writer, true)); + searcher = new IndexSearcher(DirectoryReader.open(writer, true, true)); searchers.put(repository, searcher); } return searcher; @@ -934,7 +933,7 @@ private IndexSearcher getIndexSearcher(String repository) throws IOException { /** * Gets an index writer for the repository. The index will be created if it does not already exist or if forceCreate is specified. - * + * * @param repository * @return an IndexWriter * @throws IOException @@ -960,7 +959,7 @@ private IndexWriter getIndexWriter(String repository) throws IOException { /** * Searches the specified repositories for the given text or query - * + * * @param text * if the text is null or empty, null is returned * @param page @@ -970,7 +969,7 @@ private IndexWriter getIndexWriter(String repository) throws IOException { * @param repositories * a list of repositories to search. if no repositories are specified null is returned. * @return a list of SearchResults in order from highest to the lowest score - * + * */ public List search(String text, int page, int pageSize, List repositories) { if (ArrayUtils.isEmpty(repositories)) { @@ -981,7 +980,7 @@ public List search(String text, int page, int pageSize, List search(String text, int page, int pageSize, List search(String text, int page, int pageSize, String... repositories) { if (StringUtils.isEmpty(text)) { @@ -1004,15 +1003,15 @@ public List search(String text, int page, int pageSize, String... StandardAnalyzer analyzer = new StandardAnalyzer(); try { // default search checks summary and content - BooleanQuery query = new BooleanQuery(); + BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder(); QueryParser qp; qp = new QueryParser(FIELD_SUMMARY, analyzer); qp.setAllowLeadingWildcard(true); - query.add(qp.parse(text), Occur.SHOULD); + queryBuilder.add(qp.parse(text), Occur.SHOULD); qp = new QueryParser(FIELD_CONTENT, analyzer); qp.setAllowLeadingWildcard(true); - query.add(qp.parse(text), Occur.SHOULD); + queryBuilder.add(qp.parse(text), Occur.SHOULD); IndexSearcher searcher; if (repositories.length == 1) { @@ -1030,6 +1029,7 @@ public List search(String text, int page, int pageSize, String... searcher = new IndexSearcher(reader); } + BooleanQuery query = queryBuilder.build(); Query rewrittenQuery = searcher.rewrite(query); logger.debug(rewrittenQuery.toString()); @@ -1062,7 +1062,7 @@ public List search(String text, int page, int pageSize, String... } /** - * + * * @param analyzer * @param query * @param content @@ -1192,7 +1192,7 @@ float duration() { /** * Custom subclass of MultiReader to identify the source index for a given doc id. This would not be necessary of there was a public method to * obtain this information. - * + * */ private class MultiSourceReader extends MultiReader { diff --git a/src/main/java/com/gitblit/servlet/RawServlet.java b/src/main/java/com/gitblit/servlet/RawServlet.java index 428d40b3..d07c460f 100644 --- a/src/main/java/com/gitblit/servlet/RawServlet.java +++ b/src/main/java/com/gitblit/servlet/RawServlet.java @@ -414,6 +414,8 @@ protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse String requestedPath) throws IOException { boolean served = false; + // RevWalk is disposed in finally + @SuppressWarnings("resource") RevWalk rw = new RevWalk(repository); TreeWalk tw = new TreeWalk(repository); try { diff --git a/src/main/java/com/gitblit/tickets/TicketIndexer.java b/src/main/java/com/gitblit/tickets/TicketIndexer.java index d1e88374..ced040ca 100644 --- a/src/main/java/com/gitblit/tickets/TicketIndexer.java +++ b/src/main/java/com/gitblit/tickets/TicketIndexer.java @@ -64,9 +64,9 @@ /** * Indexes tickets in a Lucene database. - * + * * @author James Moger - * + * */ public class TicketIndexer { @@ -174,8 +174,7 @@ public boolean deleteAll(RepositoryModel repository) { IndexWriter writer = getWriter(); StandardAnalyzer analyzer = new StandardAnalyzer(); QueryParser qp = new QueryParser(Lucene.rid.name(), analyzer); - BooleanQuery query = new BooleanQuery(); - query.add(qp.parse(repository.getRID()), Occur.MUST); + BooleanQuery query = (new BooleanQuery.Builder()).add(qp.parse(repository.getRID()), Occur.MUST).build(); int numDocsBefore = writer.numDocs(); writer.deleteDocuments(query); @@ -197,7 +196,7 @@ public boolean deleteAll(RepositoryModel repository) { /** * Bulk Add/Update tickets in the Lucene index - * + * * @param tickets */ public void index(List tickets) { @@ -216,7 +215,7 @@ public void index(List tickets) { /** * Add/Update a ticket in the Lucene index - * + * * @param ticket */ public void index(TicketModel ticket) { @@ -234,7 +233,7 @@ public void index(TicketModel ticket) { /** * Delete a ticket from the Lucene index. - * + * * @param ticket * @throws Exception * @return true, if deleted, false if no record was deleted @@ -251,7 +250,7 @@ public boolean delete(TicketModel ticket) { /** * Delete a ticket from the Lucene index. - * + * * @param repository * @param ticketId * @throws Exception @@ -260,8 +259,7 @@ public boolean delete(TicketModel ticket) { private boolean delete(String repository, long ticketId, IndexWriter writer) throws Exception { StandardAnalyzer analyzer = new StandardAnalyzer(); QueryParser qp = new QueryParser(Lucene.did.name(), analyzer); - BooleanQuery query = new BooleanQuery(); - query.add(qp.parse(StringUtils.getSHA1(repository + ticketId)), Occur.MUST); + BooleanQuery query = (new BooleanQuery.Builder()).add(qp.parse(StringUtils.getSHA1(repository + ticketId)), Occur.MUST).build(); int numDocsBefore = writer.numDocs(); writer.deleteDocuments(query); @@ -279,7 +277,7 @@ private boolean delete(String repository, long ticketId, IndexWriter writer) thr /** * Returns true if the repository has tickets in the index. - * + * * @param repository * @return true if there are indexed tickets */ @@ -289,7 +287,7 @@ public boolean hasTickets(RepositoryModel repository) { /** * Search for tickets matching the query. The returned tickets are shadows of the real ticket, but suitable for a results list. - * + * * @param repository * @param text * @param page @@ -304,22 +302,23 @@ public List searchFor(RepositoryModel repository, String text, int StandardAnalyzer analyzer = new StandardAnalyzer(); try { // search the title, description and content - BooleanQuery query = new BooleanQuery(); + BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder(); QueryParser qp; qp = new QueryParser(Lucene.title.name(), analyzer); qp.setAllowLeadingWildcard(true); - query.add(qp.parse(text), Occur.SHOULD); + queryBuilder.add(qp.parse(text), Occur.SHOULD); qp = new QueryParser(Lucene.body.name(), analyzer); qp.setAllowLeadingWildcard(true); - query.add(qp.parse(text), Occur.SHOULD); + queryBuilder.add(qp.parse(text), Occur.SHOULD); qp = new QueryParser(Lucene.content.name(), analyzer); qp.setAllowLeadingWildcard(true); - query.add(qp.parse(text), Occur.SHOULD); + queryBuilder.add(qp.parse(text), Occur.SHOULD); IndexSearcher searcher = getSearcher(); + BooleanQuery query = queryBuilder.build(); Query rewrittenQuery = searcher.rewrite(query); log.debug(rewrittenQuery.toString()); @@ -347,7 +346,7 @@ public List searchFor(RepositoryModel repository, String text, int /** * Search for tickets matching the query. The returned tickets are shadows of the real ticket, but suitable for a results list. - * + * * @param text * @param page * @param pageSize @@ -378,7 +377,7 @@ public List queryFor(String queryText, int page, int pageSize, Stri sort = new Sort(Lucene.fromString(sortBy).asSortField(desc)); } int maxSize = 5000; - TopFieldDocs docs = searcher.search(rewrittenQuery, null, maxSize, sort, false, false); + TopFieldDocs docs = searcher.search(rewrittenQuery, maxSize, sort, false, false); int size = (pageSize <= 0) ? maxSize : pageSize; int offset = Math.max(0, (page - 1) * size); ScoreDoc[] hits = subset(docs.scoreDocs, offset, size); @@ -440,7 +439,7 @@ private synchronized void closeWriter() { private IndexSearcher getSearcher() throws IOException { if (searcher == null) { - searcher = new IndexSearcher(DirectoryReader.open(getWriter(), true)); + searcher = new IndexSearcher(DirectoryReader.open(getWriter(), true, true)); } return searcher; } @@ -459,7 +458,7 @@ private synchronized void closeSearcher() { /** * Creates a Lucene document from a ticket. - * + * * @param ticket * @return a Lucene document */ @@ -536,7 +535,7 @@ private void toDocField(Document doc, Lucene lucene, String value) { /** * Creates a query result from the Lucene document. This result is not a high-fidelity representation of the real ticket, but it is suitable for * display in a table of search results. - * + * * @param doc * @return a query result * @throws ParseException diff --git a/src/main/java/com/gitblit/utils/MarkdownUtils.java b/src/main/java/com/gitblit/utils/MarkdownUtils.java new file mode 100644 index 00000000..4d6728e6 --- /dev/null +++ b/src/main/java/com/gitblit/utils/MarkdownUtils.java @@ -0,0 +1,215 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.utils; + +import static com.vladsch.flexmark.ext.wikilink.WikiLinkExtension.WIKI_LINK; +import static com.vladsch.flexmark.profiles.pegdown.Extensions.ALL; +import static com.vladsch.flexmark.profiles.pegdown.Extensions.ANCHORLINKS; +import static com.vladsch.flexmark.profiles.pegdown.Extensions.SMARTYPANTS; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringWriter; +import java.text.MessageFormat; + +import org.apache.commons.io.IOUtils; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.vladsch.flexmark.ast.Node; +import com.vladsch.flexmark.ext.wikilink.WikiImage; +import com.vladsch.flexmark.ext.wikilink.internal.WikiLinkLinkResolver; +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.html.IndependentLinkResolverFactory; +import com.vladsch.flexmark.html.LinkResolver; +import com.vladsch.flexmark.html.renderer.LinkResolverContext; +import com.vladsch.flexmark.html.renderer.ResolvedLink; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter; +import com.vladsch.flexmark.util.options.MutableDataHolder; + +/** + * Utility methods for transforming raw markdown text to html. + * + * @author James Moger + * + */ +public class MarkdownUtils { + + /** + * Returns the html version of the plain source text. + * + * @param text + * @return html version of plain text + * @throws java.text.ParseException + */ + public static String transformPlainText(String text) { + // url auto-linking + text = text.replaceAll("((http|https)://[0-9A-Za-z-_=\\?\\.\\$#&/]*)", "$1"); + String html = "
" + text + "
"; + return html; + } + + + /** + * Returns the html version of the markdown source text. + * + * @param markdown + * @return html version of markdown text + * @throws java.text.ParseException + */ + public static String transformMarkdown(String markdown) { + return transformMarkdown(markdown, null); + } + + public interface Linker { + + ResolvedLink link(ResolvedLink original, boolean image); + + } + + /** + * Returns the html version of the markdown source text. + * + * @param markdown + * @return html version of markdown text + * @throws java.text.ParseException + */ + public static String transformMarkdown(String markdown, Linker linkRenderer) { + MutableDataHolder options; + + if (linkRenderer == null) { + options = PegdownOptionsAdapter.flexmarkOptions(ALL & ~SMARTYPANTS & ~ANCHORLINKS).toMutable(); + } else { + options = PegdownOptionsAdapter.flexmarkOptions(ALL & ~SMARTYPANTS & ~ANCHORLINKS, new CustomExtension(linkRenderer)).toMutable(); + } + Node document = Parser.builder(options).build().parse(markdown); + return HtmlRenderer.builder(options).build().render(document); + } + + private static class CustomExtension implements HtmlRenderer.HtmlRendererExtension { + + private final Linker resolver; + + public CustomExtension(Linker resolver) { + this.resolver = resolver; + } + + @Override + public void rendererOptions(final MutableDataHolder options) { + // Nothing + } + + @Override + public void extend(final HtmlRenderer.Builder rendererBuilder, final String rendererType) { + rendererBuilder.linkResolverFactory(new Factory()); + } + + private class Resolver extends WikiLinkLinkResolver { + + public Resolver(LinkResolverContext context) { + super(context); + } + + @Override + public ResolvedLink resolveLink(Node node, LinkResolverContext context, ResolvedLink link) { + if (link.getLinkType() == WIKI_LINK && link.getUrl().indexOf("://") < 0) { + ResolvedLink result = resolver.link(link, node instanceof WikiImage); + if (result != null) { + return result; + } + } + return super.resolveLink(node, context, link); + } + } + + private class Factory extends IndependentLinkResolverFactory { + + @Override + public LinkResolver create(final LinkResolverContext context) { + return new Resolver(context); + } + } + } + /** + * Returns the html version of the markdown source reader. The reader is + * closed regardless of success or failure. + * + * @param markdownReader + * @return html version of the markdown text + * @throws java.text.ParseException + */ + public static String transformMarkdown(Reader markdownReader) throws IOException { + // Read raw markdown content and transform it to html + StringWriter writer = new StringWriter(); + try { + IOUtils.copy(markdownReader, writer); + String markdown = writer.toString(); + return transformMarkdown(markdown); + } finally { + try { + writer.close(); + } catch (IOException e) { + // IGNORE + } + } + } + + + /** + * Transforms GFM (Github Flavored Markdown) to html. + * Gitblit does not support the complete GFM specification. + * + * @param input + * @param repositoryName + * @return html + */ + public static String transformGFM(IStoredSettings settings, String input, String repositoryName) { + String text = input; + + // strikethrough + text = text.replaceAll("~~(.*)~~", "$1"); + text = text.replaceAll("\\{(?:-){2}(.*)(?:-){2}}", "$1"); + + // underline + text = text.replaceAll("\\{(?:\\+){2}(.*)(?:\\+){2}}", "$1"); + + // strikethrough, replacement + text = text.replaceAll("\\{~~(.*)~>(.*)~~}", "$1$2"); + + // highlight + text = text.replaceAll("\\{==(.*)==}", "$1"); + + String canonicalUrl = settings.getString(Keys.web.canonicalUrl, "https://localhost:8443"); + + // emphasize and link mentions + String mentionReplacement = String.format(" **[@$1](%1s/user/$1)**", canonicalUrl); + text = text.replaceAll("\\s@([A-Za-z0-9-_]+)", mentionReplacement); + + // link ticket refs + String ticketReplacement = MessageFormat.format("$1[#$2]({0}/tickets?r={1}&h=$2)$3", canonicalUrl, repositoryName); + text = text.replaceAll("([\\s,]+)#(\\d+)([\\s,:\\.\\n])", ticketReplacement); + + // link commit shas + int shaLen = settings.getInteger(Keys.web.shortCommitIdLength, 6); + String commitPattern = MessageFormat.format("\\s([A-Fa-f0-9]'{'{0}'}')([A-Fa-f0-9]'{'{1}'}')", shaLen, 40 - shaLen); + String commitReplacement = String.format(" [`$1`](%1$s/commit?r=%2$s&h=$1$2)", canonicalUrl, repositoryName); + text = text.replaceAll(commitPattern, commitReplacement); + + String html = transformMarkdown(text); + return html; + } +} diff --git a/src/main/java/com/gitblit/utils/RefLogUtils.java b/src/main/java/com/gitblit/utils/RefLogUtils.java index 67b4d550..bd710e5b 100644 --- a/src/main/java/com/gitblit/utils/RefLogUtils.java +++ b/src/main/java/com/gitblit/utils/RefLogUtils.java @@ -65,9 +65,9 @@ /** * Utility class for maintaining a reflog within a git repository on an orphan branch. - * + * * @author James Moger - * + * */ public class RefLogUtils { @@ -77,7 +77,7 @@ public class RefLogUtils { /** * Log an error message and exception. - * + * * @param t * @param repository * if repository is not null it MUST be the {0} parameter in the pattern. @@ -99,13 +99,13 @@ private static void error(Throwable t, Repository repository, String pattern, Ob /** * Returns true if the repository has a reflog branch. - * + * * @param repository * @return true if the repository has a reflog branch */ public static boolean hasRefLogBranch(Repository repository) { try { - return repository.getRef(GB_REFLOG) != null; + return repository.exactRef(GB_REFLOG) != null; } catch (Exception e) { LOGGER.error("failed to determine hasRefLogBranch", e); } @@ -114,7 +114,7 @@ public static boolean hasRefLogBranch(Repository repository) { /** * Returns a RefModel for the reflog branch in the repository. If the branch can not be found, null is returned. - * + * * @param repository * @return a refmodel for the reflog branch or null */ @@ -173,7 +173,7 @@ private static UserModel newUserModelFrom(PersonIdent ident) { /** * Logs a ref deletion. - * + * * @param user * @param repository * @param ref @@ -204,7 +204,7 @@ public static boolean deleteRef(UserModel user, Repository repository, Ref ref) /** * Updates the reflog with the received commands. - * + * * @param user * @param repository * @param commands @@ -293,7 +293,7 @@ public static boolean updateRefLog(UserModel user, Repository repository, Collec /** * Creates an in-memory index of the reflog entry. - * + * * @param repo * @param headId * @param commands @@ -405,7 +405,7 @@ public static List getRefLog(String repositoryName, Repository repo /** * Returns the list of reflog entries as they were recorded by Gitblit. Each RefLogEntry may represent multiple ref updates. - * + * * @param repositoryName * @param repository * @param minimumDate @@ -489,7 +489,7 @@ public static List getRefLog(String repositoryName, Repository repo /** * Returns the list of entries organized by ref (e.g. each ref has it's own RefLogEntry object). - * + * * @param repositoryName * @param repository * @param maxCount @@ -501,7 +501,7 @@ public static List getLogByRef(String repositoryName, Repository re /** * Returns the list of entries organized by ref (e.g. each ref has it's own RefLogEntry object). - * + * * @param repositoryName * @param repository * @param offset @@ -547,7 +547,7 @@ public static List getLogByRef(String repositoryName, Repository re /** * Returns the list of ref changes separated by ref (e.g. each ref has it's own RefLogEntry object). - * + * * @param repositoryName * @param repository * @param minimumDate @@ -584,7 +584,7 @@ public static List getLogByRef(String repositoryName, Repository re /** * Returns a commit log grouped by day. - * + * * @param repositoryName * @param repository * @param minimumDate @@ -679,7 +679,7 @@ public static List getDailyLog(String repositoryName, Repository /** * Returns the list of commits separated by ref (e.g. each ref has it's own RefLogEntry object for each day). - * + * * @param repositoryName * @param repository * @param minimumDate diff --git a/src/main/java/com/gitblit/wicket/MarkupProcessor.java b/src/main/java/com/gitblit/wicket/MarkupProcessor.java new file mode 100644 index 00000000..5d487432 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/MarkupProcessor.java @@ -0,0 +1,448 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket; + +import java.io.Serializable; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.wicket.Page; +import org.apache.wicket.RequestCycle; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.mylyn.wikitext.confluence.core.ConfluenceLanguage; +import org.eclipse.mylyn.wikitext.core.parser.Attributes; +import org.eclipse.mylyn.wikitext.core.parser.MarkupParser; +import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder; +import org.eclipse.mylyn.wikitext.core.parser.markup.MarkupLanguage; +import org.eclipse.mylyn.wikitext.mediawiki.core.MediaWikiLanguage; +import org.eclipse.mylyn.wikitext.textile.core.TextileLanguage; +import org.eclipse.mylyn.wikitext.tracwiki.core.TracWikiLanguage; +import org.eclipse.mylyn.wikitext.twiki.core.TWikiLanguage; +import org.pegdown.DefaultVerbatimSerializer; +import org.pegdown.LinkRenderer; +import org.pegdown.ToHtmlSerializer; +import org.pegdown.VerbatimSerializer; +import org.pegdown.plugins.ToHtmlSerializerPlugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.models.PathModel; +import com.gitblit.servlet.RawServlet; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.MarkdownUtils; +import com.gitblit.utils.MarkdownUtils.Linker; +import com.gitblit.utils.StringUtils; +import com.gitblit.utils.XssFilter; +import com.gitblit.wicket.pages.DocPage; +import com.google.common.base.Joiner; +import com.vladsch.flexmark.html.renderer.LinkStatus; +import com.vladsch.flexmark.html.renderer.ResolvedLink; + +/** + * Processes markup content and generates html with repository-relative page and + * image linking. + * + * @author James Moger + * + */ +public class MarkupProcessor { + + public enum MarkupSyntax { + PLAIN, MARKDOWN, TWIKI, TRACWIKI, TEXTILE, MEDIAWIKI, CONFLUENCE + } + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final IStoredSettings settings; + + private final XssFilter xssFilter; + + public static List getMarkupExtensions(IStoredSettings settings) { + List list = new ArrayList(); + list.addAll(settings.getStrings(Keys.web.confluenceExtensions)); + list.addAll(settings.getStrings(Keys.web.markdownExtensions)); + list.addAll(settings.getStrings(Keys.web.mediawikiExtensions)); + list.addAll(settings.getStrings(Keys.web.textileExtensions)); + list.addAll(settings.getStrings(Keys.web.tracwikiExtensions)); + list.addAll(settings.getStrings(Keys.web.twikiExtensions)); + return list; + } + + public MarkupProcessor(IStoredSettings settings, XssFilter xssFilter) { + this.settings = settings; + this.xssFilter = xssFilter; + } + + public List getMarkupExtensions() { + return getMarkupExtensions(settings); + } + + public List getAllExtensions() { + List list = getMarkupExtensions(settings); + list.add("txt"); + list.add("TXT"); + return list; + } + + private List getRoots() { + return settings.getStrings(Keys.web.documents); + } + + private String [] getEncodings() { + return settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]); + } + + private MarkupSyntax determineSyntax(String documentPath) { + String ext = StringUtils.getFileExtension(documentPath).toLowerCase(); + if (StringUtils.isEmpty(ext)) { + return MarkupSyntax.PLAIN; + } + + if (settings.getStrings(Keys.web.confluenceExtensions).contains(ext)) { + return MarkupSyntax.CONFLUENCE; + } else if (settings.getStrings(Keys.web.markdownExtensions).contains(ext)) { + return MarkupSyntax.MARKDOWN; + } else if (settings.getStrings(Keys.web.mediawikiExtensions).contains(ext)) { + return MarkupSyntax.MEDIAWIKI; + } else if (settings.getStrings(Keys.web.textileExtensions).contains(ext)) { + return MarkupSyntax.TEXTILE; + } else if (settings.getStrings(Keys.web.tracwikiExtensions).contains(ext)) { + return MarkupSyntax.TRACWIKI; + } else if (settings.getStrings(Keys.web.twikiExtensions).contains(ext)) { + return MarkupSyntax.TWIKI; + } + + return MarkupSyntax.PLAIN; + } + + public boolean hasRootDocs(Repository r) { + List roots = getRoots(); + List extensions = getAllExtensions(); + List paths = JGitUtils.getFilesInPath(r, null, null); + for (PathModel path : paths) { + if (!path.isTree()) { + String ext = StringUtils.getFileExtension(path.name).toLowerCase(); + String name = StringUtils.stripFileExtension(path.name).toLowerCase(); + + if (roots.contains(name)) { + if (StringUtils.isEmpty(ext) || extensions.contains(ext)) { + return true; + } + } + } + } + return false; + } + + public List getRootDocs(Repository r, String repositoryName, String commitId) { + List roots = getRoots(); + List list = getDocs(r, repositoryName, commitId, roots); + return list; + } + + public MarkupDocument getReadme(Repository r, String repositoryName, String commitId) { + List list = getDocs(r, repositoryName, commitId, Arrays.asList("readme")); + if (list.isEmpty()) { + return null; + } + return list.get(0); + } + + private List getDocs(Repository r, String repositoryName, String commitId, List names) { + List extensions = getAllExtensions(); + String [] encodings = getEncodings(); + Map map = new HashMap(); + RevCommit commit = JGitUtils.getCommit(r, commitId); + List paths = JGitUtils.getFilesInPath(r, null, commit); + for (PathModel path : paths) { + if (!path.isTree()) { + String ext = StringUtils.getFileExtension(path.name).toLowerCase(); + String name = StringUtils.stripFileExtension(path.name).toLowerCase(); + + if (names.contains(name)) { + if (StringUtils.isEmpty(ext) || extensions.contains(ext)) { + String markup = JGitUtils.getStringContent(r, commit.getTree(), path.name, encodings); + MarkupDocument doc = parse(repositoryName, commitId, path.name, markup); + map.put(name, doc); + } + } + } + } + // return document list in requested order + List list = new ArrayList(); + for (String name : names) { + if (map.containsKey(name)) { + list.add(map.get(name)); + } + } + return list; + } + + public MarkupDocument parse(String repositoryName, String commitId, String documentPath, String markupText) { + final MarkupSyntax syntax = determineSyntax(documentPath); + final MarkupDocument doc = new MarkupDocument(documentPath, markupText, syntax); + + if (markupText != null) { + try { + switch (syntax){ + case CONFLUENCE: + parse(doc, repositoryName, commitId, new ConfluenceLanguage()); + break; + case MARKDOWN: + parse(doc, repositoryName, commitId); + break; + case MEDIAWIKI: + parse(doc, repositoryName, commitId, new MediaWikiLanguage()); + break; + case TEXTILE: + parse(doc, repositoryName, commitId, new TextileLanguage()); + break; + case TRACWIKI: + parse(doc, repositoryName, commitId, new TracWikiLanguage()); + break; + case TWIKI: + parse(doc, repositoryName, commitId, new TWikiLanguage()); + break; + default: + doc.html = MarkdownUtils.transformPlainText(markupText); + break; + } + } catch (Exception e) { + logger.error("failed to transform " + syntax, e); + } + } + + if (doc.html == null) { + // failed to transform markup + if (markupText == null) { + markupText = String.format("Document %1$s not found in %2$s", documentPath, repositoryName); + } + markupText = MessageFormat.format("
{0}: {1}
{2}", "Error", "failed to parse markup", markupText); + doc.html = StringUtils.breakLinesForHtml(markupText); + } + + return doc; + } + + /** + * Parses the markup using the specified markup language + * + * @param doc + * @param repositoryName + * @param commitId + * @param lang + */ + private void parse(final MarkupDocument doc, final String repositoryName, final String commitId, MarkupLanguage lang) { + StringWriter writer = new StringWriter(); + HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer) { + + @Override + public void image(Attributes attributes, String imagePath) { + String url; + if (imagePath.indexOf("://") == -1) { + // relative image + String path = doc.getRelativePath(imagePath); + String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot(); + url = RawServlet.asLink(contextUrl, repositoryName, commitId, path); + } else { + // absolute image + url = imagePath; + } + super.image(attributes, url); + } + + @Override + public void link(Attributes attributes, String hrefOrHashName, String text) { + String url; + if (hrefOrHashName.charAt(0) != '#') { + if (hrefOrHashName.indexOf("://") == -1) { + // relative link + String path = doc.getRelativePath(hrefOrHashName); + url = getWicketUrl(DocPage.class, repositoryName, commitId, path); + } else { + // absolute link + url = hrefOrHashName; + } + } else { + // page-relative hash link + url = hrefOrHashName; + } + super.link(attributes, url, text); + } + }; + + // avoid the and tags + builder.setEmitAsDocument(false); + + MarkupParser parser = new MarkupParser(lang); + parser.setBuilder(builder); + parser.parse(doc.markup); + + final String content = writer.toString(); + final String safeContent = xssFilter.relaxed(content); + + doc.html = safeContent; + } + + /** + * Parses the document as Markdown using Pegdown. + * + * @param doc + * @param repositoryName + * @param commitId + */ + private void parse(final MarkupDocument doc, final String repositoryName, final String commitId) { + Linker renderer = new Linker() { + + @Override + public ResolvedLink link(ResolvedLink original, boolean image) { + if (image) { + String path = doc.getRelativePath(original.getUrl()); + String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot(); + String url = RawServlet.asLink(contextUrl, repositoryName, commitId, path); + return original.withStatus(LinkStatus.VALID).withUrl(url); + } else { + String path = doc.getRelativePath(original.getUrl()); + String url = getWicketUrl(DocPage.class, repositoryName, commitId, path); + return original.withStatus(LinkStatus.VALID).withUrl(url); + } + } + }; + + final String content = MarkdownUtils.transformMarkdown(doc.markup, renderer); + final String safeContent = xssFilter.relaxed(content); + + doc.html = safeContent; + } + + private String getWicketUrl(Class pageClass, final String repositoryName, final String commitId, final String document) { + String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/"); + String encodedPath = document.replace(' ', '-'); + try { + encodedPath = URLEncoder.encode(encodedPath, "UTF-8"); + } catch (UnsupportedEncodingException e) { + logger.error(null, e); + } + encodedPath = encodedPath.replace("/", fsc).replace("%2F", fsc); + + String url = RequestCycle.get().urlFor(pageClass, WicketUtils.newPathParameter(repositoryName, commitId, encodedPath)).toString(); + return url; + } + +// private String getDocumentName(final String document) { +// // extract document name +// String name = StringUtils.stripFileExtension(document); +// name = name.replace('_', ' '); +// if (name.indexOf('/') > -1) { +// name = name.substring(name.lastIndexOf('/') + 1); +// } +// return name; +// } + + public static class MarkupDocument implements Serializable { + + private static final long serialVersionUID = 1L; + + public final String documentPath; + public final String markup; + public final MarkupSyntax syntax; + public String html; + + MarkupDocument(String documentPath, String markup, MarkupSyntax syntax) { + this.documentPath = documentPath; + this.markup = markup; + this.syntax = syntax; + } + + String getCurrentPath() { + String basePath = ""; + if (documentPath.indexOf('/') > -1) { + basePath = documentPath.substring(0, documentPath.lastIndexOf('/') + 1); + if (basePath.charAt(0) == '/') { + return basePath.substring(1); + } + } + return basePath; + } + + String getRelativePath(String ref) { + if (ref.charAt(0) == '/') { + // absolute path in repository + return ref.substring(1); + } else { + // resolve relative repository path + String cp = getCurrentPath(); + if (StringUtils.isEmpty(cp)) { + return ref; + } + // this is a simple relative path resolver + List currPathStrings = new ArrayList(Arrays.asList(cp.split("/"))); + String file = ref; + while (file.startsWith("../")) { + // strip ../ from the file reference + // drop the last path element + file = file.substring(3); + currPathStrings.remove(currPathStrings.size() - 1); + } + currPathStrings.add(file); + String path = Joiner.on("/").join(currPathStrings); + return path; + } + } + } + + /** + * This class implements a workaround for a bug reported in issue-379. + * The bug was introduced by my own pegdown pull request #115. + * + * @author James Moger + * + */ + public static class WorkaroundHtmlSerializer extends ToHtmlSerializer { + + public WorkaroundHtmlSerializer(final LinkRenderer linkRenderer) { + super(linkRenderer, + Collections.singletonMap(VerbatimSerializer.DEFAULT, DefaultVerbatimSerializer.INSTANCE), + Collections.emptyList()); + } + private void printAttribute(String name, String value) { + printer.print(' ').print(name).print('=').print('"').print(value).print('"'); + } + + /* Reimplement print image tag to eliminate a trailing double-quote */ + @Override + protected void printImageTag(LinkRenderer.Rendering rendering) { + printer.print(""); + } + } +} diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java index 96ebe7af..d53cee71 100644 --- a/src/main/java/com/gitblit/wicket/pages/RootPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java @@ -79,12 +79,13 @@ import com.gitblit.wicket.panels.LinkPanel; import com.gitblit.wicket.panels.NavigationPanel; import com.google.common.base.Strings; +import com.googlesource.gerrit.plugins.gitblit.HttpUtils; /** * Root page is a topbar, navigable page like Repositories, Users, or Federation. - * + * * @author James Moger - * + * */ public abstract class RootPage extends BasePage { @@ -618,7 +619,7 @@ protected void onInitialize() { UserModel user = session.getUser(); boolean editCredentials = app().authentication().supportsCredentialChanges(user); HttpServletRequest request = ((WebRequest) getRequest()).getHttpServletRequest(); - AuthenticationType authenticationType = (AuthenticationType) request.getSession().getAttribute(Constants.ATTRIB_AUTHTYPE); + AuthenticationType authenticationType = HttpUtils.getAttribute(request.getSession(), Constants.ATTRIB_AUTHTYPE, AuthenticationType.class); boolean standardLogin = (null != authenticationType) ? authenticationType.isStandard() : true; if (app().settings().getBoolean(Keys.web.allowGravatar, true)) { @@ -681,7 +682,7 @@ protected void onInitialize() { /** * Creates a submenu. This is not actually submenu because we're using an older Twitter Bootstrap which is pre-submenu. - * + * * @param wicketId * @param submenuTitle * @param menuItems diff --git a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java new file mode 100644 index 00000000..410e0677 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java @@ -0,0 +1,236 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.panels; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.wicket.PageParameters; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.ExternalLink; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.markup.repeater.Item; +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.Repository; + +import com.gitblit.Constants; +import com.gitblit.models.RefModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.servlet.RawServlet; +import com.gitblit.servlet.SyndicationServlet; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.pages.BranchesPage; +import com.gitblit.wicket.pages.CommitPage; +import com.gitblit.wicket.pages.GitSearchPage; +import com.gitblit.wicket.pages.LogPage; +import com.gitblit.wicket.pages.MetricsPage; +import com.gitblit.wicket.pages.TreePage; + +public class BranchesPanel extends BasePanel { + + private static final long serialVersionUID = 1L; + + private final boolean hasBranches; + + public BranchesPanel(String wicketId, final RepositoryModel model, Repository r, + final int maxCount, final boolean showAdmin) { + super(wicketId); + + // branches + List branches = new ArrayList(); + UserModel user = GitBlitWebSession.get().getUser(); + if (user == null) { + user = UserModel.ANONYMOUS; + } + + List localBranches = JGitUtils.getLocalBranches(r, false, -1); + for (RefModel refModel : localBranches) { + if (user.canView(model, refModel.reference.getName())) { + branches.add(refModel); + } + } + if (model.showRemoteBranches) { + List remoteBranches = JGitUtils.getRemoteBranches(r, false, -1); + for (RefModel refModel : remoteBranches) { + if (user.canView(model, refModel.reference.getName())) { + branches.add(refModel); + } + } + } + Collections.sort(branches); + Collections.reverse(branches); + if (maxCount > 0 && branches.size() > maxCount) { + branches = new ArrayList(branches.subList(0, maxCount)); + } + + if (maxCount > 0) { + // summary page + // show branches page link + add(new LinkPanel("branches", "title", new StringResourceModel("gb.branches", this, + null), BranchesPage.class, WicketUtils.newRepositoryParameter(model.name))); + } else { + // branches page + add(new Label("branches", new StringResourceModel("gb.branches", this, null))); + } + + // only allow delete if we have multiple branches + // final boolean showDelete = showAdmin && branches.size() > 1; + + ListDataProvider branchesDp = new ListDataProvider(branches); + DataView branchesView = new DataView("branch", branchesDp) { + private static final long serialVersionUID = 1L; + int counter; + + @Override + public void populateItem(final Item item) { + final RefModel entry = item.getModelObject(); + + PageParameters shortUniqRef = WicketUtils.newObjectParameter(model.name, + Repository.shortenRefName(entry.getName())); + + item.add(WicketUtils.createDateLabel("branchDate", entry.getDate(), getTimeZone(), getTimeUtils())); + + item.add(new LinkPanel("branchName", "list name", StringUtils.trimString( + entry.displayName, 28), LogPage.class, shortUniqRef)); + + String author = entry.getAuthorIdent().getName(); + LinkPanel authorLink = new LinkPanel("branchAuthor", "list", author, + GitSearchPage.class, WicketUtils.newSearchParameter(model.name, + entry.getName(), author, Constants.SearchType.AUTHOR)); + setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR); + item.add(authorLink); + + // short message + String shortMessage = entry.getShortMessage(); + String trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG); + LinkPanel shortlog = new LinkPanel("branchLog", "list subject", trimmedMessage, + CommitPage.class, shortUniqRef); + if (!shortMessage.equals(trimmedMessage)) { + shortlog.setTooltip(shortMessage); + } + item.add(shortlog); + + if (maxCount <= 0) { + Fragment fragment = new Fragment("branchLinks", /* showDelete? "branchPageAdminLinks" : */ "branchPageLinks", this); + fragment.add(new BookmarkablePageLink("log", LogPage.class, shortUniqRef)); + fragment.add(new BookmarkablePageLink("tree", TreePage.class, shortUniqRef)); + String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null); + fragment.add(new ExternalLink("raw", rawUrl)); + fragment.add(new BookmarkablePageLink("metrics", MetricsPage.class, shortUniqRef)); + fragment.add(new ExternalLink("syndication", SyndicationServlet.asLink( + getRequest().getRelativePathPrefixToContextRoot(), model.name, + Repository.shortenRefName(entry.getName()), 0))); +// if (showDelete) { +// fragment.add(createDeleteBranchLink(model, entry)); +// } + item.add(fragment); + } else { + Fragment fragment = new Fragment("branchLinks", "branchPanelLinks", this); + fragment.add(new BookmarkablePageLink("log", LogPage.class, shortUniqRef)); + fragment.add(new BookmarkablePageLink("tree", TreePage.class, shortUniqRef)); + String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null); + fragment.add(new ExternalLink("raw", rawUrl)); + item.add(fragment); + } + WicketUtils.setAlternatingBackground(item, counter); + counter++; + } + }; + add(branchesView); + if (branches.size() < maxCount || maxCount <= 0) { + add(new Label("allBranches", "").setVisible(false)); + } else { + add(new LinkPanel("allBranches", "link", new StringResourceModel("gb.allBranches", + this, null), BranchesPage.class, WicketUtils.newRepositoryParameter(model.name))); + } + // We always have 1 branch + hasBranches = (branches.size() > 1) + || ((branches.size() == 1) && !branches.get(0).displayName + .equalsIgnoreCase("master")); + } + + public BranchesPanel hideIfEmpty() { + setVisible(hasBranches); + return this; + } + +// private Link createDeleteBranchLink(final RepositoryModel repositoryModel, final RefModel entry) +// { +// Link deleteLink = new Link("deleteBranch") { +// private static final long serialVersionUID = 1L; +// +// @Override +// public void onClick() { +// Repository r = app().repositories().getRepository(repositoryModel.name); +// if (r == null) { +// if (app().repositories().isCollectingGarbage(repositoryModel.name)) { +// error(MessageFormat.format(getString("gb.busyCollectingGarbage"), repositoryModel.name)); +// } else { +// error(MessageFormat.format("Failed to find repository {0}", repositoryModel.name)); +// } +// return; +// } +// final String branch = entry.getName(); +// Ref ref = null; +// try { +// ref = r.findRef(branch); +// if (ref == null && !branch.startsWith(Constants.R_HEADS)) { +// ref = r.exactRef(Constants.R_HEADS + branch); +// } +// } catch (IOException e) { +// } +// if (ref != null) { +// boolean success = JGitUtils.deleteBranchRef(r, ref.getName()); +// if (success) { +// // clear commit cache +// CommitCache.instance().clear(repositoryModel.name, branch); +// +// // optionally update reflog +// if (RefLogUtils.hasRefLogBranch(r)) { +// UserModel user = GitBlitWebSession.get().getUser(); +// RefLogUtils.deleteRef(user, r, ref); +// } +// } +// +// if (success) { +// info(MessageFormat.format("Branch \"{0}\" deleted", branch)); +// } else { +// error(MessageFormat.format("Failed to delete branch \"{0}\"", branch)); +// } +// } +// r.close(); +// +// // redirect to the owning page +// PageParameters params = WicketUtils.newRepositoryParameter(repositoryModel.name); +// String relativeUrl = urlFor(getPage().getClass(), params).toString(); +// String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl); +// getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl)); +// } +// }; +// +// deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format( +// "Delete branch \"{0}\"?", entry.displayName ))); +// return deleteLink; +// } +} diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java index b7d5accb..5aeb126b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java @@ -51,18 +51,14 @@ public void run() throws Exception { } private void configureGitBlit() { - Section gitWeb = sections.get("gitweb", null); - gitWeb.set("type", "custom"); - gitWeb.set("url", "plugins/" + pluginName + '/'); - gitWeb.set("project", "summary/?r=${project}"); - gitWeb.set("revision", "commit/?r=${project}&h=${commit}"); - gitWeb.set("branch", "log/?r=${project}&h=${branch}"); - gitWeb.set("filehistory", "history/?f=${file}&r=${project}&h=${branch}"); - gitWeb.set("file", "blob/?r=${project}&h=${commit}&f=${file}"); - gitWeb.set("roottree", "tree/?r=${project}&h=${commit}"); - gitWeb.string("Link name", "linkname", "GitBlit"); + String defaultLinkName = cfg.getString("gitweb", null, "linkname"); + if (defaultLinkName == null || defaultLinkName.isEmpty()) { + defaultLinkName = "GitBlit"; + } + cfg.unsetSection("gitweb", null); Section pluginCfg = sections.get("plugin", pluginName); // These values are displayed in the UI. + pluginCfg.string("Link name", "linkname", defaultLinkName, true); pluginCfg.string("\"Repositories\" submenu title", "repositories", "Repositories", true); pluginCfg.string("\"Activity\" submenu title", "activity", "Activity", true); pluginCfg.string("\"Documentation\" submenu title", "documentation", "Documentation", true); @@ -78,7 +74,7 @@ private void configureGitBlit() { pluginCfg.set("search", newValue); } } - pluginCfg.string("\"Browse\" submenu title for the \"Projects\" top-level menu", "browse", "Browse", true); + pluginCfg.unset("browse"); // If everything is at the default, then make sure we don't have the section at all. if (cfg.getNames("plugin", pluginName).isEmpty()) { cfg.unsetSection("plugin", pluginName); diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitModule.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitModule.java index 5417f559..0d29a2e3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitModule.java @@ -14,6 +14,13 @@ package com.googlesource.gerrit.plugins.gitblit; import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.gerrit.extensions.webui.BranchWebLink; +import com.google.gerrit.extensions.webui.FileHistoryWebLink; +import com.google.gerrit.extensions.webui.FileWebLink; +import com.google.gerrit.extensions.webui.ParentWebLink; +import com.google.gerrit.extensions.webui.PatchSetWebLink; +import com.google.gerrit.extensions.webui.ProjectWebLink; +import com.google.gerrit.extensions.webui.TagWebLink; import com.google.gerrit.extensions.webui.TopMenu; import com.google.inject.AbstractModule; @@ -22,6 +29,13 @@ public class GitBlitModule extends AbstractModule { @Override protected void configure() { DynamicSet.bind(binder(), TopMenu.class).to(GitBlitTopMenu.class); + DynamicSet.bind(binder(), BranchWebLink.class).to(GitBlitWebUrls.class); + DynamicSet.bind(binder(), FileHistoryWebLink.class).to(GitBlitWebUrls.class); + DynamicSet.bind(binder(), FileWebLink.class).to(GitBlitWebUrls.class); + DynamicSet.bind(binder(), ParentWebLink.class).to(GitBlitWebUrls.class); + DynamicSet.bind(binder(), PatchSetWebLink.class).to(GitBlitWebUrls.class); + DynamicSet.bind(binder(), ProjectWebLink.class).to(GitBlitWebUrls.class); + DynamicSet.bind(binder(), TagWebLink.class).to(GitBlitWebUrls.class); } } diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java index 3523cdfa..c0e8612c 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java @@ -19,7 +19,6 @@ import com.google.common.collect.Lists; import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl; import com.google.gerrit.extensions.annotations.PluginName; -import com.google.gerrit.extensions.client.GerritTopMenu; import com.google.gerrit.extensions.client.MenuItem; import com.google.gerrit.extensions.webui.TopMenu; import com.google.gerrit.server.CurrentUser; @@ -35,7 +34,6 @@ public class GitBlitTopMenu implements TopMenu { private final MenuEntry fullMenuEntries; private final MenuEntry restrictedMenuEntries; - private final MenuEntry extraProjectEntries; private final Provider userProvider; @Inject @@ -63,15 +61,10 @@ public GitBlitTopMenu(@PluginName String pluginName, @PluginCanonicalWebUrl Stri } fullMenuItems.add(documentation); fullMenuEntries = new MenuEntry(GITBLIT_TOPMENU_NAME, fullMenuItems); - // Actually, I'd like to give the project "browse" link only if the user has the right to see the contents of the repository. But how would - // I know in getEntries() below which is the "current" project? Since I don't know how to do that, we show that link always, based on the - // assumption that if a user can see a project at all, he can also see its contents. If not, gitblit will tell him so... - extraProjectEntries = new MenuEntry(GerritTopMenu.PROJECTS, Arrays.asList(new MenuItem(cfg.getString("browse", "Browse"), gitBlitBaseUrl - + "summary/?r=${projectName}", ""))); } @Override public List getEntries() { - return Arrays.asList(userProvider.get().isIdentifiedUser() ? fullMenuEntries : restrictedMenuEntries, extraProjectEntries); + return Arrays.asList(userProvider.get().isIdentifiedUser() ? fullMenuEntries : restrictedMenuEntries); } } diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitWebUrls.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitWebUrls.java new file mode 100644 index 00000000..29de3cec --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitWebUrls.java @@ -0,0 +1,104 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.googlesource.gerrit.plugins.gitblit; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.jgit.lib.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl; +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.extensions.common.WebLinkInfo; +import com.google.gerrit.extensions.webui.BranchWebLink; +import com.google.gerrit.extensions.webui.FileHistoryWebLink; +import com.google.gerrit.extensions.webui.FileWebLink; +import com.google.gerrit.extensions.webui.ParentWebLink; +import com.google.gerrit.extensions.webui.PatchSetWebLink; +import com.google.gerrit.extensions.webui.ProjectWebLink; +import com.google.gerrit.extensions.webui.TagWebLink; +import com.google.gerrit.server.config.GerritServerConfig; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class GitBlitWebUrls implements BranchWebLink, FileWebLink, PatchSetWebLink, ProjectWebLink, FileHistoryWebLink, ParentWebLink, TagWebLink { + + private static final Logger log = LoggerFactory.getLogger(GitBlitWebUrls.class); + + private final String name; + private final String baseUrl; + + @Inject + public GitBlitWebUrls(@PluginName String pluginName, @PluginCanonicalWebUrl String pluginUrl, @GerritServerConfig Config config) { + String baseGerritUrl = "/plugins/" + pluginName + '/'; + try { + if (pluginUrl != null) { + URL u = new URL(pluginUrl); + baseGerritUrl = u.getPath(); + if (!baseGerritUrl.endsWith("/")) { + baseGerritUrl += '/'; + } + } + } catch (MalformedURLException e) { + log.error("Canonical web URL is malformed: " + pluginUrl, e); + } + baseUrl = baseGerritUrl; + String linkName = config.getString("plugin", pluginName, "linkname"); + if (linkName == null || linkName.isEmpty()) { + linkName = config.getString("gitweb", null, "linkname"); + } + if (linkName == null || linkName.isEmpty()) { + linkName = "Gitblit"; + } + name = linkName; + } + + @Override + public WebLinkInfo getProjectWeblink(String projectName) { + return new WebLinkInfo(name, null, baseUrl + String.format("summary/?r=%s", projectName), Target.BLANK); + } + + @Override + public WebLinkInfo getPatchSetWebLink(String projectName, String commit) { + return new WebLinkInfo(name, null, baseUrl + String.format("commit/?r=%s&h=%s", projectName, commit), Target.BLANK); + } + + @Override + public WebLinkInfo getParentWebLink(String projectName, String commit) { + return getPatchSetWebLink(projectName, commit); + } + + @Override + public WebLinkInfo getFileWebLink(String projectName, String revision, String fileName) { + return new WebLinkInfo(name, null, baseUrl + String.format("blob/?r=%s&h=%s&f=%s", projectName, revision, fileName), Target.BLANK); + } + + @Override + public WebLinkInfo getBranchWebLink(String projectName, String branchName) { + return new WebLinkInfo(name, null, baseUrl + String.format("log/?r=%s&h=%s", projectName, branchName), Target.BLANK); + } + + @Override + public WebLinkInfo getTagWebLink(String projectName, String tagName) { + return new WebLinkInfo(name, null, baseUrl + String.format("log/?r=%s&h=%s", projectName, tagName), Target.BLANK); + } + + @Override + public WebLinkInfo getFileHistoryWebLink(String projectName, String revision, String fileName) { + return new WebLinkInfo(name, null, baseUrl + String.format("history/?f=%s&r=%s&h=%s", fileName, projectName, revision), Target.BLANK); + } +} diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/HttpUtils.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/HttpUtils.java new file mode 100644 index 00000000..e3ed1033 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/HttpUtils.java @@ -0,0 +1,47 @@ +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.googlesource.gerrit.plugins.gitblit; + +import java.util.Optional; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +public class HttpUtils { + + // Can't use injection since used in Gitblit wicket pages + + public static T getAttribute(final HttpServletRequest httpRequest, String attribute, Class clazz) { + Object obj = httpRequest.getAttribute(attribute); + if (obj instanceof Optional) { + obj = ((Optional) obj).orElse(null); + } + if (clazz.isInstance(obj)) { + return clazz.cast(obj); + } + return null; + } + + public static T getAttribute(final HttpSession session, String attribute, Class clazz) { + Object obj = session.getAttribute(attribute); + if (obj instanceof Optional) { + obj = ((Optional) obj).orElse(null); + } + if (clazz.isInstance(obj)) { + return clazz.cast(obj); + } + return null; + } + +} diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/app/GerritGitBlitWebApp.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/app/GerritGitBlitWebApp.java index ea7d2131..558ee585 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/app/GerritGitBlitWebApp.java +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/app/GerritGitBlitWebApp.java @@ -56,6 +56,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.gitblit.HttpUtils; @Singleton public class GerritGitBlitWebApp extends GitBlitWebApp { @@ -93,7 +94,7 @@ public IHeaderResponse decorate(IHeaderResponse response) { /** * Sets a unique key that is different for each plugin instance (i.e., different for each load of the plugin.) - * + * * @param key * for this plugin instance. */ @@ -166,7 +167,7 @@ private void resetWicketSessionOnPluginReload(WebRequestCycle requestCycle) { WebRequest request = requestCycle.getWebRequest(); HttpSession realSession = request.getHttpServletRequest().getSession(); if (realSession != null) { - Object sessionPluginInstanceKey = realSession.getAttribute(INSTANCE_ATTRIBUTE); + Object sessionPluginInstanceKey = HttpUtils.getAttribute(realSession, INSTANCE_ATTRIBUTE, String.class); String currentPluginInstanceKey = getPluginInstanceKey(); if (sessionPluginInstanceKey instanceof String) { if (!sessionPluginInstanceKey.equals(currentPluginInstanceKey)) { diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitAuthenticationManager.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitAuthenticationManager.java index 253e77d9..7361d9ca 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitAuthenticationManager.java +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitAuthenticationManager.java @@ -47,6 +47,7 @@ import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.gitblit.HttpUtils; @Singleton public class GerritGitBlitAuthenticationManager implements IAuthenticationManager { @@ -114,9 +115,9 @@ public IAuthenticationManager stop() { @Override public UserModel authenticate(final HttpServletRequest httpRequest) { // Added by the GerritAuthenticationFilter. - String username = (String) httpRequest.getAttribute("gerrit-username"); - String token = (String) httpRequest.getAttribute("gerrit-token"); - String password = (String) httpRequest.getAttribute("gerrit-password"); + String username = HttpUtils.getAttribute(httpRequest, "gerrit-username", String.class); + String token = HttpUtils.getAttribute(httpRequest, "gerrit-token", String.class); + String password = HttpUtils.getAttribute(httpRequest, "gerrit-password", String.class); if (!Strings.isNullOrEmpty(token)) { return authenticateFromSession(httpRequest, username, token); @@ -154,7 +155,7 @@ private UserModel authenticateFromSession(final HttpServletRequest httpRequest, return null; } - String userName = session.getUser().getUserName(); + String userName = session.getUser().getUserName().orElse(null); // Gerrit users not necessarily have a username. Google OAuth returns users without user names. UserModel user; if (Strings.isNullOrEmpty(userName)) { @@ -227,7 +228,7 @@ private boolean isStandardLogin(HttpServletRequest request) { return false; } HttpSession session = request.getSession(); - AuthenticationType authenticationType = (AuthenticationType) session.getAttribute(Constants.ATTRIB_AUTHTYPE); + AuthenticationType authenticationType = HttpUtils.getAttribute(session, Constants.ATTRIB_AUTHTYPE, AuthenticationType.class); return authenticationType != null && authenticationType.isStandard(); } diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserManager.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserManager.java index 99fff8a1..d618d3cf 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserManager.java +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserManager.java @@ -34,8 +34,8 @@ import com.google.gerrit.server.AnonymousUser; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.account.GetDiffPreferences; import com.google.gerrit.server.permissions.PermissionBackend; +import com.google.gerrit.server.restapi.account.GetDiffPreferences; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserModel.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserModel.java index 6eb79cbf..82c6ca95 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserModel.java +++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/auth/GerritGitBlitUserModel.java @@ -24,18 +24,18 @@ import com.gitblit.utils.StringUtils; import com.google.common.base.Strings; import com.google.gerrit.extensions.client.DiffPreferencesInfo; -import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.reviewdb.client.Project.NameKey; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.AccountResource; -import com.google.gerrit.server.account.GetDiffPreferences; import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend.ForProject; import com.google.gerrit.server.permissions.PermissionBackend.ForRef; import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.ProjectPermission; import com.google.gerrit.server.permissions.RefPermission; +import com.google.gerrit.server.restapi.account.GetDiffPreferences; import com.google.inject.Provider; /** @@ -81,7 +81,7 @@ public GerritGitBlitUserModel(String username, final PermissionBackend permissio @Override protected boolean canAccess(final RepositoryModel repository, final AccessRestrictionType ifRestriction, final AccessPermission requirePermission) { - ForProject projectPermissions = permissions.user(userProvider).project(new NameKey(StringUtils.stripDotGit(repository.name))); + ForProject projectPermissions = permissions.user(userProvider.get()).project(new NameKey(StringUtils.stripDotGit(repository.name))); if (projectPermissions == null) { return false; } @@ -99,13 +99,13 @@ protected boolean canAccess(final RepositoryModel repository, final AccessRestri @Override public boolean hasRepositoryPermission(String name) { - ForProject projectPermissions = permissions.user(userProvider).project(new NameKey(StringUtils.stripDotGit(name))); + ForProject projectPermissions = permissions.user(userProvider.get()).project(new NameKey(StringUtils.stripDotGit(name))); return projectPermissions != null && projectPermissions.testOrFalse(ProjectPermission.ACCESS); } @Override public boolean canView(RepositoryModel repository, String ref) { - ForProject projectPermissions = permissions.user(userProvider).project(new NameKey(StringUtils.stripDotGit(repository.name))); + ForProject projectPermissions = permissions.user(userProvider.get()).project(new NameKey(StringUtils.stripDotGit(repository.name))); if (projectPermissions != null) { ForRef refPermissions = projectPermissions.ref(ref); return refPermissions != null && refPermissions.testOrFalse(RefPermission.READ); @@ -128,7 +128,7 @@ public int diffContext() { if (diffPrefs != null) { return diffPrefs.context; } - } catch (AuthException | ConfigInvalidException | PermissionBackendException | IOException e) { + } catch (RestApiException | ConfigInvalidException | PermissionBackendException | IOException e) { // Ignore and return default below. } } diff --git a/src/main/java/com/syntevo/bugtraq/BugtraqConfig.java b/src/main/java/com/syntevo/bugtraq/BugtraqConfig.java index 3a5a6117..dcd6b228 100644 --- a/src/main/java/com/syntevo/bugtraq/BugtraqConfig.java +++ b/src/main/java/com/syntevo/bugtraq/BugtraqConfig.java @@ -178,7 +178,7 @@ private static Config getBaseConfig(@NotNull Repository repository, @NotNull Str RevWalk rw = new RevWalk(repository); try (TreeWalk tw = new TreeWalk(repository)) { tw.setFilter(PathFilterGroup.createFromStrings(configFileName)); - Ref head = repository.getRef(Constants.HEAD); + Ref head = repository.exactRef(Constants.HEAD); if (head == null) { return null; } diff --git a/src/main/resources/Documentation/index.md b/src/main/resources/Documentation/index.md index 481e87ea..787482e7 100644 --- a/src/main/resources/Documentation/index.md +++ b/src/main/resources/Documentation/index.md @@ -15,30 +15,11 @@ This is a privately maintained fork of the official Gerrit-Gitblit plugin. Pleas # Configuration -There are two different configurations: one for Gerrit so it knows how to generate links that will be processed by the plugin, and -an optional GitBlit configuration for the plugin itself. +The plugin in configured in Gerrit's global configuration file `gerrit.config`. +As of v2.16.171.0, this plugin does _not_ use the `[gitweb]` section anymore. +Configuration is done in the `[plugin "@PLUGIN@"]` section. -## Gerrit configuration - -In Gerrit's `gerrit.config`, define the `[gitweb]` section as follows: - - [gitweb] - type = custom - url = plugins/@PLUGIN@/ - linkname = browse - project = summary/?r=${project} - revision = commit/?r=${project}&h=${commit} - branch = log/?r=${project}&h=${branch} - filehistory = history/?f=${file}&r=${project}&h=${branch} - file = blob/?r=${project}&h=${commit}&f=${file} - roottree = tree/?r=${project}&h=${commit} - -This is normally done automatically if you add the plugin and run through `java -jar gerrit.war init -d site_path`, but you can also -add this manually to Gerrit's config file. The `linkname` can be adapted to your taste. - -### Configuring the top menu - -This plugin adds a "GitBlit" top menu to Gerrit, and also a new sub-menu item to the "Projects" top menu. Since v2.11.162.2 of this plugin, the link +This plugin adds a "GitBlit" top menu to Gerrit. Since v2.11.162.2 of this plugin, the link texts for all sub-menu items can be configured to your taste in a `[plugin "@PLUGIN@"]` section in your `gerrit.config`. If the section is not present, or some values in that section are not defined, the plugin uses built-in default texts. The default configuration would correspond to @@ -47,14 +28,14 @@ or some values in that section are not defined, the plugin uses built-in default activity = Activity documentation = Documentation search = - browse = Browse + linkname = Gitblit -The first four are sub-menu items of the "GitBlit" top menu, the last one is a new "browse" sub-menu item in Gerrit's "Projects" menu that is shown -for Gerrit's "current" project (since v2.11.162.2). +The first four are sub-menu items of the "GitBlit" top menu, the last one is the link text shown on links to Gitblit for individual files +or in the project list. The "search" sub-menu item is by default not set and will thus not be shown. Setting it makes only sense if you enable GitBlit indexing on some of your projects. See the [GitBlit documentation](http://gitblit.com/setup_lucene.html) for more information on that. - + ## GitBlit configuration The plugin includes a minimal default configuration to make GitBlit act only as a repository viewer. You can augment that with further @@ -66,7 +47,7 @@ To see the built-in configuration, access it at [`gitblit.properties`](@URL@plug By default, the built-in configuration does allow anonymous browsing, subject to the repository and ref-level access restrictions defined in Gerrit. If you want to lock the GitBlit plugin to allow only logged-in users to browse, set in `$GERRIT_SITE/etc/gitblit.properties` the key -`web.authenticateViewPages = true`. This is the only key of the built-in configuration that you _can_ override. +`web.authenticateViewPages = true`. This is the only key of the built-in configuration that you _can_ override. GitBlit's ticket service, fan-out service, and its plugin mechanism are disabled in this plugin, as is ssh access through GitBlit since Gerrit already provides that. Also disabled is Gitblit's LFS implementation. @@ -95,7 +76,7 @@ The GitBlit `${baseFolder}` is the plugin's data directory provided by Gerrit at

- + # Issue tracking