Merge "Fix: Plugins implementing LifecycleListener cannot use auto registration"
diff --git a/Documentation/BUCK b/Documentation/BUCK
index 4dc1f3a..faa2553 100644
--- a/Documentation/BUCK
+++ b/Documentation/BUCK
@@ -1,4 +1,6 @@
include_defs('//Documentation/asciidoc.defs')
+include_defs('//Documentation/config.defs')
+include_defs('//tools/git.defs')
MAIN = ['//gerrit-pgm:pgm', '//gerrit-gwtui:ui_module']
SRCS = glob(['*.txt'], excludes = ['licenses.txt'])
@@ -11,40 +13,39 @@
'for s in $SRCS;do ln -s $s Documentation;done;' +
'mv Documentation/*.{jpg,png} Documentation/images;' +
'rm Documentation/licenses.txt;' +
- 'ln -s $SRCDIR/licenses.txt LICENSES.txt;' +
+ 'cp $SRCDIR/licenses.txt LICENSES.txt;' +
'zip -qr $OUT *',
srcs = [genfile(d) for d in HTML] +
glob([
'images/*.jpg',
'images/*.png',
]) + [
+ genfile('doc.css'),
genfile('licenses.html'),
genfile('licenses.txt'),
],
deps = [':' + d for d in HTML] + [
':licenses.html',
':licenses.txt',
+ ':doc.css',
],
out = 'html.zip',
visibility = ['PUBLIC'],
)
+genrule(
+ name = 'doc.css',
+ cmd = 'ln -s $SRCDIR/doc.css $OUT',
+ srcs = ['doc.css'],
+ out = 'doc.css',
+)
+
genasciidoc(
- name = 'generate_html',
srcs = SRCS + [genfile('licenses.txt')],
outs = HTML + ['licenses.html'],
- deps = [':licenses.txt'],
- attributes = [
- 'toc',
- 'newline="\\n"',
- 'asterisk="*"',
- 'plus="+"',
- 'caret="^"',
- 'startsb="["',
- 'endsb="]"',
- 'tilde="~"',
- ],
- backend = 'xhtml11',
+ deps = DOCUMENTATION_DEPS,
+ attributes = documentation_attributes(git_describe()),
+ backend = 'html5',
)
genrule(
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index 97bf287..8279847 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -12,50 +12,50 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-include_defs('//tools/git.defs')
-
def genasciidoc(
- name,
srcs = [],
outs = [],
- deps = [],
+ deps = {},
attributes = [],
backend = None,
visibility = []):
- MACRO_SUFFIX = '.expanded'
+ EXPN = '.expn'
- cmd = ['asciidoc', '-o', '$OUT']
+ asciidoc = ['$(exe //lib/asciidoctor:asciidoc)']
if backend:
- cmd.extend(['-b', backend])
+ asciidoc.extend(['-b', backend])
for attribute in attributes:
- cmd.extend(['-a', attribute])
- cmd.extend(['-a', 'revision="%s"' % git_describe()])
- cmd.append('$SRCS')
+ asciidoc.extend(['-a', attribute])
+ asciidoc.extend(['-o', '$OUT'])
for p in zip(srcs, outs):
- s, o = p
- filename = s
- if filename.startswith('BUCKGEN:') :
- filename = s[8:]
+ src, out = p
+ dep = deps.get(src) or []
+
+ tx = []
+ fn = src
+ if fn.startswith('BUCKGEN:') :
+ fn = src[8:]
+ tx = [':' + fn]
+ ex = fn + EXPN
+
genrule(
- name = filename + MACRO_SUFFIX,
- cmd = '$(exe :replace_macros) -s $SRCS -o $OUT --suffix=' + MACRO_SUFFIX,
- srcs = [s],
- deps = deps + [':replace_macros'],
- out = filename + MACRO_SUFFIX,
+ name = ex,
+ cmd = '$(exe :replace_macros) --suffix=' + EXPN +
+ ' -s $SRCDIR/%s' % fn +
+ ' -o $OUT',
+ srcs = [src],
+ deps = tx + [':replace_macros'],
+ out = ex,
)
genrule(
- name = o,
- cmd = ' '.join(cmd),
- srcs = [genfile(filename + MACRO_SUFFIX)],
- deps = deps + [':' + filename + MACRO_SUFFIX],
- out = o,
+ name = out,
+ cmd = ' '.join(asciidoc + ['$SRCDIR/' + ex]),
+ srcs = [genfile(ex)] + [genfile(n + EXPN) for n in dep],
+ deps = [':' + n + EXPN for n in dep] + [
+ ':' + ex,
+ '//lib/asciidoctor:asciidoc',
+ ],
+ out = out,
visibility = visibility,
)
- genrule(
- name = name,
- cmd = ':>$OUT',
- deps = [':' + o for o in outs],
- out = name + '__done',
- visibility = visibility,
- )
diff --git a/Documentation/config.defs b/Documentation/config.defs
new file mode 100644
index 0000000..28dd2c8
--- /dev/null
+++ b/Documentation/config.defs
@@ -0,0 +1,19 @@
+DOCUMENTATION_DEPS = {
+ "install-quick.txt": ["config-login-register.txt"],
+ "install.txt": ["database-setup.txt"],
+}
+
+def documentation_attributes(revision):
+ return [
+ 'toc',
+ 'newline="\\n"',
+ 'asterisk="*"',
+ 'plus="+"',
+ 'caret="^"',
+ 'startsb="["',
+ 'endsb="]"',
+ 'tilde="~"',
+ 'source-highlighter=prettify',
+ 'stylesheet=doc.css',
+ 'revnumber="%s"' % revision,
+ ]
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 78edefd..7671767 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -178,6 +178,42 @@
is not regenerated.
+[[documentation]]
+Documentation
+~~~~~~~~~~~~~
+
+To build the documentation:
+
+----
+ buck build docs
+----
+
+The generated html files will be placed in:
+
+----
+ buck-out/gen/Documentation
+----
+
+The html files will also be bundled into `html.zip` in the same location.
+
+
+[[release]]
+Gerrit Release WAR File
+~~~~~~~~~~~~~~~~~~~~~~~
+
+To build the release of the Gerrit web application, including documentation and
+all core plugins:
+
+----
+ buck build release
+----
+
+The output release WAR will be placed in:
+
+----
+ buck-out/gen/release.war
+----
+
[[tests]]
Running Unit Tests
------------------
@@ -270,67 +306,6 @@
EOF
----
-
-Build Process Switch Exit Criteria
-----------------------------------
-
-The switch to Buck is an experimental process. Buck will become the
-primary build for Gerrit only when the following conditions are met.
-
-1. Windows support.
-+
-Facebook has an intern who will be working on this (summer 2013).
-
-2. Bootstrap and stable version support.
-+
-From a fresh Gerrit clone on a machine without Buck (but with some
-reasonable subset of Buck's dependencies, e.g. Python 2.7), a new
-Gerrit developer should be able to set up and start building with
-Buck by running approximately one command. There should also be some
-idea of a "stable" version of Buck, even if we just tie our build
-to specific known-good SHAs. Binary distributions are another plus,
-which I believe the Buck team has planned.
-
-3. Shawn's Buck fork merged upstream.
-+
-Shawn has a link:https://gerrit.googlesource.com/buck/+log/github-master..master[fork of Buck]
-with some patches necessary to build Gerrit and run its unit tests.
-These patches (or their equivalents) must be in the upstream Buck tree.
-
-4. Fix all incidental issues.
-+
-Things come up that don't work. Martin just ran out of file
-descriptors, which sounds like an upstream bug.
-+
-There should be a consensus that new bugs like this in upstream
-Buck are not constantly being introduced.
-
-5. Support development of custom plugins.
-+
-There are three different alternatives for custom plugins:
-+
-The first is to build with BUCK in tree; your plugin builds against
-whatever version of Gerrit you have the sources checked out for. This
-is the simplest method on master right now. The BUCK definition is
-only a few lines of code and the rest "just works". But you are
-working from a Gerrit source tree, which is maybe not the ideal way to
-develop.
-+
-Another is to continue to use Maven. We just have to package the
-archetypes to support creating a new plugin, but existing plugins can
-develop against the API JAR(s) if they are installed into a Maven
-repository. tools/deploy_api.sh is how we did this for release
-versions of Gerrit. Something similar probably still be used with BUCK
-to publish the precompiled JARs. (Note: this is partially done with the
-target: `buck build api_install`; after issuing this command the new API
-version can be consumed by Maven driven custom plugins).
-+
-The third option is to support a BUCK based plugin build outside of
-the Gerrit tree. This is harder because there is functionality
-developed as macros in the Gerrit tree that plugins would want to use
-(e.g. gerrit_plugin rule).
-
-
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/doc.css b/Documentation/doc.css
new file mode 100644
index 0000000..3e226fe
--- /dev/null
+++ b/Documentation/doc.css
@@ -0,0 +1,37 @@
+body {
+ margin: 1em;
+}
+
+#toctitle {
+ margin-top: 0.5em;
+ font-weight: bold;
+}
+
+h1, h2, h3, h4, h5, h6, #toctitle {
+ color: #527bbd;
+ font-family: sans-serif;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+
+p {
+ margin: 0.5em 0 0.5em 0;
+}
+li p {
+ margin: 0.2em 0 0.2em 0;
+}
+
+pre {
+ border: 2px solid silver;
+ background: #ebebeb;
+ margin-left: 2em;
+ width: 100em;
+ color: darkgreen;
+ padding: 2px;
+}
+
+dl dt {
+ margin-top: 1em;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index 2488537..e93973b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -106,7 +106,7 @@
JSchException {
SshSession s = new SshSession(server, accounts.create("user", "user@example.com", "User"));
s.exec("gerrit gc --all");
- assertError("fatal: user does not have \"runGC\" capability.", s.getError());
+ assertError("Capability runGC is required to access this resource", s.getError());
}
@Test
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
index 970b317..09aef22 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -216,7 +216,7 @@
Gerrit.displayLastChangeList();
}
});
- keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReload()) {
+ keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadChange()) {
@Override
public void onKeyPress(final KeyPressEvent event) {
reload.reload();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index 2580542..6273717 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -21,6 +21,8 @@
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
import java.util.Collections;
import java.util.Comparator;
@@ -41,7 +43,16 @@
@Override
protected void onInitUI() {
super.onInitUI();
- table = new ChangeTable2();
+ table = new ChangeTable2() {
+ {
+ keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadSearch()) {
+ @Override
+ public void onKeyPress(final KeyPressEvent event) {
+ Gerrit.display(getToken());
+ }
+ });
+ }
+ };
table.addStyleName(Gerrit.RESOURCES.css().accountDashboard());
outgoing = new ChangeTable2.Section();
@@ -63,6 +74,7 @@
@Override
protected void onLoad() {
super.onLoad();
+
String who = mine ? "self" : ownerId.toString();
ChangeList.query(
new ScreenLoadCallback<JsArray<ChangeList>>(this) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 81d4cc3..197b466 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -56,7 +56,8 @@
String expandCollapseDependencies();
String previousPatchSet();
String nextPatchSet();
- String keyReload();
+ String keyReloadChange();
+ String keyReloadSearch();
String keyPublishComments();
String keyEditTopic();
String keyEditMessage();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 7bc6eb1..f51cff0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -36,7 +36,8 @@
expandCollapseDependencies = Expands / Collapses dependencies section
previousPatchSet = Previous patch set
nextPatchSet = Next patch set
-keyReload = Reload change
+keyReloadChange = Reload change
+keyReloadSearch = Reload change list
keyPublishComments = Review and publish comments
keyEditTopic = Edit change topic
keyEditMessage = Edit commit message
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
index 2310bf8..b2ccbcb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
@@ -76,6 +76,13 @@
.changeTablePagePrev(), prev));
keysNavigation.add(new DoLinkCommand(0, ']', Util.C
.changeTablePageNext(), next));
+
+ keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadSearch()) {
+ @Override
+ public void onKeyPress(final KeyPressEvent event) {
+ Gerrit.display(getToken());
+ }
+ });
}
};
section = new ChangeTable2.Section();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 97b3696..508c41a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -43,6 +43,7 @@
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import java.io.File;
import java.io.FileNotFoundException;
@@ -80,6 +81,7 @@
private final String noCacheName;
private final PermutationSelector selector;
private final boolean refreshHeaderFooter;
+ private final StaticServlet staticServlet;
private volatile Page page;
@Inject
@@ -87,7 +89,8 @@
final SitePaths sp, final ThemeFactory themeFactory,
final GerritConfig gc, final ServletContext servletContext,
final DynamicSet<WebUiPlugin> webUiPlugins,
- @GerritServerConfig final Config cfg)
+ @GerritServerConfig final Config cfg,
+ final StaticServlet ss)
throws IOException, ServletException {
currentUser = cu;
session = w;
@@ -97,6 +100,7 @@
signedInTheme = themeFactory.getSignedInTheme();
site = sp;
refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
+ staticServlet = ss;
boolean checkUserAgent = cfg.getBoolean("site", "checkUserAgent", true);
final String pageName = "HostPage.html";
@@ -247,6 +251,26 @@
return pg.get(selector.select(req));
}
+ private void insertETags(Element e) {
+ if ("img".equalsIgnoreCase(e.getTagName())
+ || "script".equalsIgnoreCase(e.getTagName())) {
+ String src = e.getAttribute("src");
+ if (src != null && src.startsWith("static/")) {
+ String name = src.substring("static/".length());
+ StaticServlet.Resource r = staticServlet.getResource(name);
+ if (r != null) {
+ e.setAttribute("src", src + "?e=" + r.etag);
+ }
+ }
+ }
+
+ for (Node n = e.getFirstChild(); n != null; n = n.getNextSibling()) {
+ if (n instanceof Element) {
+ insertETags((Element) n);
+ }
+ }
+ }
+
private static class FileInfo {
private final File path;
private final long time;
@@ -378,7 +402,8 @@
return info;
}
- final Element content = html.getDocumentElement();
+ Element content = html.getDocumentElement();
+ insertETags(content);
banner.appendChild(hostDoc.importNode(content, true));
return info;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java
index bc0a174..b74d1ac 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java
@@ -14,37 +14,60 @@
package com.google.gerrit.httpd.raw;
+import static com.google.common.net.HttpHeaders.CONTENT_ENCODING;
+import static com.google.common.net.HttpHeaders.ETAG;
+import static com.google.common.net.HttpHeaders.IF_NONE_MATCH;
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.Weigher;
import com.google.common.collect.Maps;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteStreams;
+import com.google.gerrit.httpd.HtmlDomUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.gwtjsonrpc.server.RPCServletUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.GZIPOutputStream;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+
/** Sends static content from the site 's <code>static/</code> subdirectory. */
@SuppressWarnings("serial")
@Singleton
public class StaticServlet extends HttpServlet {
+ private static final Logger log = LoggerFactory.getLogger(StaticServlet.class);
+ private static final String JS = "application/x-javascript";
private static final Map<String, String> MIME_TYPES = Maps.newHashMap();
static {
MIME_TYPES.put("html", "text/html");
MIME_TYPES.put("htm", "text/html");
- MIME_TYPES.put("js", "application/x-javascript");
+ MIME_TYPES.put("js", JS);
MIME_TYPES.put("css", "text/css");
MIME_TYPES.put("rtf", "text/rtf");
MIME_TYPES.put("txt", "text/plain");
@@ -66,31 +89,13 @@
return type != null ? type : "application/octet-stream";
}
- private static byte[] readFile(final File p) throws IOException {
- final FileInputStream in = new FileInputStream(p);
- try {
- final byte[] r = new byte[(int) in.getChannel().size()];
- IO.readFully(in, r, 0, r.length);
- return r;
- } finally {
- in.close();
- }
- }
-
- private static byte[] compress(final byte[] raw) throws IOException {
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- final GZIPOutputStream gz = new GZIPOutputStream(out);
- gz.write(raw);
- gz.finish();
- gz.flush();
- return out.toByteArray();
- }
-
private final File staticBase;
private final String staticBasePath;
+ private final boolean refresh;
+ private final LoadingCache<String, Resource> cache;
@Inject
- StaticServlet(final SitePaths site) {
+ StaticServlet(@GerritServerConfig Config cfg, SitePaths site) {
File f;
try {
f = site.static_dir.getCanonicalFile();
@@ -99,70 +104,101 @@
}
staticBase = f;
staticBasePath = staticBase.getPath() + File.separator;
+ refresh = cfg.getBoolean("site", "refreshHeaderFooter", true);
+ cache = CacheBuilder.newBuilder()
+ .maximumWeight(1 << 20)
+ .weigher(new Weigher<String, Resource>() {
+ @Override
+ public int weigh(String name, Resource r) {
+ return 2 * name.length() + r.raw.length;
+ }
+ })
+ .build(new CacheLoader<String, Resource>() {
+ @Override
+ public Resource load(String name) throws Exception {
+ return loadResource(name);
+ }
+ });
}
- private File local(final HttpServletRequest req) {
- final String name = req.getPathInfo();
- if (name.length() < 2 || !name.startsWith("/") || isUnreasonableName(name)) {
- // Too short to be a valid file name, or doesn't start with
- // the path info separator like we expected.
- //
+ @Nullable
+ Resource getResource(String name) {
+ try {
+ return cache.get(name);
+ } catch (ExecutionException e) {
+ log.warn(String.format("Cannot load static resource %s", name), e);
return null;
}
+ }
- final File p = new File(staticBase, name.substring(1));
-
- // Ensure that the requested file is *actually* within the static dir base.
- try {
- if (!p.getCanonicalFile().getPath().startsWith(staticBasePath))
- return null;
- } catch (IOException e) {
- return null;
+ private Resource getResource(HttpServletRequest req) throws ExecutionException {
+ String name = CharMatcher.is('/').trimFrom(req.getPathInfo());
+ if (isUnreasonableName(name)) {
+ return Resource.NOT_FOUND;
}
- return p.isFile() ? p : null;
+ Resource r = cache.get(name);
+ if (r == Resource.NOT_FOUND) {
+ return Resource.NOT_FOUND;
+ }
+
+ if (refresh && r.isStale()) {
+ cache.invalidate(name);
+ r = cache.get(name);
+ }
+ return r;
}
private static boolean isUnreasonableName(String name) {
- if (name.charAt(name.length() -1) == '/') return true; // no suffix
+ if (name.length() < 1) return true;
if (name.indexOf('\\') >= 0) return true; // no windows/dos stlye paths
if (name.startsWith("../")) return true; // no "../etc/passwd"
if (name.contains("/../")) return true; // no "foo/../etc/passwd"
if (name.contains("/./")) return true; // "foo/./foo" is insane to ask
if (name.contains("//")) return true; // windows UNC path can be "//..."
-
return false; // is a reasonable name
}
@Override
- protected long getLastModified(final HttpServletRequest req) {
- final File p = local(req);
- return p != null ? p.lastModified() : -1;
- }
-
- @Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
- final File p = local(req);
- if (p == null) {
+ Resource r;
+ try {
+ r = getResource(req);
+ } catch (ExecutionException e) {
+ log.warn(String.format(
+ "Cannot load static resource %s",
+ req.getPathInfo()), e);
CacheHeaders.setNotCacheable(rsp);
- rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ rsp.setStatus(SC_INTERNAL_SERVER_ERROR);
return;
}
- final String type = contentType(p.getName());
- final byte[] tosend;
- if (!type.equals("application/x-javascript")
- && RPCServletUtils.acceptsGzipEncoding(req)) {
- rsp.setHeader("Content-Encoding", "gzip");
- tosend = compress(readFile(p));
- } else {
- tosend = readFile(p);
+ String e = req.getParameter("e");
+ if (r == Resource.NOT_FOUND || (e != null && !r.etag.equals(e))) {
+ CacheHeaders.setNotCacheable(rsp);
+ rsp.setStatus(SC_NOT_FOUND);
+ return;
+ } else if (r.etag.equals(req.getHeader(IF_NONE_MATCH))) {
+ rsp.setStatus(SC_NOT_MODIFIED);
+ return;
}
- CacheHeaders.setCacheable(req, rsp, 12, TimeUnit.HOURS);
- rsp.setDateHeader("Last-Modified", p.lastModified());
- rsp.setContentType(type);
+ byte[] tosend = r.raw;
+ if (!r.contentType.equals(JS) && RPCServletUtils.acceptsGzipEncoding(req)) {
+ byte[] gz = HtmlDomUtil.compress(tosend);
+ if ((gz.length + 24) < tosend.length) {
+ rsp.setHeader(CONTENT_ENCODING, "gzip");
+ tosend = gz;
+ }
+ }
+ if (e != null && r.etag.equals(e)) {
+ CacheHeaders.setCacheable(req, rsp, 360, DAYS, false);
+ } else {
+ CacheHeaders.setCacheable(req, rsp, 15, MINUTES, refresh);
+ }
+ rsp.setHeader(ETAG, r.etag);
+ rsp.setContentType(r.contentType);
rsp.setContentLength(tosend.length);
final OutputStream out = rsp.getOutputStream();
try {
@@ -171,4 +207,54 @@
out.close();
}
}
+
+ private Resource loadResource(String name) throws IOException {
+ File p = new File(staticBase, name);
+ try {
+ p = p.getCanonicalFile();
+ } catch (IOException e) {
+ return Resource.NOT_FOUND;
+ }
+ if (!p.getPath().startsWith(staticBasePath)) {
+ return Resource.NOT_FOUND;
+ }
+
+ long ts = p.lastModified();
+ FileInputStream in;
+ try {
+ in = new FileInputStream(p);
+ } catch (FileNotFoundException e) {
+ return Resource.NOT_FOUND;
+ }
+
+ byte[] raw;
+ try {
+ raw = ByteStreams.toByteArray(in);
+ } finally {
+ in.close();
+ }
+ return new Resource(p, ts, contentType(name), raw);
+ }
+
+ static class Resource {
+ static final Resource NOT_FOUND = new Resource(null, -1, "", new byte[] {});
+
+ final File src;
+ final long lastModified;
+ final String contentType;
+ final String etag;
+ final byte[] raw;
+
+ Resource(File src, long lastModified, String contentType, byte[] raw) {
+ this.src = src;
+ this.lastModified = lastModified;
+ this.contentType = contentType;
+ this.etag = Hashing.md5().hashBytes(raw).toString();
+ this.raw = raw;
+ }
+
+ boolean isStale() {
+ return lastModified != src.lastModified();
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index c6a925e..e1d8627 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -216,10 +216,12 @@
try {
openSchema();
openRepository();
- openBranch();
+
+ RefUpdate branchUpdate = openBranch();
+ boolean reopen = false;
+
final ListMultimap<SubmitType, Change> toSubmit =
validateChangeList(db.changes().submitted(destBranch).toList());
-
final ListMultimap<SubmitType, CodeReviewCommit> toMergeNextTurn =
ArrayListMultimap.create();
final List<CodeReviewCommit> potentiallyStillSubmittableOnNextRun =
@@ -229,10 +231,14 @@
final Set<SubmitType> submitTypes =
new HashSet<Project.SubmitType>(toMerge.keySet());
for (final SubmitType submitType : submitTypes) {
- final RefUpdate branchUpdate = openBranch();
+ if (reopen) {
+ branchUpdate = openBranch();
+ }
final SubmitStrategy strategy = createStrategy(submitType);
preMerge(strategy, toMerge.get(submitType));
updateBranch(strategy, branchUpdate);
+ reopen = true;
+
updateChangeStatus(toSubmit.get(submitType));
updateSubscriptions(toSubmit.get(submitType));
@@ -368,27 +374,15 @@
if (branchUpdate.getOldObjectId() != null) {
branchTip =
(CodeReviewCommit) rw.parseCommit(branchUpdate.getOldObjectId());
- } else {
+ } else if (repo.getFullBranch().equals(destBranch.get())) {
branchTip = null;
- }
-
- try {
- final Ref destRef = repo.getRef(destBranch.get());
- if (destRef != null) {
- branchUpdate.setExpectedOldObjectId(destRef.getObjectId());
- } else if (repo.getFullBranch().equals(destBranch.get())) {
- branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
- } else {
- for (final Change c : db.changes().submitted(destBranch).toList()) {
- setNew(c, message(c, "Your change could not be merged, "
- + "because the destination branch does not exist anymore."));
- }
+ branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
+ } else {
+ for (final Change c : db.changes().submitted(destBranch).toList()) {
+ setNew(c, message(c, "Your change could not be merged, "
+ + "because the destination branch does not exist anymore."));
}
- } catch (IOException e) {
- throw new MergeException(
- "Failed to check existence of destination branch", e);
}
-
return branchUpdate;
} catch (IOException e) {
throw new MergeException("Cannot open branch", e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index c5e2739..e9704e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -189,18 +189,18 @@
m.setBase(originalCommit.getParent(0));
if (m.merge(mergeTip, originalCommit)) {
+ ObjectId tree = m.getResultTreeId();
+ if (tree.equals(mergeTip.getTree())) {
+ return null;
+ }
- final CommitBuilder mergeCommit = new CommitBuilder();
-
- mergeCommit.setTreeId(m.getResultTreeId());
+ CommitBuilder mergeCommit = new CommitBuilder();
+ mergeCommit.setTreeId(tree);
mergeCommit.setParentId(mergeTip);
mergeCommit.setAuthor(originalCommit.getAuthorIdent());
mergeCommit.setCommitter(cherryPickCommitterIdent);
mergeCommit.setMessage(commitMsg);
-
- final ObjectId id = commit(inserter, mergeCommit);
-
- return rw.parseCommit(id);
+ return rw.parseCommit(commit(inserter, mergeCommit));
} else {
return null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
index f4a4f20..b67faa3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
@@ -89,8 +89,7 @@
* Results may not be immediately visible to searchers, but should be visible
* within a reasonable amount of time.
*
- * @param cd change document with all index fields prepopulated; see
- * {@link ChangeData#fillIndexFields}.
+ * @param cd change document
*
* @throws IOException if the change could not be inserted.
*/
@@ -103,8 +102,7 @@
* new field values. Results may not be immediately visible to searchers, but
* should be visible within a reasonable amount of time.
*
- * @param cd change document with all index fields prepopulated; see
- * {@link ChangeData#fillIndexFields}.
+ * @param cd change document
*
* @throws IOException
*/
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index 483ecaf..6647652 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -77,7 +77,7 @@
boolean perUser = false;
Map<AccessSection, Project.NameKey> sectionToProject = Maps.newLinkedHashMap();
- for (SectionMatcher matcher : matcherList) {
+ for (SectionMatcher sm : matcherList) {
// If the matcher has to expand parameters and its prefix matches the
// reference there is a very good chance the reference is actually user
// specific, even if the matcher does not match the reference. Since its
@@ -91,12 +91,12 @@
// references are usually less frequent than the non-user references.
//
if (username != null && !perUser
- && matcher instanceof SectionMatcher.ExpandParameters) {
- perUser = ((SectionMatcher.ExpandParameters) matcher).matchPrefix(ref);
+ && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
+ perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
}
- if (matcher.match(ref, username)) {
- sectionToProject.put(matcher.section, matcher.project);
+ if (sm.match(ref, username)) {
+ sectionToProject.put(sm.section, sm.project);
}
}
List<AccessSection> sections = Lists.newArrayList(sectionToProject.keySet());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
new file mode 100644
index 0000000..b71d194
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -0,0 +1,122 @@
+// Copyright (C) 2013 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.google.gerrit.server.project;
+
+import static com.google.gerrit.server.project.RefControl.isRE;
+import com.google.gerrit.common.data.ParameterizedString;
+import dk.brics.automaton.Automaton;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+abstract class RefPatternMatcher {
+ public static RefPatternMatcher getMatcher(String pattern) {
+ if (pattern.contains("${")) {
+ return new ExpandParameters(pattern);
+ } else if (isRE(pattern)) {
+ return new Regexp(pattern);
+ } else if (pattern.endsWith("/*")) {
+ return new Prefix(pattern.substring(0, pattern.length() - 1));
+ } else {
+ return new Exact(pattern);
+ }
+ }
+
+ abstract boolean match(String ref, String username);
+
+ private static class Exact extends RefPatternMatcher {
+ private final String expect;
+
+ Exact(String name) {
+ expect = name;
+ }
+
+ @Override
+ boolean match(String ref, String username) {
+ return expect.equals(ref);
+ }
+ }
+
+ private static class Prefix extends RefPatternMatcher {
+ private final String prefix;
+
+ Prefix(String pfx) {
+ prefix = pfx;
+ }
+
+ @Override
+ boolean match(String ref, String username) {
+ return ref.startsWith(prefix);
+ }
+ }
+
+ private static class Regexp extends RefPatternMatcher {
+ private final Pattern pattern;
+
+ Regexp(String re) {
+ pattern = Pattern.compile(re);
+ }
+
+ @Override
+ boolean match(String ref, String username) {
+ return pattern.matcher(ref).matches();
+ }
+ }
+
+ static class ExpandParameters extends RefPatternMatcher {
+ private final ParameterizedString template;
+ private final String prefix;
+
+ ExpandParameters(String pattern) {
+ template = new ParameterizedString(pattern);
+
+ if (isRE(pattern)) {
+ // Replace ${username} with ":USERNAME:" as : is not legal
+ // in a reference and the string :USERNAME: is not likely to
+ // be a valid part of the regex. This later allows the pattern
+ // prefix to be clipped, saving time on evaluation.
+ Automaton am =
+ RefControl.toRegExp(
+ template.replace(Collections.singletonMap("username",
+ ":USERNAME:"))).toAutomaton();
+ String rePrefix = am.getCommonPrefix();
+ prefix = rePrefix.substring(0, rePrefix.indexOf(":USERNAME:"));
+ } else {
+ prefix = pattern.substring(0, pattern.indexOf("${"));
+ }
+ }
+
+ @Override
+ boolean match(String ref, String username) {
+ if (!ref.startsWith(prefix) || username == null) {
+ return false;
+ }
+
+ String u;
+ if (isRE(template.getPattern())) {
+ u = username.replace(".", "\\.");
+ } else {
+ u = username;
+ }
+
+ RefPatternMatcher next =
+ getMatcher(template.replace(Collections.singletonMap("username", u)));
+ return next != null ? next.match(ref, username) : false;
+ }
+
+ boolean matchPrefix(String ref) {
+ return ref.startsWith(prefix);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
index 6f8af80..44c8b9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
@@ -14,147 +14,38 @@
package com.google.gerrit.server.project;
-import static com.google.gerrit.server.project.RefControl.isRE;
-
import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.Project;
-import dk.brics.automaton.Automaton;
-
-import java.util.Collections;
-import java.util.regex.Pattern;
-
/**
* Matches an AccessSection against a reference name.
* <p>
* These matchers are "compiled" versions of the AccessSection name, supporting
* faster selection of which sections are relevant to any given input reference.
*/
-abstract class SectionMatcher {
+class SectionMatcher extends RefPatternMatcher {
static SectionMatcher wrap(Project.NameKey project, AccessSection section) {
String ref = section.getName();
if (AccessSection.isValid(ref)) {
- return wrap(project, ref, section);
+ return new SectionMatcher(project, section, getMatcher(ref));
} else {
return null;
}
}
- static SectionMatcher wrap(Project.NameKey project, String pattern,
- AccessSection section) {
- if (pattern.contains("${")) {
- return new ExpandParameters(project, pattern, section);
-
- } else if (isRE(pattern)) {
- return new Regexp(project, pattern, section);
-
- } else if (pattern.endsWith("/*")) {
- return new Prefix(project, pattern.substring(0, pattern.length() - 1),
- section);
-
- } else {
- return new Exact(project, pattern, section);
- }
- }
-
final Project.NameKey project;
final AccessSection section;
+ final RefPatternMatcher matcher;
- SectionMatcher(Project.NameKey project, AccessSection section) {
+ SectionMatcher(Project.NameKey project, AccessSection section,
+ RefPatternMatcher matcher) {
this.project = project;
this.section = section;
+ this.matcher = matcher;
}
- abstract boolean match(String ref, String username);
-
- private static class Exact extends SectionMatcher {
- private final String expect;
-
- Exact(Project.NameKey project, String name, AccessSection section) {
- super(project, section);
- expect = name;
- }
-
- @Override
- boolean match(String ref, String username) {
- return expect.equals(ref);
- }
- }
-
- private static class Prefix extends SectionMatcher {
- private final String prefix;
-
- Prefix(Project.NameKey project, String pfx, AccessSection section) {
- super(project, section);
- prefix = pfx;
- }
-
- @Override
- boolean match(String ref, String username) {
- return ref.startsWith(prefix);
- }
- }
-
- private static class Regexp extends SectionMatcher {
- private final Pattern pattern;
-
- Regexp(Project.NameKey project, String re, AccessSection section) {
- super(project, section);
- pattern = Pattern.compile(re);
- }
-
- @Override
- boolean match(String ref, String username) {
- return pattern.matcher(ref).matches();
- }
- }
-
- static class ExpandParameters extends SectionMatcher {
- private final ParameterizedString template;
- private final String prefix;
-
- ExpandParameters(Project.NameKey project, String pattern,
- AccessSection section) {
- super(project, section);
- template = new ParameterizedString(pattern);
-
- if (isRE(pattern)) {
- // Replace ${username} with ":USERNAME:" as : is not legal
- // in a reference and the string :USERNAME: is not likely to
- // be a valid part of the regex. This later allows the pattern
- // prefix to be clipped, saving time on evaluation.
- Automaton am = RefControl.toRegExp(
- template.replace(Collections.singletonMap("username", ":USERNAME:")))
- .toAutomaton();
- String rePrefix = am.getCommonPrefix();
- prefix = rePrefix.substring(0, rePrefix.indexOf(":USERNAME:"));
- } else {
- prefix = pattern.substring(0, pattern.indexOf("${"));
- }
- }
-
- @Override
- boolean match(String ref, String username) {
- if (!ref.startsWith(prefix) || username == null) {
- return false;
- }
-
- String u;
- if (isRE(template.getPattern())) {
- u = username.replace(".", "\\.");
- } else {
- u = username;
- }
-
- SectionMatcher next = wrap(project,
- template.replace(Collections.singletonMap("username", u)),
- section);
- return next != null ? next.match(ref, username) : false;
- }
-
- boolean matchPrefix(String ref) {
- return ref.startsWith(prefix);
- }
+ @Override
+ boolean match(String ref, String username) {
+ return this.matcher.match(ref, username);
}
}
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
new file mode 100644
index 0000000..25b9eb8
--- /dev/null
+++ b/lib/asciidoctor/BUCK
@@ -0,0 +1,39 @@
+include_defs('//lib/maven.defs')
+
+java_binary(
+ name = 'asciidoc',
+ main_class = 'org.asciidoctor.cli.AsciidoctorInvoker',
+ deps = [':asciidoctor'],
+ visibility = ['PUBLIC'],
+)
+
+maven_jar(
+ name = 'asciidoctor',
+ id = 'org.asciidoctor:asciidoctor-java-integration:0.1.3',
+ sha1 = '5cf21b4331d737ef0f3b3f543a7e5a343c1f27ec',
+ license = 'Apache2.0',
+ visibility = [],
+ attach_source = False,
+ deps = [
+ ':jcommander',
+ ':jruby',
+ ],
+)
+
+maven_jar(
+ name = 'jcommander',
+ id = 'com.beust:jcommander:1.30',
+ sha1 = 'c440b30a944ba199751551aee393f8aa03b3c327',
+ license = 'Apache2.0',
+ visibility = [],
+ attach_source = False,
+)
+
+maven_jar(
+ name = 'jruby',
+ id = 'org.jruby:jruby-complete:1.7.4',
+ sha1 = '74984d84846523bd7da49064679ed1ccf199e1db',
+ license = 'DO_NOT_DISTRIBUTE',
+ visibility = [],
+ attach_source = False,
+)