Pull-request importer screen.
GitHub pull request importer screen: repos
configured in Gerrit for replication with GitHub
are scanned and list of opened GitHub pull requests
are displayed for selection.
Pull requests previously imported in Gerrit are
omitted from the list. Pull requests with new commits
are displayed, as the new commits will be imported
as new dependent changes.
Change-Id: I7e6a02859e6d5a7e76691b52625cdcacd2da2960
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
index 2027268..b7f45ed 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
@@ -16,6 +16,7 @@
import java.io.IOException;
import java.util.Arrays;
+import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -48,7 +49,11 @@
private GHMyself myself;
public GHMyself getMyself() {
- return myself;
+ if (isLoggedIn(scopesSet)) {
+ return myself;
+ } else {
+ return null;
+ }
}
@Inject
@@ -56,16 +61,18 @@
this.oauth = oauth;
}
- public GitHubLogin(GitHub hub, AccessToken token) {
+ public GitHubLogin(GitHub hub, AccessToken token, Scope... scopes) {
this.hub = hub;
this.token = token;
+ this.scopesSet = new TreeSet<OAuthProtocol.Scope>(Arrays.asList(scopes));
}
public boolean isLoggedIn(Scope... scopes) {
- SortedSet<Scope> inputScopes =
- new TreeSet<OAuthProtocol.Scope>(Arrays.asList(scopes));
- boolean loggedIn =
- scopesSet.equals(inputScopes) && token != null && hub != null;
+ return isLoggedIn(new TreeSet<Scope>(Arrays.asList(scopes)));
+ }
+
+ public boolean isLoggedIn(Set<Scope> scopes) {
+ boolean loggedIn = scopesSet.equals(scopes) && token != null && hub != null;
if (loggedIn) {
try {
myself = hub.getMyself();
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthConfig.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
similarity index 82%
rename from github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthConfig.java
rename to github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
index 26d9a50..6645b69 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthConfig.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
@@ -29,7 +29,8 @@
import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
@Singleton
-public class OAuthConfig {
+public class GitHubOAuthConfig {
+ protected static final String CONF_SECTION = "github";
private static final String LOGIN_OAUTH_AUTHORIZE = "/login/oauth/authorize";
private static final String GITHUB_URL = "https://github.com";
public static final String OAUTH_FINAL = "/oauth";
@@ -52,26 +53,30 @@
public final List<OAuthProtocol.Scope> scopes;
@Inject
- public OAuthConfig(@GerritServerConfig Config config)
+ public GitHubOAuthConfig(@GerritServerConfig Config config)
throws MalformedURLException {
httpHeader = config.getString("auth", null, "httpHeader");
httpDisplaynameHeader = config.getString("auth", null, "httpDisplaynameHeader");
httpEmailHeader = config.getString("auth", null, "httpEmailHeader");
- gitHubUrl =
- Objects.firstNonNull(config.getString("github", null, "url"),
- GITHUB_URL);
- gitHubClientId = config.getString("github", null, "clientId");
- gitHubClientSecret = config.getString("github", null, "clientSecret");
+ gitHubUrl = dropTrailingSlash(
+ Objects.firstNonNull(config.getString(CONF_SECTION, null, "url"),
+ GITHUB_URL));
+ gitHubClientId = config.getString(CONF_SECTION, null, "clientId");
+ gitHubClientSecret = config.getString(CONF_SECTION, null, "clientSecret");
gitHubOAuthUrl = getUrl(gitHubUrl, LOGIN_OAUTH_AUTHORIZE);
gitHubOAuthAccessTokenUrl = getUrl(gitHubUrl, LOGIN_OAUTH_ACCESS_TOKEN);
- logoutRedirectUrl = config.getString("github", null, "logoutRedirectUrl");
+ logoutRedirectUrl = config.getString(CONF_SECTION, null, "logoutRedirectUrl");
oAuthFinalRedirectUrl =
getUrl(config.getString("gerrit", null, "canonicalWebUrl"), OAUTH_FINAL);
enabled =
config.getString("auth", null, "type").equalsIgnoreCase(
AuthType.HTTP.toString());
- scopes = parseScopes(config.getString("github", null, "scopes"));
+ scopes = parseScopes(config.getString(CONF_SECTION, null, "scopes"));
+ }
+
+ private String dropTrailingSlash(String url) {
+ return (url.endsWith("/") ? url.substring(0, url.length()-1):url);
}
private List<Scope> parseScopes(String scopesString) {
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
index 830b0a7..ac4340b 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
@@ -42,12 +42,12 @@
.getLogger(OAuthFilter.class);
private static final String GERRIT_COOKIE_NAME = "GerritAccount";
- private final OAuthConfig config;
+ private final GitHubOAuthConfig config;
private final OAuthCookieProvider cookieProvider;
private final OAuthProtocol oauth;
@Inject
- public OAuthFilter(OAuthConfig config) {
+ public OAuthFilter(GitHubOAuthConfig config) {
this.config = config;
this.cookieProvider = new OAuthCookieProvider(new TokenCipher());
HttpClient httpClient;
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
index 81baa68..1d281c6 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
@@ -60,17 +60,25 @@
private static final Logger log = LoggerFactory
.getLogger(OAuthProtocol.class);
- private final OAuthConfig config;
+ private final GitHubOAuthConfig config;
private final HttpClient http;
private final Gson gson;
public static class AccessToken {
public String access_token;
public String token_type;
+
+ public AccessToken() {
+ }
+
+ public AccessToken(String token, String type) {
+ this.access_token = token;
+ this.token_type = type;
+ }
}
@Inject
- public OAuthProtocol(OAuthConfig config, HttpClient httpClient, Gson gson) {
+ public OAuthProtocol(GitHubOAuthConfig config, HttpClient httpClient, Gson gson) {
this.config = config;
this.http = httpClient;
this.gson = gson;
@@ -128,11 +136,11 @@
}
public boolean isOAuthLogin(HttpServletRequest request) {
- return request.getRequestURI().indexOf(OAuthConfig.OAUTH_LOGIN) >= 0;
+ return request.getRequestURI().indexOf(GitHubOAuthConfig.OAUTH_LOGIN) >= 0;
}
public boolean isOAuthLogout(HttpServletRequest request) {
- return request.getRequestURI().indexOf(OAuthConfig.OAUTH_LOGOUT) >= 0;
+ return request.getRequestURI().indexOf(GitHubOAuthConfig.OAUTH_LOGOUT) >= 0;
}
public GitHubLogin loginPhase2(HttpServletRequest request,
diff --git a/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImpoter.java b/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImpoter.java
new file mode 100644
index 0000000..7d96b56
--- /dev/null
+++ b/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImpoter.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.server.account;
+
+import java.io.IOException;
+
+import org.apache.http.HttpStatus;
+import org.kohsuke.github.GHUser;
+
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.CreateAccount.Factory;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+public class AccountImpoter {
+ private Factory createAccountFactory;
+
+ @Inject
+ public AccountImpoter(CreateAccount.Factory createAccountFactory) {
+ this.createAccountFactory = createAccountFactory;
+ }
+
+ public Account.Id importAccount(GHUser user) throws IOException,
+ BadRequestException, ResourceConflictException,
+ UnprocessableEntityException, OrmException {
+ CreateAccount createAccount = createAccountFactory.create(user.getLogin());
+ CreateAccount.Input accountInput = new CreateAccount.Input();
+ accountInput.email = user.getEmail();
+ accountInput.name = user.getName();
+ accountInput.username = user.getLogin();
+ Response<AccountInfo> accountResponse =
+ (Response<AccountInfo>) createAccount.apply(TopLevelResource.INSTANCE,
+ accountInput);
+ if (accountResponse.statusCode() == HttpStatus.SC_CREATED) {
+ return accountResponse.value()._id;
+ } else {
+ throw new IOException("Cannot import GitHub account " + user.getLogin()
+ + ": HTTP Status " + accountResponse.statusCode());
+ }
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
index 649929b..9c80aa6 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
@@ -13,24 +13,32 @@
// limitations under the License.
package com.googlesource.gerrit.plugins.github;
+import java.io.File;
+import java.net.MalformedURLException;
import java.util.HashMap;
import org.eclipse.jgit.lib.Config;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig;
@Singleton
-public class GitHubConfig {
+public class GitHubConfig extends GitHubOAuthConfig {
- private static final String CONF_SECTION = "github";
private static final String CONF_WIZARD_FLOW = "wizardFlow";
private HashMap<String, String> wizardFromTo = new HashMap<String, String>();
private static final String FROM_TO_SEPARATOR = "=>";
+ public final File gitDir;
+
+
@Inject
- public GitHubConfig(@GerritServerConfig Config config) {
+ public GitHubConfig(@GerritServerConfig Config config, final SitePaths site)
+ throws MalformedURLException {
+ super(config);
String[] wizardFlows =
config.getStringList(CONF_SECTION, null, CONF_WIZARD_FLOW);
for (String fromTo : wizardFlows) {
@@ -40,6 +48,10 @@
fromTo.substring(sepPos + FROM_TO_SEPARATOR.length() + 1).trim();
wizardFromTo.put(fromPage, toPage);
}
+ gitDir = site.resolve(config.getString("gerrit", null, "basePath"));
+ if (gitDir == null) {
+ throw new IllegalStateException("gerrit.basePath must be configured");
+ }
}
private int getSepPos(String fromTo) {
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubURL.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubURL.java
new file mode 100644
index 0000000..44875e4
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubURL.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@BindingAnnotation
+public @interface GitHubURL {
+
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubURLProvider.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubURLProvider.java
new file mode 100644
index 0000000..ae5ca0f
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubURLProvider.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class GitHubURLProvider implements Provider<String> {
+
+ private String gitHubUrl;
+
+ @Inject
+ public GitHubURLProvider(GitHubConfig gitHubConfig) {
+ this.gitHubUrl = gitHubConfig.gitHubUrl;
+ }
+
+ @Override
+ public String get() {
+ return gitHubUrl;
+ }
+
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java
index a24f3a5..cc00a73 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java
@@ -21,7 +21,6 @@
import com.googlesource.gerrit.plugins.github.oauth.GitHubHttpProvider;
import com.googlesource.gerrit.plugins.github.pullsync.PullRequestsServlet;
import com.googlesource.gerrit.plugins.github.replication.RemoteSiteUser;
-import com.googlesource.gerrit.plugins.github.velocity.PluginVelocityModelFilter;
import com.googlesource.gerrit.plugins.github.velocity.VelocityStaticServlet;
import com.googlesource.gerrit.plugins.github.velocity.VelocityViewServlet;
import com.googlesource.gerrit.plugins.github.wizard.VelocityControllerServlet;
@@ -40,8 +39,5 @@
serve("*.gh").with(VelocityControllerServlet.class);
filter("*").through(GitHubOAuthFilter.class);
- filter("*.html").through(PluginVelocityModelFilter.class);
-
-
}
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java
index 127da64..1199698 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java
@@ -23,6 +23,8 @@
import com.googlesource.gerrit.plugins.github.velocity.PluginVelocityRuntimeProvider;
import com.googlesrouce.gerrit.plugins.github.git.CreateProjectStep;
import com.googlesrouce.gerrit.plugins.github.git.GitCloneStep;
+import com.googlesrouce.gerrit.plugins.github.git.PullRequestImportJob;
+import com.googlesrouce.gerrit.plugins.github.git.PullRequestImporter;
import com.googlesrouce.gerrit.plugins.github.git.ReplicateProjectStep;
public class GuiceModule extends AbstractModule {
@@ -34,10 +36,13 @@
CreateProjectStep.class).build(CreateProjectStep.Factory.class));
install(new FactoryModuleBuilder().implement(ReplicateProjectStep.class,
ReplicateProjectStep.class).build(ReplicateProjectStep.Factory.class));
+ install(new FactoryModuleBuilder().implement(PullRequestImportJob.class,
+ PullRequestImportJob.class).build(PullRequestImportJob.Factory.class));
bind(RuntimeInstance.class).annotatedWith(
Names.named("PluginRuntimeInstance")).toProvider(
PluginVelocityRuntimeProvider.class);
-
+
+ bind(String.class).annotatedWith(GitHubURL.class).toProvider(GitHubURLProvider.class);
}
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/OnStartStop.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/OnStartStop.java
new file mode 100644
index 0000000..26512fd
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/OnStartStop.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.github;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class OnStartStop implements LifecycleListener {
+ private static final Logger LOG = LoggerFactory.getLogger(OnStartStop.class);
+
+ @Inject
+ public OnStartStop() {
+ }
+
+ @Override
+ public void start() {
+ LOG.info("Starting up ...");
+ }
+
+ @Override
+ public void stop() {
+ LOG.info("Stopping ...");
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/PluginVelocityModelFilter.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/PluginVelocityModelFilter.java
deleted file mode 100644
index 402bd51..0000000
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/PluginVelocityModelFilter.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.googlesource.gerrit.plugins.github.velocity;
-
-import java.io.IOException;
-import java.util.Map.Entry;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
-import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
-
-@Singleton
-public class PluginVelocityModelFilter implements Filter {
-
- private final Provider<PluginVelocityModel> modelProvider;
- private final Provider<GitHubLogin> loginProvider;
- private final Provider<IdentifiedUser> userProvider;
-
-
- @Inject
- public PluginVelocityModelFilter(Provider<PluginVelocityModel> modelProvider,
- Provider<GitHubLogin> loginProvider, Provider<IdentifiedUser> userProvider) {
- this.modelProvider = modelProvider;
- this.loginProvider = loginProvider;
- this.userProvider = userProvider;
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
-
- @Override
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
-
- PluginVelocityModel model = modelProvider.get();
- model.put("myself", loginProvider.get().getMyself());
- model.put("user", userProvider.get());
- model.put("hub", loginProvider.get().hub);
-
- for (Entry<String, String[]> reqPar : request.getParameterMap().entrySet()) {
- model.put(reqPar.getKey(), reqPar.getValue());
- }
-
- chain.doFilter(request, response);
- }
-
- @Override
- public void destroy() {
- }
-}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
index 90335ba..45b195c 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
@@ -14,8 +14,11 @@
package com.googlesource.gerrit.plugins.github.velocity;
import java.io.IOException;
+import java.util.Map.Entry;
import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -29,6 +32,7 @@
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -37,37 +41,51 @@
@Singleton
public class VelocityViewServlet extends HttpServlet {
- private static final Logger log = LoggerFactory.getLogger(VelocityViewServlet.class);
+ private static final Logger log = LoggerFactory
+ .getLogger(VelocityViewServlet.class);
private static final String STATIC_PREFIX = "/static";
private static final long serialVersionUID = 529071287765413268L;
private final RuntimeInstance velocityRuntime;
private final Provider<PluginVelocityModel> modelProvider;
+ private final Provider<GitHubLogin> loginProvider;
+ private final Provider<IdentifiedUser> userProvider;
@Inject
public VelocityViewServlet(
@Named("PluginRuntimeInstance") final RuntimeInstance velocityRuntime,
- Provider<PluginVelocityModel> modelProvider) {
+ Provider<PluginVelocityModel> modelProvider,
+ Provider<GitHubLogin> loginProvider, Provider<IdentifiedUser> userProvider) {
this.velocityRuntime = velocityRuntime;
this.modelProvider = modelProvider;
+ this.loginProvider = loginProvider;
+ this.userProvider = userProvider;
}
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- String pathInfo = req.getServletPath();
- String nextUrl = Objects.firstNonNull(req.getParameter("next"), "/");
+ @Override
+ public void service(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException {
+ HttpServletRequest req = (HttpServletRequest) request;
+ HttpServletResponse resp = (HttpServletResponse) response;
+
+ String servletPath = req.getServletPath();
+ String destUrl = (String) req.getAttribute("destUrl");
+ if (destUrl != null && !destUrl.startsWith("/")) {
+ destUrl =
+ servletPath.substring(0, servletPath.lastIndexOf("/")) + "/"
+ + destUrl;
+ }
+
+ String pathInfo = Objects.firstNonNull(destUrl, servletPath);
if (!pathInfo.startsWith(STATIC_PREFIX)) {
resp.sendError(HttpStatus.SC_NOT_FOUND);
}
try {
- Template template =
- velocityRuntime.getTemplate(
- pathInfo, "UTF-8");
- VelocityContext context = modelProvider.get().getContext();
- context.put("nextUrl", nextUrl);
+ Template template = velocityRuntime.getTemplate(pathInfo, "UTF-8");
+ VelocityContext context = initVelocityModel(req).getContext();
+ context.put("request", req);
template.merge(context, resp.getWriter());
} catch (ResourceNotFoundException e) {
log.error("Cannot load velocity template " + pathInfo, e);
@@ -79,4 +97,15 @@
}
}
+ private PluginVelocityModel initVelocityModel(HttpServletRequest request) {
+ PluginVelocityModel model = modelProvider.get();
+ model.put("myself", loginProvider.get().getMyself());
+ model.put("user", userProvider.get());
+ model.put("hub", loginProvider.get().hub);
+
+ for (Entry<String, String[]> reqPar : request.getParameterMap().entrySet()) {
+ model.put(reqPar.getKey(), reqPar.getValue());
+ }
+ return model;
+ }
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java
index 00b0500..87da1d1 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java
@@ -19,7 +19,6 @@
import java.util.HashSet;
import java.util.List;
-import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -79,8 +78,7 @@
return Lists.transform(keysInfo, new Function<SshKeyInfo, String>() {
@Override
- @Nullable
- public String apply(@Nullable SshKeyInfo keyInfo) {
+ public String apply(SshKeyInfo keyInfo) {
return keyInfo.sshPublicKey;
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/JobStatusController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/JobStatusController.java
new file mode 100644
index 0000000..9d1a917
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/JobStatusController.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.wizard;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.stream.JsonWriter;
+import com.googlesrouce.gerrit.plugins.github.git.BatchImporter;
+import com.googlesrouce.gerrit.plugins.github.git.GitJob;
+import com.googlesrouce.gerrit.plugins.github.git.GitJobStatus;
+
+
+
+public class JobStatusController {
+
+ public JobStatusController() {
+ super();
+ }
+
+ protected void respondWithJobStatusJson(HttpServletResponse resp, BatchImporter cloner)
+ throws IOException {
+ Collection<GitJob> jobs = cloner.getJobs();
+ List<GitJobStatus> jobListStatus = Lists.newArrayList();
+ for (GitJob job : jobs) {
+ jobListStatus.add(job.getStatus());
+ }
+ new Gson().toJson(jobListStatus, jobListStatus.getClass(), new JsonWriter(
+ resp.getWriter()));
+ }
+
+
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestImportController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestImportController.java
new file mode 100644
index 0000000..14f6741
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestImportController.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.wizard;
+
+import java.io.IOException;
+import java.util.Map.Entry;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
+import com.googlesrouce.gerrit.plugins.github.git.PullRequestImportType;
+import com.googlesrouce.gerrit.plugins.github.git.PullRequestImporter;
+
+@Singleton
+public class PullRequestImportController implements VelocityController {
+
+ private Provider<PullRequestImporter> prImportProvider;
+
+ @Inject
+ public PullRequestImportController(
+ final Provider<PullRequestImporter> pullRequestsImporter) {
+ this.prImportProvider = pullRequestsImporter;
+ }
+
+ @Override
+ public void doAction(IdentifiedUser user, GitHubLogin hubLogin,
+ HttpServletRequest req, HttpServletResponse resp, ControllerErrors errors)
+ throws ServletException, IOException {
+ String organisation = req.getParameter("organisation");
+ PullRequestImporter prImporter = prImportProvider.get();
+
+ for (Entry<String, String[]> param : req.getParameterMap().entrySet()) {
+ String name = param.getKey();
+ if (name.endsWith(".selected") && param.getValue().length == 1
+ && param.getValue()[0].equalsIgnoreCase("on")) {
+
+ String paramPrefix =
+ name.substring(0, name.length() - ".selected".length());
+ int idx = Integer.parseInt(req.getParameter(paramPrefix + ".idx"));
+ PullRequestImportType importType =
+ PullRequestImportType.valueOf(req.getParameter(paramPrefix
+ + ".type"));
+
+ int pullRequestId =
+ Integer.parseInt(req.getParameter(paramPrefix + ".id"));
+ String repoName =
+ req.getParameter(paramPrefix + ".repo");
+
+ prImporter.importPullRequest(idx, organisation, repoName,
+ pullRequestId, importType);
+ }
+ }
+
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesNextController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestImportStatusController.java
similarity index 67%
rename from github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesNextController.java
rename to github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestImportStatusController.java
index add17a2..0866901 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesNextController.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestImportStatusController.java
@@ -20,16 +20,26 @@
import javax.servlet.http.HttpServletResponse;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Singleton;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
+import com.googlesrouce.gerrit.plugins.github.git.PullRequestImporter;
-@Singleton
-public class RepositoriesNextController implements VelocityController {
+public class PullRequestImportStatusController extends JobStatusController
+ implements VelocityController {
+
+ private Provider<PullRequestImporter> pullRequestsImporter;
+
+ @Inject
+ public PullRequestImportStatusController(
+ final Provider<PullRequestImporter> pullRequestsImporter) {
+ this.pullRequestsImporter = pullRequestsImporter;
+ }
@Override
public void doAction(IdentifiedUser user, GitHubLogin hubLogin,
HttpServletRequest req, HttpServletResponse resp, ControllerErrors errors)
throws ServletException, IOException {
+ respondWithJobStatusJson(resp, pullRequestsImporter.get());
}
-
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestListController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestListController.java
new file mode 100644
index 0000000..29eff9c
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestListController.java
@@ -0,0 +1,204 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.wizard;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.kohsuke.github.GHIssueState;
+import org.kohsuke.github.GHPerson;
+import org.kohsuke.github.GHPullRequest;
+import org.kohsuke.github.GHPullRequestCommitDetail;
+import org.kohsuke.github.GHRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
+
+@Singleton
+public class PullRequestListController implements VelocityController {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(PullRequestListController.class);
+ private static final String DATE_FMT = "yyyy-MM-dd HH:mm z";
+ private static final int MAX_PULL_REQUESTS = 20;
+ private ProjectCache projectsCache;
+ private GitRepositoryManager repoMgr;
+ private final Provider<ReviewDb> schema;
+
+ @Inject
+ public PullRequestListController(ProjectCache projectsCache,
+ GitRepositoryManager repoMgr, Provider<ReviewDb> schema) {
+ this.projectsCache = projectsCache;
+ this.repoMgr = repoMgr;
+ this.schema = schema;
+ }
+
+ @Override
+ public void doAction(IdentifiedUser user, GitHubLogin hubLogin,
+ HttpServletRequest req, HttpServletResponse resp, ControllerErrors errors)
+ throws ServletException, IOException {
+ PrintWriter out = resp.getWriter();
+
+ SimpleDateFormat dateFmt = new SimpleDateFormat(DATE_FMT);
+ String organisation = req.getParameter("organisation");
+ String repository = req.getParameter("repository");
+ Map<String, List<GHPullRequest>> pullRequests =
+ getPullRequests(hubLogin, organisation, repository);
+
+ JsonArray reposPullRequests = new JsonArray();
+ for (Entry<String, List<GHPullRequest>> repoEntry : pullRequests.entrySet()) {
+ JsonObject repoPullRequests = new JsonObject();
+
+ repoPullRequests.add("repository", new JsonPrimitive(repoEntry.getKey()));
+
+ if (repoEntry.getValue() != null) {
+ JsonArray prArray = new JsonArray();
+ for (GHPullRequest pr : repoEntry.getValue()) {
+ JsonObject prObj = new JsonObject();
+ prObj.add("id", new JsonPrimitive(pr.getNumber()));
+ prObj.add("title", new JsonPrimitive(pr.getTitle()));
+ prObj.add("body", new JsonPrimitive(pr.getBody()));
+ prObj.add("author", new JsonPrimitive(pr.getUser() == null ? "" : pr
+ .getUser().getLogin()));
+ prObj.add("status", new JsonPrimitive(pr.getState().name()));
+ prObj.add("date",
+ new JsonPrimitive(dateFmt.format(pr.getUpdatedAt())));
+
+ prArray.add(prObj);
+ }
+ repoPullRequests.add("pullrequests", prArray);
+ }
+
+ reposPullRequests.add(repoPullRequests);
+ }
+ out.println(reposPullRequests.toString());
+ }
+
+ private Map<String, List<GHPullRequest>> getPullRequests(
+ GitHubLogin hubLogin, String organisation, String repository)
+ throws IOException {
+ GHPerson ghOwner;
+ if (organisation.equals(hubLogin.getMyself().getLogin())) {
+ ghOwner = hubLogin.getMyself();
+ } else {
+ ghOwner = hubLogin.hub.getOrganization(organisation);
+ }
+ return getPullRequests(
+ hubLogin,
+ ghOwner,
+ projectsCache.byName(organisation + "/"
+ + Strings.nullToEmpty(repository)));
+ }
+
+ private Map<String, List<GHPullRequest>> getPullRequests(GitHubLogin login,
+ GHPerson ghOwner, Iterable<NameKey> repos) throws IOException {
+ int numPullRequests = 0;
+ ReviewDb db = schema.get();
+ Map<String, List<GHPullRequest>> allPullRequests = Maps.newHashMap();
+ try {
+ for (NameKey gerritRepoName : repos) {
+ Repository gitRepo = repoMgr.openRepository(gerritRepoName);
+ try {
+ String ghRepoName = gerritRepoName.get().split("/")[1];
+ List<GHPullRequest> repoPullRequests = Lists.newArrayList();
+
+ if (numPullRequests < MAX_PULL_REQUESTS) {
+ for (GHPullRequest ghPullRequest : GHRepository.listPullRequests(
+ login.hub, ghOwner, ghRepoName, GHIssueState.OPEN)) {
+
+ if (isAnyCommitOfPullRequestToBeImported(db, gitRepo,
+ ghPullRequest)) {
+ repoPullRequests.add(ghPullRequest);
+ numPullRequests++;
+ }
+ }
+ if (repoPullRequests.size() > 0) {
+ allPullRequests.put(ghRepoName, repoPullRequests);
+ }
+ } else {
+ allPullRequests.put(ghRepoName, null);
+ }
+ } finally {
+ gitRepo.close();
+ }
+ }
+ return allPullRequests;
+ } finally {
+ db.close();
+ }
+ }
+
+ private boolean isAnyCommitOfPullRequestToBeImported(ReviewDb db,
+ Repository gitRepo, GHPullRequest ghPullRequest)
+ throws IncorrectObjectTypeException, IOException {
+ boolean pullRequestToImport = false;
+ RevWalk gitWalk = new RevWalk(gitRepo);
+ for (GHPullRequestCommitDetail pullRequestCommit : ghPullRequest
+ .listCommits()) {
+ ObjectId pullRequestHeadObjectId =
+ ObjectId.fromString(pullRequestCommit.getSha());
+
+ try {
+ gitWalk.parseCommit(pullRequestHeadObjectId);
+
+ ResultSet<PatchSet> patchSets;
+ try {
+ patchSets =
+ db.patchSets().byRevision(new RevId(pullRequestCommit.getSha()));
+ } catch (OrmException e) {
+ LOG.error("Error whilst fetching patch-sets from DB associated to commit "
+ + pullRequestCommit.getSha());
+ return false;
+ }
+ pullRequestToImport = !patchSets.iterator().hasNext();
+ patchSets.close();
+ } catch (MissingObjectException e) {
+ pullRequestToImport = true;
+ }
+ }
+ return pullRequestToImport;
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneStatusController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneStatusController.java
index d246d1b..1fe5551 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneStatusController.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneStatusController.java
@@ -20,19 +20,14 @@
import javax.servlet.http.HttpServletResponse;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
-import com.googlesrouce.gerrit.plugins.github.git.GitJob;
import com.googlesrouce.gerrit.plugins.github.git.GitImporter;
@Singleton
-public class RepositoriesCloneStatusController implements VelocityController {
+public class RepositoriesCloneStatusController extends JobStatusController implements VelocityController {
private Provider<GitImporter> clonerProvider;
@Inject
@@ -44,23 +39,6 @@
public void doAction(IdentifiedUser user, GitHubLogin hubLogin,
HttpServletRequest req, HttpServletResponse resp, ControllerErrors errors)
throws ServletException, IOException {
- GitImporter cloner = clonerProvider.get();
-
- JsonArray reposStatus = new JsonArray();
- for (GitJob job : cloner.getCloneJobs()) {
- reposStatus.add(getJsonStatus(job));
-
- }
- resp.getWriter().println(reposStatus.toString());
- }
-
- private JsonElement getJsonStatus(GitJob job) {
- JsonObject json = new JsonObject();
- json.add("index", new JsonPrimitive(job.getIndex()));
- json.add("organisation", new JsonPrimitive(job.getOrganisation()));
- json.add("repository", new JsonPrimitive(job.getRepository()));
- json.add("status", new JsonPrimitive(job.getStatus().toString()));
- json.add("value", new JsonPrimitive(job.getStatusDescription()));
- return json;
+ respondWithJobStatusJson(resp, clonerProvider.get());
}
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityController.java
index 78f3ef3..1644cce 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityController.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityController.java
@@ -29,5 +29,4 @@
void doAction(IdentifiedUser user, GitHubLogin hubLogin,
HttpServletRequest req, HttpServletResponse resp, ControllerErrors errors)
throws ServletException, IOException;
-
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityControllerServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityControllerServlet.java
index 5e1b9bd..9790176 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityControllerServlet.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/VelocityControllerServlet.java
@@ -14,7 +14,13 @@
package com.googlesource.gerrit.plugins.github.wizard;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -31,6 +37,7 @@
import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.github.GitHubConfig;
import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
+import com.googlesource.gerrit.plugins.github.velocity.PluginVelocityModel;
@Singleton
public class VelocityControllerServlet extends HttpServlet {
@@ -47,8 +54,8 @@
@Inject
public VelocityControllerServlet(final Provider<GitHubLogin> loginProvider,
- Provider<IdentifiedUser> userProvider, final Injector injector, Provider<ControllerErrors> errorsProvider,
- GitHubConfig githubConfig) {
+ Provider<IdentifiedUser> userProvider, final Injector injector,
+ Provider<ControllerErrors> errorsProvider, GitHubConfig githubConfig) {
this.loginProvider = loginProvider;
this.userProvider = userProvider;
this.injector = injector;
@@ -58,7 +65,7 @@
@SuppressWarnings("unchecked")
@Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+ protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String controllerName;
VelocityController controller;
@@ -70,9 +77,9 @@
.forName(CONTROLLER_PACKAGE + "." + controllerName + "Controller");
controller = injector.getInstance(controllerClass);
} catch (ClassNotFoundException e) {
- log.error("Cannot find any controller for servlet "
+ log.debug("Cannot find any controller for servlet "
+ req.getServletPath());
- resp.sendError(HttpStatus.SC_NOT_FOUND);
+ redirectToNextStep(req, resp);
return;
}
@@ -116,16 +123,19 @@
}
private void redirectToNextStep(HttpServletRequest req,
- HttpServletResponse resp) throws IOException {
+ HttpServletResponse resp) throws IOException, ServletException {
String sourcePath = req.getRequestURI();
- String sourcePage = sourcePath.substring(sourcePath.lastIndexOf('/')+1);
+ String sourcePage = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
int queryStringStart = sourcePage.indexOf('?');
- if(queryStringStart > 0) {
+ if (queryStringStart > 0) {
sourcePage = sourcePage.substring(0, queryStringStart);
}
String nextPage = githubConfig.getNextPage(sourcePage);
if (nextPage != null) {
- resp.sendRedirect(nextPage);
+ RequestDispatcher requestDispatcher = req.getRequestDispatcher(nextPage);
+ req.setAttribute("destUrl", nextPage);
+ requestDispatcher.forward(req, resp);
}
}
+
}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/BatchImporter.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/BatchImporter.java
new file mode 100644
index 0000000..258c610
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/BatchImporter.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesrouce.gerrit.plugins.github.git;
+
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.google.gerrit.server.IdentifiedUser;
+
+public class BatchImporter {
+
+ private final ConcurrentHashMap<Integer, GitJob> jobs = new ConcurrentHashMap<Integer, GitJob>();
+ private final JobExecutor executor;
+ protected final IdentifiedUser user;
+
+ public BatchImporter(final JobExecutor executor, final IdentifiedUser user) {
+ this.executor = executor;
+ this.user = user;
+ }
+
+ public Collection<GitJob> getJobs() {
+ return jobs.values();
+ }
+
+ public void reset() {
+ cancel();
+ jobs.clear();
+ }
+
+ public void cancel() {
+ for (GitJob job : jobs.values()) {
+ job.cancel();
+ }
+ }
+
+ public synchronized void schedule(int idx, GitJob pullRequestImportJob) {
+ jobs.put(idx, pullRequestImportJob);
+ executor.exec(pullRequestImportJob);
+ }
+
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/CreateProjectStep.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/CreateProjectStep.java
index 7027113..eea6cfa 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/CreateProjectStep.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/CreateProjectStep.java
@@ -13,7 +13,11 @@
// limitations under the License.
package com.googlesrouce.gerrit.plugins.github.git;
+import java.net.MalformedURLException;
+
import org.eclipse.jgit.lib.ProgressMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupDescription;
@@ -32,9 +36,10 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.github.GitHubURL;
public class CreateProjectStep extends ImportStep {
-
+ private static final Logger LOG = LoggerFactory.getLogger(CreateProjectStep.class);
private static final String CODE_REVIEW_REFS = "refs/for/refs/*";
private static final String TAGS_REFS = "refs/tags/*";
private static final String CODE_REVIEW_LABEL = "Code-Review";
@@ -58,18 +63,17 @@
}
@Inject
- public CreateProjectStep(GitConfig gitConfig,
+ public CreateProjectStep(@GitHubURL String gitHubUrl,
MetaDataUpdate.User metaDataUpdateFactory,
GroupBackend groupBackend,
ProjectCache projectCache,
@Assisted("organisation") String organisation,
@Assisted("name") String repository,
@Assisted("description") String description,
- @Assisted("username") String username)
- throws GitDestinationAlreadyExistsException,
- GitDestinationNotWritableException {
- super(organisation, repository);
-
+ @Assisted("username") String username) {
+ super(gitHubUrl, organisation, repository);
+ LOG.debug("Gerrit CreateProject " + organisation + "/" + repository);
+
this.organisation = organisation;
this.repository = repository;
this.description = description;
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ErrorCloneJob.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ErrorJob.java
similarity index 76%
rename from github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ErrorCloneJob.java
rename to github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ErrorJob.java
index 4620952..09605ec 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ErrorCloneJob.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ErrorJob.java
@@ -13,29 +13,29 @@
// limitations under the License.
package com.googlesrouce.gerrit.plugins.github.git;
-public class ErrorCloneJob extends AbstractCloneJob implements GitJob {
+import com.googlesrouce.gerrit.plugins.github.git.GitJobStatus.Code;
+
+public class ErrorJob extends AbstractCloneJob implements GitJob {
private int idx;
private String organisation;
private String repository;
private Throwable exception;
+ private GitJobStatus status;
- public ErrorCloneJob(int idx, String organisation, String repository,
+ public ErrorJob(int idx, String organisation, String repository,
Throwable e) {
this.idx = idx;
this.organisation = organisation;
this.repository = repository;
this.exception = e;
- }
-
- @Override
- public String getStatusDescription() {
- return getErrorDescription(exception);
+ status = new GitJobStatus(idx);
+ status.update(Code.FAILED, "Failed", getErrorDescription(exception));
}
@Override
public GitJobStatus getStatus() {
- return GitJobStatus.FAILED;
+ return status;
}
@Override
@@ -56,4 +56,8 @@
public void cancel() {
}
+ @Override
+ public void run() {
+ }
+
}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneStep.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneStep.java
index 3b418f6..b02cba5 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneStep.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneStep.java
@@ -15,38 +15,23 @@
import java.io.File;
import java.io.IOException;
-import java.util.Set;
+import java.net.MalformedURLException;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.CloneCommand;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.NameKey;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.git.MetaDataUpdate.User;
-import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.github.GitHubConfig;
public class GitCloneStep extends ImportStep {
- private static final Logger log = LoggerFactory.getLogger(GitImporter.class);
+ private static final Logger LOG = LoggerFactory.getLogger(GitImporter.class);
private final File gitDir;
private File destinationDirectory;
@@ -57,7 +42,7 @@
}
@Inject
- public GitCloneStep(GitConfig gitConfig,
+ public GitCloneStep(GitHubConfig gitConfig,
MetaDataUpdate.User metaDataUpdateFactory,
GroupBackend groupBackend,
ProjectCache projectCache,
@@ -65,8 +50,8 @@
@Assisted("name") String repository)
throws GitDestinationAlreadyExistsException,
GitDestinationNotWritableException {
- super(organisation, repository);
-
+ super(gitConfig.gitHubUrl, organisation, repository);
+ LOG.debug("GitHub Clone " + organisation + "/" + repository);
this.gitDir = gitConfig.gitDir;
this.destinationDirectory =
getDestinationDirectory(organisation, repository);
@@ -102,7 +87,7 @@
clone.setProgressMonitor(progress);
}
try {
- log.info(sourceUri + "| Clone into " + destinationDirectory);
+ LOG.info(sourceUri + "| Clone into " + destinationDirectory);
clone.call();
} catch (Throwable e) {
throw new GitCloneFailedException(sourceUri, e);
@@ -127,7 +112,7 @@
FileUtils.deleteDirectory(gitDirectory);
return true;
} catch (IOException e) {
- log.error("Cannot clean-up output Git directory " + gitDirectory);
+ LOG.error("Cannot clean-up output Git directory " + gitDirectory);
return false;
}
}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCommandsExecutor.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCommandsExecutor.java
deleted file mode 100644
index 44a9369..0000000
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCommandsExecutor.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.googlesrouce.gerrit.plugins.github.git;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-@Singleton
-public class GitCommandsExecutor {
- private static final int MAX_THREADS = 10;
- private final ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);
- private final RequestScopePropagator requestScopePropagator;
-
- @Inject
- public GitCommandsExecutor(final RequestScopePropagator requestScopePropagator) {
- this.requestScopePropagator = requestScopePropagator;
- }
-
- public void exec(GitImportJob job) {
- executor.execute(requestScopePropagator.wrap(job));
- }
-}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitConfig.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitConfig.java
deleted file mode 100644
index 211c540..0000000
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitConfig.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.googlesrouce.gerrit.plugins.github.git;
-
-import java.io.File;
-
-import org.eclipse.jgit.lib.Config;
-
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-@Singleton
-public class GitConfig {
-
- public final File gitDir;
-
- public GitConfig(File gitDir) {
- this.gitDir = gitDir;
- }
-
- @Inject
- public GitConfig(final SitePaths site, @GerritServerConfig final Config cfg) {
- gitDir = site.resolve(cfg.getString("gerrit", null, "basePath"));
- if (gitDir == null) {
- throw new IllegalStateException("gerrit.basePath must be configured");
- }
- }
-}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitHubRepository.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitHubRepository.java
new file mode 100644
index 0000000..f2c1b9c
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitHubRepository.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesrouce.gerrit.plugins.github.git;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.github.GitHubURL;
+
+public class GitHubRepository {
+ public interface Factory {
+ GitHubRepository create(@Assisted("organisation") String organisation,
+ @Assisted("repository") String repository);
+ }
+
+ public final String cloneUrl;
+ public final String organisation;
+ public final String repository;
+
+ @Inject
+ public GitHubRepository(@GitHubURL String gitHubUrl,
+ @Assisted("organisation") String organisation,
+ @Assisted("repository") String repository) {
+ this.cloneUrl = gitHubUrl + "/" + organisation + "/" + repository + ".git";
+ this.organisation = organisation;
+ this.repository = repository;
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImportJob.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImportJob.java
index 66a8410..fa8a90c 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImportJob.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImportJob.java
@@ -15,6 +15,8 @@
import org.eclipse.jgit.lib.ProgressMonitor;
+import com.googlesrouce.gerrit.plugins.github.git.GitJobStatus.Code;
+
public class GitImportJob extends AbstractCloneJob implements Runnable,
ProgressMonitor, GitJob {
private int currTask;
@@ -24,7 +26,7 @@
private boolean cancelled;
private String task = "Initializing ...";
private Exception exception;
- private GitJobStatus status = GitJobStatus.SYNC;
+ private GitJobStatus status;
private int index;
private final ImportStep[] importSteps;
private String organisation;
@@ -35,19 +37,21 @@
this.index = id;
this.organisation = organisation;
this.repository = repository;
+ this.status = new GitJobStatus(id);
}
@Override
public void run() {
try {
+ status.update(Code.SYNC, "Init", "Initializing import steps ...");
for (ImportStep importStep : importSteps) {
importStep.doImport(this);
}
- status = GitJobStatus.COMPLETE;
+ status.update(GitJobStatus.Code.COMPLETE,"Done","Done: repository replicated to Gerrit.");
} catch (Exception e) {
- if (status == GitJobStatus.SYNC) {
+ if (status.getStatus() == GitJobStatus.Code.SYNC) {
this.exception = e;
- status = GitJobStatus.FAILED;
+ status.update(GitJobStatus.Code.FAILED, "Failed", getStatusDescription());
}
rollback();
}
@@ -61,29 +65,20 @@
@Override
public void cancel() {
- if (status != GitJobStatus.SYNC) {
+ if (status.getStatus() != GitJobStatus.Code.SYNC) {
return;
}
cancelled = true;
- status = GitJobStatus.CANCELLED;
+ status.update(GitJobStatus.Code.CANCELLED, "Cancelled", "Cancelled");
rollback();
}
-
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.googlesrouce.gerrit.plugins.github.git.CloneJob#getStatusDescription()
- */
- @Override
public String getStatusDescription() {
if (exception != null) {
return getErrorDescription(exception);
} else {
- switch (status) {
+ switch (status.getStatus()) {
case COMPLETE:
return "Cloned (100%)";
case CANCELLED:
@@ -116,6 +111,8 @@
if (percentage > lastPercentage) {
lastPercentage = percentage;
}
+
+ status.update(Code.SYNC, status.getShortDescription(), getStatusDescription());
}
@Override
@@ -126,7 +123,7 @@
@Override
public boolean isCancelled() {
if (cancelled) {
- status = GitJobStatus.CANCELLED;
+ status.update(GitJobStatus.Code.CANCELLED);
}
return cancelled;
}
@@ -142,6 +139,8 @@
this.totUnits = totalUnits;
this.currUnit = 0;
this.lastPercentage = 0;
+
+ status.update(Code.SYNC, status.getShortDescription(), getStatusDescription());
}
@Override
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImporter.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImporter.java
index 6aec336..a0edbe6 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImporter.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitImporter.java
@@ -13,7 +13,6 @@
// limitations under the License.
package com.googlesrouce.gerrit.plugins.github.git;
-import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
@@ -22,16 +21,11 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.servlet.SessionScoped;
-import com.googlesrouce.gerrit.plugins.github.git.ReplicateProjectStep.Factory;
@SessionScoped
-public class GitImporter {
+public class GitImporter extends BatchImporter {
private static final Logger log = LoggerFactory.getLogger(GitImporter.class);
private final GitCloneStep.Factory cloneFactory;
- private final ConcurrentHashMap<Integer, GitJob> cloneJobs =
- new ConcurrentHashMap<Integer, GitJob>();
- private final GitCommandsExecutor executor;
- private IdentifiedUser user;
private final CreateProjectStep.Factory projectFactory;
private final ReplicateProjectStep.Factory replicateFactory;
@@ -40,12 +34,11 @@
public GitImporter(GitCloneStep.Factory cloneFactory,
CreateProjectStep.Factory projectFactory,
ReplicateProjectStep.Factory replicateFactory,
- GitCommandsExecutor executor, IdentifiedUser user) {
+ JobExecutor executor, IdentifiedUser user) {
+ super(executor, user);
this.cloneFactory = cloneFactory;
this.projectFactory = projectFactory;
this.replicateFactory = replicateFactory;
- this.executor = executor;
- this.user = user;
}
public void clone(int idx, String organisation, String repository,
@@ -60,25 +53,9 @@
new GitImportJob(idx, organisation, repository, cloneStep, projectStep,
replicateStep);
log.debug("New Git clone job created: " + gitCloneJob);
- executor.exec(gitCloneJob);
- cloneJobs.put(idx, gitCloneJob);
+ schedule(idx, gitCloneJob);
} catch (Throwable e) {
- cloneJobs.put(idx, new ErrorCloneJob(idx, organisation, repository, e));
- }
- }
-
- public Collection<GitJob> getCloneJobs() {
- return cloneJobs.values();
- }
-
- public void reset() {
- cancel();
- cloneJobs.clear();
- }
-
- public void cancel() {
- for (GitJob job : cloneJobs.values()) {
- job.cancel();
+ schedule(idx, new ErrorJob(idx, organisation, repository, e));
}
}
}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJob.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJob.java
index 5ba7090..2226aea 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJob.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJob.java
@@ -13,9 +13,7 @@
// limitations under the License.
package com.googlesrouce.gerrit.plugins.github.git;
-public interface GitJob {
-
- String getStatusDescription();
+public interface GitJob extends Runnable {
GitJobStatus getStatus();
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJobStatus.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJobStatus.java
index cf854be..004b17f 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJobStatus.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitJobStatus.java
@@ -13,10 +13,60 @@
// limitations under the License.
package com.googlesrouce.gerrit.plugins.github.git;
-public enum GitJobStatus {
- SYNC, COMPLETE, FAILED, CANCELLED;
-
- public String toString() {
- return name().toLowerCase();
- };
+import java.io.PrintWriter;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.stream.JsonWriter;
+
+
+public class GitJobStatus {
+
+ public enum Code {
+ SYNC, COMPLETE, FAILED, CANCELLED;
+
+ public String toString() {
+ return name().toLowerCase();
+ };
+ }
+
+ public final int index;
+ private Code status;
+ private String shortDescription;
+ private String value;
+
+ public GitJobStatus(int index) {
+ this.index = index;
+ this.status = GitJobStatus.Code.SYNC;
+ this.shortDescription = "Init";
+ this.value = "Initializing ...";
+ }
+
+ public void update(Code code, String shortDescription, String description) {
+ this.status = code;
+ this.shortDescription = shortDescription;
+ this.value = description;
+ }
+
+ public Code getStatus() {
+ return status;
+ }
+
+ public String getShortDescription() {
+ return shortDescription;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void update(Code status) {
+ this.status = status;
+ this.shortDescription = status.name();
+ this.value = status.name();
+ }
+
+ public void printJson(PrintWriter out) {
+ new Gson().toJson(this, GitJobStatus.class, new JsonWriter(out));
+ }
}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ImportStep.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ImportStep.java
index 69cd173..42b134d 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ImportStep.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ImportStep.java
@@ -15,31 +15,27 @@
import org.eclipse.jgit.lib.ProgressMonitor;
-
+import com.googlesource.gerrit.plugins.github.GitHubURL;
public abstract class ImportStep {
- protected static final String GITHUB_REPOSITORY_BASE_URI =
- "https://github.com";
- private static final String GITHUB_REPOSITORY_FORMAT =
- GITHUB_REPOSITORY_BASE_URI + "/%1$s/%2$s.git";
- private String organisation;
- private String repository;
+ protected String gitHubUrl;
+ private final GitHubRepository gitHubRepository;
- public ImportStep(String organisation, String repository) {
- this.organisation = organisation;
- this.repository = repository;
+ public ImportStep(@GitHubURL String gitHubUrl, String organisation, String repository) {
+ this.gitHubRepository = new GitHubRepository(gitHubUrl, organisation, repository);
+ this.gitHubUrl = gitHubUrl;
}
protected String getSourceUri() {
- return String.format(GITHUB_REPOSITORY_FORMAT, organisation, repository);
+ return gitHubRepository.cloneUrl;
}
public String getOrganisation() {
- return organisation;
+ return gitHubRepository.organisation;
}
public String getRepository() {
- return repository;
+ return gitHubRepository.repository;
}
public abstract void doImport(ProgressMonitor progress) throws Exception;
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/JobCancelledException.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/JobCancelledException.java
new file mode 100644
index 0000000..d082a7b
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/JobCancelledException.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesrouce.gerrit.plugins.github.git;
+
+public class JobCancelledException extends Exception {
+ private static final long serialVersionUID = 4358474273091335160L;
+
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/JobExecutor.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/JobExecutor.java
new file mode 100644
index 0000000..ba4d50c
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/JobExecutor.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesrouce.gerrit.plugins.github.git;
+
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import com.google.gerrit.server.util.RequestScopePropagator;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class JobExecutor {
+ private static final int MAX_THREADS = 10;
+ private static final int MAX_EXEC_TIMEOUT_SECS = 30;
+
+ private final ScheduledExecutorService executor = Executors
+ .newScheduledThreadPool(MAX_THREADS);
+ private final RequestScopePropagator requestScopePropagator;
+
+ @Inject
+ public JobExecutor(final RequestScopePropagator requestScopePropagator) {
+ this.requestScopePropagator = requestScopePropagator;
+ }
+
+ public void exec(GitJob job) {
+ executor.schedule(requestScopePropagator.wrap(job),
+ getRandomExecutionDelay(job), TimeUnit.SECONDS);
+ }
+
+ private int getRandomExecutionDelay(GitJob job) {
+ Random rnd = new Random(System.currentTimeMillis() + job.hashCode());
+ return rnd.nextInt(MAX_EXEC_TIMEOUT_SECS);
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestCreateChange.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestCreateChange.java
new file mode 100644
index 0000000..3246f39
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestCreateChange.java
@@ -0,0 +1,270 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesrouce.gerrit.plugins.github.git;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.ChangeIdUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Id;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeInserter;
+import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectControl.Factory;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.ssh.NoSshInfo;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class PullRequestCreateChange {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(PullRequestCreateChange.class);
+ private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
+
+ private final IdentifiedUser currentUser;
+ private final CommitValidators.Factory commitValidatorsFactory;
+ private final ChangeInserter.Factory changeInserterFactory;
+ final MergeUtil.Factory mergeUtilFactory;
+ private final PatchSetInserter.Factory patchSetInserterFactory;
+
+ private Factory projectControlFactor;
+
+
+ @Inject
+ PullRequestCreateChange(final IdentifiedUser currentUser,
+ final CommitValidators.Factory commitValidatorsFactory,
+ final ChangeInserter.Factory changeInserterFactory,
+ final MergeUtil.Factory mergeUtilFactory,
+ final PatchSetInserter.Factory patchSetInserterFactory,
+ final ProjectControl.Factory projectControlFactory) {
+ this.currentUser = currentUser;
+ this.commitValidatorsFactory = commitValidatorsFactory;
+ this.changeInserterFactory = changeInserterFactory;
+ this.mergeUtilFactory = mergeUtilFactory;
+ this.patchSetInserterFactory = patchSetInserterFactory;
+ this.projectControlFactor = projectControlFactory;
+ }
+
+ public Change.Id addCommitToChange(final ReviewDb db, final Project project,
+ final Repository git, final String destinationBranch,
+ final Account.Id pullRequestOwner,
+ final RevCommit pullRequestCommit, final String pullRequestMesage,
+ final String topic, boolean doValidation) throws NoSuchChangeException,
+ EmailException, OrmException, MissingObjectException,
+ IncorrectObjectTypeException, IOException,
+ InvalidChangeOperationException, MergeException, NoSuchProjectException {
+ Id newChange = null;
+ if (destinationBranch == null || destinationBranch.length() == 0) {
+ throw new InvalidChangeOperationException(
+ "Destination branch cannot be null or empty");
+ }
+
+ RefControl refControl =
+ projectControlFactor.controlFor(project.getNameKey()).controlForRef(
+ destinationBranch);
+
+ try {
+ RevWalk revWalk = new RevWalk(git);
+ try {
+ Ref destRef = git.getRef(destinationBranch);
+ if (destRef == null) {
+ throw new InvalidChangeOperationException("Branch "
+ + destinationBranch + " does not exist.");
+ }
+
+ String pullRequestSha1 = pullRequestCommit.getId().getName();
+ ResultSet<PatchSet> existingPatchSet =
+ db.patchSets().byRevision(new RevId(pullRequestSha1));
+ Iterator<PatchSet> patchSetIterator = existingPatchSet.iterator();
+ if (patchSetIterator.hasNext()) {
+ PatchSet patchSet = patchSetIterator.next();
+ LOG.debug("Pull request commit ID " + pullRequestSha1
+ + " has been already uploaded as PatchSetID="
+ + patchSet.getPatchSetId() + " in ChangeID=" + patchSet.getId());
+ return null;
+ }
+
+ Change.Key changeKey;
+ final List<String> idList = pullRequestCommit.getFooterLines(CHANGE_ID);
+ if (!idList.isEmpty()) {
+ final String idStr = idList.get(idList.size() - 1).trim();
+ changeKey = new Change.Key(idStr);
+ } else {
+ final ObjectId computedChangeId =
+ ChangeIdUtil.computeChangeId(pullRequestCommit.getTree(),
+ pullRequestCommit, pullRequestCommit.getAuthorIdent(),
+ pullRequestCommit.getCommitterIdent(), pullRequestMesage);
+
+ changeKey = new Change.Key("I" + computedChangeId.name());
+ }
+
+ List<Change> destChanges =
+ db.changes()
+ .byBranchKey(
+ new Branch.NameKey(project.getNameKey(), destRef.getName()),
+ changeKey).toList();
+
+ if (destChanges.size() > 1) {
+ throw new InvalidChangeOperationException(
+ "Multiple Changes with Change-ID "
+ + changeKey
+ + " already exist on the target branch: cannot add a new patch-set "
+ + destinationBranch);
+ } else if (destChanges.size() == 1) {
+ // The change key exists on the destination branch: adding a new
+ // patch-set
+ Change destChange = destChanges.get(0);
+ return insertPatchSet(git, revWalk, destChange, pullRequestCommit,
+ refControl, pullRequestMesage, doValidation);
+ } else {
+ // Change key not found on destination branch. We can create a new
+ // change.
+ return (newChange =
+ createNewChange(db, git, revWalk, changeKey,
+ project.getNameKey(), destRef, pullRequestOwner, pullRequestCommit, refControl,
+ pullRequestMesage, topic, doValidation));
+ }
+ } finally {
+ revWalk.release();
+ if (newChange == null) {
+ db.rollback();
+ }
+ }
+ } finally {
+ git.close();
+ }
+ }
+
+ private Change.Id insertPatchSet(Repository git, RevWalk revWalk,
+ Change change, RevCommit cherryPickCommit, RefControl refControl,
+ String pullRequestMessage, boolean doValidation)
+ throws InvalidChangeOperationException, IOException, OrmException,
+ NoSuchChangeException {
+ PatchSetInserter patchSetInserter =
+ patchSetInserterFactory.create(git, revWalk, refControl, currentUser,
+ change, cherryPickCommit);
+ // This apparently useless method call is made for triggering
+ // the creation of patchSet inside PatchSetInserter and thus avoiding a NPE
+ patchSetInserter.getPatchSetId();
+ patchSetInserter.setMessage(pullRequestMessage);
+
+ patchSetInserter.setValidatePolicy(doValidation ? ValidatePolicy.GERRIT
+ : ValidatePolicy.NONE);
+ patchSetInserter.insert();
+ return change.getId();
+ }
+
+ private Change.Id createNewChange(ReviewDb db, Repository git,
+ RevWalk revWalk, Change.Key changeKey, Project.NameKey project,
+ Ref destRef,
+ Account.Id pullRequestOwner,
+ RevCommit pullRequestCommit, RefControl refControl,
+ String pullRequestMessage, String topic, boolean doValidation)
+ throws OrmException, InvalidChangeOperationException, IOException {
+ Change change =
+ new Change(changeKey, new Change.Id(db.nextChangeId()),
+ pullRequestOwner, new Branch.NameKey(project,
+ destRef.getName()));
+ if (topic != null) {
+ change.setTopic(topic);
+ }
+ ChangeInserter ins =
+ changeInserterFactory.create(refControl, change, pullRequestCommit);
+ PatchSet newPatchSet = ins.getPatchSet();
+
+ if (doValidation) {
+ validate(git, pullRequestCommit, refControl, newPatchSet);
+ }
+
+ final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(pullRequestCommit);
+ ru.disableRefLog();
+ if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+ throw new IOException(String.format("Failed to create ref %s in %s: %s",
+ newPatchSet.getRefName(), change.getDest().getParentKey().get(),
+ ru.getResult()));
+ }
+
+ ins.setMessage(buildChangeMessage(db, change, pullRequestMessage)).insert();
+
+ return change.getId();
+ }
+
+ private void validate(Repository git, RevCommit pullRequestCommit,
+ RefControl refControl, PatchSet newPatchSet)
+ throws InvalidChangeOperationException {
+ CommitValidators commitValidators =
+ commitValidatorsFactory.create(refControl, new NoSshInfo(), git);
+ CommitReceivedEvent commitReceivedEvent =
+ new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
+ pullRequestCommit.getId(), newPatchSet.getRefName()), refControl
+ .getProjectControl().getProject(), refControl.getRefName(),
+ pullRequestCommit, currentUser);
+
+ try {
+ commitValidators.validateForGerritCommits(commitReceivedEvent);
+ } catch (CommitValidationException e) {
+ throw new InvalidChangeOperationException(e.getMessage());
+ }
+ }
+
+ private ChangeMessage buildChangeMessage(ReviewDb db, Change dest,
+ String pullRequestMessage) throws OrmException {
+ ChangeMessage cmsg =
+ new ChangeMessage(new ChangeMessage.Key(dest.getId(),
+ ChangeUtil.messageUUID(db)), currentUser.getAccountId(), null);
+ cmsg.setMessage(pullRequestMessage);
+ return cmsg;
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImportJob.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImportJob.java
new file mode 100644
index 0000000..05bb0ba
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImportJob.java
@@ -0,0 +1,318 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesrouce.gerrit.plugins.github.git;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import org.eclipse.jgit.api.FetchCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRemoteException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.RefSpec;
+import org.kohsuke.github.GHPullRequest;
+import org.kohsuke.github.GHPullRequestCommitDetail;
+import org.kohsuke.github.GHRepository;
+import org.kohsuke.github.GHUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Change.Id;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.reviewdb.server.AccountAccess;
+import com.google.gerrit.reviewdb.server.AccountExternalIdAccess;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountImpoter;
+import com.google.gerrit.server.account.CreateAccount;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.github.GitHubURL;
+import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
+import com.googlesrouce.gerrit.plugins.github.git.GitJobStatus.Code;
+
+public class PullRequestImportJob implements GitJob, ProgressMonitor {
+
+ public interface Factory {
+ PullRequestImportJob create(@Assisted("index") int jobIndex,
+ @Assisted("organisation") String organisation,
+ @Assisted("name") String repository, @Assisted int pullRequestId,
+ @Assisted PullRequestImportType importType);
+ }
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(PullRequestImportJob.class);
+
+ private static final String TOPIC_FORMAT = "GitHub #%d";
+
+ private final GitHubRepository ghRepository;
+ private final GitHubLogin ghLogin;
+ private final String organisation;
+ private final String repoName;
+ private final PullRequestImportType importType;
+ private final int prId;
+ private final GitRepositoryManager repoMgr;
+ private final int jobIndex;
+ private PullRequestCreateChange createChange;
+ private com.google.gerrit.server.project.ProjectControl.Factory projectControlFactory;
+ private Project project;
+ private GitJobStatus status;
+ private boolean cancelRequested;
+ private Provider<ReviewDb> schema;
+
+ private com.google.gerrit.server.account.CreateAccount.Factory createAccountFactory;
+
+ private AccountImpoter accountImporter;
+
+ @Inject
+ public PullRequestImportJob(@GitHubURL String gitHubUrl, GitHubLogin ghLogin,
+ GitRepositoryManager repoMgr, PullRequestCreateChange createChange,
+ ProjectCache projectCache, ProjectControl.Factory projectControlFactory,
+ Provider<ReviewDb> schema, AccountImpoter accountImporter,
+ @Assisted("index") int jobIndex,
+ @Assisted("organisation") String organisation,
+ @Assisted("name") String repoName, @Assisted int pullRequestId,
+ @Assisted PullRequestImportType importType) {
+ this.jobIndex = jobIndex;
+ this.repoMgr = repoMgr;
+ this.ghLogin = ghLogin;
+ this.organisation = organisation;
+ this.repoName = repoName;
+ this.importType = importType;
+ this.prId = pullRequestId;
+ this.createChange = createChange;
+ this.projectControlFactory = projectControlFactory;
+ this.project = fetchGerritProject(projectCache, organisation, repoName);
+ this.ghRepository = new GitHubRepository(gitHubUrl, organisation, repoName);
+ this.status = new GitJobStatus(jobIndex);
+ this.schema = schema;
+ this.accountImporter = accountImporter;
+ }
+
+ private Project fetchGerritProject(ProjectCache projectCache,
+ String organisation, String repoName) {
+ NameKey projectNameKey =
+ Project.NameKey.parse(organisation + "/" + repoName);
+ ProjectState projectState = projectCache.get(projectNameKey);
+ return projectState.getProject();
+ }
+
+ @Override
+ public void run() {
+ ReviewDb db = schema.get();
+ try {
+ status.update(GitJobStatus.Code.SYNC);
+ exitWhenCancelled();
+ GHPullRequest pr = fetchGitHubPullRequestInfo();
+
+ exitWhenCancelled();
+ Repository gitRepo =
+ repoMgr.openRepository(new Project.NameKey(organisation + "/"
+ + repoName));
+ try {
+ exitWhenCancelled();
+ fetchGitHubPullRequest(gitRepo, pr);
+
+ exitWhenCancelled();
+ List<Id> changeIds = addPullRequestToChange(db, pr, gitRepo);
+ status.update(GitJobStatus.Code.COMPLETE, "Imported",
+ "PullRequest imported as Changes " + changeIds);
+ } finally {
+ gitRepo.close();
+ }
+ db.commit();
+ } catch (JobCancelledException e) {
+ status.update(GitJobStatus.Code.CANCELLED);
+ try {
+ db.rollback();
+ } catch (OrmException e1) {
+ LOG.error("Error rolling back transation", e1);
+ }
+ } catch (Exception e) {
+ LOG.error("Pull request " + prId + " into repository " + organisation
+ + "/" + repoName + " was failed", e);
+ status.update(GitJobStatus.Code.FAILED, "Failed", getErrorDescription(e));
+ try {
+ db.rollback();
+ } catch (OrmException e1) {
+ LOG.error("Error rolling back transation", e1);
+ }
+ } finally {
+ db.close();
+ }
+ }
+
+ private String getErrorDescription(Exception e) {
+ return e.getLocalizedMessage();
+ }
+
+ private List<Id> addPullRequestToChange(ReviewDb db, GHPullRequest pr, Repository gitRepo)
+ throws Exception {
+ String destinationBranch = pr.getBase().getRef();
+ List<Id> prChanges = Lists.newArrayList();
+ ObjectId baseObjectId = ObjectId.fromString(pr.getBase().getSha());
+ ObjectId prHeadObjectId = ObjectId.fromString(pr.getHead().getSha());
+
+ RevWalk walk = new RevWalk(gitRepo);
+ walk.markUninteresting(walk.lookupCommit(baseObjectId));
+ walk.markStart(walk.lookupCommit(prHeadObjectId));
+ walk.sort(RevSort.REVERSE);
+
+ int patchNr = 1;
+ for (GHPullRequestCommitDetail ghCommitDetail : pr.listCommits()) {
+ status.update(Code.SYNC, "Patch #" + patchNr, "Patch#" + patchNr
+ + ": Inserting PullRequest into Gerrit");
+ RevCommit revCommit =
+ walk.parseCommit(ObjectId.fromString(ghCommitDetail.getSha()));
+ Account.Id pullRequestOwner = getOrRegisterAccount(db, pr.getUser());
+ Id changeId =
+ createChange.addCommitToChange(db, project, gitRepo, destinationBranch,
+ pullRequestOwner, revCommit, getChangeMessage(pr),
+ String.format(TOPIC_FORMAT, pr.getNumber()), false);
+ if (changeId != null) {
+ prChanges.add(changeId);
+ }
+ }
+
+ return prChanges;
+ }
+
+ private com.google.gerrit.reviewdb.client.Account.Id getOrRegisterAccount(ReviewDb db,
+ GHUser user) throws OrmException, BadRequestException,
+ ResourceConflictException, UnprocessableEntityException, IOException {
+ AccountExternalId.Key userExtKey =
+ new AccountExternalId.Key(AccountExternalId.SCHEME_USERNAME,
+ user.getLogin());
+ AccountExternalIdAccess gerritExtIds = db.accountExternalIds();
+ AccountExternalId userExtId = gerritExtIds.get(userExtKey);
+ if (userExtId == null) {
+ return accountImporter.importAccount(user);
+ } else {
+ return userExtId.getAccountId();
+ }
+ }
+
+ private String getChangeMessage(GHPullRequest pr) {
+ return "GitHub Pull Request: " + pr.getUrl() + "\n\n" + pr.getTitle()
+ + "\n\n" + pr.getBody().replaceAll("\n", "\n\n");
+ }
+
+ private void exitWhenCancelled() throws JobCancelledException {
+ if (cancelRequested) {
+ throw new JobCancelledException();
+ }
+ }
+
+ private void fetchGitHubPullRequest(Repository gitRepo, GHPullRequest pr)
+ throws GitAPIException, InvalidRemoteException, TransportException {
+ status.update(Code.SYNC, "Fetching", "Fetching PullRequests from GitHub");
+
+ Git git = Git.wrap(gitRepo);
+ FetchCommand fetch = git.fetch();
+ fetch.setRemote(ghRepository.cloneUrl);
+ fetch.setRefSpecs(new RefSpec("+refs/pull/" + pr.getNumber()
+ + "/head:refs/remotes/origin/pr/" + pr.getNumber()));
+ fetch.setProgressMonitor(this);
+ fetch.call();
+ }
+
+ private GHPullRequest fetchGitHubPullRequestInfo() throws IOException {
+ status.update(Code.SYNC, "Fetch GitHub", "Getting PullRequest info");
+ GHPullRequest pr = getGHRepository().getPullRequest(prId);
+ return pr;
+ }
+
+ @Override
+ public GitJobStatus getStatus() {
+ return status;
+ }
+
+ @Override
+ public int getIndex() {
+ return jobIndex;
+ }
+
+ @Override
+ public String getOrganisation() {
+ return organisation;
+ }
+
+ public GHRepository getGHRepository() throws IOException {
+ if (ghLogin.getMyself().getLogin().equals(organisation)) {
+ return ghLogin.getMyself().getRepository(repoName);
+ } else {
+ return ghLogin.hub.getOrganization(organisation).getRepository(repoName);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ cancelRequested = true;
+ }
+
+ @Override
+ public String getRepository() {
+ return repoName;
+ }
+
+ @Override
+ public void beginTask(String taskName, int numSteps) {
+ status.update(Code.SYNC, taskName, taskName + " ...");
+ }
+
+ @Override
+ public void endTask() {
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelRequested;
+ }
+
+ @Override
+ public void start(int tot) {
+ }
+
+ @Override
+ public void update(int progress) {
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImportType.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImportType.java
new file mode 100644
index 0000000..f420d1c
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImportType.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesrouce.gerrit.plugins.github.git;
+
+public enum PullRequestImportType {
+ Commits,
+ Squash
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImporter.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImporter.java
new file mode 100644
index 0000000..0b9ecb0
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/PullRequestImporter.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesrouce.gerrit.plugins.github.git;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.servlet.SessionScoped;
+
+@SessionScoped
+public class PullRequestImporter extends BatchImporter {
+ private static final Logger log = LoggerFactory.getLogger(PullRequestImporter.class);
+
+ private final PullRequestImportJob.Factory prImportJobProvider;
+
+ @Inject
+ public PullRequestImporter(JobExecutor executor, IdentifiedUser user,
+ PullRequestImportJob.Factory prImportJobProvider) {
+ super(executor, user);
+ this.prImportJobProvider = prImportJobProvider;
+ }
+
+ public void importPullRequest(int idx, String organisation, String repoName,
+ int pullRequestId, PullRequestImportType importType) {
+ try {
+ PullRequestImportJob pullRequestImportJob = prImportJobProvider.create(idx, organisation, repoName, pullRequestId, importType);
+ log.debug("New Pull request import job created: " + pullRequestImportJob);
+ schedule(idx, pullRequestImportJob);
+ } catch (Throwable e) {
+ schedule(idx, new ErrorJob(idx, organisation, repoName, e));
+ }
+
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ReplicateProjectStep.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ReplicateProjectStep.java
index e129c4a..9fa2a61 100644
--- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ReplicateProjectStep.java
+++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/ReplicateProjectStep.java
@@ -14,13 +14,17 @@
package com.googlesrouce.gerrit.plugins.github.git;
import org.eclipse.jgit.lib.ProgressMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.github.GitHubURL;
import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
public class ReplicateProjectStep extends ImportStep {
+ private static final Logger LOG = LoggerFactory.getLogger(ReplicateProjectStep.class);
private final ReplicationConfig replicationConfig;
private final String authUsername;
private final String authToken;
@@ -34,12 +38,11 @@
@Inject
public ReplicateProjectStep(final ReplicationConfig replicationConfig,
final Provider<GitHubLogin> gitHubLoginProvider,
+ @GitHubURL String gitHubUrl,
@Assisted("organisation") String organisation,
- @Assisted("name") String repository)
- throws GitDestinationAlreadyExistsException,
- GitDestinationNotWritableException {
- super(organisation, repository);
-
+ @Assisted("name") String repository) {
+ super(gitHubUrl, organisation, repository);
+ LOG.debug("Gerrit ReplicateProject " + organisation + "/" + repository);
this.replicationConfig = replicationConfig;
this.authUsername = gitHubLoginProvider.get().getMyself().getLogin();
this.authToken = gitHubLoginProvider.get().token.access_token;
@@ -48,15 +51,14 @@
@Override
public void doImport(ProgressMonitor progress) throws Exception {
progress.beginTask("Setting up Gerrit replication", 2);
-
+
String repositoryName = getOrganisation() + "/" + getRepository();
progress.update(1);
- replicationConfig.addSecureCredentials(getOrganisation(), authUsername, authToken);
+ replicationConfig.addSecureCredentials(getOrganisation(), authUsername,
+ authToken);
progress.update(1);
- replicationConfig.addReplicationRemote(
- getOrganisation(),
- GITHUB_REPOSITORY_BASE_URI + "/${name}.git",
- repositoryName);
+ replicationConfig.addReplicationRemote(getOrganisation(), gitHubUrl
+ + "/${name}.git", repositoryName);
progress.endTask();
}
diff --git a/github-plugin/src/main/resources/static/js/datatables/license-bsd.txt b/github-plugin/src/main/resources/static/js/datatables/license-bsd.txt
new file mode 100755
index 0000000..cdb85aa
--- /dev/null
+++ b/github-plugin/src/main/resources/static/js/datatables/license-bsd.txt
@@ -0,0 +1,10 @@
+Copyright (c) 2008-2010, Allan Jardine
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of Allan Jardine nor SpryMedia UK may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/github-plugin/src/main/resources/static/js/jquery.tablesorter.min.js b/github-plugin/src/main/resources/static/js/jquery.tablesorter.min.js
new file mode 100755
index 0000000..b8605df
--- /dev/null
+++ b/github-plugin/src/main/resources/static/js/jquery.tablesorter.min.js
@@ -0,0 +1,4 @@
+
+(function($){$.extend({tablesorter:new
+function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
+var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(table.config.parsers[c].type=="text")?((order==0)?makeSortFunction("text","asc",c):makeSortFunction("text","desc",c)):((order==0)?makeSortFunction("numeric","asc",c):makeSortFunction("numeric","desc",c));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling expression:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
\ No newline at end of file
diff --git a/github-plugin/src/main/resources/static/js/pullrequests.js b/github-plugin/src/main/resources/static/js/pullrequests.js
new file mode 100644
index 0000000..449a48b
--- /dev/null
+++ b/github-plugin/src/main/resources/static/js/pullrequests.js
@@ -0,0 +1,245 @@
+$(function() {
+ $("select#organisation").change(function() {
+ loadPullRequests();
+ $("input#filter").val("");
+ });
+
+ var completed = false;
+ var running = false;
+
+ var refresh = function() {
+ $.post('pull-request-import-status.gh', function(data) {
+ var pullRequests = eval('(' + data + ')');
+ var allCompleted = false;
+ if(pullRequests.length == 0) {
+ $("#submit").prop("disabled", "disabled");
+ } else {
+ allCompleted = true;
+ for (var i=0; i<pullRequests.length; i++) {
+ var id = pullRequests[i].index;
+ var description = pullRequests[i].value;
+ var status = pullRequests[i].status.toLowerCase();
+ var shortDescription = pullRequests[i].shortDescription;
+ $("#status_" + id).attr("class", "status " + status);
+ $("#status_description_" + id).text(shortDescription);
+ $("#status_description_" + id).attr("title", description);
+ if(status == 'sync') {
+ allCompleted = false;
+ }
+ }
+ }
+
+ if(allCompleted || repos.length <= 0) {
+ completed = true;
+ $("#submit").prop("disabled", "");
+ $("#submit").html("<span class=\"button green\"><span>Next ></span></span>")
+ clearInterval(refreshInterval);
+ running = false;
+ }
+ });
+ }
+
+ $("#submit").click(function() {
+ var destination;
+
+ if(completed || $("tr").length <= 0) {
+ $('#pullrequests').submit();
+ return true;
+ } else {
+
+ $("tr").each(function() {
+ var importCheckbox = $(this).find("td input:checkbox");
+ if(importCheckbox.prop("checked")) {
+ var importTypeSelect = $(this).find("#importtype");
+ $(importTypeSelect).attr("style","display: none;");
+ }
+ });
+
+ $.ajax({
+ type : "POST",
+ url : "pull-request-import.gh",
+ data : $("#pullrequests").serialize(),
+ success : function() {
+ $(".status").each(function() {
+ $(this).attr("display","block");
+ });
+ refresh();
+ refreshInterval = setInterval(refresh, 2000);
+ }
+ });
+ running = true;
+ $("#submit").prop("disabled", "disabled");
+ return false;
+ }
+ });
+
+ $("#cancel").click(function() {
+ if(running) {
+ $.ajax({
+ type : "POST",
+ url : "pull-request-import-cancel.gh",
+ success: function() {
+ refresh();
+ }
+ });
+ } else {
+ window.location = "/";
+ }
+ });
+});
+
+// Table sort - DataTables
+var table = $('#pull-requests'),
+ tableStyled = false;
+
+table.dataTable({
+ 'aoColumnDefs': [
+ { 'bSortable': false, 'aTargets': [ 0, 4 ] }
+ ],
+ 'oLanguage': {
+ 'sLengthMenu': '_MENU_ Rows',
+ 'sSearch':'Search pull request'
+ },
+ 'sPaginationType': 'full_numbers',
+ 'sDom': '<"dataTables_header"fpl>t',
+ 'bAutoWidth': true,
+ 'fnDrawCallback': function( oSettings )
+ {
+ // Only run once
+ }
+});
+
+$("table thead input#checkall").change(function() {
+ var checked = $(this).prop("checked");
+ $("table tbody td input#checkall").each(function() {
+ $(this).prop("checked", checked);
+ });
+});
+
+$("table thead select#importtype").change(function() {
+ var importType = $("table thead select#importtype option:selected").val();
+ $("table tbody select#importtype option").filter(function() {
+ return $(this).text() == importType;
+ }).prop('selected', true);
+});
+
+var loadPullRequests = function (repository) {
+ $('#pull-requests').attr("style","display: none;");
+ $('.dataTables_header').attr("style","display: none;");
+ $("div.loading").attr("style","display: visible;");
+ if (repository == undefined) {
+ $("ul.repo-list").empty();
+ }
+ $("table#pull-requests tbody").empty();
+ $("#submit").prop("disabled", "disabled");
+
+ var organisation = $("select#organisation option:selected").val();
+ $.post('pull-request-list.gh',
+ {
+ "organisation": organisation,
+ "repository": repository
+ },
+ function(data) {
+
+ var prs = eval('(' + data + ')');
+ var numItems = 0;
+ table.fnClearTable();
+ var idx = 0;
+ for (var i=0; i<prs.length; i++) {
+ var pr = prs[i];
+
+ if(repository == undefined) {
+ repoLi = $("ul.repo-list li#" + pr.repository);
+ if(repoLi.length == 0) {
+ $('<li id="' + pr.repository + '"><a href="#">' + pr.repository + '</a>' +
+ (pr.pullrequests == undefined ? '':'<p>' + pr.pullrequests.length + '</p>') +
+ '</li>').appendTo('ul.repo-list');
+ }
+ }
+
+ if(pr.pullrequests != undefined) {
+ for(var j=0; j<pr.pullrequests.length; j++) {
+ var req = pr.pullrequests[j];
+ var paramPrefix = "pr." + idx;
+ table.fnAddData( [
+ '<input type="checkbox" name="' + paramPrefix + '.selected" id="checkall" checked="checked">',
+ '#' + req.id +
+ '<input type="hidden" name="' + paramPrefix + '.idx" value="' + idx + '"/>' +
+ '<input type="hidden" name="' + paramPrefix + '.id" value="' + req.id + '"/>' +
+ '<input type="hidden" name="' + paramPrefix + '.repo" value="' + pr.repository + '"/>',
+ '<p class="repository">' + pr.repository + '</p> | ' +
+ '<p class="title">' + req.title + '</p>' +
+ '<p class="body">' + req.body + '</p>' +
+ '<p class="author">by ' + req.author + '</p>',
+ '<p class="timestamp">' + req.date + '</p>',
+ '<select name="' + paramPrefix + '.type" id="importtype"><option>Squash</option><option>Commits</option> </select>' +
+ '<span id="status_' + idx + '" class="status" ></span>' +
+ '<label id="status_description_' + idx + '" class="synch-status">'
+ ] );
+ idx = idx + 1;
+ }
+ numItems = numItems + pr.pullrequests.length;
+ }
+ }
+
+ if(repository == undefined) {
+ var repoSort = function sortAlpha(a,b){
+ var cmpA = ($(a).find("p") == undefined ? "0":$(a).find("p").text()) + $(a).find("a").text();
+ var cmpB = ($(b).find("p") == undefined ? "0":$(b).find("p").text()) + $(b).find("a").text();
+ return cmpA > cmpB ? 1 : -1;
+ };
+
+ $('ul.repo-list li').sort(repoSort).appendTo('ul.repo-list');
+ $('<li class="all selected"><a href="#">All repositories</a><p>' + numItems + "</p></li>").prependTo('ul.repo-list');
+ $('ul.repo-list li a').click(function() {
+ var prevClass = $("ul.repo-list li.selected").attr("class");
+ $("ul.repo-list li.selected").attr("class", prevClass == "all selected" ? "all":"");
+
+ var currClass = $(this).parent().attr("class");
+ $(this).parent().attr("class", currClass + " selected");
+
+ var repository = $(this).text();
+ if($(this).parent().attr("class").indexOf("all") < 0) {
+ loadPullRequests($(this).text());
+ } else {
+ loadPullRequests();
+ }
+ });
+ }
+
+ $("#submit").prop("disabled", "");
+ $(".filter").attr("style","display: visible;");
+ $("div.loading").attr("style","display: none;");
+ $('#pull-requests').attr("style","display: visible;");
+ $('.dataTables_header').attr("style","display: visible;");
+ });
+};
+
+var filterTimeout;
+$("input#repo-filter").keyup(function() {
+ if (filterTimeout) {
+ clearTimeout(filterTimeout);
+ }
+ filterTimeout = setTimeout(function () {
+ filterRepositories();
+ },500);
+});
+
+var filterRepositories = function() {
+ var filter = $("input#repo-filter").val().toLowerCase();
+ var numRepos = 0;
+ $("ul.repo-list li").each(function() {
+ var repoName = $(this).find("a").text();
+ var matched = repoName.toLowerCase().indexOf(filter)>=0;
+ if(matched) {
+ $(this).attr("style","display: visible;");
+ } else {
+ $(this).attr("style","display: none;");
+ }
+ });
+}
+
+$(document).ready(function () {
+ loadPullRequests();
+});
+
diff --git a/github-plugin/src/main/resources/static/js/repositories.js b/github-plugin/src/main/resources/static/js/repositories.js
index 073d6c0..3cf495b 100644
--- a/github-plugin/src/main/resources/static/js/repositories.js
+++ b/github-plugin/src/main/resources/static/js/repositories.js
@@ -15,7 +15,7 @@
for (var i=0; i<repos.length; i++) {
var id = repos[i].index;
var value = repos[i].value;
- var status = repos[i].status;
+ var status = repos[i].status.toLowerCase();
$("#status_" + id).attr("class", "status " + status);
$("#repo_" + id).text(value);
if(status == 'sync') {
@@ -65,10 +65,12 @@
var checkbox = $(this).find("input.keycheckbox");
if(matched && numRepos < maxItems) {
$(this).attr("style","display: visible;");
+ $(this).find("input").removeAttr("disabled");
checkbox.prop("checked", true);
numRepos++;
} else {
$(this).attr("style","display: none;");
+ $(this).find("input").attr("disabled","disabled");
checkbox.prop("checked", false);
}
});
@@ -156,6 +158,7 @@
"repo": repo }));
if(i >= maxItems) {
repoLine.attr("style","display:none;");
+ repoLine.find("input").attr("disabled","disabled");
repoLine.find("input.keycheckbox").prop("checked", false);
} else {
repoLine.find("input.keycheckbox").prop("checked", true);
diff --git a/github-plugin/src/main/resources/static/pullrequests.html b/github-plugin/src/main/resources/static/pullrequests.html
new file mode 100644
index 0000000..883b0ed
--- /dev/null
+++ b/github-plugin/src/main/resources/static/pullrequests.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html dir="ltr" lang="en-US">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>GitHub plugin for Gerrit Code Review - Pull Requests
+ replication</title>
+#include ("static/styles.html")
+#include ("static/scripts.html")
+</head>
+<body>
+ <div class="header">
+ <div>
+ <div class="center">
+ #include ("static/header.html")
+ <div class="page-title">
+ <div>
+ <h2>
+ Import Pull Requests for Review
+ </h2>
+ <div class="right">
+ <button type="button" id="cancel">
+ <span class="button"><span>Cancel</span></span>
+ </button>
+ <button type="submit" id="submit" disabled="disabled">
+ <span class="button green"><span>Import selected</span></span>
+ </button> <input type="hidden" name="next" value="$nextUrl" />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!--div.header end -->
+
+ <!--div.container start -->
+ <div class="container">
+ <div class="center">
+ <form id="pullrequests" action="pullrequests-next.gh" method="get" class="signupform">
+ <div id="repositories">
+ <ul class="pullrequest-repos">
+ <li>
+ <select id="organisation" name="organisation">
+ <option selected="selected">$myself.login</option>
+ #foreach( $organisation in $myself.organizations )
+ #if ( $request.getParameter("organisation") == $organisation.login )
+ <option selected="selected">$organisation.login</option>
+ #else
+ <option>$organisation.login</option>
+ #end
+ #end
+ </select>
+ </li>
+ <li class="filter" style="display: none;">
+ <input type="text" id="repo-filter" class="filter" name="filter" placeholder="Filter repositories" />
+ </li>
+ </ul>
+ <ul class="repo-list">
+ </ul>
+ </div>
+ <div id="pullrequests">
+ <div class="loading">
+ <p>Loading Pull Requests from GitHub ...</p>
+ </div>
+ <table class="table simple-table responsive-table responsive-table-on" style="display: none;" id="pull-requests">
+
+ <thead>
+ <tr>
+ <th scope="col" ><input type="checkbox" name="checkall" id="checkall" value="1" checked="checked"></th>
+ <th scope="col" >Id</th>
+ <th scope="col" >Repository / Pull request subject / Author</th>
+ <th scope="col" >Timestamp</th>
+ <th scope="col" >
+ <select id="importtype">
+ <option selected="selected">Squash</option>
+ <option>Commits</option>
+ </select>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </form>
+ </div>
+ </div>
+ <script type='text/javascript' src='js/pullrequests.js'></script>
+
+ <!--div.container end -->
+ #include ("static/footer.html")
+</body>
+</html>
\ No newline at end of file
diff --git a/github-plugin/src/main/resources/static/repositories.html b/github-plugin/src/main/resources/static/repositories.html
index 39af529..752a737 100644
--- a/github-plugin/src/main/resources/static/repositories.html
+++ b/github-plugin/src/main/resources/static/repositories.html
@@ -36,7 +36,8 @@
<!--div.container start -->
<div class="container">
<div class="center">
- <form id="repositories" action="repositories-next.gh" method="post" class="signupform">
+ <form id="repositories" action="repositories-next.gh" method="get"
+ class="signupform">
<h5>
Select GitHub repositories to clone and replicate
</h5>
diff --git a/github-plugin/src/main/resources/static/scripts.html b/github-plugin/src/main/resources/static/scripts.html
index f7b790d..9f73a7c 100644
--- a/github-plugin/src/main/resources/static/scripts.html
+++ b/github-plugin/src/main/resources/static/scripts.html
@@ -4,5 +4,5 @@
-->
<script type='text/javascript' src='js/jquery-1.10.2.min.js'></script>
<script type='text/javascript' src='js/jquery.cookie.min.js?ver=3.0'></script>
-<script src="js/datatables/jquery.datatables.min.js"></script>
+<script src="js/datatables/jquery.datatables.js"></script>
<script src="js/underscore-min.js"></script>