Browse Source

Support for gh-pages branch serving as /pages/repo.git

tags/v0.8.0
James Moger 12 years ago
parent
commit
11924dc5db

+ 1
- 0
docs/01_features.mkd View File

@@ -19,6 +19,7 @@
- Repository Owners may edit repositories through the web UI
- Gravatar integration
- Git-notes display support
- gh-pages display support (Jekyll is not supported)
- Branch metrics (uses Google Charts)
- HEAD and Branch RSS feeds
- Blame annotations view

+ 2
- 0
docs/04_releases.mkd View File

@@ -33,6 +33,8 @@ The original `users.properties` file and it's corresponding implementation are *
**New:** *web.allowFlashCopyToClipboard = true*
- JavaScript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url in the event that you do not want to use Flash on your installation
- Empty repositories now link to an *empty repository* page which gives some direction to the user for the next step in using Gitblit. This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31)
- automatic *gh-pages* branch serving (Jekyll is not supported)
Gitblit does not checkout your gh-pages branch to a temporary filesystem, all page and resource requests are live through the repository
- Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud <span class="label warning">BETA</span>
#### changes

+ 37
- 4
src/WEB-INF/web.xml View File

@@ -82,8 +82,23 @@
<servlet-name>RpcServlet</servlet-name>
<url-pattern>/rpc/*</url-pattern>
</servlet-mapping>
<!-- Pages Servlet
<url-pattern> MUST match:
* PagesFilter
* com.gitblit.Constants.PAGES_PATH
* Wicket Filter ignorePaths parameter -->
<servlet>
<servlet-name>PagesServlet</servlet-name>
<servlet-class>com.gitblit.PagesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PagesServlet</servlet-name>
<url-pattern>/pages/*</url-pattern>
</servlet-mapping>
<!-- Git Access Restriction Filter
<url-pattern> MUST match:
* GitServlet
@@ -143,7 +158,22 @@
<url-pattern>/rpc/*</url-pattern>
</filter-mapping>
<!-- Pges Restriction Filter
<url-pattern> MUST match:
* PagesServlet
* com.gitblit.Constants.PAGES_PATH
* Wicket Filter ignorePaths parameter -->
<filter>
<filter-name>PagesFilter</filter-name>
<filter-class>com.gitblit.PagesFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>PagesFilter</filter-name>
<url-pattern>/pages/*</url-pattern>
</filter-mapping>
<!-- Wicket Filter -->
<filter>
<filter-name>wicketFilter</filter-name>
@@ -168,8 +198,11 @@
* com.gitblit.Constants.ZIP_PATH
* FederationServlet <url-pattern>
* RpcFilter <url-pattern>
* RpcServlet <url-pattern> -->
<param-value>git/,feed/,zip/,federation/,rpc/</param-value>
* RpcServlet <url-pattern>
* PagesFilter <url-pattern>
* PagesServlet <url-pattern>
* com.gitblit.Constants.PAGES_PATH -->
<param-value>git/,feed/,zip/,federation/,rpc/,pages/</param-value>
</init-param>
</filter>
<filter-mapping>

+ 23
- 6
src/com/gitblit/GitBlit.java View File

@@ -653,21 +653,38 @@ public class GitBlit implements ServletContextListener {
* @return repository or null
*/
public Repository getRepository(String repositoryName) {
return getRepository(repositoryName, true);
}
/**
* Returns the JGit repository for the specified name.
*
* @param repositoryName
* @param logError
* @return repository or null
*/
public Repository getRepository(String repositoryName, boolean logError) {
Repository r = null;
try {
r = repositoryResolver.open(null, repositoryName);
} catch (RepositoryNotFoundException e) {
r = null;
logger.error("GitBlit.getRepository(String) failed to find "
+ new File(repositoriesFolder, repositoryName).getAbsolutePath());
if (logError) {
logger.error("GitBlit.getRepository(String) failed to find "
+ new File(repositoriesFolder, repositoryName).getAbsolutePath());
}
} catch (ServiceNotAuthorizedException e) {
r = null;
logger.error("GitBlit.getRepository(String) failed to find "
+ new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
if (logError) {
logger.error("GitBlit.getRepository(String) failed to find "
+ new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
}
} catch (ServiceNotEnabledException e) {
r = null;
logger.error("GitBlit.getRepository(String) failed to find "
+ new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
if (logError) {
logger.error("GitBlit.getRepository(String) failed to find "
+ new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
}
}
return r;
}

+ 103
- 0
src/com/gitblit/PagesFilter.java View File

@@ -0,0 +1,103 @@
/*
* Copyright 2012 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;
import org.eclipse.jgit.lib.Repository;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
/**
* The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages
* requests for a view-restricted repository are authenticated and authorized.
*
* @author James Moger
*
*/
public class PagesFilter extends AccessRestrictionFilter {
/**
* Extract the repository name from the url.
*
* @param url
* @return repository name
*/
@Override
protected String extractRepositoryName(String url) {
// get the repository name from the url by finding a known url suffix
String repository = "";
Repository r = null;
int offset = 0;
while (r == null) {
int slash = url.indexOf('/', offset);
if (slash == -1) {
repository = url;
} else {
repository = url.substring(0, slash);
}
r = GitBlit.self().getRepository(repository, false);
if (r == null) {
// try again
offset = slash + 1;
} else {
// close the repo
r.close();
}
if (repository.equals(url)) {
// either only repository in url or no repository found
break;
}
}
return repository;
}
/**
* Analyze the url and returns the action of the request.
*
* @param url
* @return action of the request
*/
@Override
protected String getUrlRequestAction(String suffix) {
return "VIEW";
}
/**
* Determine if the repository requires authentication.
*
* @param repository
* @return true if authentication required
*/
@Override
protected boolean requiresAuthentication(RepositoryModel repository) {
return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
}
/**
* Determine if the user can access the repository and perform the specified
* action.
*
* @param repository
* @param user
* @param action
* @return true if user may execute the action on the repository
*/
@Override
protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
return user.canAccessRepository(repository);
}
}

+ 227
- 0
src/com/gitblit/PagesServlet.java View File

@@ -0,0 +1,227 @@
/*
* Copyright 2012 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;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.ParseException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.models.RefModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
/**
* Serves the content of a gh-pages branch.
*
* @author James Moger
*
*/
public class PagesServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class);
public PagesServlet() {
super();
}
/**
* Returns an url to this servlet for the specified parameters.
*
* @param baseURL
* @param repository
* @param path
* @return an url
*/
public static String asLink(String baseURL, String repository, String path) {
if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
baseURL = baseURL.substring(0, baseURL.length() - 1);
}
return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
}
/**
* Retrieves the specified resource from the gh-pages branch of the
* repository.
*
* @param request
* @param response
* @throws javax.servlet.ServletException
* @throws java.io.IOException
*/
private void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String path = request.getPathInfo();
if (path.toLowerCase().endsWith(".git")) {
// forward to url with trailing /
// this is important for relative pages links
response.sendRedirect(request.getServletPath() + path + "/");
return;
}
if (path.charAt(0) == '/') {
// strip leading /
path = path.substring(1);
}
// determine repository and resource from url
String repository = "";
String resource = "";
Repository r = null;
int offset = 0;
while (r == null) {
int slash = path.indexOf('/', offset);
if (slash == -1) {
repository = path;
} else {
repository = path.substring(0, slash);
}
r = GitBlit.self().getRepository(repository, false);
offset = slash + 1;
if (offset > 0) {
resource = path.substring(offset);
}
if (repository.equals(path)) {
// either only repository in url or no repository found
break;
}
}
ServletContext context = request.getSession().getServletContext();
try {
if (r == null) {
// repository not found!
String mkd = MessageFormat.format(
"# Error\nSorry, no valid **repository** specified in this url: {0}!",
repository);
error(response, mkd);
return;
}
// retrieve the content from the repository
RefModel pages = JGitUtils.getPagesBranch(r);
RevCommit commit = JGitUtils.getCommit(r, pages.getObjectId().getName());
if (commit == null) {
// branch not found!
String mkd = MessageFormat.format(
"# Error\nSorry, the repository {0} does not have a **gh-pages** branch!",
repository);
error(response, mkd);
r.close();
return;
}
response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
RevTree tree = commit.getTree();
byte[] content = null;
if (StringUtils.isEmpty(resource)) {
// find resource
String[] files = { "index.html", "index.htm", "index.mkd" };
for (String file : files) {
content = JGitUtils.getStringContent(r, tree, file)
.getBytes(Constants.ENCODING);
if (content != null) {
resource = file;
// assume text/html unless the servlet container
// overrides
response.setContentType("text/html; charset=" + Constants.ENCODING);
break;
}
}
} else {
// specific resource
String contentType = context.getMimeType(resource);
if (contentType.startsWith("text")) {
content = JGitUtils.getStringContent(r, tree, resource).getBytes(
Constants.ENCODING);
} else {
content = JGitUtils.getByteContent(r, tree, resource);
}
response.setContentType(contentType);
}
// no content, try custom 404 page
if (ArrayUtils.isEmpty(content)) {
content = JGitUtils.getStringContent(r, tree, "404.html").getBytes(
Constants.ENCODING);
// still no content
if (ArrayUtils.isEmpty(content)) {
content = (MessageFormat.format(
"# Error\nSorry, the requested resource **{0}** was not found.",
resource)).getBytes(Constants.ENCODING);
resource = "404.mkd";
}
}
// check to see if we should transform markdown files
for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
if (resource.endsWith(ext)) {
String mkd = new String(content, Constants.ENCODING);
content = MarkdownUtils.transformMarkdown(mkd).getBytes(Constants.ENCODING);
break;
}
}
try {
// output the content
response.getOutputStream().write(content);
response.flushBuffer();
} catch (Throwable t) {
logger.error("Failed to write page to client", t);
}
// close the repository
r.close();
} catch (Throwable t) {
logger.error("Failed to write page to client", t);
}
}
private void error(HttpServletResponse response, String mkd) throws ServletException,
IOException, ParseException {
String content = MarkdownUtils.transformMarkdown(mkd);
response.setContentType("text/html; charset=" + Constants.ENCODING);
response.getWriter().write(content);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}

+ 4
- 0
src/com/gitblit/utils/ArrayUtils.java View File

@@ -26,6 +26,10 @@ import java.util.Collection;
*/
public class ArrayUtils {
public static boolean isEmpty(byte [] array) {
return array == null || array.length == 0;
}
public static boolean isEmpty(Object [] array) {
return array == null || array.length == 0;
}

+ 33
- 0
src/com/gitblit/utils/JGitUtils.java View File

@@ -1283,6 +1283,39 @@ public class JGitUtils {
return list;
}
/**
* Returns a RefModel for the gh-pages branch in the repository. If the
* branch can not be found, null is returned.
*
* @param repository
* @return a refmodel for the gh-pages branch or null
*/
public static RefModel getPagesBranch(Repository repository) {
RefModel ghPages = null;
try {
// search for gh-pages branch in local heads
for (RefModel ref : JGitUtils.getLocalBranches(repository, false, -1)) {
if (ref.displayName.endsWith("gh-pages")) {
ghPages = ref;
break;
}
}
// search for gh-pages branch in remote heads
if (ghPages == null) {
for (RefModel ref : JGitUtils.getRemoteBranches(repository, false, -1)) {
if (ref.displayName.endsWith("gh-pages")) {
ghPages = ref;
break;
}
}
}
} catch (Throwable t) {
LOGGER.error("Failed to find gh-pages branch!", t);
}
return ghPages;
}
/**
* Returns the list of notes entered about the commit from the refs/notes
* namespace. If the repository does not exist or is empty, an empty list is

+ 2
- 1
src/com/gitblit/wicket/GitBlitWebApp.properties View File

@@ -208,4 +208,5 @@ gb.accessPermissionsForUserDescription = set team memberships or grant access to
gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
gb.federationRepositoryDescription = share this repository with other Gitblit servers
gb.hookScriptsDescription = run Groovy scripts on pushes to this Gitblit server
gb.reset = reset
gb.reset = reset
gb.pages = pages

+ 18
- 0
src/com/gitblit/wicket/PageRegistration.java View File

@@ -48,6 +48,24 @@ public class PageRegistration implements Serializable {
this.params = params;
}
/**
* Represents a page link to a non-Wicket page. Might be external.
*
* @author James Moger
*
*/
public static class OtherPageLink extends PageRegistration {
private static final long serialVersionUID = 1L;
public final String url;
public OtherPageLink(String translationKey, String url) {
super(translationKey, null);
this.url = url;
}
}
/**
* Represents a DropDownMenu for the topbar
*

+ 17
- 7
src/com/gitblit/wicket/pages/RepositoryPage.java View File

@@ -41,6 +41,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.Constants;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.PagesServlet;
import com.gitblit.SyndicationServlet;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.JGitUtils;
@@ -48,6 +49,7 @@ import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TicgitUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.OtherPageLink;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.NavigationPanel;
@@ -123,6 +125,12 @@ public abstract class RepositoryPage extends BasePage {
if (model.useDocs) {
pages.put("docs", new PageRegistration("gb.docs", DocsPage.class, params));
}
if (JGitUtils.getPagesBranch(r) != null) {
OtherPageLink pagesLink = new OtherPageLink("gb.pages", PagesServlet.asLink(
getRequest().getRelativePathPrefixToContextRoot(), repositoryName, null));
pages.put("pages", pagesLink);
}
// Conditionally add edit link
final boolean showAdmin;
if (GitBlit.getBoolean(Keys.web.authenticateAdminPages, true)) {
@@ -141,9 +149,9 @@ public abstract class RepositoryPage extends BasePage {
}
@Override
protected void setupPage(String repositoryName, String pageName) {
add(new LinkPanel("repositoryName", null, StringUtils.stripDotGit(repositoryName), SummaryPage.class,
WicketUtils.newRepositoryParameter(repositoryName)));
protected void setupPage(String repositoryName, String pageName) {
add(new LinkPanel("repositoryName", null, StringUtils.stripDotGit(repositoryName),
SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
add(new Label("pageName", pageName));
super.setupPage(repositoryName, pageName);
@@ -245,7 +253,8 @@ public abstract class RepositoryPage extends BasePage {
}
}
protected void setPersonSearchTooltip(Component component, String value, Constants.SearchType searchType) {
protected void setPersonSearchTooltip(Component component, String value,
Constants.SearchType searchType) {
if (searchType.equals(Constants.SearchType.AUTHOR)) {
WicketUtils.setHtmlTooltip(component, getString("gb.searchForAuthor") + " " + value);
} else if (searchType.equals(Constants.SearchType.COMMITTER)) {
@@ -302,13 +311,14 @@ public abstract class RepositoryPage extends BasePage {
private final IModel<String> searchBoxModel = new Model<String>("");
private final IModel<Constants.SearchType> searchTypeModel = new Model<Constants.SearchType>(Constants.SearchType.COMMIT);
private final IModel<Constants.SearchType> searchTypeModel = new Model<Constants.SearchType>(
Constants.SearchType.COMMIT);
public SearchForm(String id, String repositoryName) {
super(id);
this.repositoryName = repositoryName;
DropDownChoice<Constants.SearchType> searchType = new DropDownChoice<Constants.SearchType>("searchType",
Arrays.asList(Constants.SearchType.values()));
DropDownChoice<Constants.SearchType> searchType = new DropDownChoice<Constants.SearchType>(
"searchType", Arrays.asList(Constants.SearchType.values()));
searchType.setModel(searchTypeModel);
add(searchType.setVisible(GitBlit.getBoolean(Keys.web.showSearchTypeSelection, false)));
TextField<String> searchBox = new TextField<String>("searchBox", searchBoxModel);

+ 20
- 0
src/com/gitblit/wicket/panels/LinkPanel.java View File

@@ -20,6 +20,7 @@ import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.WebPage;
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.link.Link;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
@@ -71,4 +72,23 @@ public class LinkPanel extends Panel {
add(link);
}
public LinkPanel(String wicketId, String linkCssClass, String label, String href) {
this(wicketId, linkCssClass, label, href, false);
}
public LinkPanel(String wicketId, String linkCssClass, String label, String href,
boolean newWindow) {
super(wicketId);
this.labelModel = new Model<String>(label);
ExternalLink link = new ExternalLink("link", href);
if (newWindow) {
link.add(new SimpleAttributeModifier("target", "_blank"));
}
if (linkCssClass != null) {
link.add(new SimpleAttributeModifier("class", linkCssClass));
}
link.add(new Label("label", labelModel));
add(link);
}
}

+ 7
- 1
src/com/gitblit/wicket/panels/NavigationPanel.java View File

@@ -25,6 +25,7 @@ import org.apache.wicket.markup.repeater.data.ListDataProvider;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.PageRegistration.OtherPageLink;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.BasePage;
@@ -43,7 +44,12 @@ public class NavigationPanel extends Panel {
public void populateItem(final Item<PageRegistration> item) {
PageRegistration entry = item.getModelObject();
if (entry instanceof DropDownMenuRegistration) {
if (entry instanceof OtherPageLink) {
// other link
OtherPageLink link = (OtherPageLink) entry;
Component c = new LinkPanel("link", null, getString(entry.translationKey), link.url);
item.add(c);
} else if (entry instanceof DropDownMenuRegistration) {
// drop down menu
DropDownMenuRegistration reg = (DropDownMenuRegistration) entry;
Component c = new DropDownMenu("link", getString(entry.translationKey), reg);

+ 11
- 1
tests/com/gitblit/tests/GitBlitSuite.java View File

@@ -82,6 +82,14 @@ public class GitBlitSuite {
return new FileRepository(new File(REPOSITORIES, "test/bluez-gnome.git"));
}
public static Repository getAmbitionRepository() throws Exception {
return new FileRepository(new File(REPOSITORIES, "test/ambition.git"));
}
public static Repository getTheoreticalPhysicsRepository() throws Exception {
return new FileRepository(new File(REPOSITORIES, "test/theoretical-physics.git"));
}
public static boolean startGitblit() throws Exception {
if (started.get()) {
// already started
@@ -123,7 +131,9 @@ public class GitBlitSuite {
"https://git.kernel.org/pub/scm/bluetooth/bluez-gnome.git");
cloneOrFetch("test/jgit.git", "https://github.com/eclipse/jgit.git");
cloneOrFetch("test/helloworld.git", "https://github.com/git/hello-world.git");
cloneOrFetch("test/ambition.git", "https://github.com/defunkt/ambition.git");
cloneOrFetch("test/theoretical-physics.git", "https://github.com/certik/theoretical-physics.git");
enableTickets("ticgit.git");
enableDocs("ticgit.git");
showRemoteBranches("ticgit.git");

Loading…
Cancel
Save