Merge branch 'dev-spearce'

* dev-spearce: (33 commits)
  Use transactions to handle comments when possible
  Avoid opening extra ReviewDb connection in PatchSetInfoFactory
  Minor ORM cleanups to support other backends
  Add command to output a Protobuf message file for the DB
  Support gwtorm 1.2
  Fix reference of Database<T> to SchemaFactory<T>
  Support Velocity 1.5
  Move replication queue binding out of GerritGlobalModule
  Remove static initialization of Velocity
  Move WorkQueue out of GerritGlobalModule
  Support auth.type = CUSTOM_EXTENSION
  Extract Git /p/ module configuration
  Make WebSession an abstract interface
  Move GitRepositoryManager setup out of SchemaModule
  Move SmtpEmailSender to its own module
  Make Address, EmailHeader visible to other EmailSenders
  Disable SSH Keys in the web UI if SSHD is disabled
  Refactor how we tie the SSH objects into the HTTP injector
  daemon: Allow httpd without sshd
  Allow sshd.listenAddress = off to disable the daemon
  ...

Conflicts:
	gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
	gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java

Change-Id: If957ce2eeb9b1de4ed2b134b0db129c336900442
This commit is contained in:
Shawn O. Pearce
2011-11-01 18:18:06 -07:00
80 changed files with 1272 additions and 520 deletions

View File

@@ -1040,6 +1040,16 @@ By default unset, as the git daemon must be configured externally
by the system administrator, and might not even be running on the
same host as Gerrit.
[[gerrit.gitHttpUrl]]gerrit.gitHttpUrl::
+
Optional base URL for repositories available over the HTTP
protocol. For example, set this to `http://mirror.example.com/base/`
to have Gerrit display URLs from this server, rather than itself.
+
By default unset, as the HTTP daemon must be configured externally
by the system administrator, and might not even be running on the
same host as Gerrit.
[[gerrit.replicateOnStartup]]gerrit.replicateOnStartup::
+
If true, replicates to all remotes on startup to ensure they are
@@ -1073,9 +1083,10 @@ For example, "?p=$project.git;h=$commit".
[[gitweb.type]]gitweb.type::
+
Optional type of affiliated gitweb service. This allows using
alternatives to gitweb, such as cgit.
alternatives to gitweb, such as cgit. If set to disabled there
is no gitweb hyperlinking support.
+
Valid values are `gitweb`, `cgit` or `custom`.
Valid values are `gitweb`, `cgit`, `disabled` or `custom`.
[[gitweb.type]]gitweb.revision::
+
@@ -1656,6 +1667,17 @@ A name of a group which exists in the database. Zero, one or many
groups are allowed. Each on its own line. Groups which don't exist
in the database are ignored.
[[rules]]Section rules
~~~~~~~~~~~~~~~~~~~~~~
[[rules.enable]]rules.enable::
+
If true, Gerrit will load and excute 'rules.pl' files in each
project's refs/meta/config branch, if present. When set to false,
only the default internal rules will be used.
+
Default is true, to execute project specific rules.
[[sendemail]]Section sendemail
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1767,6 +1789,25 @@ should expire.
+
By default, unset, so no Expiry-Date header is generated.
[[site]]Section site
~~~~~~~~~~~~~~~~~~~~
[[site.checkUserAgent]]site.checkUserAgent::
+
If true the server checks the User-Agent HTTP header and sends the
correct JavaScript to the client as part of the initial page load.
This usually reduces a round-trip for the client, allowing the UI to
start more quickly. If false, a tiny JavaScript loader is sent to the
client instead to determine the correct code to use. Default is true.
[[site.refreshHeaderFooter]]site.refreshHeaderFooter::
+
If true the server checks the site header, footer and CSS files for
updated versions. If false, a server restart is required to change
any of these resources. Default is true, allowing automatic reloads.
[[sshd]] Section sshd
~~~~~~~~~~~~~~~~~~~~~
@@ -1786,6 +1827,8 @@ default of 29418.
If multiple values are supplied, the daemon will listen on all
of them.
+
To disable the internal SSHD, set listenAddress to `off`.
+
By default, *:29418.
[[sshd.advertisedAddress]]sshd.advertisedAddress::

View File

@@ -52,6 +52,18 @@ limitations under the License.
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -26,6 +26,7 @@ import java.util.Set;
public class GerritConfig implements Cloneable {
protected String registerUrl;
protected String httpPasswordUrl;
protected List<OpenIdProviderPattern> allowedOpenIDs;
protected GitwebLink gitweb;
@@ -35,6 +36,7 @@ public class GerritConfig implements Cloneable {
protected AuthType authType;
protected Set<DownloadScheme> downloadSchemes;
protected String gitDaemonUrl;
protected String gitHttpUrl;
protected String sshdAddress;
protected Project.NameKey wildProject;
protected ApprovalTypes approvalTypes;
@@ -52,6 +54,14 @@ public class GerritConfig implements Cloneable {
registerUrl = u;
}
public String getHttpPasswordUrl() {
return httpPasswordUrl;
}
public void setHttpPasswordUrl(String url) {
httpPasswordUrl = url;
}
public List<OpenIdProviderPattern> getAllowedOpenIDs() {
return allowedOpenIDs;
}
@@ -111,6 +121,17 @@ public class GerritConfig implements Cloneable {
gitDaemonUrl = url;
}
public String getGitHttpUrl() {
return gitHttpUrl;
}
public void setGitHttpUrl(String url) {
if (url != null && !url.endsWith("/")) {
url += "/";
}
gitHttpUrl = url;
}
public String getSshdAddress() {
return sshdAddress;
}

View File

@@ -44,6 +44,9 @@ public class GitWebType {
// The custom name is not defined, let's keep the old style of using GitWeb
type.setLinkName("gitweb");
} else if (name.equalsIgnoreCase("disabled")) {
type = null;
} else {
type = null;
}

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.AccountDiffPreference;
public class HostPageData {
public Account account;
public AccountDiffPreference accountDiffPref;
public String xsrfToken;
public GerritConfig config;
public Theme theme;

View File

@@ -73,13 +73,13 @@ public class Gerrit implements EntryPoint {
public static final GerritResources RESOURCES =
GWT.create(GerritResources.class);
public static final SystemInfoService SYSTEM_SVC;
private static final String SESSION_COOKIE = "GerritAccount";
private static String myHost;
private static GerritConfig myConfig;
private static HostPageData.Theme myTheme;
private static Account myAccount;
private static AccountDiffPreference myAccountDiffPref;
private static String xsrfToken;
private static MorphingTabPanel menuLeft;
private static LinkMenuBar menuRight;
@@ -241,12 +241,16 @@ public class Gerrit implements EntryPoint {
}
/** Sign the user into the application. */
public static void doSignIn(final String token) {
public static void doSignIn(String token) {
switch (myConfig.getAuthType()) {
case HTTP:
case HTTP_LDAP:
case CLIENT_SSL_CERT_LDAP:
Location.assign(Location.getPath() + "login/" + token);
case CUSTOM_EXTENSION:
if (!token.startsWith("/")) {
token = "/" + token;
}
Location.assign(Location.getPath() + "login" + token);
break;
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
@@ -265,10 +269,15 @@ public class Gerrit implements EntryPoint {
}
static void deleteSessionCookie() {
Cookies.removeCookie(SESSION_COOKIE);
myAccount = null;
myAccountDiffPref = null;
xsrfToken = null;
refreshMenuBar();
// If the cookie was HttpOnly, this request to delete it will
// most likely not be successful. We can try anyway though.
//
Cookies.removeCookie("GerritAccount");
}
public void onModuleLoad() {
@@ -305,9 +314,11 @@ public class Gerrit implements EntryPoint {
myTheme = result.theme;
if (result.account != null) {
myAccount = result.account;
xsrfToken = result.xsrfToken;
}
if (result.accountDiffPref != null) {
myAccountDiffPref = result.accountDiffPref;
applyUserPreferences();
}
onModuleLoad2();
}
@@ -436,7 +447,7 @@ public class Gerrit implements EntryPoint {
JsonUtil.setDefaultXsrfManager(new XsrfManager() {
@Override
public String getToken(JsonDefTarget proxy) {
return Cookies.getCookie(SESSION_COOKIE);
return xsrfToken;
}
@Override
@@ -553,6 +564,7 @@ public class Gerrit implements EntryPoint {
case LDAP:
case LDAP_BIND:
case CUSTOM_EXTENSION:
if (cfg.getRegisterUrl() != null) {
menuRight.add(anchor(C.menuRegister(), cfg.getRegisterUrl()));
}

View File

@@ -56,6 +56,7 @@ public interface AccountConstants extends Constants {
String buttonChangeUserName();
String buttonClearPassword();
String buttonGeneratePassword();
String linkObtainPassword();
String invalidUserName();
String invalidUserEmail();

View File

@@ -37,6 +37,7 @@ buttonSetUserName = Select Username
buttonChangeUserName = Change Username
buttonClearPassword = Clear Password
buttonGeneratePassword = Generate Password
linkObtainPassword = Obtain Password
invalidUserName = Username must contain only letters, numbers, _, - or .
invalidUserEmail = Email format is wrong.
sshKeyInvalid = Invalid Key

View File

@@ -23,11 +23,12 @@ import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import java.util.List;
@@ -42,6 +43,16 @@ public class MyPasswordScreen extends SettingsScreen {
protected void onInitUI() {
super.onInitUI();
String url = Gerrit.getConfig().getHttpPasswordUrl();
if (url != null) {
Anchor link = new Anchor();
link.setText(Util.C.linkObtainPassword());
link.setHref(url);
link.setTarget("_blank");
add(link);
return;
}
password = new CopyableLabel("");
password.addStyleName(Gerrit.RESOURCES.css().accountPassword());
@@ -84,6 +95,11 @@ public class MyPasswordScreen extends SettingsScreen {
protected void onLoad() {
super.onLoad();
if (password == null) {
display();
return;
}
enableUI(false);
Util.ACCOUNT_SEC
.myExternalIds(new ScreenLoadCallback<List<AccountExternalId>>(this) {

View File

@@ -99,18 +99,20 @@ public class RegisterScreen extends AccountScreen {
formBody.add(fp);
}
final FlowPanel sshKeyGroup = new FlowPanel();
sshKeyGroup.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
sshKeyGroup.add(new SmallHeading(Util.C.welcomeSshKeyHeading()));
final HTML whySshKey = new HTML(Util.C.welcomeSshKeyText());
whySshKey.setStyleName(Gerrit.RESOURCES.css().registerScreenExplain());
sshKeyGroup.add(whySshKey);
sshKeyGroup.add(new SshPanel() {
{
setKeyTableVisible(false);
}
});
formBody.add(sshKeyGroup);
if (Gerrit.getConfig().getSshdAddress() != null) {
final FlowPanel sshKeyGroup = new FlowPanel();
sshKeyGroup.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
sshKeyGroup.add(new SmallHeading(Util.C.welcomeSshKeyHeading()));
final HTML whySshKey = new HTML(Util.C.welcomeSshKeyText());
whySshKey.setStyleName(Gerrit.RESOURCES.css().registerScreenExplain());
sshKeyGroup.add(whySshKey);
sshKeyGroup.add(new SshPanel() {
{
setKeyTableVisible(false);
}
});
formBody.add(sshKeyGroup);
}
final FlowPanel choices = new FlowPanel();
choices.setStyleName(Gerrit.RESOURCES.css().registerScreenNextLinks());

View File

@@ -26,7 +26,9 @@ public abstract class SettingsScreen extends MenuScreen {
link(Util.C.tabPreferences(), PageLinks.SETTINGS_PREFERENCES);
link(Util.C.tabWatchedProjects(), PageLinks.SETTINGS_PROJECTS);
link(Util.C.tabContactInformation(), PageLinks.SETTINGS_CONTACT);
link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
if (Gerrit.getConfig().getSshdAddress() != null) {
link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
}
link(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
link(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
link(Util.C.tabMyGroups(), PageLinks.SETTINGS_MYGROUPS);

View File

@@ -205,8 +205,12 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
&& (allowedSchemes.contains(DownloadScheme.ANON_HTTP) ||
allowedSchemes.contains(DownloadScheme.DEFAULT_DOWNLOADS))) {
StringBuilder r = new StringBuilder();
r.append(GWT.getHostPageBaseURL());
r.append("p/");
if (Gerrit.getConfig().getGitHttpUrl() != null) {
r.append(Gerrit.getConfig().getGitHttpUrl());
} else {
r.append(GWT.getHostPageBaseURL());
r.append("p/");
}
r.append(projectName);
r.append(" ");
r.append(patchSet.getRefName());
@@ -242,24 +246,29 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
&& Gerrit.getUserAccount().getUserName().length() > 0
&& (allowedSchemes.contains(DownloadScheme.HTTP) ||
allowedSchemes.contains(DownloadScheme.DEFAULT_DOWNLOADS))) {
String base = GWT.getHostPageBaseURL();
int p = base.indexOf("://");
int s = base.indexOf('/', p + 3);
if (s < 0) {
s = base.length();
}
String host = base.substring(p + 3, s);
if (host.contains("@")) {
host = host.substring(host.indexOf('@') + 1);
}
final StringBuilder r = new StringBuilder();
r.append(base.substring(0, p + 3));
r.append(Gerrit.getUserAccount().getUserName());
r.append('@');
r.append(host);
r.append(base.substring(s));
r.append("p/");
if (Gerrit.getConfig().getGitHttpUrl() != null
&& changeDetail.isAllowsAnonymous()) {
r.append(Gerrit.getConfig().getGitHttpUrl());
} else {
String base = GWT.getHostPageBaseURL();
int p = base.indexOf("://");
int s = base.indexOf('/', p + 3);
if (s < 0) {
s = base.length();
}
String host = base.substring(p + 3, s);
if (host.contains("@")) {
host = host.substring(host.indexOf('@') + 1);
}
r.append(base.substring(0, p + 3));
r.append(Gerrit.getUserAccount().getUserName());
r.append('@');
r.append(host);
r.append(base.substring(s));
r.append("p/");
}
r.append(projectName);
r.append(" ");
r.append(patchSet.getRefName());

View File

@@ -93,4 +93,20 @@ limitations under the License.
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,218 @@
// Copyright (C) 2009 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.httpd;
import static java.util.concurrent.TimeUnit.HOURS;
import com.google.gerrit.httpd.WebSessionManager.Key;
import com.google.gerrit.httpd.WebSessionManager.Val;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.servlet.RequestScoped;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RequestScoped
public final class CacheBasedWebSession implements WebSession {
private static final String ACCOUNT_COOKIE = "GerritAccount";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
final String cacheName = WebSessionManager.CACHE_NAME;
final TypeLiteral<Cache<Key, Val>> type =
new TypeLiteral<Cache<Key, Val>>() {};
disk(type, cacheName) //
.memoryLimit(1024) // reasonable default for many sites
.maxAge(12, HOURS) // expire sessions if they are inactive
.evictionPolicy(EvictionPolicy.LRU) // keep most recently used
;
bind(WebSessionManager.class);
bind(WebSession.class)
.to(CacheBasedWebSession.class)
.in(RequestScoped.class);
}
};
}
private final HttpServletRequest request;
private final HttpServletResponse response;
private final WebSessionManager manager;
private final AuthConfig authConfig;
private final Provider<AnonymousUser> anonymousProvider;
private final IdentifiedUser.RequestFactory identified;
private AccessPath accessPath = AccessPath.WEB_UI;
private Cookie outCookie;
private Key key;
private Val val;
@Inject
CacheBasedWebSession(final HttpServletRequest request,
final HttpServletResponse response, final WebSessionManager manager,
final AuthConfig authConfig,
final Provider<AnonymousUser> anonymousProvider,
final IdentifiedUser.RequestFactory identified) {
this.request = request;
this.response = response;
this.manager = manager;
this.authConfig = authConfig;
this.anonymousProvider = anonymousProvider;
this.identified = identified;
final String cookie = readCookie();
if (cookie != null) {
key = new Key(cookie);
val = manager.get(key);
} else {
key = null;
val = null;
}
if (isSignedIn() && val.needsCookieRefresh()) {
// Cookie is more than half old. Send the cookie again to the
// client with an updated expiration date. We don't dare to
// change the key token here because there may be other RPCs
// queued up in the browser whose xsrfKey would not get updated
// with the new token, causing them to fail.
//
val = manager.createVal(key, val);
saveCookie();
}
}
private String readCookie() {
final Cookie[] all = request.getCookies();
if (all != null) {
for (final Cookie c : all) {
if (ACCOUNT_COOKIE.equals(c.getName())) {
final String v = c.getValue();
return v != null && !"".equals(v) ? v : null;
}
}
}
return null;
}
public boolean isSignedIn() {
return val != null;
}
public String getToken() {
return isSignedIn() ? val.getXsrfToken() : null;
}
public boolean isTokenValid(final String inputToken) {
return isSignedIn() //
&& val.getXsrfToken() != null //
&& val.getXsrfToken().equals(inputToken);
}
public AccountExternalId.Key getLastLoginExternalId() {
return val != null ? val.getExternalId() : null;
}
public CurrentUser getCurrentUser() {
if (isSignedIn()) {
return identified.create(accessPath, val.getAccountId());
}
return anonymousProvider.get();
}
public void login(final AuthResult res, final boolean rememberMe) {
final Account.Id id = res.getAccountId();
final AccountExternalId.Key identity = res.getExternalId();
if (val != null) {
manager.destroy(key);
}
key = manager.createKey(id);
val = manager.createVal(key, id, rememberMe, identity, null);
saveCookie();
}
/** Change the access path from the default of {@link AccessPath#WEB_UI}. */
public void setAccessPath(AccessPath path) {
accessPath = path;
}
/** Set the user account for this current request only. */
public void setUserAccountId(Account.Id id) {
key = new Key("id:" + id);
val = new Val(id, 0, false, null, "");
}
public void logout() {
if (val != null) {
manager.destroy(key);
key = null;
val = null;
saveCookie();
}
}
private void saveCookie() {
final String token;
final int ageSeconds;
if (key == null) {
token = "";
ageSeconds = 0 /* erase at client */;
} else {
token = key.getToken();
ageSeconds = manager.getCookieAge(val);
}
String path = authConfig.getCookiePath();
if (path == null || path.isEmpty()) {
path = request.getContextPath();
if (path.isEmpty()) {
path = "/";
}
}
if (outCookie != null) {
throw new IllegalStateException("Cookie " + ACCOUNT_COOKIE + " was set");
}
outCookie = new Cookie(ACCOUNT_COOKIE, token);
outCookie.setSecure(isSecure(request));
outCookie.setPath(path);
outCookie.setMaxAge(ageSeconds);
outCookie.setSecure(authConfig.getCookieSecure());
response.addCookie(outCookie);
}
private static boolean isSecure(final HttpServletRequest req) {
return req.isSecure() || "https".equals(req.getScheme());
}
}

View File

@@ -94,10 +94,16 @@ class GerritConfigProvider implements Provider<GerritConfig> {
case LDAP_BIND:
config.setRegisterUrl(cfg.getString("auth", null, "registerurl"));
break;
case CUSTOM_EXTENSION:
config.setRegisterUrl(cfg.getString("auth", null, "registerurl"));
config.setHttpPasswordUrl(cfg.getString("auth", null, "httpPasswordUrl"));
break;
}
config.setUseContributorAgreements(cfg.getBoolean("auth",
"contributoragreements", false));
config.setGitDaemonUrl(cfg.getString("gerrit", null, "canonicalgiturl"));
config.setGitHttpUrl(cfg.getString("gerrit", null, "gitHttpUrl"));
config.setUseContactInfo(contactStore != null && contactStore.isEnabled());
config.setDownloadSchemes(schemeConfig.getDownloadScheme());
config.setAuthType(authConfig.getAuthType());

View File

@@ -0,0 +1,42 @@
// Copyright (C) 2009 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.httpd;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.servlet.ServletModule;
/**
* Configures Git access over {@code "/p/PROJECT_NAME"} URLs.
*/
public class GitOverHttpModule extends ServletModule {
private final AuthConfig authConfig;
@Inject
GitOverHttpModule(AuthConfig authConfig) {
this.authConfig = authConfig;
}
@Override
protected void configureServlets() {
filter("/p/*").through(ProjectAccessPathFilter.class);
if (authConfig.isTrustContainerAuth()) {
filter("/p/*").through(ContainerAuthFilter.class);
} else {
filter("/p/*").through(ProjectDigestFilter.class);
}
serve("/p/*").with(ProjectServlet.class);
}
}

View File

@@ -41,6 +41,15 @@ public class GitWebConfig {
final String cfgCgi = cfg.getString("gitweb", null, "cgi");
type = GitWebType.fromName(cfg.getString("gitweb", null, "type"));
if (type == null) {
url = null;
gitweb_cgi = null;
gitweb_css = null;
gitweb_js = null;
git_logo_png = null;
return;
}
type.setLinkName(cfg.getString("gitweb", null, "linkname"));
type.setBranch(cfg.getString("gitweb", null, "branch"));
type.setProject(cfg.getString("gitweb", null, "project"));
@@ -58,7 +67,7 @@ public class GitWebConfig {
}
if ((cfgUrl != null && cfgUrl.isEmpty())
|| (cfgCgi != null && cfgCgi.isEmpty()) || type == null) {
|| (cfgCgi != null && cfgCgi.isEmpty())) {
// Either setting was explicitly set to the empty string disabling
// gitweb for this server. Disable the configuration.
//

View File

@@ -30,7 +30,7 @@ import javax.servlet.ServletResponse;
/** Set the WebSession to {@link AccessPath#GIT}. */
@Singleton
class ProjectAccessPathFilter implements Filter {
public class ProjectAccessPathFilter implements Filter {
private final Provider<WebSession> session;
@Inject

View File

@@ -24,7 +24,6 @@ import com.google.gerrit.httpd.raw.SshInfoServlet;
import com.google.gerrit.httpd.raw.StaticServlet;
import com.google.gerrit.httpd.raw.ToolServlet;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtexpui.server.CacheControlFilter;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -38,12 +37,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
class UrlModule extends ServletModule {
private final AuthConfig authConfig;
public UrlModule(AuthConfig authConfig) {
this.authConfig = authConfig;
}
@Override
protected void configureServlets() {
filter("/*").through(Key.get(CacheControlFilter.class));
@@ -60,14 +53,6 @@ class UrlModule extends ServletModule {
serve("/static/*").with(StaticServlet.class);
serve("/tools/*").with(ToolServlet.class);
filter("/p/*").through(ProjectAccessPathFilter.class);
if (authConfig.isTrustContainerAuth()) {
filter("/p/*").through(ContainerAuthFilter.class);
} else {
filter("/p/*").through(ProjectDigestFilter.class);
}
serve("/p/*").with(ProjectServlet.class);
serve("/Main.class").with(notFound());
serve("/com/google/gerrit/launcher/*").with(notFound());
serve("/servlet/*").with(notFound());

View File

@@ -24,7 +24,6 @@ import com.google.gerrit.httpd.auth.ldap.LdapAuthModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.gitweb.GitWebModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.reviewdb.AuthType;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer;
@@ -38,12 +37,9 @@ import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritRequestModule;
import com.google.gerrit.server.contact.ContactStore;
import com.google.gerrit.server.contact.ContactStoreProvider;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
@@ -53,20 +49,14 @@ import java.net.SocketAddress;
import javax.annotation.Nullable;
public class WebModule extends FactoryModule {
private final Provider<SshInfo> sshInfoProvider;
private final Provider<SshKeyCache> sshKeyCacheProvider;
private final AuthConfig authConfig;
private final boolean wantSSL;
private final GitWebConfig gitWebConfig;
@Inject
WebModule(final Provider<SshInfo> sshInfoProvider,
final Provider<SshKeyCache> sshKeyCacheProvider,
final AuthConfig authConfig,
WebModule(final AuthConfig authConfig,
@CanonicalWebUrl @Nullable final String canonicalUrl,
final Injector creatingInjector) {
this.sshInfoProvider = sshInfoProvider;
this.sshKeyCacheProvider = sshKeyCacheProvider;
this.authConfig = authConfig;
this.wantSSL = canonicalUrl != null && canonicalUrl.startsWith("https:");
@@ -120,18 +110,17 @@ public class WebModule extends FactoryModule {
});
break;
case CUSTOM_EXTENSION:
break;
default:
throw new ProvisionException("Unsupported loginType: " + authConfig.getAuthType());
}
install(new UrlModule(authConfig));
install(new UrlModule());
install(new UiRpcModule());
install(new GerritRequestModule());
install(new ProjectServlet.Module());
bind(SshInfo.class).toProvider(sshInfoProvider);
bind(SshKeyCache.class).toProvider(sshKeyCacheProvider);
bind(GitWebConfig.class).toInstance(gitWebConfig);
if (gitWebConfig.getGitwebCGI() != null) {
install(new GitWebModule());
@@ -151,8 +140,6 @@ public class WebModule extends FactoryModule {
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
HttpRemotePeerProvider.class).in(RequestScoped.class);
install(WebSession.module());
bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class).in(
RequestScoped.class);
bind(IdentifiedUser.class).toProvider(HttpIdentifiedUserProvider.class).in(

View File

@@ -14,194 +14,30 @@
package com.google.gerrit.httpd;
import static java.util.concurrent.TimeUnit.HOURS;
import com.google.gerrit.httpd.WebSessionManager.Key;
import com.google.gerrit.httpd.WebSessionManager.Val;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.servlet.RequestScoped;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface WebSession {
public boolean isSignedIn();
@RequestScoped
public final class WebSession {
private static final String ACCOUNT_COOKIE = "GerritAccount";
public String getToken();
static Module module() {
return new CacheModule() {
@Override
protected void configure() {
final String cacheName = WebSessionManager.CACHE_NAME;
final TypeLiteral<Cache<Key, Val>> type =
new TypeLiteral<Cache<Key, Val>>() {};
disk(type, cacheName) //
.memoryLimit(1024) // reasonable default for many sites
.maxAge(12, HOURS) // expire sessions if they are inactive
.evictionPolicy(EvictionPolicy.LRU) // keep most recently used
;
bind(WebSessionManager.class);
bind(WebSession.class).in(RequestScoped.class);
}
};
}
public boolean isTokenValid(String inputToken);
private final HttpServletRequest request;
private final HttpServletResponse response;
private final WebSessionManager manager;
private final AuthConfig authConfig;
private final Provider<AnonymousUser> anonymousProvider;
private final IdentifiedUser.RequestFactory identified;
private AccessPath accessPath = AccessPath.WEB_UI;
private Cookie outCookie;
public AccountExternalId.Key getLastLoginExternalId();
private Key key;
private Val val;
public CurrentUser getCurrentUser();
@Inject
WebSession(final HttpServletRequest request,
final HttpServletResponse response, final WebSessionManager manager,
final AuthConfig authConfig,
final Provider<AnonymousUser> anonymousProvider,
final IdentifiedUser.RequestFactory identified) {
this.request = request;
this.response = response;
this.manager = manager;
this.authConfig = authConfig;
this.anonymousProvider = anonymousProvider;
this.identified = identified;
final String cookie = readCookie();
if (cookie != null) {
key = new Key(cookie);
val = manager.get(key);
} else {
key = null;
val = null;
}
if (isSignedIn() && val.needsCookieRefresh()) {
// Cookie is more than half old. Send the cookie again to the
// client with an updated expiration date. We don't dare to
// change the key token here because there may be other RPCs
// queued up in the browser whose xsrfKey would not get updated
// with the new token, causing them to fail.
//
val = manager.createVal(key, val);
saveCookie();
}
}
private String readCookie() {
final Cookie[] all = request.getCookies();
if (all != null) {
for (final Cookie c : all) {
if (ACCOUNT_COOKIE.equals(c.getName())) {
final String v = c.getValue();
return v != null && !"".equals(v) ? v : null;
}
}
}
return null;
}
public boolean isSignedIn() {
return val != null;
}
String getToken() {
return isSignedIn() ? key.getToken() : null;
}
public boolean isTokenValid(final String inputToken) {
return isSignedIn() && key.getToken().equals(inputToken);
}
public AccountExternalId.Key getLastLoginExternalId() {
return val != null ? val.getExternalId() : null;
}
CurrentUser getCurrentUser() {
if (isSignedIn()) {
return identified.create(accessPath, val.getAccountId());
}
return anonymousProvider.get();
}
public void login(final AuthResult res, final boolean rememberMe) {
final Account.Id id = res.getAccountId();
final AccountExternalId.Key identity = res.getExternalId();
logout();
key = manager.createKey(id);
val = manager.createVal(key, id, rememberMe, identity);
saveCookie();
}
public void login(AuthResult res, boolean rememberMe);
/** Change the access path from the default of {@link AccessPath#WEB_UI}. */
void setAccessPath(AccessPath path) {
accessPath = path;
}
public void setAccessPath(AccessPath path);
/** Set the user account for this current request only. */
void setUserAccountId(Account.Id id) {
key = new Key("id:" + id);
val = new Val(id, 0, false, null);
}
public void setUserAccountId(Account.Id id);
public void logout() {
if (val != null) {
manager.destroy(key);
key = null;
val = null;
saveCookie();
}
}
private void saveCookie() {
final String token;
final int ageSeconds;
if (key == null) {
token = "";
ageSeconds = 0 /* erase at client */;
} else {
token = key.getToken();
ageSeconds = manager.getCookieAge(val);
}
if (outCookie == null) {
String path = authConfig.getCookiePath();
if (path == null || path.isEmpty()) {
path = request.getContextPath();
if (path.isEmpty()) {
path = "/";
}
}
outCookie = new Cookie(ACCOUNT_COOKIE, token);
outCookie.setPath(path);
outCookie.setMaxAge(ageSeconds);
outCookie.setSecure(authConfig.getCookieSecure());
response.addCookie(outCookie);
} else {
outCookie.setValue(token);
outCookie.setMaxAge(ageSeconds);
}
}
public void logout();
}

View File

@@ -78,12 +78,13 @@ class WebSessionManager {
final Account.Id who = val.getAccountId();
final boolean remember = val.isPersistentCookie();
final AccountExternalId.Key lastLogin = val.getExternalId();
final String xsrfToken = val.getXsrfToken();
return createVal(key, who, remember, lastLogin);
return createVal(key, who, remember, lastLogin, xsrfToken);
}
Val createVal(final Key key, final Account.Id who, final boolean remember,
final AccountExternalId.Key lastLogin) {
final AccountExternalId.Key lastLogin, String xsrfToken) {
// Refresh the cookie every hour or when it is half-expired.
// This reduces the odds that the user session will be kicked
// early but also avoids us needing to refresh the cookie on
@@ -94,7 +95,17 @@ class WebSessionManager {
final long refresh = Math.min(halfAgeRefresh, minRefresh);
final long refreshCookieAt = now() + refresh;
final Val val = new Val(who, refreshCookieAt, remember, lastLogin);
if (xsrfToken == null) {
// If we don't yet have a token for this session, establish one.
//
final int nonceLen = 20;
final ByteArrayOutputStream buf;
final byte[] rnd = new byte[nonceLen];
prng.nextBytes(rnd);
xsrfToken = CookieBase64.encode(rnd);
}
Val val = new Val(who, refreshCookieAt, remember, lastLogin, xsrfToken);
self.put(key, val);
return val;
}
@@ -162,13 +173,16 @@ class WebSessionManager {
private transient long refreshCookieAt;
private transient boolean persistentCookie;
private transient AccountExternalId.Key externalId;
private transient String xsrfToken;
Val(final Account.Id accountId, final long refreshCookieAt,
final boolean persistentCookie, final AccountExternalId.Key externalId) {
final boolean persistentCookie, final AccountExternalId.Key externalId,
final String xsrfToken) {
this.accountId = accountId;
this.refreshCookieAt = refreshCookieAt;
this.persistentCookie = persistentCookie;
this.externalId = externalId;
this.xsrfToken = xsrfToken;
}
Account.Id getAccountId() {
@@ -187,6 +201,10 @@ class WebSessionManager {
return persistentCookie;
}
String getXsrfToken() {
return xsrfToken;
}
private void writeObject(final ObjectOutputStream out) throws IOException {
writeVarInt32(out, 1);
writeVarInt32(out, accountId.get());
@@ -202,6 +220,9 @@ class WebSessionManager {
writeString(out, externalId.get());
}
writeVarInt32(out, 5);
writeString(out, xsrfToken);
writeVarInt32(out, 0);
}
@@ -223,6 +244,9 @@ class WebSessionManager {
case 4:
externalId = new AccountExternalId.Key(readString(in));
continue;
case 5:
xsrfToken = readString(in);
continue;
default:
throw new IOException("Unknown tag found in object: " + tag);
}

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2010 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.httpd;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
/**
* Pulls objects from the SSH injector over the HTTP injector.
* <p>
* This mess is only necessary because we build up two different injectors, in
* order to have different request scopes. But some HTTP RPCs can cause changes
* to the SSH side of the house, and thus needs access to it.
*/
public class WebSshGlueModule extends AbstractModule {
private final Provider<SshInfo> sshInfoProvider;
private final Provider<SshKeyCache> sshKeyCacheProvider;
@Inject
WebSshGlueModule(Provider<SshInfo> sshInfoProvider,
Provider<SshKeyCache> sshKeyCacheProvider) {
this.sshInfoProvider = sshInfoProvider;
this.sshKeyCacheProvider = sshKeyCacheProvider;
}
@Override
protected void configure() {
bind(SshInfo.class).toProvider(sshInfoProvider);
bind(SshKeyCache.class).toProvider(sshKeyCacheProvider);
}
}

View File

@@ -80,7 +80,7 @@ class HttpLoginServlet extends HttpServlet {
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws ServletException, IOException {
final String token = getToken(req);
if ("logout".equals(token) || "signout".equals(token)) {
if ("/logout".equals(token) || "/signout".equals(token)) {
req.getRequestDispatcher("/logout").forward(req, rsp);
return;
}
@@ -166,6 +166,8 @@ class HttpLoginServlet extends HttpServlet {
String token = req.getPathInfo();
if (token == null || token.isEmpty()) {
token = PageLinks.MINE;
} else if (!token.startsWith("/")) {
token = "/" + token;
}
return token;
}

View File

@@ -77,6 +77,8 @@ class LoginRedirectServlet extends HttpServlet {
String token = req.getPathInfo();
if (token == null || token.isEmpty()) {
token = PageLinks.MINE;
} else if (!token.startsWith("/")) {
token = "/" + token;
}
return token;
}

View File

@@ -17,8 +17,10 @@ package com.google.gerrit.httpd.raw;
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.gwtexpui.linker.server.Permutation;
@@ -28,6 +30,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.slf4j.Logger;
@@ -61,6 +64,7 @@ public class HostPageServlet extends HttpServlet {
private static final String HPD_ID = "gerrit_hostpagedata";
private final Provider<CurrentUser> currentUser;
private final Provider<WebSession> session;
private final GerritConfig config;
private final HostPageData.Theme signedOutTheme;
private final HostPageData.Theme signedInTheme;
@@ -68,17 +72,22 @@ public class HostPageServlet extends HttpServlet {
private final Document template;
private final String noCacheName;
private final PermutationSelector selector;
private final boolean refreshHeaderFooter;
private volatile Page page;
@Inject
HostPageServlet(final Provider<CurrentUser> cu, final SitePaths sp,
final ThemeFactory themeFactory, final GerritConfig gc,
final ServletContext servletContext) throws IOException, ServletException {
HostPageServlet(final Provider<CurrentUser> cu, final Provider<WebSession> w,
final SitePaths sp, final ThemeFactory themeFactory,
final GerritConfig gc, final ServletContext servletContext,
@GerritServerConfig final Config cfg)
throws IOException, ServletException {
currentUser = cu;
session = w;
config = gc;
signedOutTheme = themeFactory.getSignedOutTheme();
signedInTheme = themeFactory.getSignedInTheme();
site = sp;
refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
final String pageName = "HostPage.html";
template = HtmlDomUtil.parseFile(getClass(), pageName);
@@ -95,7 +104,7 @@ public class HostPageServlet extends HttpServlet {
final String src = "gerrit/gerrit.nocache.js";
selector = new PermutationSelector("gerrit");
if (IS_DEV) {
if (IS_DEV || !cfg.getBoolean("site", "checkUserAgent", true)) {
noCacheName = src;
} else {
final Element devmode = HtmlDomUtil.find(template, "gerrit_gwtdevmode");
@@ -136,7 +145,7 @@ public class HostPageServlet extends HttpServlet {
private Page get() {
Page p = page;
if (p.isStale()) {
if (refreshHeaderFooter && p.isStale()) {
final Page newPage;
try {
newPage = new Page();
@@ -163,6 +172,10 @@ public class HostPageServlet extends HttpServlet {
json(((IdentifiedUser) user).getAccount(), w);
w.write(";");
w.write(HPD_ID + ".xsrfToken=");
json(session.get().getToken(), w);
w.write(";");
w.write(HPD_ID + ".accountDiffPref=");
json(((IdentifiedUser) user).getAccountDiffPreference(), w);
w.write(";");

View File

@@ -128,7 +128,7 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
byKey.put(p.getKey(), p);
}
for (final PatchLineComment c : db.patchComments().published(psIdNew)) {
for (final PatchLineComment c : db.patchComments().publishedByPatchSet(psIdNew)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setCommentCount(p.getCommentCount() + 1);
@@ -138,7 +138,7 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
detail = new PatchSetDetail();
detail.setPatchSet(patchSet);
detail.setInfo(infoFactory.get(psIdNew));
detail.setInfo(infoFactory.get(db, psIdNew));
detail.setPatches(patches);
final CurrentUser user = control.getCurrentUser();
@@ -148,7 +148,7 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
// quickly locate where they have pending drafts, and review them.
//
final Account.Id me = ((IdentifiedUser) user).getAccountId();
for (final PatchLineComment c : db.patchComments().draft(psIdNew, me)) {
for (final PatchLineComment c : db.patchComments().draftByPatchSetAuthor(psIdNew, me)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setDraftCount(p.getDraftCount() + 1);

View File

@@ -83,8 +83,8 @@ final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail>
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
change = control.getChange();
patchSetInfo = infoFactory.get(patchSetId);
drafts = db.patchComments().draft(patchSetId, user.getAccountId()).toList();
patchSetInfo = infoFactory.get(db, patchSetId);
drafts = db.patchComments().draftByPatchSetAuthor(patchSetId, user.getAccountId()).toList();
aic.want(change.getOwner());

View File

@@ -110,18 +110,25 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException, Failure {
final PatchLineComment comment = db.patchComments().get(commentKey);
if (comment == null) {
throw new Failure(new NoSuchEntityException());
Change.Id id = commentKey.getParentKey().getParentKey().getParentKey();
db.changes().beginTransaction(id);
try {
final PatchLineComment comment = db.patchComments().get(commentKey);
if (comment == null) {
throw new Failure(new NoSuchEntityException());
}
if (!getAccountId().equals(comment.getAuthor())) {
throw new Failure(new NoSuchEntityException());
}
if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
throw new Failure(new IllegalStateException("Comment published"));
}
db.patchComments().delete(Collections.singleton(comment));
db.commit();
return VoidResult.INSTANCE;
} finally {
db.rollback();
}
if (!getAccountId().equals(comment.getAuthor())) {
throw new Failure(new NoSuchEntityException());
}
if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
throw new Failure(new IllegalStateException("Comment published"));
}
db.patchComments().delete(Collections.singleton(comment));
return VoidResult.INSTANCE;
}
});
}
@@ -142,14 +149,20 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
Account.Id account = getAccountId();
AccountPatchReview.Key key =
new AccountPatchReview.Key(patchKey, account);
AccountPatchReview apr = db.accountPatchReviews().get(key);
if (apr == null && reviewed) {
db.accountPatchReviews().insert(
Collections.singleton(new AccountPatchReview(patchKey, account)));
} else if (apr != null && !reviewed) {
db.accountPatchReviews().delete(Collections.singleton(apr));
db.accounts().beginTransaction(account);
try {
AccountPatchReview apr = db.accountPatchReviews().get(key);
if (apr == null && reviewed) {
db.accountPatchReviews().insert(
Collections.singleton(new AccountPatchReview(patchKey, account)));
} else if (apr != null && !reviewed) {
db.accountPatchReviews().delete(Collections.singleton(apr));
}
db.commit();
return VoidResult.INSTANCE;
} finally {
db.rollback();
}
return VoidResult.INSTANCE;
}
});
}

View File

@@ -287,7 +287,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
private void loadPublished(final Map<Patch.Key, Patch> byKey,
final AccountInfoCacheFactory aic, final String file) throws OrmException {
for (PatchLineComment c : db.patchComments().published(changeId, file)) {
for (PatchLineComment c : db.patchComments().publishedByChangeFile(changeId, file)) {
if (comments.include(c)) {
aic.want(c.getAuthor());
}
@@ -303,7 +303,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
private void loadDrafts(final Map<Patch.Key, Patch> byKey,
final AccountInfoCacheFactory aic, final Account.Id me, final String file)
throws OrmException {
for (PatchLineComment c : db.patchComments().draft(changeId, file, me)) {
for (PatchLineComment c : db.patchComments().draftByChangeFileAuthor(changeId, file, me)) {
if (comments.include(c)) {
aic.want(me);
}

View File

@@ -49,7 +49,6 @@ class SaveDraft extends Handler<PatchLineComment> {
this.changeControlFactory = changeControlFactory;
this.db = db;
this.currentUser = currentUser;
this.comment = comment;
}
@@ -62,42 +61,50 @@ class SaveDraft extends Handler<PatchLineComment> {
final Patch.Key patchKey = comment.getKey().getParentKey();
final PatchSet.Id patchSetId = patchKey.getParentKey();
final Change.Id changeId = patchKey.getParentKey().getParentKey();
changeControlFactory.validateFor(changeId);
if (db.patchSets().get(patchSetId) == null) {
throw new NoSuchChangeException(changeId);
}
final Account.Id me = currentUser.getAccountId();
if (comment.getKey().get() == null) {
if (comment.getLine() < 1) {
throw new IllegalStateException("Comment line must be >= 1, not "
+ comment.getLine());
}
if (comment.getParentUuid() != null) {
final PatchLineComment parent =
db.patchComments().get(
new PatchLineComment.Key(patchKey, comment.getParentUuid()));
if (parent == null || parent.getSide() != comment.getSide()) {
throw new IllegalStateException("Parent comment must be on same side");
}
}
final PatchLineComment nc =
new PatchLineComment(new PatchLineComment.Key(patchKey, ChangeUtil
.messageUUID(db)), comment.getLine(), me, comment.getParentUuid());
nc.setSide(comment.getSide());
nc.setMessage(comment.getMessage());
db.patchComments().insert(Collections.singleton(nc));
return nc;
} else {
if (!me.equals(comment.getAuthor())) {
db.changes().beginTransaction(changeId);
try {
changeControlFactory.validateFor(changeId);
if (db.patchSets().get(patchSetId) == null) {
throw new NoSuchChangeException(changeId);
}
comment.updated();
db.patchComments().update(Collections.singleton(comment));
return comment;
final Account.Id me = currentUser.getAccountId();
if (comment.getKey().get() == null) {
if (comment.getLine() < 1) {
throw new IllegalStateException("Comment line must be >= 1, not "
+ comment.getLine());
}
if (comment.getParentUuid() != null) {
final PatchLineComment parent =
db.patchComments().get(
new PatchLineComment.Key(patchKey, comment.getParentUuid()));
if (parent == null || parent.getSide() != comment.getSide()) {
throw new IllegalStateException("Parent comment must be on same side");
}
}
final PatchLineComment nc =
new PatchLineComment(new PatchLineComment.Key(patchKey, ChangeUtil
.messageUUID(db)), comment.getLine(), me, comment.getParentUuid());
nc.setSide(comment.getSide());
nc.setMessage(comment.getMessage());
db.patchComments().insert(Collections.singleton(nc));
db.commit();
return nc;
} else {
if (!me.equals(comment.getAuthor())) {
throw new NoSuchChangeException(changeId);
}
comment.updated();
db.patchComments().update(Collections.singleton(comment));
db.commit();
return comment;
}
} finally {
db.rollback();
}
}
}

View File

@@ -8,14 +8,17 @@
var token;
if (p >= 0) {
token = href.substring(p + 1);
if (token.length != 0 && token.charAt(0) == '/') {
token = token.substring(1);
}
href = href.substring(0, p);
} else {
token = 'mine';
token = '';
}
window.location.replace(href + 'login/' + token);
</script>
</head>
<body>
<p>Redirecting to <a href="login/mine">Gerrit Code Review</a>.</p>
<p>Redirecting to <a href="login/">Gerrit Code Review</a>.</p>
</body>
</html>

View File

@@ -16,8 +16,11 @@ package com.google.gerrit.pgm;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import com.google.gerrit.httpd.CacheBasedWebSession;
import com.google.gerrit.httpd.GitOverHttpModule;
import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
import com.google.gerrit.httpd.WebModule;
import com.google.gerrit.httpd.WebSshGlueModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.pgm.http.jetty.JettyEnv;
import com.google.gerrit.pgm.http.jetty.JettyModule;
@@ -31,7 +34,11 @@ import com.google.gerrit.server.config.CanonicalWebUrlModule;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.git.PushReplication;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
import com.google.gerrit.sshd.commands.SlaveCommandModule;
@@ -114,10 +121,6 @@ public class Daemon extends SiteProgram {
if (slave && httpd) {
throw die("Cannot combine --slave and --enable-httpd");
}
if (httpd && !sshd) {
// TODO Support HTTP without SSH.
throw die("--enable-httpd currently requires --enable-sshd");
}
if (consoleLog) {
} else {
@@ -188,7 +191,10 @@ public class Daemon extends SiteProgram {
final List<Module> modules = new ArrayList<Module>();
modules.add(SchemaVersionCheck.module());
modules.add(new LogFileCompressor.Module());
modules.add(new WorkQueue.Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
modules.add(new SmtpEmailSender.Module());
modules.add(new PushReplication.Module());
if (httpd) {
modules.add(new CanonicalWebUrlModule() {
@Override
@@ -217,11 +223,15 @@ public class Daemon extends SiteProgram {
private Injector createSshInjector() {
final List<Module> modules = new ArrayList<Module>();
modules.add(new SshModule());
if (slave) {
modules.add(new SlaveCommandModule());
if (sshd) {
modules.add(new SshModule());
if (slave) {
modules.add(new SlaveCommandModule());
} else {
modules.add(new MasterCommandModule());
}
} else {
modules.add(new MasterCommandModule());
modules.add(new NoSshModule());
}
return sysInjector.createChildInjector(modules);
}
@@ -240,7 +250,12 @@ public class Daemon extends SiteProgram {
private Injector createWebInjector() {
final List<Module> modules = new ArrayList<Module>();
modules.add(sshInjector.getInstance(WebModule.class));
modules.add(sshInjector.getInstance(ProjectQoSFilter.Module.class));
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
modules.add(sshInjector.getInstance(WebSshGlueModule.class));
modules.add(CacheBasedWebSession.module());
if (sshd) {
modules.add(sshInjector.getInstance(ProjectQoSFilter.Module.class));
}
return sysInjector.createChildInjector(modules);
}

View File

@@ -0,0 +1,75 @@
// Copyright (C) 2010 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.pgm;
import com.google.gerrit.pgm.util.AbstractProgram;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.schema.java.JavaSchemaModel;
import org.eclipse.jgit.storage.file.LockFile;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
import org.kohsuke.args4j.Option;
import java.io.BufferedWriter;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
public class ProtoGen extends AbstractProgram {
@Option(name = "--output", aliases = {"-o"}, required = true, metaVar = "FILE", usage = "File to write .proto into")
private File file;
@Override
public int run() throws Exception {
LockFile lock = new LockFile(file.getAbsoluteFile(), FS.DETECTED);
if (!lock.lock()) {
throw die("Cannot lock " + file);
}
try {
JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(lock.getOutputStream(), "UTF-8")));
try {
String header;
InputStream in = getClass().getResourceAsStream("ProtoGenHeader.txt");
try {
ByteBuffer buf = IO.readWholeStream(in, 1024);
int ptr = buf.arrayOffset() + buf.position();
int len = buf.remaining();
header = new String(buf.array(), ptr, len, "UTF-8");
} finally {
in.close();
}
String version = com.google.gerrit.common.Version.getVersion();
out.write(header.replace("@@VERSION@@", version));
jsm.generateProto(out);
out.flush();
} finally {
out.close();
}
if (!lock.commit()) {
throw die("Could not write to " + file);
}
} finally {
lock.unlock();
}
System.out.println("Created " + file.getPath());
return 0;
}
}

View File

@@ -29,6 +29,7 @@ import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.GuiceHelper;
import com.google.inject.servlet.GuiceServletContextListener;
import org.eclipse.jetty.io.EndPoint;
@@ -67,6 +68,10 @@ import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
public class JettyServer {
static class Lifecycle implements LifecycleListener {
@@ -117,7 +122,23 @@ public class JettyServer {
Handler app = makeContext(env, cfg);
if (cfg.getBoolean("httpd", "requestlog", !reverseProxy)) {
RequestLogHandler handler = new RequestLogHandler();
RequestLogHandler handler = new RequestLogHandler() {
@Override
public void handle(String target, Request baseRequest,
final HttpServletRequest req, final HttpServletResponse rsp)
throws IOException, ServletException {
// Force the user to construct, so it's available to our HttpLog
// later on when the request gets logged out to the access file.
//
GuiceHelper.runInContext(req, rsp, new Runnable() {
@Override
public void run() {
userProvider.get();
}
});
super.handle(target, baseRequest, req, rsp);
}
};
handler.setRequestLog(new HttpLog(site, userProvider));
handler.setHandler(app);
app = handler;

View File

@@ -54,13 +54,20 @@ class InitSshd implements InitStep {
String hostname = "*";
int port = 29418;
String listenAddress = sshd.get("listenAddress");
if (listenAddress != null && !listenAddress.isEmpty()) {
if (isOff(listenAddress)) {
hostname = "off";
} else if (listenAddress != null && !listenAddress.isEmpty()) {
final InetSocketAddress addr = SocketUtil.parse(listenAddress, port);
hostname = SocketUtil.hostname(addr);
port = addr.getPort();
}
hostname = ui.readString(hostname, "Listen on address");
if (isOff(hostname)) {
sshd.set("listenAddress", "off");
return;
}
port = ui.readInt(port, "Listen on port");
sshd.set("listenAddress", SocketUtil.format(hostname, port));
@@ -73,6 +80,12 @@ class InitSshd implements InitStep {
generateSshHostKeys();
}
private static boolean isOff(String listenHostname) {
return "off".equalsIgnoreCase(listenHostname)
|| "none".equalsIgnoreCase(listenHostname)
|| "no".equalsIgnoreCase(listenHostname);
}
private void generateSshHostKeys() throws InterruptedException, IOException {
if (!site.ssh_key.exists() //
&& !site.ssh_rsa.exists() //

View File

@@ -20,6 +20,7 @@ import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DatabaseModule;
import com.google.gerrit.server.schema.SchemaModule;
@@ -164,6 +165,7 @@ public abstract class SiteProgram extends AbstractProgram {
modules.add(new GerritServerConfigModule());
modules.add(new DatabaseModule());
modules.add(new SchemaModule());
modules.add(new LocalDiskRepositoryManager.Module());
try {
return Guice.createInjector(PRODUCTION, modules);

View File

@@ -0,0 +1,22 @@
// Copyright (C) 2011 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.
//
// Gerrit Code Review (version @@VERSION@@)
syntax = "proto2";
option java_api_version = 2;
option java_package = "com.google.gerrit.proto.reviewdb";
package devtools.gerritcodereview;

View File

@@ -45,6 +45,10 @@ public final class AccountAgreement implements AbstractAgreement {
return accountId;
}
public ContributorAgreement.Id getContributorAgreementId() {
return claId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {claId};

View File

@@ -47,6 +47,10 @@ public final class AccountGroupAgreement implements AbstractAgreement {
return groupId;
}
public ContributorAgreement.Id getContributorAgreementId() {
return claId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {claId};

View File

@@ -49,6 +49,14 @@ public final class AccountGroupIncludeAudit {
return groupId;
}
public AccountGroup.Id getIncludedId() {
return includeId;
}
public Timestamp getAddedOn() {
return addedOn;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {includeId};

View File

@@ -49,6 +49,14 @@ public final class AccountGroupMemberAudit {
return accountId;
}
public AccountGroup.Id getGroupId() {
return groupId;
}
public Timestamp getAddedOn() {
return addedOn;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {groupId};

View File

@@ -27,7 +27,7 @@ public interface AccountGroupNameAccess extends
AccountGroupName get(AccountGroup.NameKey name) throws OrmException;
@Query("ORDER BY name")
ResultSet<AccountGroupName> all();
ResultSet<AccountGroupName> all() throws OrmException;
@Query("WHERE name.name >= ? AND name.name <= ? ORDER BY name LIMIT ?")
ResultSet<AccountGroupName> suggestByName(String nameA, String nameB,

View File

@@ -56,6 +56,14 @@ public final class AccountProjectWatch {
return accountId;
}
public Project.NameKey getProjectName() {
return projectName;
}
public Filter getFilter() {
return filter;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {projectName, filter};

View File

@@ -73,6 +73,9 @@ public enum AuthType {
*/
LDAP_BIND,
/** Login is managed by additional, unspecified code. */
CUSTOM_EXTENSION,
/** Development mode to enable becoming anyone you want. */
DEVELOPMENT_BECOME_ANY_ACCOUNT;
}

View File

@@ -28,37 +28,29 @@ public interface PatchLineCommentAccess extends
@Query("WHERE key.patchKey.patchSetId.changeId = ?")
ResultSet<PatchLineComment> byChange(Change.Id id) throws OrmException;
@Query("WHERE key.patchKey = ? AND status = '"
+ PatchLineComment.STATUS_PUBLISHED + "' ORDER BY lineNbr,writtenOn")
ResultSet<PatchLineComment> published(Patch.Key patch) throws OrmException;
@Query("WHERE key.patchKey.patchSetId.changeId = ?"
+ " AND key.patchKey.fileName = ? AND status = '"
+ PatchLineComment.STATUS_PUBLISHED + "' ORDER BY lineNbr,writtenOn")
ResultSet<PatchLineComment> published(Change.Id id, String file)
ResultSet<PatchLineComment> publishedByChangeFile(Change.Id id, String file)
throws OrmException;
@Query("WHERE key.patchKey.patchSetId = ? AND status = '"
+ PatchLineComment.STATUS_PUBLISHED + "'")
ResultSet<PatchLineComment> published(PatchSet.Id patchset)
ResultSet<PatchLineComment> publishedByPatchSet(PatchSet.Id patchset)
throws OrmException;
@Query("WHERE key.patchKey.patchSetId = ? AND status = '"
+ PatchLineComment.STATUS_DRAFT
+ "' AND author = ? ORDER BY key.patchKey,lineNbr,writtenOn")
ResultSet<PatchLineComment> draft(PatchSet.Id patchset, Account.Id author)
throws OrmException;
@Query("WHERE key.patchKey = ? AND status = '"
+ PatchLineComment.STATUS_DRAFT
+ "' AND author = ? ORDER BY lineNbr,writtenOn")
ResultSet<PatchLineComment> draft(Patch.Key patch, Account.Id author)
ResultSet<PatchLineComment> draftByPatchSetAuthor
(PatchSet.Id patchset, Account.Id author)
throws OrmException;
@Query("WHERE key.patchKey.patchSetId.changeId = ?"
+ " AND key.patchKey.fileName = ? AND author = ? AND status = '"
+ PatchLineComment.STATUS_DRAFT + "' ORDER BY lineNbr,writtenOn")
ResultSet<PatchLineComment> draft(Change.Id id, String file, Account.Id author)
ResultSet<PatchLineComment> draftByChangeFileAuthor
(Change.Id id, String file, Account.Id author)
throws OrmException;
@Query("WHERE status = '" + PatchLineComment.STATUS_DRAFT

View File

@@ -27,10 +27,6 @@ public interface PatchSetAccess extends Access<PatchSet, PatchSet.Id> {
@Query("WHERE id.changeId = ? ORDER BY id.patchSetId")
ResultSet<PatchSet> byChange(Change.Id id) throws OrmException;
@Query("WHERE id.changeId = ? AND revision = ?")
ResultSet<PatchSet> byChangeRevision(Change.Id id, RevId rev)
throws OrmException;
@Query("WHERE revision = ? LIMIT 2")
ResultSet<PatchSet> byRevision(RevId rev) throws OrmException;

View File

@@ -51,6 +51,14 @@ public final class PatchSetApproval {
return patchSetId;
}
public Account.Id getAccountId() {
return accountId;
}
public ApprovalCategory.Id getCategoryId() {
return categoryId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {accountId, categoryId};

View File

@@ -32,85 +32,85 @@ import com.google.gwtorm.client.Sequence;
public interface ReviewDb extends Schema {
/* If you change anything, update SchemaVersion.C to use a new version. */
@Relation
@Relation(id = 1)
SchemaVersionAccess schemaVersion();
@Relation
@Relation(id = 2)
SystemConfigAccess systemConfig();
@Relation
@Relation(id = 3)
ApprovalCategoryAccess approvalCategories();
@Relation
@Relation(id = 4)
ApprovalCategoryValueAccess approvalCategoryValues();
@Relation
@Relation(id = 5)
ContributorAgreementAccess contributorAgreements();
@Relation
@Relation(id = 6)
AccountAccess accounts();
@Relation
@Relation(id = 7)
AccountExternalIdAccess accountExternalIds();
@Relation
@Relation(id = 8)
AccountSshKeyAccess accountSshKeys();
@Relation
@Relation(id = 9)
AccountAgreementAccess accountAgreements();
@Relation
@Relation(id = 10)
AccountGroupAccess accountGroups();
@Relation
@Relation(id = 11)
AccountGroupNameAccess accountGroupNames();
@Relation
@Relation(id = 12)
AccountGroupMemberAccess accountGroupMembers();
@Relation
@Relation(id = 13)
AccountGroupMemberAuditAccess accountGroupMembersAudit();
@Relation
@Relation(id = 14)
AccountGroupIncludeAccess accountGroupIncludes();
@Relation
@Relation(id = 15)
AccountGroupIncludeAuditAccess accountGroupIncludesAudit();
@Relation
@Relation(id = 16)
AccountGroupAgreementAccess accountGroupAgreements();
@Relation
@Relation(id = 17)
AccountDiffPreferenceAccess accountDiffPreferences();
@Relation
@Relation(id = 18)
StarredChangeAccess starredChanges();
@Relation
@Relation(id = 19)
AccountProjectWatchAccess accountProjectWatches();
@Relation
@Relation(id = 20)
AccountPatchReviewAccess accountPatchReviews();
@Relation
@Relation(id = 21)
ChangeAccess changes();
@Relation
@Relation(id = 22)
PatchSetApprovalAccess patchSetApprovals();
@Relation
@Relation(id = 23)
ChangeMessageAccess changeMessages();
@Relation
@Relation(id = 24)
PatchSetAccess patchSets();
@Relation
@Relation(id = 25)
PatchSetAncestorAccess patchSetAncestors();
@Relation
@Relation(id = 26)
PatchLineCommentAccess patchComments();
@Relation
@Relation(id = 27)
TrackingIdAccess trackingIds();
/** Create the next unique id for an {@link Account}. */

View File

@@ -43,6 +43,10 @@ public class StarredChange {
return accountId;
}
public Change.Id getChangeId() {
return changeId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {changeId};
@@ -59,6 +63,10 @@ public class StarredChange {
key = k;
}
public StarredChange.Key getKey() {
return key;
}
public Account.Id getAccountId() {
return key.accountId;
}

View File

@@ -102,6 +102,14 @@ public final class TrackingId {
return changeId;
}
public TrackingId.Id getTrackingId() {
return trackingId;
}
public TrackingId.System getTrackingSystem() {
return trackingSystem;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {trackingId, trackingSystem};
@@ -123,6 +131,10 @@ public final class TrackingId {
key = new Key(ch, new TrackingId.Id(id), new TrackingId.System(s));
}
public TrackingId.Key getKey() {
return key;
}
public Change.Id getChangeId() {
return key.changeId;
}

View File

@@ -215,6 +215,18 @@ limitations under the License.
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -90,6 +90,7 @@ public class RulesCache {
}
}
private final boolean enableProjectRules;
private final File cacheDir;
private final File rulesDir;
private final GitRepositoryManager gitMgr;
@@ -99,6 +100,7 @@ public class RulesCache {
@Inject
protected RulesCache(@GerritServerConfig Config config, SitePaths site,
GitRepositoryManager gm) {
enableProjectRules = config.getBoolean("rules", null, "enable", true);
cacheDir = site.resolve(config.getString("cache", null, "directory"));
rulesDir = cacheDir != null ? new File(cacheDir, "rules") : null;
gitMgr = gm;
@@ -117,7 +119,7 @@ public class RulesCache {
Project.NameKey project,
ObjectId rulesId)
throws CompileException {
if (project == null || rulesId == null) {
if (!enableProjectRules || project == null || rulesId == null) {
return defaultMachine;
}

View File

@@ -51,7 +51,7 @@ public final class StoredValues {
PatchSetInfoFactory patchInfoFactory =
env.getInjector().getInstance(PatchSetInfoFactory.class);
try {
return patchInfoFactory.get(patchSetId);
return patchInfoFactory.get(REVIEW_DB.get(engine), patchSetId);
} catch (PatchSetInfoNotAvailableException e) {
throw new SystemException(e.getMessage());
}

View File

@@ -33,16 +33,21 @@ public class RequestCleanup implements Runnable {
LoggerFactory.getLogger(RequestCleanup.class);
private final List<Runnable> cleanup = new LinkedList<Runnable>();
private boolean ran;
/** Register a task to be completed after the request ends. */
public void add(final Runnable task) {
synchronized (cleanup) {
if (ran) {
throw new IllegalStateException("Request has already been cleaned up");
}
cleanup.add(task);
}
}
public void run() {
synchronized (cleanup) {
ran = true;
for (final Iterator<Runnable> i = cleanup.iterator(); i.hasNext();) {
try {
i.next().run();

View File

@@ -21,7 +21,7 @@ import com.google.inject.Inject;
import java.util.Collections;
import java.util.Set;
public final class DefaultRealm implements Realm {
public class DefaultRealm implements Realm {
private final EmailExpander emailExpander;
private final AccountByEmailCache byEmail;

View File

@@ -22,7 +22,6 @@ import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import net.sf.ehcache.CacheManager;
@@ -214,8 +213,8 @@ public class CachePool {
}
}
private void configureDefaultCache() {
final CacheConfiguration c = new CacheConfiguration();
private CacheConfiguration newConfiguration() {
CacheConfiguration c = new CacheConfiguration();
c.setMaxElementsInMemory(1024);
c.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU);
@@ -232,19 +231,17 @@ public class CachePool {
c.setDiskSpoolBufferSizeMB(5);
c.setDiskExpiryThreadIntervalSeconds(60 * 60);
}
return c;
}
mgr.setDefaultCacheConfiguration(c);
private void configureDefaultCache() {
mgr.setDefaultCacheConfiguration(newConfiguration());
}
private CacheConfiguration newCache(final String name) {
try {
final CacheConfiguration c;
c = mgr.getDefaultCacheConfiguration().clone();
c.setName(name);
return c;
} catch (CloneNotSupportedException e) {
throw new ProvisionException("Cannot configure cache " + name, e);
}
CacheConfiguration c = newConfiguration();
c.setName(name);
return c;
}
}
}

View File

@@ -140,6 +140,7 @@ public class AuthConfig {
case LDAP:
case LDAP_BIND:
case CLIENT_SSL_CERT_LDAP:
case CUSTOM_EXTENSION:
// Its safe to assume yes for an HTTP authentication type, as the
// only way in is through some external system that the admin trusts
//

View File

@@ -17,7 +17,6 @@ package com.google.gerrit.server.config;
import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.AuthType;
import com.google.gerrit.rules.PrologModule;
@@ -43,17 +42,13 @@ import com.google.gerrit.server.git.ChangeMergeQueue;
import com.google.gerrit.server.git.GitModule;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.PushReplication;
import com.google.gerrit.server.git.ReloadSubmitQueueOp;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.git.SecureCredentialsProvider;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.EmailSender;
import com.google.gerrit.server.mail.FromAddressGenerator;
import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.mail.VelocityRuntimeProvider;
import com.google.gerrit.server.patch.PatchListCacheImpl;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.AccessControlModule;
@@ -68,51 +63,14 @@ import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.inject.Inject;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeInstance;
import org.eclipse.jgit.lib.Config;
import java.util.Properties;
/** Starts global state with standard dependencies. */
public class GerritGlobalModule extends FactoryModule {
private final AuthType loginType;
public static class VelocityLifecycle implements LifecycleListener {
private final SitePaths site;
@Inject
VelocityLifecycle(final SitePaths site) {
this.site = site;
}
@Override
public void start() {
String rl = "resource.loader";
String pkg = "org.apache.velocity.runtime.resource.loader";
Properties p = new Properties();
p.setProperty(rl, "file, class");
p.setProperty("file." + rl + ".class", pkg + ".FileResourceLoader");
p.setProperty("file." + rl + ".path", site.mail_dir.getAbsolutePath());
p.setProperty("class." + rl + ".class", pkg + ".ClasspathResourceLoader");
p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
"org.apache.velocity.runtime.log.SimpleLog4JLogSystem" );
p.setProperty("runtime.log.logsystem.log4j.category", "velocity");
try {
Velocity.init(p);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void stop() {
}
}
@Inject
GerritGlobalModule(final AuthConfig authConfig,
@GerritServerConfig final Config config) {
@@ -129,6 +87,9 @@ public class GerritGlobalModule extends FactoryModule {
install(new LdapModule());
break;
case CUSTOM_EXTENSION:
break;
default:
bind(Realm.class).to(DefaultRealm.class);
break;
@@ -161,21 +122,21 @@ public class GerritGlobalModule extends FactoryModule {
bind(PermissionCollection.Factory.class);
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
bind(WorkQueue.class);
bind(ToolsCatalog.class);
bind(EventFactory.class);
bind(TransferConfig.class);
bind(ReplicationQueue.class).to(PushReplication.class).in(SINGLETON);
factory(SecureCredentialsProvider.Factory.class);
factory(PushAllProjectsOp.Factory.class);
bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON);
factory(ReloadSubmitQueueOp.Factory.class);
bind(RuntimeInstance.class)
.toProvider(VelocityRuntimeProvider.class)
.in(SINGLETON);
bind(FromAddressGenerator.class).toProvider(
FromAddressGeneratorProvider.class).in(SINGLETON);
bind(EmailSender.class).to(SmtpEmailSender.class).in(SINGLETON);
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
@@ -188,8 +149,6 @@ public class GerritGlobalModule extends FactoryModule {
@Override
protected void configure() {
listener().to(CachePool.Lifecycle.class);
listener().to(WorkQueue.Lifecycle.class);
listener().to(VelocityLifecycle.class);
}
});
}

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.RequestCleanup;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.Database;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
@@ -26,11 +26,11 @@ import com.google.inject.Singleton;
/** Provides {@link ReviewDb} database handle live only for this request. */
@Singleton
final class RequestScopedReviewDbProvider implements Provider<ReviewDb> {
private final Database<ReviewDb> schema;
private final SchemaFactory<ReviewDb> schema;
private final Provider<RequestCleanup> cleanup;
@Inject
RequestScopedReviewDbProvider(final Database<ReviewDb> schema,
RequestScopedReviewDbProvider(final SchemaFactory<ReviewDb> schema,
final Provider<RequestCleanup> cleanup) {
this.schema = schema;
this.cleanup = cleanup;

View File

@@ -15,9 +15,11 @@
package com.google.gerrit.server.git;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -54,6 +56,20 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
private static final String UNNAMED =
"Unnamed repository; edit this file to name it for gitweb.";
public static class Module extends AbstractModule {
@Override
protected void configure() {
bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
install(new LifecycleModule() {
@Override
protected void configure() {
listener().to(LocalDiskRepositoryManager.Lifecycle.class);
}
});
}
}
public static class Lifecycle implements LifecycleListener {
private final Config cfg;

View File

@@ -1309,7 +1309,7 @@ public class MergeOp {
// Go back to the patch set that was actually merged.
//
try {
c.setCurrentPatchSet(patchSetInfoFactory.get(merged));
c.setCurrentPatchSet(patchSetInfoFactory.get(schema, merged));
} catch (PatchSetInfoNotAvailableException e1) {
log.error("Cannot read merged patch set " + merged, e1);
}

View File

@@ -72,6 +72,13 @@ import java.util.concurrent.TimeUnit;
public class PushReplication implements ReplicationQueue {
static final Logger log = LoggerFactory.getLogger(PushReplication.class);
public static class Module extends AbstractModule {
@Override
protected void configure() {
bind(ReplicationQueue.class).to(PushReplication.class);
}
}
static {
// Install our own factory which always runs in batch mode, as we
// have no UI available for interactive prompting.

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.util.IdGenerator;
import com.google.inject.Inject;
@@ -61,6 +62,14 @@ public class WorkQueue {
}
}
public static class Module extends LifecycleModule {
@Override
protected void configure() {
bind(WorkQueue.class);
listener().to(Lifecycle.class);
}
}
private static final Logger log = LoggerFactory.getLogger(WorkQueue.class);
private static final UncaughtExceptionHandler LOG_UNCAUGHT_EXCEPTION =
new UncaughtExceptionHandler() {

View File

@@ -16,8 +16,8 @@ package com.google.gerrit.server.mail;
import java.io.UnsupportedEncodingException;
class Address {
static Address parse(final String in) {
public class Address {
public static Address parse(final String in) {
final int lt = in.indexOf('<');
final int gt = in.indexOf('>');
final int at = in.indexOf("@");
@@ -37,15 +37,23 @@ class Address {
final String name;
final String email;
Address(String email) {
public Address(String email) {
this(null, email);
}
Address(String name, String email) {
public Address(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
@Override
public String toString() {
try {
@@ -55,7 +63,7 @@ class Address {
}
}
String toHeaderString() throws UnsupportedEncodingException {
public String toHeaderString() throws UnsupportedEncodingException {
if (name != null) {
return quotedPhrase(name) + " <" + email + ">";
} else if (isSimple()) {

View File

@@ -72,7 +72,8 @@ public abstract class ChangeEmail extends OutgoingEmail {
final IdentifiedUser user = args.identifiedUserFactory.create(id);
final Set<AccountGroup.UUID> gids = user.getEffectiveGroups();
for (final AccountGroup.UUID gid : gids) {
if (args.groupCache.get(gid).isEmailOnlyAuthors()) {
AccountGroup group = args.groupCache.get(gid);
if (group != null && group.isEmailOnlyAuthors()) {
emailOnlyAuthors = true;
break;
}
@@ -136,7 +137,7 @@ public abstract class ChangeEmail extends OutgoingEmail {
if (patchSet != null && patchSetInfo == null) {
try {
patchSetInfo = args.patchSetInfoFactory.get(patchSet.getId());
patchSetInfo = args.patchSetInfoFactory.get(args.db.get(), patchSet.getId());
} catch (PatchSetInfoNotAvailableException err) {
patchSetInfo = null;
}

View File

@@ -21,7 +21,6 @@ import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -31,6 +30,8 @@ import com.google.gerrit.server.query.change.ChangeQueryRewriter;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.velocity.runtime.RuntimeInstance;
import javax.annotation.Nullable;
class EmailArguments {
@@ -49,7 +50,7 @@ class EmailArguments {
final ChangeQueryBuilder.Factory queryBuilder;
final Provider<ChangeQueryRewriter> queryRewriter;
final Provider<ReviewDb> db;
final SitePaths site;
final RuntimeInstance velocityRuntime;
@Inject
EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
@@ -61,7 +62,7 @@ class EmailArguments {
AllProjectsName allProjectsName,
ChangeQueryBuilder.Factory queryBuilder,
Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db,
SitePaths site) {
RuntimeInstance velocityRuntime) {
this.server = server;
this.projectCache = projectCache;
this.groupCache = groupCache;
@@ -76,6 +77,6 @@ class EmailArguments {
this.queryBuilder = queryBuilder;
this.queryRewriter = queryRewriter;
this.db = db;
this.site = site;
this.velocityRuntime = velocityRuntime;
}
}

View File

@@ -20,28 +20,33 @@ import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
abstract class EmailHeader {
abstract boolean isEmpty();
public abstract class EmailHeader {
public abstract boolean isEmpty();
abstract void write(Writer w) throws IOException;
public abstract void write(Writer w) throws IOException;
static class String extends EmailHeader {
public static class String extends EmailHeader {
private java.lang.String value;
String(java.lang.String v) {
public String(java.lang.String v) {
value = v;
}
public java.lang.String getString() {
return value;
}
@Override
boolean isEmpty() {
public boolean isEmpty() {
return value == null || value.length() == 0;
}
@Override
void write(Writer w) throws IOException {
public void write(Writer w) throws IOException {
if (needsQuotedPrintable(value)) {
w.write(quotedPrintable(value));
} else {
@@ -84,20 +89,24 @@ abstract class EmailHeader {
return r.toString();
}
static class Date extends EmailHeader {
public static class Date extends EmailHeader {
private java.util.Date value;
Date(java.util.Date v) {
public Date(java.util.Date v) {
value = v;
}
public java.util.Date getDate() {
return value;
}
@Override
boolean isEmpty() {
public boolean isEmpty() {
return value == null;
}
@Override
void write(Writer w) throws IOException {
public void write(Writer w) throws IOException {
final SimpleDateFormat fmt;
// Mon, 1 Jun 2009 10:49:44 -0700
fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
@@ -105,17 +114,21 @@ abstract class EmailHeader {
}
}
static class AddressList extends EmailHeader {
public static class AddressList extends EmailHeader {
private final List<Address> list = new ArrayList<Address>();
AddressList() {
public AddressList() {
}
AddressList(Address addr) {
public AddressList(Address addr) {
add(addr);
}
void add(Address addr) {
public List<Address> getAddressList() {
return Collections.unmodifiableList(list);
}
public void add(Address addr) {
list.add(addr);
}
@@ -128,12 +141,12 @@ abstract class EmailHeader {
}
@Override
boolean isEmpty() {
public boolean isEmpty() {
return list.isEmpty();
}
@Override
void write(Writer w) throws IOException {
public void write(Writer w) throws IOException {
int len = 8;
boolean firstAddress = true;
boolean needComma = false;

View File

@@ -20,12 +20,16 @@ import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.mail.EmailHeader.AddressList;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.context.InternalContextAdapterImpl;
import org.apache.velocity.runtime.RuntimeInstance;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
@@ -349,24 +353,41 @@ public abstract class OutgoingEmail {
protected String velocify(String template) throws EmailException {
try {
StringWriter w = new StringWriter();
Velocity.evaluate(velocityContext, w, "OutgoingEmail", template);
return w.toString();
} catch(Exception e) {
throw new EmailException("Velocity template " + template, e);
RuntimeInstance runtime = args.velocityRuntime;
String templateName = "OutgoingEmail";
SimpleNode tree = runtime.parse(new StringReader(template), templateName);
InternalContextAdapterImpl ica = new InternalContextAdapterImpl(velocityContext);
ica.pushCurrentTemplateName(templateName);
try {
tree.init(ica, runtime);
StringWriter w = new StringWriter();
tree.render(ica, w);
return w.toString();
} finally {
ica.popCurrentTemplateName();
}
} catch (Exception e) {
throw new EmailException("Cannot format velocity template: " + template, e);
}
}
protected String velocifyFile(String name) throws EmailException {
if (!Velocity.resourceExists(name)) {
name = "com/google/gerrit/server/mail/" + name;
}
try {
RuntimeInstance runtime = args.velocityRuntime;
Template template;
try {
template = runtime.getTemplate(name, "UTF-8");
} catch (org.apache.velocity.exception.ResourceNotFoundException notFound) {
name = "com/google/gerrit/server/mail/" + name;
template = runtime.getTemplate(name, "UTF-8");
}
StringWriter w = new StringWriter();
Velocity.mergeTemplate(name, "UTF-8", velocityContext, w);
template.merge(velocityContext, w);
return w.toString();
} catch(Exception e) {
throw new EmailException("Velocity template " + name + ".\n", e);
} catch (EmailException e) {
throw e;
} catch (Exception e) {
throw new EmailException("Cannot format velocity template " + name, e);
}
}

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.server.mail;
import com.google.gerrit.common.Version;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -40,6 +41,13 @@ import java.util.Set;
/** Sends email via a nearby SMTP server. */
@Singleton
public class SmtpEmailSender implements EmailSender {
public static class Module extends AbstractModule {
@Override
protected void configure() {
bind(EmailSender.class).to(SmtpEmailSender.class);
}
}
public static enum Encryption {
NONE, SSL, TLS;
}

View File

@@ -0,0 +1,63 @@
// Copyright (C) 2011 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.mail;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeInstance;
import java.util.Properties;
/** Configures Velocity template engine for sending email. */
public class VelocityRuntimeProvider implements Provider<RuntimeInstance> {
private final SitePaths site;
@Inject
VelocityRuntimeProvider(SitePaths site) {
this.site = site;
}
public RuntimeInstance get() {
String rl = "resource.loader";
String pkg = "org.apache.velocity.runtime.resource.loader";
Properties p = new Properties();
p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
"org.apache.velocity.runtime.log.SimpleLog4JLogSystem" );
p.setProperty("runtime.log.logsystem.log4j.category", "velocity");
if (site.mail_dir.isDirectory()) {
p.setProperty(rl, "file, class");
p.setProperty("file." + rl + ".class", pkg + ".FileResourceLoader");
p.setProperty("file." + rl + ".path", site.mail_dir.getAbsolutePath());
p.setProperty("class." + rl + ".class", pkg + ".ClasspathResourceLoader");
} else {
p.setProperty(rl, "class");
p.setProperty("class." + rl + ".class", pkg + ".ClasspathResourceLoader");
}
RuntimeInstance ri = new RuntimeInstance();
try {
ri.init(p);
} catch (Exception err) {
throw new ProvisionException("Cannot configure Velocity templates", err);
}
return ri;
}
}

View File

@@ -25,7 +25,6 @@ import com.google.gerrit.reviewdb.UserIdentity;
import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -49,15 +48,12 @@ import java.util.Set;
@Singleton
public class PatchSetInfoFactory {
private final GitRepositoryManager repoManager;
private final SchemaFactory<ReviewDb> schemaFactory;
private final AccountByEmailCache byEmailCache;
@Inject
public PatchSetInfoFactory(final GitRepositoryManager grm,
final SchemaFactory<ReviewDb> schemaFactory,
final AccountByEmailCache byEmailCache) {
this.repoManager = grm;
this.schemaFactory = schemaFactory;
this.byEmailCache = byEmailCache;
}
@@ -68,16 +64,13 @@ public class PatchSetInfoFactory {
info.setAuthor(toUserIdentity(src.getAuthorIdent()));
info.setCommitter(toUserIdentity(src.getCommitterIdent()));
info.setRevId(src.getName());
return info;
}
public PatchSetInfo get(PatchSet.Id patchSetId)
throws PatchSetInfoNotAvailableException {
ReviewDb db = null;
public PatchSetInfo get(ReviewDb db, PatchSet.Id patchSetId)
throws PatchSetInfoNotAvailableException {
Repository repo = null;
try {
db = schemaFactory.open();
final PatchSet patchSet = db.patchSets().get(patchSetId);
final Change change = db.changes().get(patchSet.getId().getParentKey());
final Project.NameKey projectKey = change.getProject();
@@ -97,9 +90,6 @@ public class PatchSetInfoFactory {
} catch (IOException e) {
throw new PatchSetInfoNotAvailableException(e);
} finally {
if (db != null) {
db.close();
}
if (repo != null) {
repo.close();
}

View File

@@ -116,18 +116,25 @@ public class PublishComments implements Callable<VoidResult> {
}
drafts = drafts();
publishDrafts();
db.changes().beginTransaction(changeId);
try {
publishDrafts();
final boolean isCurrent = patchSetId.equals(change.currentPatchSetId());
if (isCurrent && change.getStatus().isOpen()) {
publishApprovals(ctl);
} else if (! approvals.isEmpty()) {
throw new InvalidChangeOperationException("Change is closed");
} else {
publishMessageOnly();
final boolean isCurrent = patchSetId.equals(change.currentPatchSetId());
if (isCurrent && change.getStatus().isOpen()) {
publishApprovals(ctl);
} else if (!approvals.isEmpty()) {
throw new InvalidChangeOperationException("Change is closed");
} else {
publishMessageOnly();
}
touchChange();
db.commit();
} finally {
db.rollback();
}
touchChange();
email();
fireHook();
return VoidResult.INSTANCE;
@@ -280,7 +287,7 @@ public class PublishComments implements Callable<VoidResult> {
}
private List<PatchLineComment> drafts() throws OrmException {
return db.patchComments().draft(patchSetId, user.getAccountId()).toList();
return db.patchComments().draftByPatchSetAuthor(patchSetId, user.getAccountId()).toList();
}
private void email() {
@@ -288,7 +295,7 @@ public class PublishComments implements Callable<VoidResult> {
if (message != null) {
final CommentSender cm = commentSenderFactory.create(change);
cm.setFrom(user.getAccountId());
cm.setPatchSet(patchSet, patchSetInfoFactory.get(patchSetId));
cm.setPatchSet(patchSet, patchSetInfoFactory.get(db, patchSetId));
cm.setChangeMessage(message);
cm.setPatchLineComments(drafts);
cm.send();

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.server.schema;
import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.config.AllProjectsName;
@@ -24,8 +23,6 @@ import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import org.eclipse.jgit.lib.PersonIdent;
@@ -44,13 +41,5 @@ public class SchemaModule extends FactoryModule {
bind(String.class).annotatedWith(AnonymousCowardName.class).toProvider(
AnonymousCowardNameProvider.class);
bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
install(new LifecycleModule() {
@Override
protected void configure() {
listener().to(LocalDiskRepositoryManager.Lifecycle.class);
}
});
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2010 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.ssh;
import com.google.gerrit.server.ssh.SshInfo;
import com.jcraft.jsch.HostKey;
import java.util.Collections;
import java.util.List;
class NoSshInfo implements SshInfo {
@Override
public List<HostKey> getHostKeys() {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2010 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.ssh;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.reviewdb.AccountSshKey;
class NoSshKeyCache implements SshKeyCache {
@Override
public void evict(String username) {
}
@Override
public AccountSshKey create(AccountSshKey.Id id, String encoded)
throws InvalidSshKeyException {
throw new InvalidSshKeyException();
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2010 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.ssh;
import com.google.inject.AbstractModule;
/**
* Disables the SSH support by stubbing out relevant objects.
*/
public class NoSshModule extends AbstractModule {
@Override
protected void configure() {
bind(SshInfo.class).to(NoSshInfo.class);
bind(SshKeyCache.class).to(NoSshKeyCache.class);
}
}

View File

@@ -216,7 +216,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
@Override
public synchronized void start() {
if (acceptor == null) {
if (acceptor == null && !listen.isEmpty()) {
checkConfig();
acceptor = createAcceptor();
@@ -257,6 +257,10 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
}
private List<HostKey> computeHostKeys() {
if (listen.isEmpty()) {
return Collections.emptyList();
}
final List<PublicKey> keys = myHostKeys();
final ArrayList<HostKey> r = new ArrayList<HostKey>();
for (final PublicKey pub : keys) {
@@ -348,6 +352,10 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
return bind;
}
if (want.length == 1 && isOff(want[0])) {
return bind;
}
for (final String desc : want) {
try {
bind.add(SocketUtil.resolve(desc, DEFAULT_PORT));
@@ -358,6 +366,12 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
return bind;
}
private static boolean isOff(String listenHostname) {
return "off".equalsIgnoreCase(listenHostname)
|| "none".equalsIgnoreCase(listenHostname)
|| "no".equalsIgnoreCase(listenHostname);
}
@SuppressWarnings("unchecked")
private void initProviderBouncyCastle() {
setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>> asList(

View File

@@ -25,6 +25,10 @@ import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.PushReplication;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DatabaseModule;
import com.google.gerrit.server.schema.SchemaModule;
@@ -169,6 +173,7 @@ public class WebAppInitializer extends GuiceServletContextListener {
modules.add(new GerritServerConfigModule());
}
modules.add(new SchemaModule());
modules.add(new LocalDiskRepositoryManager.Module());
modules.add(SchemaVersionCheck.module());
modules.add(new AuthConfigModule());
return dbInjector.createChildInjector(modules);
@@ -176,7 +181,10 @@ public class WebAppInitializer extends GuiceServletContextListener {
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<Module>();
modules.add(new WorkQueue.Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
modules.add(new SmtpEmailSender.Module());
modules.add(new PushReplication.Module());
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
@@ -197,6 +205,9 @@ public class WebAppInitializer extends GuiceServletContextListener {
private Injector createWebInjector() {
final List<Module> modules = new ArrayList<Module>();
modules.add(sshInjector.getInstance(WebModule.class));
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
modules.add(sshInjector.getInstance(WebSshGlueModule.class));
modules.add(CacheBasedWebSession.module());
return sysInjector.createChildInjector(modules);
}

View File

@@ -47,7 +47,7 @@ limitations under the License.
<properties>
<jgitVersion>1.0.0.201106090707-r.19-g8d88a84</jgitVersion>
<gwtormVersion>1.1.5</gwtormVersion>
<gwtormVersion>1.2-SNAPSHOT</gwtormVersion>
<gwtjsonrpcVersion>1.2.5</gwtjsonrpcVersion>
<gwtexpuiVersion>1.2.5</gwtexpuiVersion>
<gwtVersion>2.3.0</gwtVersion>