diff options
Diffstat (limited to 'src/com/gitblit')
44 files changed, 3193 insertions, 101 deletions
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index 4d9a87c5..a58242b5 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -41,6 +41,8 @@ public class Constants { public static final String ADMIN_ROLE = "#admin";
+ public static final String NOT_FEDERATED_ROLE = "#notfederated";
+
public static final String PROPERTIES_FILE = "gitblit.properties";
public static final String GIT_PATH = "/git/";
@@ -49,10 +51,20 @@ public class Constants { public static final String SYNDICATION_PATH = "/feed/";
+ public static final String FEDERATION_PATH = "/federation/";
+
public static final String BORDER = "***********************************************************";
+ public static final String FEDERATION_USER = "$gitblit";
+
+ public static final String PROPOSAL_EXT = ".json";
+
+ public static String getGitBlitVersion() {
+ return NAME + " v" + VERSION;
+ }
+
/**
- * Enumeration representing the 4 access restriction levels.
+ * Enumeration representing the four access restriction levels.
*/
public static enum AccessRestrictionType {
NONE, PUSH, CLONE, VIEW;
@@ -79,7 +91,86 @@ public class Constants { }
}
- public static String getGitBlitVersion() {
- return NAME + " v" + VERSION;
+ /**
+ * Enumeration representing the types of federation tokens.
+ */
+ public static enum FederationToken {
+ ALL, USERS_AND_REPOSITORIES, REPOSITORIES;
+
+ public static FederationToken fromName(String name) {
+ for (FederationToken type : values()) {
+ if (type.name().equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ return REPOSITORIES;
+ }
+
+ public String toString() {
+ return name();
+ }
+ }
+
+ /**
+ * Enumeration representing the types of federation requests.
+ */
+ public static enum FederationRequest {
+ PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_SETTINGS, STATUS;
+
+ public static FederationRequest fromName(String name) {
+ for (FederationRequest type : values()) {
+ if (type.name().equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ return PULL_REPOSITORIES;
+ }
+
+ public String toString() {
+ return name();
+ }
+ }
+
+ /**
+ * Enumeration representing the statii of federation requests.
+ */
+ public static enum FederationPullStatus {
+ PENDING, FAILED, SKIPPED, PULLED, EXCLUDED;
+
+ public static FederationPullStatus fromName(String name) {
+ for (FederationPullStatus type : values()) {
+ if (type.name().equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ return PENDING;
+ }
+
+ @Override
+ public String toString() {
+ return name();
+ }
+ }
+
+ /**
+ * Enumeration representing the federation types.
+ */
+ public static enum FederationStrategy {
+ EXCLUDE, FEDERATE_THIS, FEDERATE_ORIGIN;
+
+ public static FederationStrategy fromName(String name) {
+ for (FederationStrategy type : values()) {
+ if (type.name().equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ return FEDERATE_THIS;
+ }
+
+ @Override
+ public String toString() {
+ return name();
+ }
}
+
}
diff --git a/src/com/gitblit/FederationPullExecutor.java b/src/com/gitblit/FederationPullExecutor.java new file mode 100644 index 00000000..127e1fc8 --- /dev/null +++ b/src/com/gitblit/FederationPullExecutor.java @@ -0,0 +1,310 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.net.InetAddress;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.FederationPullStatus;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.JGitUtils.CloneResult;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * FederationPullExecutor pulls repository updates and, optionally, user
+ * accounts and server settings from registered Gitblit instances.
+ */
+public class FederationPullExecutor implements Runnable {
+
+ private final Logger logger = LoggerFactory.getLogger(FederationPullExecutor.class);
+
+ private final List<FederationModel> registrations;
+
+ /**
+ * Constructor for specifying a single federation registration. This
+ * constructor is used to schedule the next pull execution.
+ *
+ * @param registration
+ */
+ private FederationPullExecutor(FederationModel registration) {
+ this(Arrays.asList(registration));
+ }
+
+ /**
+ * Constructor to specify a group of federation registrations. This is
+ * normally used at startup to pull and then schedule the next update based
+ * on each registrations frequency setting.
+ *
+ * @param registrations
+ */
+ public FederationPullExecutor(List<FederationModel> registrations) {
+ this.registrations = registrations;
+ }
+
+ /**
+ * Run method for this pull executor.
+ */
+ @Override
+ public void run() {
+ for (FederationModel registration : registrations) {
+ FederationPullStatus was = registration.getLowestStatus();
+ try {
+ Date now = new Date(System.currentTimeMillis());
+ pull(registration);
+ sendStatusAcknowledgment(registration);
+ registration.lastPull = now;
+ FederationPullStatus is = registration.getLowestStatus();
+ if (is.ordinal() < was.ordinal()) {
+ // the status for this registration has downgraded
+ logger.warn("Federation pull status of {0} is now {1}", registration.name,
+ is.name());
+ if (registration.notifyOnError) {
+ String message = "Federation pull of " + registration.name + " @ "
+ + registration.url + " is now at " + is.name();
+ GitBlit.self()
+ .notifyAdministrators(
+ "Pull Status of " + registration.name + " is " + is.name(),
+ message);
+ }
+ }
+ } catch (Throwable t) {
+ logger.error(MessageFormat.format(
+ "Failed to pull from federated gitblit ({0} @ {1})", registration.name,
+ registration.url), t);
+ } finally {
+ schedule(registration);
+ }
+ }
+ }
+
+ /**
+ * Mirrors a repository and, optionally, the server's users, and/or
+ * configuration settings from a remote Gitblit instance.
+ *
+ * @param registration
+ * @throws Exception
+ */
+ private void pull(FederationModel registration) throws Exception {
+ Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration,
+ true);
+ String registrationFolder = registration.folder.toLowerCase().trim();
+ // confirm valid characters in server alias
+ Character c = StringUtils.findInvalidCharacter(registrationFolder);
+ if (c != null) {
+ logger.error(MessageFormat
+ .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!",
+ c, registrationFolder, registration.name));
+ return;
+ }
+ File repositoriesFolder = new File(GitBlit.getString(Keys.git.repositoriesFolder, "git"));
+ File registrationFolderFile = new File(repositoriesFolder, registrationFolder);
+ registrationFolderFile.mkdirs();
+
+ // Clone/Pull the repository
+ for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) {
+ String cloneUrl = entry.getKey();
+ RepositoryModel repository = entry.getValue();
+ if (!repository.hasCommits) {
+ logger.warn(MessageFormat.format(
+ "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.",
+ repository.name, registration.name, registration.url));
+ registration.updateStatus(repository, FederationPullStatus.SKIPPED);
+ continue;
+ }
+
+ String repositoryName;
+ if (StringUtils.isEmpty(registrationFolder)) {
+ repositoryName = repository.name;
+ } else {
+ repositoryName = registrationFolder + "/" + repository.name;
+ }
+
+ // confirm that the origin of any pre-existing repository matches
+ // the clone url
+ Repository existingRepository = GitBlit.self().getRepository(repositoryName);
+ if (existingRepository != null) {
+ StoredConfig config = existingRepository.getConfig();
+ config.load();
+ String origin = config.getString("remote", "origin", "url");
+ existingRepository.close();
+ if (!origin.startsWith(registration.url)) {
+ logger.warn(MessageFormat
+ .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.",
+ repository.name, registration.name, registration.url));
+ registration.updateStatus(repository, FederationPullStatus.SKIPPED);
+ continue;
+ }
+ }
+
+ // clone/pull this repository
+ CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
+ Constants.FEDERATION_USER, registration.token);
+ logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
+ repository.name, registration.name, registration.url));
+ CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
+ cloneUrl, credentials);
+ Repository r = GitBlit.self().getRepository(repositoryName);
+ RepositoryModel rm = GitBlit.self().getRepositoryModel(repositoryName);
+ if (result.createdRepository) {
+ // default local settings
+ repository.federationStrategy = FederationStrategy.EXCLUDE;
+ repository.isFrozen = true;
+ } else {
+ // preserve local settings
+ repository.isFrozen = rm.isFrozen;
+ repository.federationStrategy = rm.federationStrategy;
+ }
+ // only repositories that are actually _cloned_ from the source
+ // Gitblit repository are marked as federated. If the origin
+ // is from somewhere else, these repositories are not considered
+ // "federated" repositories.
+ repository.isFederated = cloneUrl.startsWith(registration.url);
+
+ GitBlit.self().updateConfiguration(r, repository);
+ r.close();
+ registration.updateStatus(repository, FederationPullStatus.PULLED);
+ }
+
+ try {
+ // Pull USERS
+ Collection<UserModel> users = FederationUtils.getUsers(registration);
+ if (users != null && users.size() > 0) {
+ File realmFile = new File(registrationFolderFile, registration.name
+ + "_users.properties");
+ realmFile.delete();
+ FileUserService userService = new FileUserService(realmFile);
+ for (UserModel user : users) {
+ userService.updateUserModel(user.username, user);
+
+ // merge the remote permissions and remote accounts into
+ // the user accounts of this Gitblit instance
+ if (registration.mergeAccounts) {
+ // reparent all repository permissions if the local
+ // repositories are stored within subfolders
+ if (!StringUtils.isEmpty(registrationFolder)) {
+ List<String> permissions = new ArrayList<String>(user.repositories);
+ user.repositories.clear();
+ for (String permission : permissions) {
+ user.addRepository(registrationFolder + "/" + permission);
+ }
+ }
+
+ // insert new user or update local user
+ UserModel localUser = GitBlit.self().getUserModel(user.username);
+ if (localUser == null) {
+ // create new local user
+ GitBlit.self().updateUserModel(user.username, user, true);
+ } else {
+ // update repository permissions of local user
+ for (String repository : user.repositories) {
+ localUser.addRepository(repository);
+ }
+ localUser.password = user.password;
+ localUser.canAdmin = user.canAdmin;
+ GitBlit.self().updateUserModel(localUser.username, localUser, false);
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ // a 403 error code is normal for a PULL_REPOSITORIES token
+ if (!e.getMessage().contains("403")) {
+ logger.warn(MessageFormat.format(
+ "Failed to retrieve USERS from federated gitblit ({0} @ {1})",
+ registration.name, registration.url), e);
+ }
+ }
+
+ try {
+ // Pull SETTINGS
+ Map<String, String> settings = FederationUtils.getSettings(registration);
+ if (settings != null && settings.size() > 0) {
+ Properties properties = new Properties();
+ properties.putAll(settings);
+ FileOutputStream os = new FileOutputStream(new File(registrationFolderFile,
+ registration.name + "_" + Constants.PROPERTIES_FILE));
+ properties.store(os, null);
+ os.close();
+ }
+ } catch (Exception e) {
+ // a 403 error code is normal for a PULL_REPOSITORIES token
+ if (!e.getMessage().contains("403")) {
+ logger.warn(MessageFormat.format(
+ "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
+ registration.name, registration.url), e);
+ }
+ }
+ }
+
+ /**
+ * Sends a status acknowledgment to the source Gitblit instance. This
+ * includes the results of the federated pull.
+ *
+ * @param registration
+ * @throws Exception
+ */
+ private void sendStatusAcknowledgment(FederationModel registration) throws Exception {
+ if (!registration.sendStatus) {
+ // skip status acknowledgment
+ return;
+ }
+ InetAddress addr = InetAddress.getLocalHost();
+ String federationName = GitBlit.getString(Keys.federation.name, null);
+ if (StringUtils.isEmpty(federationName)) {
+ federationName = addr.getHostName();
+ }
+ FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration);
+ }
+
+ /**
+ * Schedules the next check of the federated Gitblit instance.
+ *
+ * @param registration
+ */
+ private void schedule(FederationModel registration) {
+ // schedule the next pull
+ int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency);
+ registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
+ GitBlit.self().executor()
+ .schedule(new FederationPullExecutor(registration), mins, TimeUnit.MINUTES);
+ logger.info(MessageFormat.format(
+ "Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}",
+ registration.name, registration.url, registration.nextPull));
+ }
+}
diff --git a/src/com/gitblit/FederationServlet.java b/src/com/gitblit/FederationServlet.java new file mode 100644 index 00000000..b8b6408c --- /dev/null +++ b/src/com/gitblit/FederationServlet.java @@ -0,0 +1,318 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.BufferedReader;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Handles federation requests.
+ *
+ * @author James Moger
+ *
+ */
+public class FederationServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private transient Logger logger = LoggerFactory.getLogger(FederationServlet.class);
+
+ public FederationServlet() {
+ super();
+ }
+
+ /**
+ * Returns an url to this servlet for the specified parameters.
+ *
+ * @param sourceURL
+ * the url of the source gitblit instance
+ * @param token
+ * the federation token of the source gitblit instance
+ * @param req
+ * the pull type request
+ */
+ public static String asPullLink(String sourceURL, String token, FederationRequest req) {
+ return asFederationLink(sourceURL, null, token, req, null);
+ }
+
+ /**
+ *
+ * @param remoteURL
+ * the url of the remote gitblit instance
+ * @param tokenType
+ * the type of federation token of a gitblit instance
+ * @param token
+ * the federation token of a gitblit instance
+ * @param req
+ * the pull type request
+ * @param myURL
+ * the url of this gitblit instance
+ * @return
+ */
+ public static String asFederationLink(String remoteURL, FederationToken tokenType,
+ String token, FederationRequest req, String myURL) {
+ if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') {
+ remoteURL = remoteURL.substring(0, remoteURL.length() - 1);
+ }
+ if (req == null) {
+ req = FederationRequest.PULL_REPOSITORIES;
+ }
+ return remoteURL + Constants.FEDERATION_PATH + "?req=" + req.name().toLowerCase()
+ + "&token=" + token
+ + (tokenType == null ? "" : ("&tokenType=" + tokenType.name().toLowerCase()))
+ + (myURL == null ? "" : ("&url=" + StringUtils.encodeURL(myURL)));
+ }
+
+ /**
+ * Returns the list of repositories for federation requests.
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ private void processRequest(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
+ logger.warn(Keys.git.enableGitServlet + " must be set TRUE for federation requests.");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ String uuid = GitBlit.getString(Keys.federation.uuid, "");
+ if (StringUtils.isEmpty(uuid)) {
+ logger.warn(Keys.federation.uuid + " is not properly set! Federation request denied.");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ String token = request.getParameter("token");
+ FederationRequest reqType = FederationRequest.fromName(request.getParameter("req"));
+ logger.info(MessageFormat.format("Federation {0} request from {1}", reqType,
+ request.getRemoteAddr()));
+
+ if (FederationRequest.PROPOSAL.equals(reqType)) {
+ // Receive a gitblit federation proposal
+ String url = StringUtils.decodeFromHtml(request.getParameter("url"));
+ FederationToken tokenType = FederationToken.fromName(request.getParameter("tokenType"));
+
+ if (!GitBlit.getBoolean(Keys.federation.allowProposals, false)) {
+ logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}",
+ tokenType.name(), url));
+ response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+
+ BufferedReader reader = request.getReader();
+ StringBuilder json = new StringBuilder();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ json.append(line);
+ }
+ reader.close();
+
+ // check to see if we have repository data
+ if (json.length() == 0) {
+ logger.error(MessageFormat.format(
+ "Failed to receive proposed repositories list from {0}", url));
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ // deserialize the repository data
+ Gson gson = new Gson();
+ Map<String, RepositoryModel> repositories = gson.fromJson(json.toString(),
+ FederationUtils.REPOSITORIES_TYPE);
+
+ // submit a proposal
+ FederationProposal proposal = new FederationProposal(url, tokenType, token,
+ repositories);
+ String hosturl = HttpUtils.getHostURL(request);
+ String gitblitUrl = hosturl + request.getContextPath();
+ GitBlit.self().submitFederationProposal(proposal, gitblitUrl);
+ logger.info(MessageFormat.format(
+ "Submitted {0} federation proposal to pull {1} repositories from {2}",
+ tokenType.name(), repositories.size(), url));
+ response.setStatus(HttpServletResponse.SC_OK);
+ return;
+ }
+
+ if (FederationRequest.STATUS.equals(reqType)) {
+ // Receive a gitblit federation status acknowledgment
+ String remoteId = StringUtils.decodeFromHtml(request.getParameter("url"));
+ String identification = MessageFormat.format("{0} ({1})", remoteId,
+ request.getRemoteAddr());
+ BufferedReader reader = request.getReader();
+ StringBuilder json = new StringBuilder();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ json.append(line);
+ }
+ reader.close();
+
+ // check to see if we have repository data
+ if (json.length() == 0) {
+ logger.error(MessageFormat.format(
+ "Failed to receive pulled repositories list from {0}", identification));
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ // deserialize the status data
+ Gson gson = new Gson();
+ FederationModel results = gson.fromJson(json.toString(), FederationModel.class);
+ // setup the last and netx pull dates
+ results.lastPull = new Date();
+ int mins = TimeUtils.convertFrequencyToMinutes(results.frequency);
+ results.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
+
+ // acknowledge the receipt of status
+ GitBlit.self().acknowledgeFederationStatus(identification, results);
+ logger.info(MessageFormat.format(
+ "Received status of {0} federated repositories from {1}", results
+ .getStatusList().size(), identification));
+ response.setStatus(HttpServletResponse.SC_OK);
+ return;
+ }
+
+ // Determine the federation tokens for this gitblit instance
+ List<String> tokens = GitBlit.self().getFederationTokens();
+ if (!tokens.contains(token)) {
+ logger.warn(MessageFormat.format(
+ "Received Federation token ''{0}'' does not match the server tokens", token));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ Object result = null;
+ if (FederationRequest.PULL_REPOSITORIES.equals(reqType)) {
+ // Determine the Gitblit clone url
+ StringBuilder sb = new StringBuilder();
+ sb.append(HttpUtils.getHostURL(request));
+ sb.append(Constants.GIT_PATH);
+ sb.append("{0}");
+ String cloneUrl = sb.toString();
+
+ // Retrieve all available repositories
+ UserModel user = new UserModel(Constants.FEDERATION_USER);
+ user.canAdmin = true;
+ List<RepositoryModel> list = GitBlit.self().getRepositoryModels(user);
+
+ // create the [cloneurl, repositoryModel] map
+ Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+ for (RepositoryModel model : list) {
+ // by default, setup the url for THIS repository
+ String url = MessageFormat.format(cloneUrl, model.name);
+ switch (model.federationStrategy) {
+ case EXCLUDE:
+ // skip this repository
+ continue;
+ case FEDERATE_ORIGIN:
+ // federate the origin, if it is defined
+ if (!StringUtils.isEmpty(model.origin)) {
+ url = model.origin;
+ }
+ break;
+ }
+ repositories.put(url, model);
+ }
+ result = repositories;
+ } else {
+ if (FederationRequest.PULL_SETTINGS.equals(reqType)) {
+ // pull settings
+ if (!GitBlit.self().validateFederationRequest(reqType, token)) {
+ // invalid token to pull users or settings
+ logger.warn(MessageFormat.format(
+ "Federation token from {0} not authorized to pull SETTINGS",
+ request.getRemoteAddr()));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ Map<String, String> settings = new HashMap<String, String>();
+ List<String> keys = GitBlit.getAllKeys(null);
+ for (String key : keys) {
+ settings.put(key, GitBlit.getString(key, ""));
+ }
+ result = settings;
+ } else if (FederationRequest.PULL_USERS.equals(reqType)) {
+ // pull users
+ if (!GitBlit.self().validateFederationRequest(reqType, token)) {
+ // invalid token to pull users or settings
+ logger.warn(MessageFormat.format(
+ "Federation token from {0} not authorized to pull USERS",
+ request.getRemoteAddr()));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ List<String> usernames = GitBlit.self().getAllUsernames();
+ List<UserModel> users = new ArrayList<UserModel>();
+ for (String username : usernames) {
+ UserModel user = GitBlit.self().getUserModel(username);
+ if (!user.excludeFromFederation) {
+ users.add(user);
+ }
+ }
+ result = users;
+ }
+ }
+
+ if (result != null) {
+ // Send JSON response
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ String json = gson.toJson(result);
+ response.getWriter().append(json);
+ }
+ }
+
+ @Override
+ protected void doPost(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ processRequest(request, response);
+ }
+}
diff --git a/src/com/gitblit/FileUserService.java b/src/com/gitblit/FileUserService.java index 12164ce6..9dc80087 100644 --- a/src/com/gitblit/FileUserService.java +++ b/src/com/gitblit/FileUserService.java @@ -149,6 +149,8 @@ public class FileUserService extends FileSettings implements IUserService { // Permissions
if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
model.canAdmin = true;
+ } else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) {
+ model.excludeFromFederation = true;
}
break;
default:
@@ -189,6 +191,9 @@ public class FileUserService extends FileSettings implements IUserService { if (model.canAdmin) {
roles.add(Constants.ADMIN_ROLE);
}
+ if (model.excludeFromFederation) {
+ roles.add(Constants.NOT_FEDERATED_ROLE);
+ }
StringBuilder sb = new StringBuilder();
sb.append(model.password);
@@ -499,14 +504,15 @@ public class FileUserService extends FileSettings implements IUserService { // If the write is successful, delete the current file and rename
// the temporary copy to the original filename.
if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
- if (propertiesFile.delete()) {
- if (!realmFileCopy.renameTo(propertiesFile)) {
- throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
- realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));
+ if (propertiesFile.exists()) {
+ if (!propertiesFile.delete()) {
+ throw new IOException(MessageFormat.format("Failed to delete {0}!",
+ propertiesFile.getAbsolutePath()));
}
- } else {
- throw new IOException(MessageFormat.format("Failed to delete (0)!",
- propertiesFile.getAbsolutePath()));
+ }
+ if (!realmFileCopy.renameTo(propertiesFile)) {
+ throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
+ realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));
}
} else {
throw new IOException(MessageFormat.format("Failed to save {0}!",
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index c54fbe14..7c499698 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -16,6 +16,7 @@ package com.gitblit;
import java.io.File;
+import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.MessageFormat;
@@ -25,8 +26,14 @@ import java.util.HashMap; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.mail.Message;
+import javax.mail.MessagingException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.Cookie;
@@ -46,10 +53,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
/**
* GitBlit is the servlet context listener singleton that acts as the core for
@@ -73,6 +87,13 @@ public class GitBlit implements ServletContextListener { private final Logger logger = LoggerFactory.getLogger(GitBlit.class);
+ private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
+
+ private final List<FederationModel> federationRegistrations = Collections
+ .synchronizedList(new ArrayList<FederationModel>());
+
+ private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
+
private RepositoryResolver<Void> repositoryResolver;
private File repositoriesFolder;
@@ -83,6 +104,8 @@ public class GitBlit implements ServletContextListener { private IStoredSettings settings;
+ private MailExecutor mailExecutor;
+
public GitBlit() {
if (gitblit == null) {
// set the static singleton reference
@@ -103,6 +126,15 @@ public class GitBlit implements ServletContextListener { }
/**
+ * Determine if this is the GO variant of Gitblit.
+ *
+ * @return true if this is the GO variant of Gitblit.
+ */
+ public static boolean isGO() {
+ return self().settings instanceof FileSettings;
+ }
+
+ /**
* Returns the boolean value for the specified key. If the key does not
* exist or the value for the key can not be interpreted as a boolean, the
* defaultValue is returned.
@@ -226,6 +258,30 @@ public class GitBlit implements ServletContextListener { * @return a user object or null
*/
public UserModel authenticate(String username, char[] password) {
+ if (StringUtils.isEmpty(username)) {
+ // can not authenticate empty username
+ return null;
+ }
+ String pw = new String(password);
+ if (StringUtils.isEmpty(pw)) {
+ // can not authenticate empty password
+ return null;
+ }
+
+ // check to see if this is the federation user
+ if (canFederate()) {
+ if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
+ List<String> tokens = getFederationTokens();
+ if (tokens.contains(pw)) {
+ // the federation user is an administrator
+ UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
+ federationUser.canAdmin = true;
+ return federationUser;
+ }
+ }
+ }
+
+ // delegate authentication to the user service
if (userService == null) {
return null;
}
@@ -463,6 +519,10 @@ public class GitBlit implements ServletContextListener { model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
model.isFrozen = getConfig(config, "isFrozen", false);
model.showReadme = getConfig(config, "showReadme", false);
+ model.federationStrategy = FederationStrategy.fromName(getConfig(config,
+ "federationStrategy", null));
+ model.isFederated = getConfig(config, "isFederated", false);
+ model.origin = config.getString("remote", "origin", "url");
}
r.close();
return model;
@@ -614,26 +674,40 @@ public class GitBlit implements ServletContextListener { // update settings
if (r != null) {
- StoredConfig config = JGitUtils.readConfig(r);
- config.setString("gitblit", null, "description", repository.description);
- config.setString("gitblit", null, "owner", repository.owner);
- config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
- config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
- config.setString("gitblit", null, "accessRestriction",
- repository.accessRestriction.name());
- config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
- config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
- config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
- try {
- config.save();
- } catch (IOException e) {
- logger.error("Failed to save repository config!", e);
- }
+ updateConfiguration(r, repository);
r.close();
}
}
/**
+ * Updates the Gitblit configuration for the specified repository.
+ *
+ * @param r
+ * the Git repository
+ * @param repository
+ * the Gitblit repository model
+ */
+ public void updateConfiguration(Repository r, RepositoryModel repository) {
+ StoredConfig config = JGitUtils.readConfig(r);
+ config.setString("gitblit", null, "description", repository.description);
+ config.setString("gitblit", null, "owner", repository.owner);
+ config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
+ config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
+ config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.name());
+ config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
+ config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
+ config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
+ config.setString("gitblit", null, "federationStrategy",
+ repository.federationStrategy.name());
+ config.setBoolean("gitblit", null, "isFederated", repository.isFederated);
+ try {
+ config.save();
+ } catch (IOException e) {
+ logger.error("Failed to save repository config!", e);
+ }
+ }
+
+ /**
* Deletes the repository from the file system and removes the repository
* permission from all repository users.
*
@@ -711,6 +785,341 @@ public class GitBlit implements ServletContextListener { }
/**
+ * Returns Gitblit's scheduled executor service for scheduling tasks.
+ *
+ * @return scheduledExecutor
+ */
+ public ScheduledExecutorService executor() {
+ return scheduledExecutor;
+ }
+
+ public static boolean canFederate() {
+ String uuid = getString(Keys.federation.uuid, "");
+ return !StringUtils.isEmpty(uuid);
+ }
+
+ /**
+ * Configures this Gitblit instance to pull any registered federated gitblit
+ * instances.
+ */
+ private void configureFederation() {
+ boolean validUuid = true;
+ String uuid = settings.getString(Keys.federation.uuid, "");
+ if (StringUtils.isEmpty(uuid)) {
+ logger.warn("Federation UUID is blank! This server can not be PULLED from.");
+ validUuid = false;
+ }
+ if (validUuid) {
+ for (FederationToken tokenType : FederationToken.values()) {
+ logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
+ getFederationToken(tokenType)));
+ }
+ }
+
+ // Schedule the federation executor
+ List<FederationModel> registrations = getFederationRegistrations();
+ if (registrations.size() > 0) {
+ scheduledExecutor.schedule(new FederationPullExecutor(registrations), 1,
+ TimeUnit.MINUTES);
+ }
+ }
+
+ /**
+ * Returns the list of federated gitblit instances that this instance will
+ * try to pull.
+ *
+ * @return list of registered gitblit instances
+ */
+ public List<FederationModel> getFederationRegistrations() {
+ if (federationRegistrations.isEmpty()) {
+ List<String> keys = settings.getAllKeys(Keys.federation._ROOT);
+ keys.remove(Keys.federation.name);
+ keys.remove(Keys.federation.uuid);
+ keys.remove(Keys.federation.allowProposals);
+ keys.remove(Keys.federation.proposalsFolder);
+ keys.remove(Keys.federation.defaultFrequency);
+ Collections.sort(keys);
+ Map<String, FederationModel> federatedModels = new HashMap<String, FederationModel>();
+ for (String key : keys) {
+ String value = key.substring(Keys.federation._ROOT.length() + 1);
+ List<String> values = StringUtils.getStringsFromValue(value, "\\.");
+ String server = values.get(0);
+ if (!federatedModels.containsKey(server)) {
+ federatedModels.put(server, new FederationModel(server));
+ }
+ String setting = values.get(1);
+ if (setting.equals("url")) {
+ // url of the remote Gitblit instance
+ federatedModels.get(server).url = settings.getString(key, "");
+ } else if (setting.equals("token")) {
+ // token for the remote Gitblit instance
+ federatedModels.get(server).token = settings.getString(key, "");
+ } else if (setting.equals("frequency")) {
+ // frequency of the pull operation
+ federatedModels.get(server).frequency = settings.getString(key, "");
+ } else if (setting.equals("folder")) {
+ // destination folder of the pull operation
+ federatedModels.get(server).folder = settings.getString(key, "");
+ } else if (setting.equals("mergeAccounts")) {
+ // merge remote accounts into local accounts
+ federatedModels.get(server).mergeAccounts = settings.getBoolean(key, false);
+ } else if (setting.equals("sendStatus")) {
+ // send a status acknowledgment to source Gitblit instance
+ // at end of git pull
+ federatedModels.get(server).sendStatus = settings.getBoolean(key, false);
+ } else if (setting.equals("notifyOnError")) {
+ // notify administrators on federation pull failures
+ federatedModels.get(server).notifyOnError = settings.getBoolean(key, false);
+ } else if (setting.equals("exclude")) {
+ // excluded repositories
+ federatedModels.get(server).exclusions = settings.getStrings(key);
+ } else if (setting.equals("include")) {
+ // included repositories
+ federatedModels.get(server).inclusions = settings.getStrings(key);
+ }
+ }
+
+ // verify that registrations have a url and a token
+ for (FederationModel model : federatedModels.values()) {
+ if (StringUtils.isEmpty(model.url)) {
+ logger.warn(MessageFormat.format(
+ "Dropping federation registration {0}. Missing url.", model.name));
+ continue;
+ }
+ if (StringUtils.isEmpty(model.token)) {
+ logger.warn(MessageFormat.format(
+ "Dropping federation registration {0}. Missing token.", model.name));
+ continue;
+ }
+ // set default frequency if unspecified
+ if (StringUtils.isEmpty(model.frequency)) {
+ model.frequency = settings.getString(Keys.federation.defaultFrequency,
+ "60 mins");
+ }
+ federationRegistrations.add(model);
+ }
+ }
+ return federationRegistrations;
+ }
+
+ /**
+ * Retrieve the specified federation registration.
+ *
+ * @param name
+ * the name of the registration
+ * @return a federation registration
+ */
+ public FederationModel getFederationRegistration(String url, String name) {
+ // check registrations
+ for (FederationModel r : getFederationRegistrations()) {
+ if (r.name.equals(name) && r.url.equals(url)) {
+ return r;
+ }
+ }
+
+ // check the results
+ for (FederationModel r : getFederationResultRegistrations()) {
+ if (r.name.equals(name) && r.url.equals(url)) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the list of possible federation tokens for this Gitblit instance.
+ *
+ * @return list of federation tokens
+ */
+ public List<String> getFederationTokens() {
+ List<String> tokens = new ArrayList<String>();
+ for (FederationToken type : FederationToken.values()) {
+ tokens.add(getFederationToken(type));
+ }
+ return tokens;
+ }
+
+ /**
+ * Returns the specified federation token for this Gitblit instance.
+ *
+ * @param type
+ * @return a federation token
+ */
+ public String getFederationToken(FederationToken type) {
+ String uuid = settings.getString(Keys.federation.uuid, "");
+ return StringUtils.getSHA1(uuid + "-" + type.name());
+ }
+
+ /**
+ * Compares the provided token with this Gitblit instance's tokens and
+ * determines if the requested permission may be granted to the token.
+ *
+ * @param req
+ * @param token
+ * @return true if the request can be executed
+ */
+ public boolean validateFederationRequest(FederationRequest req, String token) {
+ String all = getFederationToken(FederationToken.ALL);
+ String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
+ String jur = getFederationToken(FederationToken.REPOSITORIES);
+ switch (req) {
+ case PULL_REPOSITORIES:
+ return token.equals(all) || token.equals(unr) || token.equals(jur);
+ case PULL_USERS:
+ return token.equals(all) || token.equals(unr);
+ case PULL_SETTINGS:
+ return token.equals(all);
+ }
+ return false;
+ }
+
+ /**
+ * Acknowledge and cache the status of a remote Gitblit instance.
+ *
+ * @param identification
+ * the identification of the pulling Gitblit instance
+ * @param registration
+ * the registration from the pulling Gitblit instance
+ * @return true if acknowledged
+ */
+ public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
+ // reset the url to the identification of the pulling Gitblit instance
+ registration.url = identification;
+ String id = identification;
+ if (!StringUtils.isEmpty(registration.folder)) {
+ id += "-" + registration.folder;
+ }
+ federationPullResults.put(id, registration);
+ return true;
+ }
+
+ /**
+ * Returns the list of registration results.
+ *
+ * @return the list of registration results
+ */
+ public List<FederationModel> getFederationResultRegistrations() {
+ return new ArrayList<FederationModel>(federationPullResults.values());
+ }
+
+ /**
+ * Submit a federation proposal. The proposal is cached locally and the
+ * Gitblit administrator(s) are notified via email.
+ *
+ * @param proposal
+ * the proposal
+ * @param gitblitUrl
+ * the url of your gitblit instance
+ * @return true if the proposal was submitted
+ */
+ public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
+ // convert proposal to json
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ String json = gson.toJson(proposal);
+
+ try {
+ // make the proposals folder
+ File proposalsFolder = new File(getString(Keys.federation.proposalsFolder, "proposals")
+ .trim());
+ proposalsFolder.mkdirs();
+
+ // cache json to a file
+ File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
+ com.gitblit.utils.FileUtils.writeContent(file, json);
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
+ }
+
+ // send an email, if possible
+ try {
+ Message message = mailExecutor.createMessageForAdministrators();
+ if (message != null) {
+ message.setSubject("Federation proposal from " + proposal.url);
+ message.setText("Please review the proposal @ " + gitblitUrl + "/proposal/"
+ + proposal.token);
+ mailExecutor.queue(message);
+ }
+ } catch (Throwable t) {
+ logger.error("Failed to notify administrators of proposal", t);
+ }
+ return true;
+ }
+
+ /**
+ * Returns the list of pending federation proposals
+ *
+ * @return list of federation proposals
+ */
+ public List<FederationProposal> getPendingFederationProposals() {
+ List<FederationProposal> list = new ArrayList<FederationProposal>();
+ File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
+ if (folder.exists()) {
+ File[] files = folder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isFile()
+ && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
+ }
+ });
+ Gson gson = new Gson();
+ for (File file : files) {
+ String json = com.gitblit.utils.FileUtils.readContent(file, null);
+ FederationProposal proposal = gson.fromJson(json, FederationProposal.class);
+ list.add(proposal);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Returns the proposal identified by the supplied token.
+ *
+ * @param token
+ * @return the specified proposal or null
+ */
+ public FederationProposal getPendingFederationProposal(String token) {
+ List<FederationProposal> list = getPendingFederationProposals();
+ for (FederationProposal proposal : list) {
+ if (proposal.token.equals(token)) {
+ return proposal;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Deletes a pending federation proposal.
+ *
+ * @param a
+ * proposal
+ * @return true if the proposal was deleted
+ */
+ public boolean deletePendingFederationProposal(FederationProposal proposal) {
+ File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
+ File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
+ return file.delete();
+ }
+
+ /**
+ * Notify the administrators by email.
+ *
+ * @param subject
+ * @param message
+ */
+ public void notifyAdministrators(String subject, String message) {
+ try {
+ Message mail = mailExecutor.createMessageForAdministrators();
+ if (mail != null) {
+ mail.setSubject(subject);
+ mail.setText(message);
+ mailExecutor.queue(mail);
+ }
+ } catch (MessagingException e) {
+ logger.error("Messaging error", e);
+ }
+ }
+
+ /**
* Configure the Gitblit singleton with the specified settings source. This
* source may be file settings (Gitblit GO) or may be web.xml settings
* (Gitblit WAR).
@@ -746,6 +1155,13 @@ public class GitBlit implements ServletContextListener { loginService = new FileUserService(realmFile);
}
setUserService(loginService);
+ configureFederation();
+ mailExecutor = new MailExecutor(settings);
+ if (mailExecutor.isReady()) {
+ scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
+ } else {
+ logger.warn("Mail server is not properly configured. Mail services disabled.");
+ }
}
/**
@@ -770,5 +1186,6 @@ public class GitBlit implements ServletContextListener { @Override
public void contextDestroyed(ServletContextEvent contextEvent) {
logger.info("Gitblit context destroyed by servlet container.");
+ scheduledExecutor.shutdownNow();
}
}
diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java index 3011413f..83e7ac83 100644 --- a/src/com/gitblit/GitFilter.java +++ b/src/com/gitblit/GitFilter.java @@ -61,7 +61,7 @@ public class GitFilter extends AccessRestrictionFilter { * Analyze the url and returns the action of the request. Return values are
* either "/git-receive-pack" or "/git-upload-pack".
*
- * @param url
+ * @param serverUrl
* @return action of the request
*/
@Override
@@ -106,11 +106,12 @@ public class GitFilter extends AccessRestrictionFilter { // Git Servlet disabled
return false;
}
- if (repository.isFrozen || repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
+ boolean readOnly = repository.isFrozen;
+ if (readOnly || repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
boolean authorizedUser = user.canAccessRepository(repository.name);
if (action.equals(gitReceivePack)) {
// Push request
- if (!repository.isFrozen && authorizedUser) {
+ if (!readOnly && authorizedUser) {
// clone-restricted or push-authorized
return true;
} else {
diff --git a/src/com/gitblit/IStoredSettings.java b/src/com/gitblit/IStoredSettings.java index be836c9b..a376c81a 100644 --- a/src/com/gitblit/IStoredSettings.java +++ b/src/com/gitblit/IStoredSettings.java @@ -86,7 +86,7 @@ public abstract class IStoredSettings { if (props.containsKey(name)) {
String value = props.getProperty(name);
if (!StringUtils.isEmpty(value)) {
- return Boolean.parseBoolean(value);
+ return Boolean.parseBoolean(value.trim());
}
}
return defaultValue;
@@ -107,7 +107,7 @@ public abstract class IStoredSettings { try {
String value = props.getProperty(name);
if (!StringUtils.isEmpty(value)) {
- return Integer.parseInt(value);
+ return Integer.parseInt(value.trim());
}
} catch (NumberFormatException e) {
logger.warn("Failed to parse integer for " + name + " using default of "
@@ -131,7 +131,7 @@ public abstract class IStoredSettings { if (props.containsKey(name)) {
String value = props.getProperty(name);
if (!StringUtils.isEmpty(value)) {
- return value.charAt(0);
+ return value.trim().charAt(0);
}
}
return defaultValue;
@@ -151,7 +151,7 @@ public abstract class IStoredSettings { if (props.containsKey(name)) {
String value = props.getProperty(name);
if (value != null) {
- return value;
+ return value.trim();
}
}
return defaultValue;
diff --git a/src/com/gitblit/Launcher.java b/src/com/gitblit/Launcher.java index da616499..abd4098e 100644 --- a/src/com/gitblit/Launcher.java +++ b/src/com/gitblit/Launcher.java @@ -78,12 +78,15 @@ public class Launcher { if (jars.size() == 0) {
for (String folder : folders) {
File libFolder = new File(folder);
- System.err.println("Failed to find any JARs in " + libFolder.getPath());
+ // this is a test of adding a comment
+ // more really interesting things
+ System.err.println("Failed to find any really cool JARs in " + libFolder.getPath());
}
System.exit(-1);
} else {
for (File jar : jars) {
try {
+ jar.canRead();
addJarFile(jar);
} catch (Throwable t) {
t.printStackTrace();
@@ -113,6 +116,7 @@ public class Launcher { }
}
}
+
return jars;
}
diff --git a/src/com/gitblit/MailExecutor.java b/src/com/gitblit/MailExecutor.java new file mode 100644 index 00000000..202875ed --- /dev/null +++ b/src/com/gitblit/MailExecutor.java @@ -0,0 +1,234 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.mail.Authenticator;
+import javax.mail.Message;
+import javax.mail.Message.RecipientType;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The mail executor handles sending email messages asynchronously from queue.
+ *
+ * @author James Moger
+ *
+ */
+public class MailExecutor implements Runnable {
+
+ private final Logger logger = LoggerFactory.getLogger(MailExecutor.class);
+
+ private final Queue<Message> queue = new ConcurrentLinkedQueue<Message>();
+
+ private final Set<Message> failures = Collections.synchronizedSet(new HashSet<Message>());
+
+ private final Session session;
+
+ private final IStoredSettings settings;
+
+ public MailExecutor(IStoredSettings settings) {
+ this.settings = settings;
+
+ final String mailUser = settings.getString(Keys.mail.username, null);
+ final String mailPassword = settings.getString(Keys.mail.password, null);
+ boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword);
+ String server = settings.getString(Keys.mail.server, "");
+ if (StringUtils.isEmpty(server)) {
+ session = null;
+ return;
+ }
+ int port = settings.getInteger(Keys.mail.port, 25);
+ boolean isGMail = false;
+ if (server.equals("smtp.gmail.com")) {
+ port = 465;
+ isGMail = true;
+ }
+
+ Properties props = new Properties();
+ props.setProperty("mail.smtp.host", server);
+ props.setProperty("mail.smtp.port", String.valueOf(port));
+ props.setProperty("mail.smtp.auth", String.valueOf(authenticate));
+ props.setProperty("mail.smtp.auths", String.valueOf(authenticate));
+
+ if (isGMail) {
+ props.setProperty("mail.smtp.starttls.enable", "true");
+ props.put("mail.smtp.socketFactory.port", String.valueOf(port));
+ props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+ props.put("mail.smtp.socketFactory.fallback", "false");
+ }
+
+ if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
+ // SMTP requires authentication
+ session = Session.getInstance(props, new Authenticator() {
+ protected PasswordAuthentication getPasswordAuthentication() {
+ PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
+ mailUser, mailPassword);
+ return passwordAuthentication;
+ }
+ });
+ } else {
+ // SMTP does not require authentication
+ session = Session.getInstance(props);
+ }
+ }
+
+ /**
+ * Indicates if the mail executor can send emails.
+ *
+ * @return true if the mail executor is ready to send emails
+ */
+ public boolean isReady() {
+ return session != null;
+ }
+
+ /**
+ * Creates a message for the administrators.
+ *
+ * @returna message
+ */
+ public Message createMessageForAdministrators() {
+ List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
+ if (toAddresses.size() == 0) {
+ logger.warn("Can not notify administrators because no email addresses are defined!");
+ return null;
+ }
+ return createMessage(toAddresses);
+ }
+
+ /**
+ * Create a message.
+ *
+ * @param toAddresses
+ * @return a message
+ */
+ public Message createMessage(String... toAddresses) {
+ return createMessage(Arrays.asList(toAddresses));
+ }
+
+ /**
+ * Create a message.
+ *
+ * @param toAddresses
+ * @return a message
+ */
+ public Message createMessage(List<String> toAddresses) {
+ MimeMessage message = new MimeMessage(session);
+ try {
+ InternetAddress from = new InternetAddress(settings.getString(Keys.mail.fromAddress,
+ "gitblit@gitblit.com"), "Gitblit");
+ message.setFrom(from);
+
+ InternetAddress[] tos = new InternetAddress[toAddresses.size()];
+ for (int i = 0; i < toAddresses.size(); i++) {
+ tos[i] = new InternetAddress(toAddresses.get(i));
+ }
+ message.setRecipients(Message.RecipientType.TO, tos);
+ message.setSentDate(new Date());
+ } catch (Exception e) {
+ logger.error("Failed to properly create message", e);
+ }
+ return message;
+ }
+
+ /**
+ * Queue's an email message to be sent.
+ *
+ * @param message
+ * @return true if the message was queued
+ */
+ public boolean queue(Message message) {
+ if (!isReady()) {
+ return false;
+ }
+ try {
+ message.saveChanges();
+ } catch (Throwable t) {
+ logger.error("Failed to save changes to message!", t);
+ }
+ queue.add(message);
+ return true;
+ }
+
+ @Override
+ public void run() {
+ if (!queue.isEmpty()) {
+ if (session != null) {
+ // send message via mail server
+ Message message = null;
+ while ((message = queue.peek()) != null) {
+ try {
+ if (settings.getBoolean(Keys.mail.debug, false)) {
+ logger.info("send: "
+ + StringUtils.trimString(
+ message.getSubject()
+ + " => "
+ + message.getRecipients(RecipientType.TO)[0]
+ .toString(), 60));
+ }
+ Transport.send(message);
+ queue.remove();
+ failures.remove(message);
+ } catch (Throwable e) {
+ if (!failures.contains(message)) {
+ logger.error("Failed to send message", e);
+ failures.add(message);
+ }
+ }
+ }
+ }
+ } else {
+ // log message to console and drop
+ if (!queue.isEmpty()) {
+ Message message = null;
+ while ((message = queue.peek()) != null) {
+ try {
+ logger.info("drop: "
+ + StringUtils.trimString(
+ (message.getSubject())
+ + " => "
+ + message.getRecipients(RecipientType.TO)[0]
+ .toString(), 60));
+ queue.remove();
+ failures.remove(message);
+ } catch (Throwable e) {
+ if (!failures.contains(message)) {
+ logger.error("Failed to remove message from queue");
+ failures.add(message);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/gitblit/SyndicationServlet.java b/src/com/gitblit/SyndicationServlet.java index df5bb4e5..99497544 100644 --- a/src/com/gitblit/SyndicationServlet.java +++ b/src/com/gitblit/SyndicationServlet.java @@ -26,10 +26,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.SyndicationUtils;
-import com.gitblit.wicket.WicketUtils;
/**
* SyndicationServlet generates RSS 2.0 feeds and feed links.
@@ -116,7 +116,7 @@ public class SyndicationServlet extends HttpServlet { javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
java.io.IOException {
- String hostURL = WicketUtils.getHostURL(request);
+ String hostURL = HttpUtils.getHostURL(request);
String url = request.getRequestURI().substring(request.getServletPath().length());
if (url.charAt(0) == '/' && url.length() > 1) {
url = url.substring(1);
diff --git a/src/com/gitblit/build/Build.java b/src/com/gitblit/build/Build.java index 5b04d241..2d1db6d3 100644 --- a/src/com/gitblit/build/Build.java +++ b/src/com/gitblit/build/Build.java @@ -81,6 +81,8 @@ public class Build { downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME);
downloadFromApache(MavenObject.ROME, BuildType.RUNTIME);
downloadFromApache(MavenObject.JDOM, BuildType.RUNTIME);
+ downloadFromApache(MavenObject.GSON, BuildType.RUNTIME);
+ downloadFromApache(MavenObject.MAIL, BuildType.RUNTIME);
downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
@@ -104,6 +106,8 @@ public class Build { downloadFromApache(MavenObject.JSCH, BuildType.COMPILETIME);
downloadFromApache(MavenObject.ROME, BuildType.COMPILETIME);
downloadFromApache(MavenObject.JDOM, BuildType.COMPILETIME);
+ downloadFromApache(MavenObject.GSON, BuildType.COMPILETIME);
+ downloadFromApache(MavenObject.MAIL, BuildType.COMPILETIME);
downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
@@ -442,6 +446,16 @@ public class Build { "a7ed425c4c46605b8f2bf2ee118c1609682f4f2c",
"f3df91edccba2f07a0fced70887c2f7b7836cb75");
+ public static final MavenObject GSON = new MavenObject("gson", "com/google/code/gson",
+ "gson", "1.7.1", 174000, 142000, 247000,
+ "0697e3a1fa094a983cd12f7f6f61abf9c6ea52e2",
+ "51f6f78aec2d30d0c2bfb4a5f00d456a6f7a5e7e",
+ "f0872fe17d484815328538b89909d5e46d85db74");
+
+ public static final MavenObject MAIL = new MavenObject("javax.mail", "javax/mail", "mail",
+ "1.4.3", 462000, 642000, 0, "8154bf8d666e6db154c548dc31a8d512c273f5ee",
+ "5875e2729de83a4e46391f8f979ec8bd03810c10", null);
+
public final String name;
public final String group;
public final String artifact;
diff --git a/src/com/gitblit/models/FederationModel.java b/src/com/gitblit/models/FederationModel.java new file mode 100644 index 00000000..017c2e01 --- /dev/null +++ b/src/com/gitblit/models/FederationModel.java @@ -0,0 +1,215 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.gitblit.Constants.FederationPullStatus;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Represents a federated server registration. Gitblit federation allows one
+ * Gitblit instance to pull the repositories and configuration from another
+ * Gitblit instance. This is a backup operation and can be considered something
+ * like svn-sync.
+ *
+ */
+public class FederationModel implements Serializable, Comparable<FederationModel> {
+
+ private static final long serialVersionUID = 1L;
+
+ public String name;
+
+ public String url;
+
+ public String token;
+
+ public String frequency;
+
+ public String folder;
+
+ public boolean mergeAccounts;
+
+ public boolean sendStatus;
+
+ public boolean notifyOnError;
+
+ public List<String> exclusions = new ArrayList<String>();
+
+ public List<String> inclusions = new ArrayList<String>();
+
+ public Date lastPull;
+
+ public Date nextPull;
+
+ private Map<String, FederationPullStatus> results = new ConcurrentHashMap<String, FederationPullStatus>();
+
+ /**
+ * The constructor for a remote server configuration.
+ *
+ * @param serverName
+ */
+ public FederationModel(String serverName) {
+ this.name = serverName;
+ this.lastPull = new Date(0);
+ this.nextPull = new Date(0);
+ }
+
+ public boolean isIncluded(RepositoryModel repository) {
+ // if exclusions has the all wildcard, then check for specific
+ // inclusions
+ if (exclusions.contains("*")) {
+ for (String name : inclusions) {
+ if (StringUtils.fuzzyMatch(repository.name, name)) {
+ results.put(repository.name, FederationPullStatus.PENDING);
+ return true;
+ }
+ }
+ results.put(repository.name, FederationPullStatus.EXCLUDED);
+ return false;
+ }
+
+ // named exclusions
+ for (String name : exclusions) {
+ if (StringUtils.fuzzyMatch(repository.name, name)) {
+ results.put(repository.name, FederationPullStatus.EXCLUDED);
+ return false;
+ }
+ }
+
+ // included by default
+ results.put(repository.name, FederationPullStatus.PENDING);
+ return true;
+ }
+
+ /**
+ * Updates the pull status of a particular repository in this federation
+ * registration.
+ *
+ * @param repository
+ * @param status
+ */
+ public void updateStatus(RepositoryModel repository, FederationPullStatus status) {
+ if (!results.containsKey(repository)) {
+ results.put(repository.name, FederationPullStatus.PENDING);
+ }
+ if (status != null) {
+ results.put(repository.name, status);
+ }
+ }
+
+ public List<RepositoryStatus> getStatusList() {
+ List<RepositoryStatus> list = new ArrayList<RepositoryStatus>();
+ for (Map.Entry<String, FederationPullStatus> entry : results.entrySet()) {
+ list.add(new RepositoryStatus(entry.getKey(), entry.getValue()));
+ }
+ return list;
+ }
+
+ /**
+ * Iterates over the current pull results and returns the lowest pull
+ * status.
+ *
+ * @return the lowest pull status of the registration
+ */
+ public FederationPullStatus getLowestStatus() {
+ if (results.size() == 0) {
+ return FederationPullStatus.PENDING;
+ }
+ FederationPullStatus status = FederationPullStatus.PULLED;
+ for (FederationPullStatus result : results.values()) {
+ if (result.ordinal() < status.ordinal()) {
+ status = result;
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Returns true if this registration represents the result data sent by a
+ * pulling Gitblit instance.
+ *
+ * @return true, if this is result data
+ */
+ public boolean isResultData() {
+ return !url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://");
+ }
+
+ @Override
+ public String toString() {
+ return "Federated " + name + " (" + url + ")";
+ }
+
+ @Override
+ public int compareTo(FederationModel o) {
+ boolean r1 = isResultData();
+ boolean r2 = o.isResultData();
+ if ((r1 && r2) || (!r1 && !r2)) {
+ // sort registrations and results by name
+ return name.compareTo(o.name);
+ }
+ // sort registrations first
+ if (r1) {
+ return 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Class that encapsulates a point-in-time pull result.
+ *
+ */
+ public static class RepositoryStatus implements Serializable, Comparable<RepositoryStatus> {
+
+ private static final long serialVersionUID = 1L;
+
+ public final String name;
+ public final FederationPullStatus status;
+
+ RepositoryStatus(String name, FederationPullStatus status) {
+ this.name = name;
+ this.status = status;
+ }
+
+ @Override
+ public int compareTo(RepositoryStatus o) {
+ if (status.equals(o.status)) {
+ // sort root repositories first, alphabetically
+ // then sort grouped repositories, alphabetically
+ int s1 = name.indexOf('/');
+ int s2 = o.name.indexOf('/');
+ if (s1 == -1 && s2 == -1) {
+ // neither grouped
+ return name.compareTo(o.name);
+ } else if (s1 > -1 && s2 > -1) {
+ // both grouped
+ return name.compareTo(o.name);
+ } else if (s1 == -1) {
+ return -1;
+ } else if (s2 == -1) {
+ return 1;
+ }
+ return 0;
+ }
+ return status.compareTo(o.status);
+ }
+ }
+}
diff --git a/src/com/gitblit/models/FederationProposal.java b/src/com/gitblit/models/FederationProposal.java new file mode 100644 index 00000000..03b31ce7 --- /dev/null +++ b/src/com/gitblit/models/FederationProposal.java @@ -0,0 +1,79 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+
+import com.gitblit.Constants.FederationToken;
+
+/**
+ * Represents a proposal from a Gitblit instance to pull its repositories.
+ */
+public class FederationProposal implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public Date received;
+
+ public String name;
+
+ public String url;
+
+ public FederationToken tokenType;
+
+ public String token;
+
+ public Map<String, RepositoryModel> repositories;
+
+ /**
+ * The constructor for a federation proposal.
+ *
+ * @param url
+ * the url of the source Gitblit instance
+ * @param tokenType
+ * the type of token from the source Gitblit instance
+ * @param token
+ * the federation token from the source Gitblit instance
+ * @param repositories
+ * the map of repositories to be pulled from the source Gitblit
+ * instance keyed by the repository clone url
+ */
+ public FederationProposal(String url, FederationToken tokenType, String token,
+ Map<String, RepositoryModel> repositories) {
+ this.received = new Date();
+ this.url = url;
+ this.tokenType = tokenType;
+ this.token = token;
+ this.repositories = repositories;
+ try {
+ // determine server name and set that as the proposal name
+ name = url.substring(url.indexOf("//") + 2);
+ if (name.contains("/")) {
+ name = name.substring(0, name.indexOf('/'));
+ }
+ name = name.replace(".", "");
+ } catch (Exception e) {
+ name = Long.toHexString(System.currentTimeMillis());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Federation Proposal (" + url + ")";
+ }
+}
diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java index 38637378..cd54ca54 100644 --- a/src/com/gitblit/models/RepositoryModel.java +++ b/src/com/gitblit/models/RepositoryModel.java @@ -19,6 +19,7 @@ import java.io.Serializable; import java.util.Date;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationStrategy;
/**
* RepositoryModel is a serializable model class that represents a Gitblit
@@ -27,7 +28,7 @@ import com.gitblit.Constants.AccessRestrictionType; * @author James Moger
*
*/
-public class RepositoryModel implements Serializable {
+public class RepositoryModel implements Serializable, Comparable<RepositoryModel> {
private static final long serialVersionUID = 1L;
@@ -43,6 +44,11 @@ public class RepositoryModel implements Serializable { public AccessRestrictionType accessRestriction;
public boolean isFrozen;
public boolean showReadme;
+ public FederationStrategy federationStrategy;
+ public boolean isFederated;
+ public String frequency;
+ public String origin;
+ public String size;
public RepositoryModel() {
this("", "", "", new Date(0));
@@ -60,4 +66,9 @@ public class RepositoryModel implements Serializable { public String toString() {
return name;
}
+
+ @Override
+ public int compareTo(RepositoryModel o) {
+ return name.compareTo(o.name);
+ }
}
\ No newline at end of file diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java index aeeecf4b..0d88ce2d 100644 --- a/src/com/gitblit/models/UserModel.java +++ b/src/com/gitblit/models/UserModel.java @@ -17,8 +17,8 @@ package com.gitblit.models; import java.io.Serializable;
import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
/**
* UserModel is a serializable model class that represents a user and the user's
@@ -36,7 +36,8 @@ public class UserModel implements Principal, Serializable { public String username;
public String password;
public boolean canAdmin;
- public final List<String> repositories = new ArrayList<String>();
+ public boolean excludeFromFederation;
+ public final Set<String> repositories = new HashSet<String>();
public UserModel(String username) {
this.username = username;
diff --git a/src/com/gitblit/utils/FederationUtils.java b/src/com/gitblit/utils/FederationUtils.java new file mode 100644 index 00000000..129fe42c --- /dev/null +++ b/src/com/gitblit/utils/FederationUtils.java @@ -0,0 +1,305 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.servlet.http.HttpServletResponse;
+
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.FederationServlet;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Utility methods for federation functions.
+ *
+ * @author James Moger
+ *
+ */
+public class FederationUtils {
+
+ public static final String CHARSET;
+
+ public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
+ }.getType();
+
+ public static final Type SETTINGS_TYPE = new TypeToken<Map<String, String>>() {
+ }.getType();
+
+ public static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
+ }.getType();
+
+ public static final Type RESULTS_TYPE = new TypeToken<List<FederationModel>>() {
+ }.getType();
+
+ private static final SSLContext SSL_CONTEXT;
+
+ private static final DummyHostnameVerifier HOSTNAME_VERIFIER;
+
+ static {
+ SSLContext context = null;
+ try {
+ context = SSLContext.getInstance("SSL");
+ context.init(null, new TrustManager[] { new DummyTrustManager() }, new SecureRandom());
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ SSL_CONTEXT = context;
+ HOSTNAME_VERIFIER = new DummyHostnameVerifier();
+ CHARSET = "UTF-8";
+ }
+
+ /**
+ * Sends a federation proposal to the Gitblit instance at remoteUrl
+ *
+ * @param remoteUrl
+ * the remote Gitblit instance to send a federation proposal to
+ * @param tokenType
+ * type of the provided federation token
+ * @param myToken
+ * my federation token
+ * @param myUrl
+ * my Gitblit url
+ * @param myRepositories
+ * the repositories I want to share keyed by their clone url
+ * @return true if the proposal was received
+ */
+ public static boolean propose(String remoteUrl, FederationToken tokenType, String myToken,
+ String myUrl, Map<String, RepositoryModel> myRepositories) throws Exception {
+ String url = FederationServlet.asFederationLink(remoteUrl, tokenType, myToken,
+ FederationRequest.PROPOSAL, myUrl);
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ String json = gson.toJson(myRepositories);
+ int status = writeJson(url, json);
+ return status == HttpServletResponse.SC_OK;
+ }
+
+ /**
+ * Retrieves a map of the repositories at the remote gitblit instance keyed
+ * by the repository clone url.
+ *
+ * @param registration
+ * @param checkExclusions
+ * should returned repositories remove registration exclusions
+ * @return a map of cloneable repositories
+ * @throws Exception
+ */
+ public static Map<String, RepositoryModel> getRepositories(FederationModel registration,
+ boolean checkExclusions) throws Exception {
+ String url = FederationServlet.asPullLink(registration.url, registration.token,
+ FederationRequest.PULL_REPOSITORIES);
+ Map<String, RepositoryModel> models = readGson(url, REPOSITORIES_TYPE);
+ if (checkExclusions) {
+ Map<String, RepositoryModel> includedModels = new HashMap<String, RepositoryModel>();
+ for (Map.Entry<String, RepositoryModel> entry : models.entrySet()) {
+ if (registration.isIncluded(entry.getValue())) {
+ includedModels.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return includedModels;
+ }
+ return models;
+ }
+
+ /**
+ * Tries to pull the gitblit user accounts from the remote gitblit instance.
+ *
+ * @param registration
+ * @return a collection of UserModel objects
+ * @throws Exception
+ */
+ public static Collection<UserModel> getUsers(FederationModel registration) throws Exception {
+ String url = FederationServlet.asPullLink(registration.url, registration.token,
+ FederationRequest.PULL_USERS);
+ Collection<UserModel> models = readGson(url, USERS_TYPE);
+ return models;
+ }
+
+ /**
+ * Tries to pull the gitblit server settings from the remote gitblit
+ * instance.
+ *
+ * @param registration
+ * @return a map of the remote gitblit settings
+ * @throws Exception
+ */
+ public static Map<String, String> getSettings(FederationModel registration) throws Exception {
+ String url = FederationServlet.asPullLink(registration.url, registration.token,
+ FederationRequest.PULL_SETTINGS);
+ Map<String, String> settings = readGson(url, SETTINGS_TYPE);
+ return settings;
+ }
+
+ /**
+ * Send an status acknowledgment to the remote Gitblit server.
+ *
+ * @param identification
+ * identification of this pulling instance
+ * @param registration
+ * the source Gitblit instance to receive an acknowledgment
+ * @param results
+ * the results of your pull operation
+ * @return true, if the remote Gitblit instance acknowledged your results
+ * @throws Exception
+ */
+ public static boolean acknowledgeStatus(String identification, FederationModel registration)
+ throws Exception {
+ String url = FederationServlet.asFederationLink(registration.url, null, registration.token,
+ FederationRequest.STATUS, identification);
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ String json = gson.toJson(registration);
+ int status = writeJson(url, json);
+ return status == HttpServletResponse.SC_OK;
+ }
+
+ /**
+ * Reads a gson object from the specified url.
+ *
+ * @param url
+ * @param type
+ * @return
+ * @throws Exception
+ */
+ public static <X> X readGson(String url, Type type) throws Exception {
+ String json = readJson(url);
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ Gson gson = new Gson();
+ return gson.fromJson(json, type);
+ }
+
+ /**
+ * Reads a JSON response.
+ *
+ * @param url
+ * @return the JSON response as a string
+ * @throws Exception
+ */
+ public static String readJson(String url) throws Exception {
+ URL urlObject = new URL(url);
+ URLConnection conn = urlObject.openConnection();
+ conn.setRequestProperty("Accept-Charset", CHARSET);
+ conn.setUseCaches(false);
+ conn.setDoInput(true);
+ if (conn instanceof HttpsURLConnection) {
+ HttpsURLConnection secureConn = (HttpsURLConnection) conn;
+ secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
+ secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
+ }
+ InputStream is = conn.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, CHARSET));
+ StringBuilder json = new StringBuilder();
+ char[] buffer = new char[4096];
+ int len = 0;
+ while ((len = reader.read(buffer)) > -1) {
+ json.append(buffer, 0, len);
+ }
+ is.close();
+ return json.toString();
+ }
+
+ /**
+ * Writes a JSON message to the specified url.
+ *
+ * @param url
+ * the url to write to
+ * @param json
+ * the json message to send
+ * @return the http request result code
+ * @throws Exception
+ */
+ public static int writeJson(String url, String json) throws Exception {
+ byte[] jsonBytes = json.getBytes(CHARSET);
+ URL urlObject = new URL(url);
+ URLConnection conn = urlObject.openConnection();
+ conn.setRequestProperty("Content-Type", "text/plain;charset=" + CHARSET);
+ conn.setRequestProperty("Content-Length", "" + jsonBytes.length);
+ conn.setUseCaches(false);
+ conn.setDoOutput(true);
+ if (conn instanceof HttpsURLConnection) {
+ HttpsURLConnection secureConn = (HttpsURLConnection) conn;
+ secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
+ secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
+ }
+
+ // write json body
+ OutputStream os = conn.getOutputStream();
+ os.write(jsonBytes);
+ os.close();
+
+ int status = ((HttpURLConnection) conn).getResponseCode();
+ return status;
+ }
+
+ /**
+ * DummyTrustManager trusts all certificates.
+ */
+ private static class DummyTrustManager implements X509TrustManager {
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] certs, String authType)
+ throws CertificateException {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType)
+ throws CertificateException {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+
+ /**
+ * Trusts all hostnames from a certificate, including self-signed certs.
+ */
+ private static class DummyHostnameVerifier implements HostnameVerifier {
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ }
+}
diff --git a/src/com/gitblit/utils/HttpUtils.java b/src/com/gitblit/utils/HttpUtils.java new file mode 100644 index 00000000..6f0bedeb --- /dev/null +++ b/src/com/gitblit/utils/HttpUtils.java @@ -0,0 +1,45 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Collection of utility methods for http requests.
+ *
+ * @author James Moger
+ *
+ */
+public class HttpUtils {
+
+ /**
+ * Returns the host URL based on the request.
+ *
+ * @param request
+ * @return the host url
+ */
+ public static String getHostURL(HttpServletRequest request) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(request.getScheme());
+ sb.append("://");
+ sb.append(request.getServerName());
+ if ((request.getScheme().equals("http") && request.getServerPort() != 80)
+ || (request.getScheme().equals("https") && request.getServerPort() != 443)) {
+ sb.append(":" + request.getServerPort());
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java index e62795b3..b6b13abb 100644 --- a/src/com/gitblit/utils/JGitUtils.java +++ b/src/com/gitblit/utils/JGitUtils.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.treewalk.TreeWalk;
@@ -134,6 +135,14 @@ public class JGitUtils { }
/**
+ * Encapsulates the result of cloning or pulling from a repository.
+ */
+ public static class CloneResult {
+ public FetchResult fetchResult;
+ public boolean createdRepository;
+ }
+
+ /**
* Clone or Fetch a repository. If the local repository does not exist,
* clone is called. If the repository does exist, fetch is called. By
* default the clone/fetch retrieves the remote heads, tags, and notes.
@@ -141,12 +150,29 @@ public class JGitUtils { * @param repositoriesFolder
* @param name
* @param fromUrl
- * @return FetchResult
+ * @return CloneResult
* @throws Exception
*/
- public static FetchResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
+ public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
throws Exception {
- FetchResult result = null;
+ return cloneRepository(repositoriesFolder, name, fromUrl, null);
+ }
+
+ /**
+ * Clone or Fetch a repository. If the local repository does not exist,
+ * clone is called. If the repository does exist, fetch is called. By
+ * default the clone/fetch retrieves the remote heads, tags, and notes.
+ *
+ * @param repositoriesFolder
+ * @param name
+ * @param fromUrl
+ * @param credentialsProvider
+ * @return CloneResult
+ * @throws Exception
+ */
+ public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl,
+ CredentialsProvider credentialsProvider) throws Exception {
+ CloneResult result = new CloneResult();
if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
name += Constants.DOT_GIT_EXT;
}
@@ -154,7 +180,7 @@ public class JGitUtils { if (folder.exists()) {
File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
FileRepository repository = new FileRepository(gitDir);
- result = fetchRepository(repository);
+ result.fetchResult = fetchRepository(credentialsProvider, repository);
repository.close();
} else {
CloneCommand clone = new CloneCommand();
@@ -162,12 +188,16 @@ public class JGitUtils { clone.setCloneAllBranches(true);
clone.setURI(fromUrl);
clone.setDirectory(folder);
+ if (credentialsProvider != null) {
+ clone.setCredentialsProvider(credentialsProvider);
+ }
clone.call();
// Now we have to fetch because CloneCommand doesn't fetch
// refs/notes nor does it allow manual RefSpec.
File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
FileRepository repository = new FileRepository(gitDir);
- result = fetchRepository(repository);
+ result.createdRepository = true;
+ result.fetchResult = fetchRepository(credentialsProvider, repository);
repository.close();
}
return result;
@@ -177,13 +207,14 @@ public class JGitUtils { * Fetch updates from the remote repository. If refSpecs is unspecifed,
* remote heads, tags, and notes are retrieved.
*
+ * @param credentialsProvider
* @param repository
* @param refSpecs
* @return FetchResult
* @throws Exception
*/
- public static FetchResult fetchRepository(Repository repository, RefSpec... refSpecs)
- throws Exception {
+ public static FetchResult fetchRepository(CredentialsProvider credentialsProvider,
+ Repository repository, RefSpec... refSpecs) throws Exception {
Git git = new Git(repository);
FetchCommand fetch = git.fetch();
List<RefSpec> specs = new ArrayList<RefSpec>();
@@ -194,6 +225,9 @@ public class JGitUtils { } else {
specs.addAll(Arrays.asList(refSpecs));
}
+ if (credentialsProvider != null) {
+ fetch.setCredentialsProvider(credentialsProvider);
+ }
fetch.setRefSpecs(specs);
FetchResult result = fetch.call();
return result;
diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java index bb1928a2..77d3cbbc 100644 --- a/src/com/gitblit/utils/StringUtils.java +++ b/src/com/gitblit/utils/StringUtils.java @@ -342,4 +342,57 @@ public class StringUtils { }
return strings;
}
+
+ /**
+ * Validates that a name is composed of letters, digits, or limited other
+ * characters.
+ *
+ * @param name
+ * @return the first invalid character found or null if string is acceptable
+ */
+ public static Character findInvalidCharacter(String name) {
+ char[] validChars = { '/', '.', '_', '-' };
+ for (char c : name.toCharArray()) {
+ if (!Character.isLetterOrDigit(c)) {
+ boolean ok = false;
+ for (char vc : validChars) {
+ ok |= c == vc;
+ }
+ if (!ok) {
+ return c;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Simple fuzzy string comparison. This is a case-insensitive check. A
+ * single wildcard * value is supported.
+ *
+ * @param value
+ * @param pattern
+ * @return true if the value matches the pattern
+ */
+ public static boolean fuzzyMatch(String value, String pattern) {
+ if (value.equalsIgnoreCase(pattern)) {
+ return true;
+ }
+ if (pattern.contains("*")) {
+ boolean prefixMatches = false;
+ boolean suffixMatches = false;
+
+ int wildcard = pattern.indexOf('*');
+ String prefix = pattern.substring(0, wildcard).toLowerCase();
+ prefixMatches = value.toLowerCase().startsWith(prefix);
+
+ if (pattern.length() > (wildcard + 1)) {
+ String suffix = pattern.substring(wildcard + 1).toLowerCase();
+ suffixMatches = value.toLowerCase().endsWith(suffix);
+ return prefixMatches && suffixMatches;
+ }
+ return prefixMatches || suffixMatches;
+ }
+ return false;
+ }
}
diff --git a/src/com/gitblit/utils/TimeUtils.java b/src/com/gitblit/utils/TimeUtils.java index dbd2d9a8..056735c2 100644 --- a/src/com/gitblit/utils/TimeUtils.java +++ b/src/com/gitblit/utils/TimeUtils.java @@ -18,6 +18,8 @@ package com.gitblit.utils; import java.util.Calendar;
import java.util.Date;
+import com.gitblit.models.FederationModel;
+
/**
* Utility class of time functions.
*
@@ -238,4 +240,41 @@ public class TimeUtils { }
}
}
+
+ /**
+ * Convert a frequency string into minutes.
+ *
+ * @param frequency
+ * @return minutes
+ */
+ public static int convertFrequencyToMinutes(String frequency) {
+ // parse the frequency
+ frequency = frequency.toLowerCase();
+ int mins = 60;
+ if (!StringUtils.isEmpty(frequency)) {
+ try {
+ String str;
+ if (frequency.indexOf(' ') > -1) {
+ str = frequency.substring(0, frequency.indexOf(' ')).trim();
+ } else {
+ str = frequency.trim();
+ }
+ mins = (int) Float.parseFloat(str);
+ } catch (NumberFormatException e) {
+ }
+ if (mins < 5) {
+ mins = 5;
+ }
+ }
+ if (frequency.indexOf("day") > -1) {
+ // convert to minutes
+ mins *= 24 * 60;
+ } else if (frequency.indexOf("hour") > -1) {
+ // convert to minutes
+ mins *= 60;
+ } else if (frequency.indexOf("min") > -1) {
+ // default mins
+ }
+ return mins;
+ }
}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java index 8cbab9c4..3e7c209f 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/com/gitblit/wicket/GitBlitWebApp.java @@ -32,6 +32,8 @@ import com.gitblit.wicket.pages.BranchesPage; import com.gitblit.wicket.pages.CommitDiffPage;
import com.gitblit.wicket.pages.CommitPage;
import com.gitblit.wicket.pages.DocsPage;
+import com.gitblit.wicket.pages.FederationProposalPage;
+import com.gitblit.wicket.pages.FederationRegistrationPage;
import com.gitblit.wicket.pages.HistoryPage;
import com.gitblit.wicket.pages.LogPage;
import com.gitblit.wicket.pages.LoginPage;
@@ -95,6 +97,10 @@ public class GitBlitWebApp extends WebApplication { mount("/docs", DocsPage.class, "r");
mount("/markdown", MarkdownPage.class, "r", "h", "f");
+ // federation urls
+ mount("/proposal", FederationProposalPage.class, "t");
+ mount("/registration", FederationRegistrationPage.class, "u", "n");
+
// setup login/logout urls, if we are using authentication
if (useAuthentication) {
mount("/login", LoginPage.class);
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index dab5fa03..ab7ef4bd 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -101,4 +101,36 @@ gb.commitActivityDOW = commit activity by day of week gb.commitActivityAuthors = primary authors by commit activity
gb.feed = feed
gb.cancel = cancel
-gb.changePassword = change password
\ No newline at end of file +gb.changePassword = change password
+gb.isFederated = is federated
+gb.federateThis = federate this repository
+gb.federateOrigin = federate the origin
+gb.excludeFromFederation = exclude from federation
+gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this object
+gb.tokens = federation tokens
+gb.tokenAllDescription = federate repositories, users, & settings
+gb.tokenUnrDescription = federate repositories & users
+gb.tokenJurDescription = federate repositories
+gb.federatedRepositoryDefinitions = repository definitions
+gb.federatedUserDefinitions = user definitions
+gb.federatedSettingDefinitions = setting definitions
+gb.proposals = federation proposals
+gb.received = received
+gb.type = type
+gb.token = token
+gb.repositories = repositories
+gb.proposal = proposal
+gb.frequency = frequency
+gb.folder = folder
+gb.lastPull = last pull
+gb.nextPull = next pull
+gb.inclusions = inclusions
+gb.exclusions = exclusions
+gb.registration = registration
+gb.registrations = federation registrations
+gb.sendProposal send proposal
+gb.status = status
+gb.origin = origin
+gb.federationStrategy = federation strategy
+gb.federationRegistration = federation registration
+gb.federationResults = federation pull results
diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java index 614cb7d1..a6435d5a 100644 --- a/src/com/gitblit/wicket/WicketUtils.java +++ b/src/com/gitblit/wicket/WicketUtils.java @@ -39,9 +39,12 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.wicketstuff.googlecharts.AbstractChartData;
import org.wicketstuff.googlecharts.IChartData;
+import com.gitblit.Constants.FederationPullStatus;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
+import com.gitblit.models.FederationModel;
import com.gitblit.models.Metric;
+import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JGitUtils.SearchType;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
@@ -109,6 +112,28 @@ public class WicketUtils { return label;
}
+ public static ContextImage getPullStatusImage(String wicketId, FederationPullStatus status) {
+ String filename = null;
+ switch (status) {
+ case PULLED:
+ filename = "bullet_green.png";
+ break;
+ case SKIPPED:
+ filename = "bullet_yellow.png";
+ break;
+ case FAILED:
+ filename = "bullet_red.png";
+ break;
+ case EXCLUDED:
+ filename = "bullet_white.png";
+ break;
+ case PENDING:
+ default:
+ filename = "bullet_black.png";
+ }
+ return WicketUtils.newImage(wicketId, filename, status.name());
+ }
+
public static ContextImage getFileImage(String wicketId, String filename) {
filename = filename.toLowerCase();
if (filename.endsWith(".java")) {
@@ -155,6 +180,17 @@ public class WicketUtils { return newImage(wicketId, "file_16x16.png");
}
+ public static ContextImage getRegistrationImage(String wicketId, FederationModel registration,
+ Component c) {
+ if (registration.isResultData()) {
+ return WicketUtils.newImage(wicketId, "information_16x16.png",
+ c.getString("gb.federationResults"));
+ } else {
+ return WicketUtils.newImage(wicketId, "arrow_left.png",
+ c.getString("gb.federationRegistration"));
+ }
+ }
+
public static ContextImage newClearPixel(String wicketId) {
return newImage(wicketId, "pixel.png");
}
@@ -181,19 +217,7 @@ public class WicketUtils { public static String getHostURL(Request request) {
HttpServletRequest req = ((WebRequest) request).getHttpServletRequest();
- return getHostURL(req);
- }
-
- public static String getHostURL(HttpServletRequest request) {
- StringBuilder sb = new StringBuilder();
- sb.append(request.getScheme());
- sb.append("://");
- sb.append(request.getServerName());
- if ((request.getScheme().equals("http") && request.getServerPort() != 80)
- || (request.getScheme().equals("https") && request.getServerPort() != 443)) {
- sb.append(":" + request.getServerPort());
- }
- return sb.toString();
+ return HttpUtils.getHostURL(req);
}
public static HeaderContributor syndicationDiscoveryLink(final String feedTitle,
@@ -213,6 +237,14 @@ public class WicketUtils { }
});
}
+
+ public static PageParameters newTokenParameter(String token) {
+ return new PageParameters("t=" + token);
+ }
+
+ public static PageParameters newRegistrationParameter(String url, String name) {
+ return new PageParameters("u=" + url + ",n=" + name);
+ }
public static PageParameters newUsernameParameter(String username) {
return new PageParameters("user=" + username);
@@ -222,6 +254,10 @@ public class WicketUtils { return new PageParameters("r=" + repositoryName);
}
+ public static PageParameters newObjectParameter(String objectId) {
+ return new PageParameters("h=" + objectId);
+ }
+
public static PageParameters newObjectParameter(String repositoryName, String objectId) {
if (StringUtils.isEmpty(objectId)) {
return newRepositoryParameter(repositoryName);
@@ -324,14 +360,35 @@ public class WicketUtils { return params.getString("user", "");
}
+ public static String getToken(PageParameters params) {
+ return params.getString("t", "");
+ }
+
+ public static String getUrlParameter(PageParameters params) {
+ return params.getString("u", "");
+ }
+
+ public static String getNameParameter(PageParameters params) {
+ return params.getString("n", "");
+ }
+
public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone) {
String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");
DateFormat df = new SimpleDateFormat(format);
if (timeZone != null) {
df.setTimeZone(timeZone);
}
- String dateString = df.format(date);
- String title = TimeUtils.timeAgo(date);
+ String dateString;
+ if (date.getTime() == 0) {
+ dateString = "--";
+ } else {
+ dateString = df.format(date);
+ }
+ String title = null;
+ if (date.getTime() <= System.currentTimeMillis()) {
+ // past
+ title = TimeUtils.timeAgo(date);
+ }
if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
String tmp = dateString;
dateString = title;
@@ -339,7 +396,9 @@ public class WicketUtils { }
Label label = new Label(wicketId, dateString);
WicketUtils.setCssClass(label, TimeUtils.timeAgoCss(date));
- WicketUtils.setHtmlTooltip(label, title);
+ if (!StringUtils.isEmpty(title)) {
+ WicketUtils.setHtmlTooltip(label, title);
+ }
return label;
}
@@ -356,9 +415,15 @@ public class WicketUtils { } else {
dateString = df.format(date);
}
- String title = TimeUtils.timeAgo(date);
+ String title = null;
+ if (date.getTime() <= System.currentTimeMillis()) {
+ // past
+ title = TimeUtils.timeAgo(date);
+ }
Label label = new Label(wicketId, dateString);
- WicketUtils.setHtmlTooltip(label, title);
+ if (!StringUtils.isEmpty(title)) {
+ WicketUtils.setHtmlTooltip(label, title);
+ }
return label;
}
diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java index acfb3236..0169c8e3 100644 --- a/src/com/gitblit/wicket/pages/BasePage.java +++ b/src/com/gitblit/wicket/pages/BasePage.java @@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.UserModel;
@@ -140,6 +141,24 @@ public abstract class BasePage extends WebPage { }
return map;
}
+
+ protected Map<FederationStrategy, String> getFederationTypes() {
+ Map<FederationStrategy, String> map = new LinkedHashMap<FederationStrategy, String>();
+ for (FederationStrategy type : FederationStrategy.values()) {
+ switch (type) {
+ case EXCLUDE:
+ map.put(type, getString("gb.excludeFromFederation"));
+ break;
+ case FEDERATE_THIS:
+ map.put(type, getString("gb.federateThis"));
+ break;
+ case FEDERATE_ORIGIN:
+ map.put(type, getString("gb.federateOrigin"));
+ break;
+ }
+ }
+ return map;
+ }
protected TimeZone getTimeZone() {
return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html index 36075737..2dd031be 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html @@ -17,15 +17,17 @@ <tbody>
<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" id="name" size="40" tabindex="1" /> <i><wicket:message key="gb.nameDescription"></wicket:message></i></td></tr>
<tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input type="text" wicket:id="description" size="40" tabindex="2" /></td></tr>
- <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select wicket:id="owner" tabindex="3" /> <i><wicket:message key="gb.ownerDescription"></wicket:message></i></td></tr>
- <tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="4" /> <i><wicket:message key="gb.useTicketsDescription"></wicket:message></i></td></tr>
- <tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="5" /> <i><wicket:message key="gb.useDocsDescription"></wicket:message></i></td></tr>
- <tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="6" /> <i><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></i></td></tr>
- <tr><th><wicket:message key="gb.showReadme"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showReadme" tabindex="7" /> <i><wicket:message key="gb.showReadmeDescription"></wicket:message></i></td></tr>
- <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="8" /></td></tr>
- <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="isFrozen" tabindex="9" /> <i><wicket:message key="gb.isFrozenDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.origin"></wicket:message></th><td class="edit"><input type="text" wicket:id="origin" size="80" tabindex="3" /></td></tr>
+ <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select wicket:id="owner" tabindex="4" /> <i><wicket:message key="gb.ownerDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="5" /> <i><wicket:message key="gb.useTicketsDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="6" /> <i><wicket:message key="gb.useDocsDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="7" /> <i><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.showReadme"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showReadme" tabindex="8" /> <i><wicket:message key="gb.showReadmeDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="9" /></td></tr>
+ <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="isFrozen" tabindex="10" /> <i><wicket:message key="gb.isFrozenDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select wicket:id="federationStrategy" tabindex="11" /></td></tr>
<tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
- <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="10" /> <input type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="11" /></td></tr>
+ <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="12" /> <input type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="13" /></td></tr>
</tbody>
</table>
</form>
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java index eea389ea..3b6e6f38 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java @@ -37,6 +37,7 @@ import org.apache.wicket.model.util.CollectionModel; import org.apache.wicket.model.util.ListModel;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
@@ -122,19 +123,11 @@ public class EditRepositoryPage extends BasePage { }
// confirm valid characters in repository name
- char[] validChars = { '/', '.', '_', '-' };
- for (char c : repositoryModel.name.toCharArray()) {
- if (!Character.isLetterOrDigit(c)) {
- boolean ok = false;
- for (char vc : validChars) {
- ok |= c == vc;
- }
- if (!ok) {
- error(MessageFormat.format(
- "Illegal character ''{0}'' in repository name!", c));
- return;
- }
- }
+ Character c = StringUtils.findInvalidCharacter(repositoryModel.name);
+ if (c != null) {
+ error(MessageFormat.format("Illegal character ''{0}'' in repository name!",
+ c));
+ return;
}
// confirm access restriction selection
@@ -177,6 +170,18 @@ public class EditRepositoryPage extends BasePage { form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()));
form.add(new CheckBox("isFrozen"));
+ // TODO enable origin definition
+ form.add(new TextField<String>("origin").setEnabled(false/*isCreate*/));
+
+ // federation strategies - remove ORIGIN choice if this repository has
+ // no origin.
+ List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
+ Arrays.asList(FederationStrategy.values()));
+ if (StringUtils.isEmpty(repositoryModel.origin)) {
+ federationStrategies.remove(FederationStrategy.FEDERATE_ORIGIN);
+ }
+ form.add(new DropDownChoice<FederationStrategy>("federationStrategy", federationStrategies,
+ new FederationTypeRenderer()));
form.add(new CheckBox("useTickets"));
form.add(new CheckBox("useDocs"));
form.add(new CheckBox("showRemoteBranches"));
@@ -264,4 +269,25 @@ public class EditRepositoryPage extends BasePage { return Integer.toString(index);
}
}
+
+ private class FederationTypeRenderer implements IChoiceRenderer<FederationStrategy> {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Map<FederationStrategy, String> map;
+
+ public FederationTypeRenderer() {
+ map = getFederationTypes();
+ }
+
+ @Override
+ public String getDisplayValue(FederationStrategy type) {
+ return map.get(type);
+ }
+
+ @Override
+ public String getIdValue(FederationStrategy type, int index) {
+ return Integer.toString(index);
+ }
+ }
}
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.html b/src/com/gitblit/wicket/pages/EditUserPage.html index 9aef9a34..d36bf5a6 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.html +++ b/src/com/gitblit/wicket/pages/EditUserPage.html @@ -19,8 +19,9 @@ <tr><th><wicket:message key="gb.password"></wicket:message></th><td class="edit"><input type="password" wicket:id="password" size="30" tabindex="2" /></td></tr>
<tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr>
<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> <i><wicket:message key="gb.canAdminDescription"></wicket:message></i></td></tr>
+ <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="7" /> <i><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></i></td></tr>
<tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>
- <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="7" /> <input type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="8" /></td></tr>
+ <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="8" /> <input type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="9" /></td></tr>
</tbody>
</table>
</form>
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java index dd50d56d..a3589117 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/com/gitblit/wicket/pages/EditUserPage.java @@ -83,8 +83,8 @@ public class EditUserPage extends BasePage { }
final String oldName = userModel.username;
final Palette<String> repositories = new Palette<String>("repositories",
- new ListModel<String>(userModel.repositories), new CollectionModel<String>(repos),
- new ChoiceRenderer<String>("", ""), 10, false);
+ new ListModel<String>(new ArrayList<String>(userModel.repositories)),
+ new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);
Form<UserModel> form = new Form<UserModel>("editForm", model) {
private static final long serialVersionUID = 1L;
@@ -172,6 +172,7 @@ public class EditUserPage extends BasePage { confirmPasswordField.setResetPassword(false);
form.add(confirmPasswordField);
form.add(new CheckBox("canAdmin"));
+ form.add(new CheckBox("excludeFromFederation"));
form.add(repositories);
form.add(new Button("save"));
diff --git a/src/com/gitblit/wicket/pages/FederationProposalPage.html b/src/com/gitblit/wicket/pages/FederationProposalPage.html new file mode 100644 index 00000000..160ca3fa --- /dev/null +++ b/src/com/gitblit/wicket/pages/FederationProposalPage.html @@ -0,0 +1,27 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<body>
+<wicket:extend>
+
+ <div style="padding-top:20px"></div>
+
+ <div style="text-align:center;" wicket:id="feedback">[Feedback Panel]</div>
+
+ <!-- proposal info -->
+ <table class="plain">
+ <tr><th><wicket:message key="gb.url">url</wicket:message></th><td><span wicket:id="url">[url]</span></td></tr>
+ <tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
+ <tr><th><wicket:message key="gb.type">type</wicket:message></th><td><span wicket:id="tokenType">[token type]</span></td></tr>
+ <tr><th><wicket:message key="gb.received">received</wicket:message></th><td><span wicket:id="received">[received]</span></td></tr>
+ <tr><th valign="top"><wicket:message key="gb.proposal">proposal</wicket:message></th><td><span class="sha1" wicket:id="definition">[definition]</span></td></tr>
+ </table>
+
+ <div wicket:id="repositories"></div>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/FederationProposalPage.java b/src/com/gitblit/wicket/pages/FederationProposalPage.java new file mode 100644 index 00000000..4685d2fe --- /dev/null +++ b/src/com/gitblit/wicket/pages/FederationProposalPage.java @@ -0,0 +1,100 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.RequiresAdminRole;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.RepositoriesPanel;
+
+@RequiresAdminRole
+public class FederationProposalPage extends BasePage {
+
+ private final String PROPS_PATTERN = "{0} = {1}\n";
+
+ private final String WEBXML_PATTERN = "\n<context-param>\n\t<param-name>{0}</param-name>\n\t<param-value>{1}</param-value>\n</context-param>\n";
+
+ public FederationProposalPage(PageParameters params) {
+ super(params);
+
+ setupPage("", getString("gb.proposals"));
+ setStatelessHint(true);
+
+ final String token = WicketUtils.getToken(params);
+
+ FederationProposal proposal = GitBlit.self().getPendingFederationProposal(token);
+ if (proposal == null) {
+ error("Could not find federation proposal!", true);
+ }
+
+ add(new Label("url", proposal.url));
+ add(WicketUtils.createTimestampLabel("received", proposal.received, getTimeZone()));
+ add(new Label("tokenType", proposal.tokenType.name()));
+ add(new Label("token", proposal.token));
+
+ boolean go = true;
+ String p;
+ if (GitBlit.isGO()) {
+ // gitblit.properties definition
+ p = PROPS_PATTERN;
+ } else {
+ // web.xml definition
+ p = WEBXML_PATTERN;
+ }
+
+ // build proposed definition
+ StringBuilder sb = new StringBuilder();
+ sb.append(asParam(p, proposal.name, "url", proposal.url));
+ sb.append(asParam(p, proposal.name, "token", proposal.token));
+
+ if (FederationToken.USERS_AND_REPOSITORIES.equals(proposal.tokenType)
+ || FederationToken.ALL.equals(proposal.tokenType)) {
+ sb.append(asParam(p, proposal.name, "mergeAccounts", "false"));
+ }
+ sb.append(asParam(p, proposal.name, "frequency",
+ GitBlit.getString(Keys.federation.defaultFrequency, "60 mins")));
+ sb.append(asParam(p, proposal.name, "folder", proposal.name));
+ sb.append(asParam(p, proposal.name, "sendStatus", "true"));
+ sb.append(asParam(p, proposal.name, "notifyOnError", "true"));
+ sb.append(asParam(p, proposal.name, "exclude", ""));
+ sb.append(asParam(p, proposal.name, "include", ""));
+
+ add(new Label("definition", StringUtils.breakLinesForHtml(StringUtils.escapeForHtml(sb
+ .toString().trim(), true))).setEscapeModelStrings(false));
+
+ List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(
+ proposal.repositories.values());
+ RepositoriesPanel repositoriesPanel = new RepositoriesPanel("repositories", false,
+ repositories, getAccessRestrictions());
+ add(repositoriesPanel);
+ }
+
+ private String asParam(String pattern, String name, String key, String value) {
+ return MessageFormat.format(pattern, Keys.federation._ROOT + "." + name + "." + key, value);
+ }
+}
diff --git a/src/com/gitblit/wicket/pages/FederationRegistrationPage.html b/src/com/gitblit/wicket/pages/FederationRegistrationPage.html new file mode 100644 index 00000000..fe8f7f2d --- /dev/null +++ b/src/com/gitblit/wicket/pages/FederationRegistrationPage.html @@ -0,0 +1,45 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<body>
+<wicket:extend>
+
+ <div style="padding-top:20px"></div>
+
+ <div style="text-align:center;" wicket:id="feedback">[Feedback Panel]</div>
+
+ <!-- registration info -->
+ <table class="plain">
+ <tr><th><wicket:message key="gb.url">url</wicket:message></th><td><span wicket:id="url">[url]</span></td></tr>
+ <tr><th></th><td><img style="border:0px;vertical-align:middle;" wicket:id="typeIcon" /> <span wicket:id="typeName">[url]</span></td></tr>
+ <tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
+ <tr><th><wicket:message key="gb.folder">folder</wicket:message></th><td><span wicket:id="folder">[folder]</span></td></tr>
+ <tr><th><wicket:message key="gb.frequency">frequency</wicket:message></th><td><span wicket:id="frequency">[frequency]</span></td></tr>
+ <tr><th><wicket:message key="gb.lastPull">lastPull</wicket:message></th><td><span wicket:id="lastPull">[lastPull]</span></td></tr>
+ <tr><th><wicket:message key="gb.nextPull">nextPull</wicket:message></th><td><span wicket:id="nextPull">[nextPull]</span></td></tr>
+ <tr><th valign="top"><wicket:message key="gb.exclusions">exclusions</wicket:message></th><td><span class="sha1" wicket:id="exclusions">[exclusions]</span></td></tr>
+ <tr><th valign="top"><wicket:message key="gb.inclusions">inclusions</wicket:message></th><td><span class="sha1" wicket:id="inclusions">[inclusions]</span></td></tr>
+ </table>
+
+ <table class="repositories">
+ <tr>
+ <th class="left">
+ <img style="vertical-align: top; border: 1px solid #888; background-color: white;" src="gitweb-favicon.png"/>
+ <wicket:message key="gb.repositories">[repositories]</wicket:message>
+ </th>
+ <th class="right"><wicket:message key="gb.status">[status]</wicket:message></th>
+ </tr>
+ <tbody>
+ <tr wicket:id="row">
+ <td class="left"><img style="border:0px;vertical-align:middle;" wicket:id="statusIcon" /><span wicket:id="name">[name]</span></td>
+ <td class="right"><span wicket:id="status">[status]</span></td>
+ </tr>
+ </tbody>
+ </table>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/FederationRegistrationPage.java b/src/com/gitblit/wicket/pages/FederationRegistrationPage.java new file mode 100644 index 00000000..65c4d01d --- /dev/null +++ b/src/com/gitblit/wicket/pages/FederationRegistrationPage.java @@ -0,0 +1,105 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationModel.RepositoryStatus;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+
+public class FederationRegistrationPage extends BasePage {
+
+ public FederationRegistrationPage(PageParameters params) {
+ super(params);
+
+ setupPage("", getString("gb.registrations"));
+
+ final boolean showAdmin;
+ if (GitBlit.getBoolean(Keys.web.authenticateAdminPages, true)) {
+ boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
+ showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
+ } else {
+ showAdmin = false;
+ }
+ setStatelessHint(true);
+
+ String url = WicketUtils.getUrlParameter(params);
+ String name = WicketUtils.getNameParameter(params);
+
+ FederationModel registration = GitBlit.self().getFederationRegistration(url, name);
+ if (registration == null) {
+ error("Could not find federation registration!", true);
+ }
+
+ add(new Label("url", registration.url));
+ add(WicketUtils.getRegistrationImage("typeIcon", registration, this));
+ add(new Label("typeName", registration.isResultData() ? getString("gb.federationResults")
+ : getString("gb.federationRegistration")));
+ add(new Label("frequency", registration.frequency));
+ add(new Label("folder", registration.folder));
+ add(new Label("token", showAdmin ? registration.token : "--"));
+ add(WicketUtils.createTimestampLabel("lastPull", registration.lastPull, getTimeZone()));
+ add(WicketUtils.createTimestampLabel("nextPull", registration.nextPull, getTimeZone()));
+
+ StringBuilder inclusions = new StringBuilder();
+ for (String inc : registration.inclusions) {
+ inclusions.append(inc).append("<br/>");
+ }
+ StringBuilder exclusions = new StringBuilder();
+ for (String ex : registration.exclusions) {
+ exclusions.append(ex).append("<br/>");
+ }
+
+ add(new Label("inclusions", inclusions.toString()).setEscapeModelStrings(false));
+
+ add(new Label("exclusions", exclusions.toString()).setEscapeModelStrings(false));
+
+ List<RepositoryStatus> list = registration.getStatusList();
+ Collections.sort(list);
+ DataView<RepositoryStatus> dataView = new DataView<RepositoryStatus>("row",
+ new ListDataProvider<RepositoryStatus>(list)) {
+ private static final long serialVersionUID = 1L;
+ private int counter;
+
+ @Override
+ protected void onBeforeRender() {
+ super.onBeforeRender();
+ counter = 0;
+ }
+
+ public void populateItem(final Item<RepositoryStatus> item) {
+ final RepositoryStatus entry = item.getModelObject();
+ item.add(WicketUtils.getPullStatusImage("statusIcon", entry.status));
+ item.add(new Label("name", entry.name));
+ item.add(new Label("status", entry.status.name()));
+ WicketUtils.setAlternatingBackground(item, counter);
+ counter++;
+ }
+ };
+ add(dataView);
+ }
+}
diff --git a/src/com/gitblit/wicket/pages/LoginPage.java b/src/com/gitblit/wicket/pages/LoginPage.java index b13a1c8d..45e1e2dc 100644 --- a/src/com/gitblit/wicket/pages/LoginPage.java +++ b/src/com/gitblit/wicket/pages/LoginPage.java @@ -69,6 +69,11 @@ public class LoginPage extends WebPage { UserModel user = GitBlit.self().authenticate(username, password);
if (user == null) {
error("Invalid username or password!");
+ } else if (user.username.equals(Constants.FEDERATION_USER)) {
+ // disallow the federation user from logging in via the
+ // web ui
+ error("Invalid username or password!");
+ user = null;
} else {
loginUser(user);
}
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.html b/src/com/gitblit/wicket/pages/RepositoriesPage.html index c8ab5978..a0c58c9e 100644 --- a/src/com/gitblit/wicket/pages/RepositoriesPage.html +++ b/src/com/gitblit/wicket/pages/RepositoriesPage.html @@ -19,6 +19,12 @@ <div wicket:id="repositoriesPanel">[repositories panel]</div>
<div style="padding-top: 10px;"wicket:id="usersPanel">[users panel]</div>
+
+ <div style="padding-top: 10px;"wicket:id="federationTokensPanel">[federation tokens panel]</div>
+
+ <div style="padding-top: 10px;"wicket:id="federationProposalsPanel">[federation proposals panel]</div>
+
+ <div style="padding-top: 10px;"wicket:id="federationRegistrationsPanel">[federation registrations panel]</div>
</wicket:extend>
</body>
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java index 053bee09..619d42ed 100644 --- a/src/com/gitblit/wicket/pages/RepositoriesPage.java +++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java @@ -19,6 +19,7 @@ import java.io.File; import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.text.MessageFormat;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
@@ -30,6 +31,9 @@ import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.FederationProposalsPanel;
+import com.gitblit.wicket.panels.FederationRegistrationsPanel;
+import com.gitblit.wicket.panels.FederationTokensPanel;
import com.gitblit.wicket.panels.RepositoriesPanel;
import com.gitblit.wicket.panels.UsersPanel;
@@ -60,6 +64,14 @@ public class RepositoriesPage extends BasePage { String cachedMessage = GitBlitWebSession.get().clearErrorMessage();
if (!StringUtils.isEmpty(cachedMessage)) {
error(cachedMessage);
+ } else if (showAdmin) {
+ int pendingProposals = GitBlit.self().getPendingFederationProposals().size();
+ if (pendingProposals == 1) {
+ info("There is 1 federation proposal awaiting review.");
+ } else if (pendingProposals > 1) {
+ info(MessageFormat.format("There are {0} federation proposals awaiting review.",
+ pendingProposals));
+ }
}
// Load the markdown welcome message
@@ -97,7 +109,28 @@ public class RepositoriesPage extends BasePage { Component repositoriesMessage = new Label("repositoriesMessage", message)
.setEscapeModelStrings(false);
add(repositoriesMessage);
- add(new RepositoriesPanel("repositoriesPanel", showAdmin, getAccessRestrictions()));
+ add(new RepositoriesPanel("repositoriesPanel", showAdmin, null, getAccessRestrictions()));
add(new UsersPanel("usersPanel", showAdmin).setVisible(showAdmin));
+ boolean showFederation = showAdmin && GitBlit.canFederate();
+ add(new FederationTokensPanel("federationTokensPanel", showFederation)
+ .setVisible(showFederation));
+ FederationProposalsPanel proposalsPanel = new FederationProposalsPanel(
+ "federationProposalsPanel");
+ if (showFederation) {
+ proposalsPanel.hideIfEmpty();
+ } else {
+ proposalsPanel.setVisible(false);
+ }
+
+ boolean showRegistrations = GitBlit.getBoolean(Keys.web.showFederationRegistrations, false);
+ FederationRegistrationsPanel registrationsPanel = new FederationRegistrationsPanel(
+ "federationRegistrationsPanel");
+ if (showAdmin || showRegistrations) {
+ registrationsPanel.hideIfEmpty();
+ } else {
+ registrationsPanel.setVisible(false);
+ }
+ add(proposalsPanel);
+ add(registrationsPanel);
}
}
diff --git a/src/com/gitblit/wicket/panels/BasePanel.java b/src/com/gitblit/wicket/panels/BasePanel.java index 02789fb4..9dda03c2 100644 --- a/src/com/gitblit/wicket/panels/BasePanel.java +++ b/src/com/gitblit/wicket/panels/BasePanel.java @@ -67,4 +67,23 @@ public abstract class BasePanel extends Panel { return result;
}
}
+
+ public static class JavascriptTextPrompt extends AttributeModifier {
+
+ private static final long serialVersionUID = 1L;
+
+ public JavascriptTextPrompt(String event, String msg) {
+ super(event, true, new Model<String>(msg));
+ }
+
+ protected String newValue(final String currentValue, final String message) {
+ String result = "var userText = prompt('" + message + "','"
+ + (currentValue == null ? "" : currentValue) + "'); " + "return userText; ";
+ // String result = prefix;
+ // if (currentValue != null) {
+ // result = prefix + currentValue;
+ // }
+ return result;
+ }
+ }
}
diff --git a/src/com/gitblit/wicket/panels/FederationProposalsPanel.html b/src/com/gitblit/wicket/panels/FederationProposalsPanel.html new file mode 100644 index 00000000..e451b7c5 --- /dev/null +++ b/src/com/gitblit/wicket/panels/FederationProposalsPanel.html @@ -0,0 +1,34 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<body>
+<wicket:panel>
+
+ <table class="repositories">
+ <tr>
+ <th class="left">
+ <img style="vertical-align: top; border: 1px solid #888; background-color: white;" src="federated_16x16.png"/>
+ <wicket:message key="gb.proposals">[proposals]</wicket:message>
+ </th>
+ <th><wicket:message key="gb.received">[received]</wicket:message></th>
+ <th><wicket:message key="gb.type">[type]</wicket:message></th>
+ <th><wicket:message key="gb.token">[token]</wicket:message></th>
+ <th class="right"></th>
+ </tr>
+ <tbody>
+ <tr wicket:id="row">
+ <td class="left"><span class="list" wicket:id="url">[field]</span></td>
+ <td><span class="date"" wicket:id="received">[received]</span></td>
+ <td><span wicket:id="tokenType">[token type]</span></td>
+ <td><span class="sha1"" wicket:id="token">[token]</span></td>
+ <td class="rightAlign"><span class="link"><a wicket:id="deleteProposal"><wicket:message key="gb.delete">[delete]</wicket:message></a></span></td>
+ </tr>
+ </tbody>
+ </table>
+
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/panels/FederationProposalsPanel.java b/src/com/gitblit/wicket/panels/FederationProposalsPanel.java new file mode 100644 index 00000000..f83f9262 --- /dev/null +++ b/src/com/gitblit/wicket/panels/FederationProposalsPanel.java @@ -0,0 +1,92 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.text.MessageFormat;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.FederationProposalPage;
+
+public class FederationProposalsPanel extends BasePanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final boolean hasProposals;
+
+ public FederationProposalsPanel(String wicketId) {
+ super(wicketId);
+
+ final List<FederationProposal> list = GitBlit.self().getPendingFederationProposals();
+ hasProposals = list.size() > 0;
+ DataView<FederationProposal> dataView = new DataView<FederationProposal>("row",
+ new ListDataProvider<FederationProposal>(list)) {
+ private static final long serialVersionUID = 1L;
+ private int counter;
+
+ @Override
+ protected void onBeforeRender() {
+ super.onBeforeRender();
+ counter = 0;
+ }
+
+ public void populateItem(final Item<FederationProposal> item) {
+ final FederationProposal entry = item.getModelObject();
+ item.add(new LinkPanel("url", "list", entry.url, FederationProposalPage.class,
+ WicketUtils.newTokenParameter(entry.token)));
+ item.add(WicketUtils.createDateLabel("received", entry.received, getTimeZone()));
+ item.add(new Label("tokenType", entry.tokenType.name()));
+ item.add(new LinkPanel("token", "list", entry.token, FederationProposalPage.class,
+ WicketUtils.newTokenParameter(entry.token)));
+
+ Link<Void> deleteLink = new Link<Void>("deleteProposal") {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+ if (GitBlit.self().deletePendingFederationProposal(entry)) {
+ list.remove(entry);
+ info(MessageFormat.format("Proposal ''{0}'' deleted.", entry.name));
+ } else {
+ error(MessageFormat.format("Failed to delete proposal ''{0}''!",
+ entry.name));
+ }
+ }
+ };
+ deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
+ "Delete proposal \"{0}\"?", entry.name)));
+ item.add(deleteLink);
+ WicketUtils.setAlternatingBackground(item, counter);
+ counter++;
+ }
+ };
+ add(dataView);
+ }
+
+ public Component hideIfEmpty() {
+ return super.setVisible(hasProposals);
+ }
+}
diff --git a/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.html b/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.html new file mode 100644 index 00000000..6fab778f --- /dev/null +++ b/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.html @@ -0,0 +1,38 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<body>
+<wicket:panel>
+
+ <table class="repositories">
+ <tr>
+ <th class="left">
+ <img style="vertical-align: top; border: 1px solid #888; background-color: white;" src="federated_16x16.png"/>
+ <wicket:message key="gb.registrations">[registrations]</wicket:message>
+ </th>
+ <th><wicket:message key="gb.name">[name]</wicket:message></th>
+ <th><wicket:message key="gb.frequency">[frequency]</wicket:message></th>
+ <th></th>
+ <th><wicket:message key="gb.lastPull">[lastPull]</wicket:message></th>
+ <th><wicket:message key="gb.nextPull">[nextPull]</wicket:message></th>
+ <th class="right"></th>
+ </tr>
+ <tbody>
+ <tr wicket:id="row">
+ <td class="left"><img style="border:0px;vertical-align:middle;" wicket:id="statusIcon" /><span class="list" wicket:id="url">[url]</span></td>
+ <td><span class="list" wicket:id="name">[name]</span></td>
+ <td><span wicket:id="frequency">[frequency]</span></td>
+ <td><img style="border:0px;vertical-align:middle;" wicket:id="typeIcon" /></td>
+ <td><span class="date"" wicket:id="lastPull">[lastPull]</span></td>
+ <td><span class="date"" wicket:id="nextPull">[nextPull]</span></td>
+ <td class="rightAlign"><span class="link"></span></td>
+ </tr>
+ </tbody>
+ </table>
+
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.java b/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.java new file mode 100644 index 00000000..a2bfd8cf --- /dev/null +++ b/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.java @@ -0,0 +1,83 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.FederationModel;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.FederationRegistrationPage;
+
+public class FederationRegistrationsPanel extends BasePanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final boolean hasRegistrations;
+
+ public FederationRegistrationsPanel(String wicketId) {
+ super(wicketId);
+
+ final List<FederationModel> list = new ArrayList<FederationModel>(GitBlit.self()
+ .getFederationRegistrations());
+ list.addAll(GitBlit.self().getFederationResultRegistrations());
+ Collections.sort(list);
+ hasRegistrations = list.size() > 0;
+ DataView<FederationModel> dataView = new DataView<FederationModel>("row",
+ new ListDataProvider<FederationModel>(list)) {
+ private static final long serialVersionUID = 1L;
+ private int counter;
+
+ @Override
+ protected void onBeforeRender() {
+ super.onBeforeRender();
+ counter = 0;
+ }
+
+ public void populateItem(final Item<FederationModel> item) {
+ final FederationModel entry = item.getModelObject();
+ item.add(new LinkPanel("url", "list", entry.url, FederationRegistrationPage.class,
+ WicketUtils.newRegistrationParameter(entry.url, entry.name)));
+ item.add(WicketUtils.getPullStatusImage("statusIcon", entry.getLowestStatus()));
+ item.add(new LinkPanel("name", "list", entry.name,
+ FederationRegistrationPage.class, WicketUtils.newRegistrationParameter(
+ entry.url, entry.name)));
+
+ item.add(WicketUtils.getRegistrationImage("typeIcon", entry, this));
+
+ item.add(WicketUtils.createDateLabel("lastPull", entry.lastPull, getTimeZone()));
+ item.add(WicketUtils
+ .createTimestampLabel("nextPull", entry.nextPull, getTimeZone()));
+ item.add(new Label("frequency", entry.frequency));
+ WicketUtils.setAlternatingBackground(item, counter);
+ counter++;
+ }
+ };
+ add(dataView);
+ }
+
+ public Component hideIfEmpty() {
+ return super.setVisible(hasRegistrations);
+ }
+}
diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.html b/src/com/gitblit/wicket/panels/FederationTokensPanel.html new file mode 100644 index 00000000..6749e40e --- /dev/null +++ b/src/com/gitblit/wicket/panels/FederationTokensPanel.html @@ -0,0 +1,38 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<body>
+<wicket:panel>
+
+ <div class="admin_nav">
+ <a wicket:id="federatedRepositories"><wicket:message key="gb.federatedRepositoryDefinitions">[repositories]</wicket:message></a>
+ | <a wicket:id="federatedUsers"><wicket:message key="gb.federatedUserDefinitions">[users]</wicket:message></a>
+ | <a wicket:id="federatedSettings"><wicket:message key="gb.federatedSettingDefinitions">[settings]</wicket:message></a>
+ </div>
+
+ <table class="repositories">
+ <tr>
+ <th class="left">
+ <img style="vertical-align: top; border: 1px solid #888; background-color: white;" src="federated_16x16.png"/>
+ <wicket:message key="gb.tokens">[tokens]</wicket:message>
+ </th>
+ <th></th>
+ <th></th>
+ <th class="right"></th>
+ </tr>
+ <tbody>
+ <tr wicket:id="row">
+ <td class="left"><span class="list" wicket:id="field">[field]</span></td>
+ <td><span class="sha1"" wicket:id="value">[value]</span></td>
+ <td><span wicket:id="description"></span></td>
+ <td class="rightAlign"><span class="link"><a wicket:id="send"><wicket:message key="gb.sendProposal">[send proposal]</wicket:message></a></span></td>
+ </tr>
+ </tbody>
+ </table>
+
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.java b/src/com/gitblit/wicket/panels/FederationTokensPanel.java new file mode 100644 index 00000000..166f1bd0 --- /dev/null +++ b/src/com/gitblit/wicket/panels/FederationTokensPanel.java @@ -0,0 +1,109 @@ +/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.FederationServlet;
+import com.gitblit.GitBlit;
+import com.gitblit.wicket.WicketUtils;
+
+public class FederationTokensPanel extends BasePanel {
+
+ private static final long serialVersionUID = 1L;
+
+ public FederationTokensPanel(String wicketId, final boolean showFederation) {
+ super(wicketId);
+
+ String baseUrl = getRequest().getRelativePathPrefixToContextRoot();
+ add(new ExternalLink("federatedRepositories", FederationServlet.asPullLink(baseUrl, GitBlit
+ .self().getFederationToken(FederationToken.REPOSITORIES),
+ FederationRequest.PULL_REPOSITORIES)));
+
+ add(new ExternalLink("federatedUsers", FederationServlet.asPullLink(baseUrl, GitBlit.self()
+ .getFederationToken(FederationToken.USERS_AND_REPOSITORIES),
+ FederationRequest.PULL_USERS)));
+
+ add(new ExternalLink("federatedSettings", FederationServlet.asPullLink(baseUrl, GitBlit
+ .self().getFederationToken(FederationToken.ALL), FederationRequest.PULL_SETTINGS)));
+
+ final List<String[]> data = new ArrayList<String[]>();
+ for (FederationToken token : FederationToken.values()) {
+ data.add(new String[] { token.name(), GitBlit.self().getFederationToken(token) });
+ }
+
+ DataView<String[]> dataView = new DataView<String[]>("row", new ListDataProvider<String[]>(
+ data)) {
+ private static final long serialVersionUID = 1L;
+ private int counter;
+
+ @Override
+ protected void onBeforeRender() {
+ super.onBeforeRender();
+ counter = 0;
+ }
+
+ public void populateItem(final Item<String[]> item) {
+ final String[] entry = item.getModelObject();
+ final FederationToken token = FederationToken.fromName(entry[0]);
+
+ item.add(new Label("field", entry[0]));
+ item.add(new Label("value", entry[1]));
+
+ // TODO make this work
+ Link<Void> sendProposal = new Link<Void>("send") {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+ error("Sorry, this does not work yet. :(");
+ }
+ };
+ sendProposal.add(new JavascriptTextPrompt("onclick",
+ "Please enter URL for remote Gitblit instance:"));
+ item.add(sendProposal);
+
+ item.add(new Label("description", describeToken(token)));
+ WicketUtils.setAlternatingBackground(item, counter);
+ counter++;
+ }
+ };
+ add(dataView.setVisible(showFederation));
+ }
+
+ private String describeToken(FederationToken token) {
+ switch (token) {
+ case ALL:
+ return getString("gb.tokenAllDescription");
+ case USERS_AND_REPOSITORIES:
+ return getString("gb.tokenUnrDescription");
+ case REPOSITORIES:
+ default:
+ return getString("gb.tokenJurDescription");
+ }
+ }
+}
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html index 48d337c6..8d64f54b 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.html +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html @@ -74,7 +74,7 @@ <td class="left"><div class="list" wicket:id="repositoryName">[repository name]</div></td>
<td><div class="list" wicket:id="repositoryDescription">[repository description]</div></td>
<td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
- <td style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
+ <td style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
<td><span wicket:id="repositoryLastChange">[last change]</span></td>
<td style="text-align: right;padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span></td>
<td class="rightAlign">
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java index 2527f4fd..f644546b 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java @@ -60,11 +60,29 @@ public class RepositoriesPanel extends BasePanel { private static final long serialVersionUID = 1L;
public RepositoriesPanel(String wicketId, final boolean showAdmin,
+ List<RepositoryModel> models,
final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
super(wicketId);
+ final boolean linksActive;
+ final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
+
final UserModel user = GitBlitWebSession.get().getUser();
- List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
+ if (models == null) {
+ linksActive = true;
+ models = GitBlit.self().getRepositoryModels(user);
+ final ByteFormat byteFormat = new ByteFormat();
+ if (showSize) {
+ for (RepositoryModel model : models) {
+ model.size = byteFormat.format(GitBlit.self().calculateSize(model));
+ }
+ }
+ } else {
+ // disable links if the repositories are already provided
+ // the repositories are most likely from a proposal
+ linksActive = false;
+ }
+
final IDataProvider<RepositoryModel> dp;
Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
@@ -100,6 +118,7 @@ public class RepositoriesPanel extends BasePanel { for (String root : roots) {
List<RepositoryModel> subModels = groups.get(root);
groupedModels.add(new GroupRepositoryModel(root, subModels.size()));
+ Collections.sort(subModels);
groupedModels.addAll(subModels);
}
dp = new RepositoriesProvider(groupedModels);
@@ -107,8 +126,6 @@ public class RepositoriesPanel extends BasePanel { dp = new SortableRepositoriesProvider(models);
}
- final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
- final ByteFormat byteFormat = new ByteFormat();
DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
private static final long serialVersionUID = 1L;
int counter;
@@ -130,23 +147,27 @@ public class RepositoriesPanel extends BasePanel { }
Fragment row = new Fragment("rowContent", "repositoryRow", this);
item.add(row);
- if (entry.hasCommits) {
- // Existing repository
+ if (entry.hasCommits && linksActive) {
PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
row.add(new LinkPanel("repositoryName", "list", entry.name, SummaryPage.class,
pp));
row.add(new LinkPanel("repositoryDescription", "list", entry.description,
SummaryPage.class, pp));
+ } else {
+ // new/empty repository OR proposed repository
+ row.add(new Label("repositoryName", entry.name));
+ row.add(new Label("repositoryDescription", entry.description));
+ }
+
+ if (entry.hasCommits) {
+ // Existing repository
if (showSize) {
- row.add(new Label("repositorySize", byteFormat.format(GitBlit.self()
- .calculateSize(entry))));
+ row.add(new Label("repositorySize", entry.size));
} else {
row.add(new Label("repositorySize").setVisible(false));
}
} else {
// New repository
- row.add(new Label("repositoryName", entry.name));
- row.add(new Label("repositoryDescription", entry.description));
row.add(new Label("repositorySize", "<span class='empty'>(empty)</span>")
.setEscapeModelStrings(false));
}
@@ -171,6 +192,13 @@ public class RepositoriesPanel extends BasePanel { } else {
row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
}
+
+ if (entry.isFederated) {
+ row.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png",
+ getString("gb.isFederated")));
+ } else {
+ row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
+ }
switch (entry.accessRestriction) {
case NONE:
row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
@@ -244,7 +272,8 @@ public class RepositoriesPanel extends BasePanel { row.add(new Label("repositoryLinks"));
}
row.add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
- .getRelativePathPrefixToContextRoot(), entry.name, null, 0)));
+ .getRelativePathPrefixToContextRoot(), entry.name, null, 0))
+ .setVisible(linksActive));
WicketUtils.setAlternatingBackground(item, counter);
counter++;
}
|