diff options
author | Rafael Cavazin <rafaelcavazin@gmail.com> | 2013-01-27 12:46:50 -0200 |
---|---|---|
committer | Rafael Cavazin <rafaelcavazin@gmail.com> | 2013-01-27 12:46:50 -0200 |
commit | 06ae63123c94038b90153f4847de2c57c0193db8 (patch) | |
tree | 552bf7cfa3af0f314ac1337a12147b3ec515a1a5 /src/com/gitblit | |
parent | 71a71dad2df82bf8256c91bc48a600bafdef0d33 (diff) | |
parent | c7acc2e1fa86102bb87e715c8fe4e336329fbcc6 (diff) | |
download | gitblit-06ae63123c94038b90153f4847de2c57c0193db8.tar.gz gitblit-06ae63123c94038b90153f4847de2c57c0193db8.zip |
updating current development
Merge remote-tracking branch 'upstream/master' into translation_to_pt-br
Diffstat (limited to 'src/com/gitblit')
84 files changed, 5472 insertions, 620 deletions
diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java index 068bbe3a..67ad0537 100644 --- a/src/com/gitblit/ConfigUserService.java +++ b/src/com/gitblit/ConfigUserService.java @@ -409,6 +409,10 @@ public class ConfigUserService implements IUserService { // Read realm file
read();
UserModel model = users.remove(username.toLowerCase());
+ if (model == null) {
+ // user does not exist
+ return false;
+ }
// remove user from team
for (TeamModel team : model.teams) {
TeamModel t = teams.get(team.name);
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index d152651b..bcca8c72 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -34,7 +34,7 @@ public class Constants { // The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
- public static final String VERSION = "1.2.0-SNAPSHOT";
+ public static final String VERSION = "1.3.0-SNAPSHOT";
// The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
@@ -42,7 +42,7 @@ public class Constants { // The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
- public static final String JGIT_VERSION = "JGit 2.1.0 (201209190230-r)";
+ public static final String JGIT_VERSION = "JGit 2.2.0 (201212191850-r)";
public static final String ADMIN_ROLE = "#admin";
@@ -88,10 +88,18 @@ public class Constants { public static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ";
+ public static final String R_GITBLIT = "refs/gitblit/";
+
+ public static final String baseFolder = "baseFolder";
+
+ public static final String baseFolder$ = "${" + baseFolder + "}";
+
+ public static final String contextFolder$ = "${contextFolder}";
+
public static String getGitBlitVersion() {
return NAME + " v" + VERSION;
}
-
+
/**
* Enumeration representing the four access restriction levels.
*/
@@ -405,6 +413,14 @@ public class Constants { return ordinal() <= COOKIE.ordinal();
}
}
+
+ public static enum AccountType {
+ LOCAL, LDAP, REDMINE;
+
+ public boolean isLocal() {
+ return this == LOCAL;
+ }
+ }
@Documented
@Retention(RetentionPolicy.RUNTIME)
diff --git a/src/com/gitblit/FederationClient.java b/src/com/gitblit/FederationClient.java index daa9bfbf..f6661394 100644 --- a/src/com/gitblit/FederationClient.java +++ b/src/com/gitblit/FederationClient.java @@ -76,7 +76,7 @@ public class FederationClient { }
// configure the Gitblit singleton for minimal, non-server operation
- GitBlit.self().configureContext(settings, false);
+ GitBlit.self().configureContext(settings, null, false);
FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon);
executor.run();
if (!params.isDaemon) {
diff --git a/src/com/gitblit/FileSettings.java b/src/com/gitblit/FileSettings.java index be1f44f2..3a42cad0 100644 --- a/src/com/gitblit/FileSettings.java +++ b/src/com/gitblit/FileSettings.java @@ -104,7 +104,7 @@ public class FileSettings extends IStoredSettings { }
private String regExEscape(String input) {
- return input.replace(".", "\\.");
+ return input.replace(".", "\\.").replace("$", "\\$").replace("{", "\\{");
}
/**
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index 46b0d406..6bf75d75 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -85,6 +85,9 @@ import com.gitblit.Constants.FederationStrategy; import com.gitblit.Constants.FederationToken;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
+import com.gitblit.fanout.FanoutNioService;
+import com.gitblit.fanout.FanoutService;
+import com.gitblit.fanout.FanoutSocketService;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
@@ -154,8 +157,14 @@ public class GitBlit implements ServletContextListener { private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");
+
+ private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
+
+ private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
private ServletContext servletContext;
+
+ private File baseFolder;
private File repositoriesFolder;
@@ -176,6 +185,8 @@ public class GitBlit implements ServletContextListener { private TimeZone timezone;
private FileBasedConfig projectConfigs;
+
+ private FanoutService fanoutService;
public GitBlit() {
if (gitblit == null) {
@@ -385,12 +396,8 @@ public class GitBlit implements ServletContextListener { * @return the file
*/
public static File getFileOrFolder(String fileOrFolder) {
- String openShift = System.getenv("OPENSHIFT_DATA_DIR");
- if (!StringUtils.isEmpty(openShift)) {
- // running on RedHat OpenShift
- return new File(openShift, fileOrFolder);
- }
- return new File(fileOrFolder);
+ return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
+ self().baseFolder, fileOrFolder);
}
/**
@@ -400,7 +407,7 @@ public class GitBlit implements ServletContextListener { * @return the repositories folder path
*/
public static File getRepositoriesFolder() {
- return getFileOrFolder(Keys.git.repositoriesFolder, "git");
+ return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
}
/**
@@ -410,7 +417,7 @@ public class GitBlit implements ServletContextListener { * @return the proposals folder path
*/
public static File getProposalsFolder() {
- return getFileOrFolder(Keys.federation.proposalsFolder, "proposals");
+ return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
}
/**
@@ -420,9 +427,9 @@ public class GitBlit implements ServletContextListener { * @return the Groovy scripts folder path
*/
public static File getGroovyScriptsFolder() {
- return getFileOrFolder(Keys.groovy.scriptsFolder, "groovy");
+ return getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
}
-
+
/**
* Updates the list of server settings.
*
@@ -467,36 +474,48 @@ public class GitBlit implements ServletContextListener { this.userService.setup(settings);
}
+ public boolean supportsAddUser() {
+ return supportsCredentialChanges(new UserModel(""));
+ }
+
/**
+ * Returns true if the user's credentials can be changed.
*
+ * @param user
* @return true if the user service supports credential changes
*/
- public boolean supportsCredentialChanges() {
- return userService.supportsCredentialChanges();
+ public boolean supportsCredentialChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsCredentialChanges();
}
/**
+ * Returns true if the user's display name can be changed.
*
+ * @param user
* @return true if the user service supports display name changes
*/
- public boolean supportsDisplayNameChanges() {
- return userService.supportsDisplayNameChanges();
+ public boolean supportsDisplayNameChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
}
/**
+ * Returns true if the user's email address can be changed.
*
+ * @param user
* @return true if the user service supports email address changes
*/
- public boolean supportsEmailAddressChanges() {
- return userService.supportsEmailAddressChanges();
+ public boolean supportsEmailAddressChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
}
/**
+ * Returns true if the user's team memberships can be changed.
*
+ * @param user
* @return true if the user service supports team membership changes
*/
- public boolean supportsTeamMembershipChanges() {
- return userService.supportsTeamMembershipChanges();
+ public boolean supportsTeamMembershipChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
}
/**
@@ -785,6 +804,10 @@ public class GitBlit implements ServletContextListener { * @return the effective list of permissions for the user
*/
public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
+ if (StringUtils.isEmpty(user.username)) {
+ // new user
+ return new ArrayList<RegistrantAccessPermission>();
+ }
Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
set.addAll(user.getRepositoryPermissions());
// Flag missing repositories
@@ -916,14 +939,14 @@ public class GitBlit implements ServletContextListener { for (RepositoryModel model : getRepositoryModels(user)) {
if (model.isUsersPersonalRepository(username)) {
// personal repository
- model.owner = user.username;
+ model.addOwner(user.username);
String oldRepositoryName = model.name;
model.name = "~" + user.username + model.name.substring(model.projectPath.length());
model.projectPath = "~" + user.username;
updateRepositoryModel(oldRepositoryName, model, false);
} else if (model.isOwner(username)) {
// common/shared repo
- model.owner = user.username;
+ model.addOwner(user.username);
updateRepositoryModel(model.name, model, false);
}
}
@@ -1339,7 +1362,7 @@ public class GitBlit implements ServletContextListener { }
// check for updates
- Repository r = getRepository(repositoryName);
+ Repository r = getRepository(model.name);
if (r == null) {
// repository is missing
removeFromCachedRepositoryList(repositoryName);
@@ -1351,8 +1374,8 @@ public class GitBlit implements ServletContextListener { if (config.isOutdated()) {
// reload model
logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
- model = loadRepositoryModel(repositoryName);
- removeFromCachedRepositoryList(repositoryName);
+ model = loadRepositoryModel(model.name);
+ removeFromCachedRepositoryList(model.name);
addToCachedRepositoryList(model);
} else {
// update a few repository parameters
@@ -1402,7 +1425,30 @@ public class GitBlit implements ServletContextListener { }
project.title = projectConfigs.getString("project", name, "title");
project.description = projectConfigs.getString("project", name, "description");
- configs.put(name.toLowerCase(), project);
+
+ // project markdown
+ File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/project.mkd");
+ if (pmkd.exists()) {
+ Date lm = new Date(pmkd.lastModified());
+ if (!projectMarkdownCache.hasCurrent(name, lm)) {
+ String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n");
+ projectMarkdownCache.updateObject(name, lm, mkd);
+ }
+ project.projectMarkdown = projectMarkdownCache.getObject(name);
+ }
+
+ // project repositories markdown
+ File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/repositories.mkd");
+ if (rmkd.exists()) {
+ Date lm = new Date(rmkd.lastModified());
+ if (!projectRepositoriesMarkdownCache.hasCurrent(name, lm)) {
+ String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n");
+ projectRepositoriesMarkdownCache.updateObject(name, lm, mkd);
+ }
+ project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(name);
+ }
+
+ configs.put(name.toLowerCase(), project);
}
projectCache.clear();
projectCache.putAll(configs);
@@ -1526,6 +1572,49 @@ public class GitBlit implements ServletContextListener { }
/**
+ * Returns the list of project models that are referenced by the supplied
+ * repository model list. This is an alternative method exists to ensure
+ * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
+ *
+ * @param repositoryModels
+ * @param includeUsers
+ * @return a list of project models
+ */
+ public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
+ Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
+ for (RepositoryModel repository : repositoryModels) {
+ if (!includeUsers && repository.isPersonalRepository()) {
+ // exclude personal repositories
+ continue;
+ }
+ if (!projects.containsKey(repository.projectPath)) {
+ ProjectModel project = getProjectModel(repository.projectPath);
+ if (project == null) {
+ logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
+ repository.projectPath));
+ continue;
+ }
+ projects.put(repository.projectPath, project);
+ // clear the repo list in the project because that is the system
+ // list, not the user-accessible list and start building the
+ // user-accessible list
+ project.repositories.clear();
+ project.repositories.add(repository.name);
+ project.lastChange = repository.lastChange;
+ } else {
+ // update the user-accessible list
+ // this is used for repository count
+ ProjectModel project = projects.get(repository.projectPath);
+ project.repositories.add(repository.name);
+ if (project.lastChange.before(repository.lastChange)) {
+ project.lastChange = repository.lastChange;
+ }
+ }
+ }
+ return new ArrayList<ProjectModel>(projects.values());
+ }
+
+ /**
* Workaround JGit. I need to access the raw config object directly in order
* to see if the config is dirty so that I can reload a repository model.
* If I use the stock JGit method to get the config it already reloads the
@@ -1561,7 +1650,7 @@ public class GitBlit implements ServletContextListener { }
RepositoryModel model = new RepositoryModel();
model.isBare = r.isBare();
- File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "git");
+ File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
if (model.isBare) {
model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
} else {
@@ -1576,7 +1665,7 @@ public class GitBlit implements ServletContextListener { if (config != null) {
model.description = getConfig(config, "description", "");
- model.owner = getConfig(config, "owner", "");
+ model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
model.useTickets = getConfig(config, "useTickets", false);
model.useDocs = getConfig(config, "useDocs", false);
model.allowForks = getConfig(config, "allowForks", true);
@@ -1624,6 +1713,7 @@ public class GitBlit implements ServletContextListener { }
model.HEAD = JGitUtils.getHEADRef(r);
model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
+ model.sparkleshareId = JGitUtils.getSparkleshareId(r);
r.close();
if (model.origin != null && model.origin.startsWith("file://")) {
@@ -1652,7 +1742,18 @@ public class GitBlit implements ServletContextListener { * @return true if the repository exists
*/
public boolean hasRepository(String repositoryName) {
- if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ return hasRepository(repositoryName, false);
+ }
+
+ /**
+ * Determines if this server has the requested repository.
+ *
+ * @param name
+ * @param caseInsensitive
+ * @return true if the repository exists
+ */
+ public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
+ if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
// if we are caching use the cache to determine availability
// otherwise we end up adding a phantom repository to the cache
return repositoryListCache.containsKey(repositoryName.toLowerCase());
@@ -1728,7 +1829,7 @@ public class GitBlit implements ServletContextListener { ProjectModel project = getProjectModel(userProject);
for (String repository : project.repositories) {
if (repository.startsWith(userProject)) {
- RepositoryModel model = repositoryListCache.get(repository);
+ RepositoryModel model = getRepositoryModel(repository);
if (model.originRepository.equalsIgnoreCase(origin)) {
// user has a fork
return model.name;
@@ -1749,24 +1850,53 @@ public class GitBlit implements ServletContextListener { */
public ForkModel getForkNetwork(String repository) {
if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
- // find the root
+ // find the root, cached
RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
while (model.originRepository != null) {
model = repositoryListCache.get(model.originRepository);
}
+ ForkModel root = getForkModelFromCache(model.name);
+ return root;
+ } else {
+ // find the root, non-cached
+ RepositoryModel model = getRepositoryModel(repository.toLowerCase());
+ while (model.originRepository != null) {
+ model = getRepositoryModel(model.originRepository);
+ }
ForkModel root = getForkModel(model.name);
return root;
}
- return null;
}
- private ForkModel getForkModel(String repository) {
+ private ForkModel getForkModelFromCache(String repository) {
RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
+ if (model == null) {
+ return null;
+ }
+ ForkModel fork = new ForkModel(model);
+ if (!ArrayUtils.isEmpty(model.forks)) {
+ for (String aFork : model.forks) {
+ ForkModel fm = getForkModelFromCache(aFork);
+ if (fm != null) {
+ fork.forks.add(fm);
+ }
+ }
+ }
+ return fork;
+ }
+
+ private ForkModel getForkModel(String repository) {
+ RepositoryModel model = getRepositoryModel(repository.toLowerCase());
+ if (model == null) {
+ return null;
+ }
ForkModel fork = new ForkModel(model);
if (!ArrayUtils.isEmpty(model.forks)) {
for (String aFork : model.forks) {
ForkModel fm = getForkModel(aFork);
- fork.forks.add(fm);
+ if (fm != null) {
+ fork.forks.add(fm);
+ }
}
}
return fork;
@@ -1937,7 +2067,7 @@ public class GitBlit implements ServletContextListener { if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
}
- if (new File(repositoriesFolder, repository.name).exists()) {
+ if (hasRepository(repository.name)) {
throw new GitBlitException(MessageFormat.format(
"Can not create repository ''{0}'' because it already exists.",
repository.name));
@@ -2053,7 +2183,7 @@ public class GitBlit implements ServletContextListener { public void updateConfiguration(Repository r, RepositoryModel repository) {
StoredConfig config = r.getConfig();
config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
- config.setString(Constants.CONFIG_GITBLIT, null, "owner", repository.owner);
+ config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));
config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets);
config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs);
config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks);
@@ -2911,12 +3041,15 @@ public class GitBlit implements ServletContextListener { *
* @param settings
*/
- public void configureContext(IStoredSettings settings, boolean startFederation) {
- logger.info("Reading configuration from " + settings.toString());
+ public void configureContext(IStoredSettings settings, File folder, boolean startFederation) {
this.settings = settings;
+ this.baseFolder = folder;
repositoriesFolder = getRepositoriesFolder();
- logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
+
+ logger.info("Gitblit base folder = " + folder.getAbsolutePath());
+ logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
+ logger.info("Gitblit settings = " + settings.toString());
// prepare service executors
mailExecutor = new MailExecutor(settings);
@@ -2938,7 +3071,7 @@ public class GitBlit implements ServletContextListener { serverStatus = new ServerStatus(isGO());
if (this.userService == null) {
- String realm = settings.getString(Keys.realm.userService, "users.properties");
+ String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
IUserService loginService = null;
try {
// check to see if this "file" is a login service class
@@ -2951,7 +3084,7 @@ public class GitBlit implements ServletContextListener { }
// load and cache the project metadata
- projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "projects.conf"), FS.detect());
+ projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
getProjectConfigs();
// schedule mail engine
@@ -3017,6 +3150,32 @@ public class GitBlit implements ServletContextListener { }
ContainerUtils.CVE_2007_0450.test();
+
+ // startup Fanout PubSub service
+ if (settings.getInteger(Keys.fanout.port, 0) > 0) {
+ String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
+ int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
+ boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
+ int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
+
+ if (useNio) {
+ if (StringUtils.isEmpty(bindInterface)) {
+ fanoutService = new FanoutNioService(port);
+ } else {
+ fanoutService = new FanoutNioService(bindInterface, port);
+ }
+ } else {
+ if (StringUtils.isEmpty(bindInterface)) {
+ fanoutService = new FanoutSocketService(port);
+ } else {
+ fanoutService = new FanoutSocketService(bindInterface, port);
+ }
+ }
+
+ fanoutService.setConcurrentConnectionLimit(limit);
+ fanoutService.setAllowAllChannelAnnouncements(false);
+ fanoutService.start();
+ }
}
private void logTimezone(String type, TimeZone zone) {
@@ -3040,39 +3199,63 @@ public class GitBlit implements ServletContextListener { public void contextInitialized(ServletContextEvent contextEvent, InputStream referencePropertiesInputStream) {
servletContext = contextEvent.getServletContext();
if (settings == null) {
- // Gitblit WAR is running in a servlet container
+ // Gitblit is running in a servlet container
ServletContext context = contextEvent.getServletContext();
WebXmlSettings webxmlSettings = new WebXmlSettings(context);
-
- // gitblit.properties file located within the webapp
- String webProps = context.getRealPath("/WEB-INF/gitblit.properties");
- if (!StringUtils.isEmpty(webProps)) {
- File overrideFile = new File(webProps);
- webxmlSettings.applyOverrides(overrideFile);
- }
+ File contextFolder = new File(context.getRealPath("/"));
+ String openShift = System.getenv("OPENSHIFT_DATA_DIR");
- // gitblit.properties file located outside the deployed war
- // folder lie, for example, on RedHat OpenShift.
- File overrideFile = getFileOrFolder("gitblit.properties");
- if (!overrideFile.getPath().equals("gitblit.properties")) {
+ if (!StringUtils.isEmpty(openShift)) {
+ // Gitblit is running in OpenShift/JBoss
+ File base = new File(openShift);
+
+ // gitblit.properties setting overrides
+ File overrideFile = new File(base, "gitblit.properties");
webxmlSettings.applyOverrides(overrideFile);
- }
-
- configureContext(webxmlSettings, true);
-
- // Copy the included scripts to the configured groovy folder
- File localScripts = getFileOrFolder(Keys.groovy.scriptsFolder, "groovy");
- if (!localScripts.exists()) {
- File includedScripts = new File(context.getRealPath("/WEB-INF/groovy"));
- if (!includedScripts.equals(localScripts)) {
- try {
- com.gitblit.utils.FileUtils.copy(localScripts, includedScripts.listFiles());
- } catch (IOException e) {
- logger.error(MessageFormat.format(
- "Failed to copy included Groovy scripts from {0} to {1}",
- includedScripts, localScripts));
+
+ // Copy the included scripts to the configured groovy folder
+ File localScripts = new File(base, webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"));
+ if (!localScripts.exists()) {
+ File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
+ if (!warScripts.equals(localScripts)) {
+ try {
+ com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
+ } catch (IOException e) {
+ logger.error(MessageFormat.format(
+ "Failed to copy included Groovy scripts from {0} to {1}",
+ warScripts, localScripts));
+ }
+ }
+ }
+
+ // configure context using the web.xml
+ configureContext(webxmlSettings, base, true);
+ } else {
+ // Gitblit is running in a standard servlet container
+ logger.info("WAR contextFolder is " + contextFolder.getAbsolutePath());
+
+ String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
+ File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
+ base.mkdirs();
+
+ // try to copy the data folder contents to the baseFolder
+ File localSettings = new File(base, "gitblit.properties");
+ if (!localSettings.exists()) {
+ File contextData = new File(contextFolder, "/WEB-INF/data");
+ if (!base.equals(contextData)) {
+ try {
+ com.gitblit.utils.FileUtils.copy(base, contextData.listFiles());
+ } catch (IOException e) {
+ logger.error(MessageFormat.format(
+ "Failed to copy included data from {0} to {1}",
+ contextData, base));
+ }
}
}
+
+ // delegate all config to baseFolder/gitblit.properties file
+ FileSettings settings = new FileSettings(localSettings.getAbsolutePath());
+ configureContext(settings, base, true);
}
}
@@ -3090,6 +3273,9 @@ public class GitBlit implements ServletContextListener { scheduledExecutor.shutdownNow();
luceneExecutor.close();
gcExecutor.close();
+ if (fanoutService != null) {
+ fanoutService.stop();
+ }
}
/**
@@ -3134,15 +3320,17 @@ public class GitBlit implements ServletContextListener { // create a Gitblit repository model for the clone
RepositoryModel cloneModel = repository.cloneAs(cloneName);
// owner has REWIND/RW+ permissions
- cloneModel.owner = user.username;
+ cloneModel.addOwner(user.username);
updateRepositoryModel(cloneName, cloneModel, false);
// add the owner of the source repository to the clone's access list
- if (!StringUtils.isEmpty(repository.owner)) {
- UserModel originOwner = getUserModel(repository.owner);
- if (originOwner != null) {
- originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
- updateUserModel(originOwner.username, originOwner, false);
+ if (!ArrayUtils.isEmpty(repository.owners)) {
+ for (String owner : repository.owners) {
+ UserModel originOwner = getUserModel(owner);
+ if (originOwner != null) {
+ originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ updateUserModel(originOwner.username, originOwner, false);
+ }
}
}
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java index 4c0e89f6..feddb93f 100644 --- a/src/com/gitblit/GitBlitServer.java +++ b/src/com/gitblit/GitBlitServer.java @@ -84,10 +84,29 @@ public class GitBlitServer { private static Logger logger;
public static void main(String... args) {
+ // filter out the baseFolder parameter
+ List<String> filtered = new ArrayList<String>();
+ String folder = "data";
+ for (int i = 0; i< args.length; i++) {
+ String arg = args[i];
+ if (arg.equals("--baseFolder")) {
+ if (i + 1 == args.length) {
+ System.out.println("Invalid --baseFolder parameter!");
+ System.exit(-1);
+ } else if (args[i + 1] != ".") {
+ folder = args[i + 1];
+ }
+ i = i + 1;
+ } else {
+ filtered.add(arg);
+ }
+ }
+
+ Params.baseFolder = folder;
Params params = new Params();
JCommander jc = new JCommander(params);
try {
- jc.parse(args);
+ jc.parse(filtered.toArray(new String[filtered.size()]));
if (params.help) {
usage(jc, null);
}
@@ -147,13 +166,13 @@ public class GitBlitServer { * Start Gitblit GO.
*/
private static void start(Params params) {
- FileSettings settings = Params.FILESETTINGS;
+ final File baseFolder = new File(Params.baseFolder).getAbsoluteFile();
+ FileSettings settings = params.FILESETTINGS;
if (!StringUtils.isEmpty(params.settingsfile)) {
if (new File(params.settingsfile).exists()) {
settings = new FileSettings(params.settingsfile);
}
}
-
logger = LoggerFactory.getLogger(GitBlitServer.class);
logger.info(Constants.BORDER);
logger.info(" _____ _ _ _ _ _ _");
@@ -197,11 +216,10 @@ public class GitBlitServer { // conditionally configure the https connector
if (params.securePort > 0) {
- final File folder = new File(System.getProperty("user.dir"));
- File certificatesConf = new File(folder, X509Utils.CA_CONFIG);
- File serverKeyStore = new File(folder, X509Utils.SERVER_KEY_STORE);
- File serverTrustStore = new File(folder, X509Utils.SERVER_TRUST_STORE);
- File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);
+ File certificatesConf = new File(baseFolder, X509Utils.CA_CONFIG);
+ File serverKeyStore = new File(baseFolder, X509Utils.SERVER_KEY_STORE);
+ File serverTrustStore = new File(baseFolder, X509Utils.SERVER_TRUST_STORE);
+ File caRevocationList = new File(baseFolder, X509Utils.CA_REVOCATION_LIST);
// generate CA & web certificates, create certificate stores
X509Metadata metadata = new X509Metadata("localhost", params.storePassword);
@@ -218,12 +236,12 @@ public class GitBlitServer { }
metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
- X509Utils.prepareX509Infrastructure(metadata, folder, new X509Log() {
+ X509Utils.prepareX509Infrastructure(metadata, baseFolder, new X509Log() {
@Override
public void log(String message) {
BufferedWriter writer = null;
try {
- writer = new BufferedWriter(new FileWriter(new File(folder, X509Utils.CERTS + File.separator + "log.txt"), true));
+ writer = new BufferedWriter(new FileWriter(new File(baseFolder, X509Utils.CERTS + File.separator + "log.txt"), true));
writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message));
writer.newLine();
writer.flush();
@@ -277,7 +295,7 @@ public class GitBlitServer { // tempDir is where the embedded Gitblit web application is expanded and
// where Jetty creates any necessary temporary files
- File tempDir = new File(params.temp);
+ File tempDir = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.temp);
if (tempDir.exists()) {
try {
FileUtils.delete(tempDir, FileUtils.RECURSIVE | FileUtils.RETRY);
@@ -361,7 +379,7 @@ public class GitBlitServer { // Setup the GitBlit context
GitBlit gitblit = GitBlit.self();
- gitblit.configureContext(settings, true);
+ gitblit.configureContext(settings, baseFolder, true);
rootContext.addEventListener(gitblit);
try {
@@ -532,7 +550,9 @@ public class GitBlitServer { @Parameters(separators = " ")
private static class Params {
- private static final FileSettings FILESETTINGS = new FileSettings(Constants.PROPERTIES_FILE);
+ public static String baseFolder;
+
+ private final FileSettings FILESETTINGS = new FileSettings(new File(baseFolder, Constants.PROPERTIES_FILE).getAbsolutePath());
/*
* Server parameters
@@ -551,14 +571,14 @@ public class GitBlitServer { */
@Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")
public String repositoriesFolder = FILESETTINGS.getString(Keys.git.repositoriesFolder,
- "repos");
+ "git");
/*
* Authentication Parameters
*/
@Parameter(names = { "--userService" }, description = "Authentication and Authorization Service (filename or fully qualified classname)")
public String userService = FILESETTINGS.getString(Keys.realm.userService,
- "users.properties");
+ "users.conf");
/*
* JETTY Parameters
@@ -567,10 +587,10 @@ public class GitBlitServer { public Boolean useNIO = FILESETTINGS.getBoolean(Keys.server.useNio, true);
@Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)")
- public Integer port = FILESETTINGS.getInteger(Keys.server.httpPort, 80);
+ public Integer port = FILESETTINGS.getInteger(Keys.server.httpPort, 0);
@Parameter(names = "--httpsPort", description = "HTTPS port to serve. (port <= 0 will disable this connector)")
- public Integer securePort = FILESETTINGS.getInteger(Keys.server.httpsPort, 443);
+ public Integer securePort = FILESETTINGS.getInteger(Keys.server.httpsPort, 8443);
@Parameter(names = "--ajpPort", description = "AJP port to serve. (port <= 0 will disable this connector)")
public Integer ajpPort = FILESETTINGS.getInteger(Keys.server.ajpPort, 0);
diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java index 2b769d4b..a0d395b1 100644 --- a/src/com/gitblit/GitFilter.java +++ b/src/com/gitblit/GitFilter.java @@ -222,7 +222,7 @@ public class GitFilter extends AccessRestrictionFilter { // create repository
RepositoryModel model = new RepositoryModel();
model.name = repository;
- model.owner = user.username;
+ model.addOwner(user.username);
model.projectPath = StringUtils.getFirstPathElement(repository);
if (model.isUsersPersonalRepository(user.username)) {
// personal repository, default to private for user
diff --git a/src/com/gitblit/GitServlet.java b/src/com/gitblit/GitServlet.java index 42d88c91..77be963f 100644 --- a/src/com/gitblit/GitServlet.java +++ b/src/com/gitblit/GitServlet.java @@ -18,17 +18,14 @@ package com.gitblit; import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.servlet.ServletConfig;
@@ -37,7 +34,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
+import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PostReceiveHook;
@@ -45,6 +44,8 @@ import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.RefFilter;
+import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.slf4j.Logger;
@@ -55,7 +56,9 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel;
import com.gitblit.utils.ClientLogger;
import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.IssueUtils;
import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.PushLogUtils;
import com.gitblit.utils.StringUtils;
/**
@@ -83,7 +86,7 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { groovyDir = GitBlit.getGroovyScriptsFolder();
try {
// set Grape root
- File grapeRoot = new File(GitBlit.getString(Keys.groovy.grapeFolder, "groovy/grape")).getAbsoluteFile();
+ File grapeRoot = GitBlit.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape").getAbsoluteFile();
grapeRoot.mkdirs();
System.setProperty("grape.root", grapeRoot.getAbsolutePath());
@@ -124,9 +127,42 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { rp.setAllowDeletes(user.canDeleteRef(repository));
rp.setAllowNonFastForwards(user.canRewindRef(repository));
+ if (repository.isFrozen) {
+ throw new ServiceNotEnabledException();
+ }
+
return rp;
}
});
+
+ // override the default upload pack to exclude gitblit refs
+ setUploadPackFactory(new DefaultUploadPackFactory() {
+ @Override
+ public UploadPack create(final HttpServletRequest req, final Repository db)
+ throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+ UploadPack up = super.create(req, db);
+ RefFilter refFilter = new RefFilter() {
+ @Override
+ public Map<String, Ref> filter(Map<String, Ref> refs) {
+ // admin accounts can access all refs
+ UserModel user = GitBlit.self().authenticate(req);
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ }
+ if (user.canAdmin()) {
+ return refs;
+ }
+
+ // normal users can not clone gitblit refs
+ refs.remove(IssueUtils.GB_ISSUES);
+ refs.remove(PushLogUtils.GB_PUSHES);
+ return refs;
+ }
+ };
+ up.setRefFilter(refFilter);
+ return up;
+ }
+ });
super.init(new GitblitServletConfig(config));
}
@@ -244,9 +280,6 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { .getName(), cmd.getResult(), cmd.getMessage()));
}
}
-
- // Experimental
- // runNativeScript(rp, "hooks/pre-receive", commands);
}
/**
@@ -260,12 +293,11 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { logger.info("skipping post-receive hooks, no refs created, updated, or removed");
return;
}
- RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
- Set<String> scripts = new LinkedHashSet<String>();
- scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
- scripts.addAll(repository.postReceiveScripts);
+
UserModel user = getUserModel(rp);
- runGroovy(repository, user, commands, rp, scripts);
+ RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
+
+ // log ref changes
for (ReceiveCommand cmd : commands) {
if (Result.OK.equals(cmd.getResult())) {
// add some logging for important ref changes
@@ -284,9 +316,20 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { }
}
}
+
+ // update push log
+ try {
+ PushLogUtils.updatePushLog(user, rp.getRepository(), commands);
+ logger.info(MessageFormat.format("{0} push log updated", repository.name));
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
+ }
- // Experimental
- // runNativeScript(rp, "hooks/post-receive", commands);
+ // run Groovy hook scripts
+ Set<String> scripts = new LinkedHashSet<String>();
+ scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
+ scripts.addAll(repository.postReceiveScripts);
+ runGroovy(repository, user, commands, rp, scripts);
}
/**
@@ -358,76 +401,5 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { }
}
}
-
- /**
- * Runs the native push hook script.
- *
- * http://book.git-scm.com/5_git_hooks.html
- * http://longair.net/blog/2011/04/09/missing-git-hooks-documentation/
- *
- * @param rp
- * @param script
- * @param commands
- */
- @SuppressWarnings("unused")
- protected void runNativeScript(ReceivePack rp, String script,
- Collection<ReceiveCommand> commands) {
-
- Repository repository = rp.getRepository();
- File scriptFile = new File(repository.getDirectory(), script);
-
- int resultCode = 0;
- if (scriptFile.exists()) {
- try {
- logger.debug("executing " + scriptFile);
- Process process = Runtime.getRuntime().exec(scriptFile.getAbsolutePath(), null,
- repository.getDirectory());
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- process.getInputStream()));
- BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
- process.getOutputStream()));
- for (ReceiveCommand command : commands) {
- switch (command.getType()) {
- case UPDATE:
- // updating a ref
- writer.append(MessageFormat.format("{0} {1} {2}\n", command.getOldId()
- .getName(), command.getNewId().getName(), command.getRefName()));
- break;
- case CREATE:
- // new ref
- // oldrev hard-coded to 40? weird.
- writer.append(MessageFormat.format("40 {0} {1}\n", command.getNewId()
- .getName(), command.getRefName()));
- break;
- }
- }
- resultCode = process.waitFor();
-
- // read and buffer stdin
- // this is supposed to be piped back to the git client.
- // not sure how to do that right now.
- StringBuilder sb = new StringBuilder();
- String line = null;
- while ((line = reader.readLine()) != null) {
- sb.append(line).append('\n');
- }
- logger.debug(sb.toString());
- } catch (Throwable e) {
- resultCode = -1;
- logger.error(
- MessageFormat.format("Failed to execute {0}",
- scriptFile.getAbsolutePath()), e);
- }
- }
-
- // reject push
- if (resultCode != 0) {
- for (ReceiveCommand command : commands) {
- command.setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format(
- "Native script {0} rejected push or failed",
- scriptFile.getAbsolutePath()));
- }
- }
- }
}
}
diff --git a/src/com/gitblit/GitblitUserService.java b/src/com/gitblit/GitblitUserService.java index 141ad8f1..37f22b01 100644 --- a/src/com/gitblit/GitblitUserService.java +++ b/src/com/gitblit/GitblitUserService.java @@ -23,9 +23,11 @@ import java.util.List; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccountType;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.StringUtils;
/**
* This class wraps the default user service and is recommended as the starting
@@ -48,6 +50,8 @@ import com.gitblit.utils.DeepCopier; public class GitblitUserService implements IUserService {
protected IUserService serviceImpl;
+
+ protected final String ExternalAccount = "#externalAccount";
private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class);
@@ -56,7 +60,7 @@ public class GitblitUserService implements IUserService { @Override
public void setup(IStoredSettings settings) {
- File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "users.conf");
+ File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
serviceImpl = createUserService(realmFile);
logger.info("GUS delegating to " + serviceImpl.toString());
}
@@ -144,12 +148,16 @@ public class GitblitUserService implements IUserService { @Override
public UserModel authenticate(char[] cookie) {
- return serviceImpl.authenticate(cookie);
+ UserModel user = serviceImpl.authenticate(cookie);
+ setAccountType(user);
+ return user;
}
@Override
public UserModel authenticate(String username, char[] password) {
- return serviceImpl.authenticate(username, password);
+ UserModel user = serviceImpl.authenticate(username, password);
+ setAccountType(user);
+ return user;
}
@Override
@@ -159,7 +167,9 @@ public class GitblitUserService implements IUserService { @Override
public UserModel getUserModel(String username) {
- return serviceImpl.getUserModel(username);
+ UserModel user = serviceImpl.getUserModel(username);
+ setAccountType(user);
+ return user;
}
@Override
@@ -174,8 +184,8 @@ public class GitblitUserService implements IUserService { @Override
public boolean updateUserModel(String username, UserModel model) {
- if (supportsCredentialChanges()) {
- if (!supportsTeamMembershipChanges()) {
+ if (model.isLocalAccount() || supportsCredentialChanges()) {
+ if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
// teams are externally controlled - copy from original model
UserModel existingModel = getUserModel(username);
@@ -188,7 +198,7 @@ public class GitblitUserService implements IUserService { if (model.username.equals(username)) {
// passwords are not persisted by the backing user service
model.password = null;
- if (!supportsTeamMembershipChanges()) {
+ if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
// teams are externally controlled- copy from original model
UserModel existingModel = getUserModel(username);
@@ -218,7 +228,11 @@ public class GitblitUserService implements IUserService { @Override
public List<UserModel> getAllUsers() {
- return serviceImpl.getAllUsers();
+ List<UserModel> users = serviceImpl.getAllUsers();
+ for (UserModel user : users) {
+ setAccountType(user);
+ }
+ return users;
}
@Override
@@ -300,4 +314,25 @@ public class GitblitUserService implements IUserService { public boolean deleteRepositoryRole(String role) {
return serviceImpl.deleteRepositoryRole(role);
}
+
+ protected boolean isLocalAccount(String username) {
+ UserModel user = getUserModel(username);
+ return user != null && user.isLocalAccount();
+ }
+
+ protected void setAccountType(UserModel user) {
+ if (user != null) {
+ if (!StringUtils.isEmpty(user.password)
+ && !ExternalAccount.equalsIgnoreCase(user.password)
+ && !"StoredInLDAP".equalsIgnoreCase(user.password)) {
+ user.accountType = AccountType.LOCAL;
+ } else {
+ user.accountType = getAccountType();
+ }
+ }
+ }
+
+ protected AccountType getAccountType() {
+ return AccountType.LOCAL;
+ }
}
diff --git a/src/com/gitblit/LdapUserService.java b/src/com/gitblit/LdapUserService.java index 9ce18f6d..595c6589 100644 --- a/src/com/gitblit/LdapUserService.java +++ b/src/com/gitblit/LdapUserService.java @@ -25,6 +25,7 @@ import java.util.List; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccountType;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
@@ -50,9 +51,9 @@ import com.unboundid.util.ssl.TrustAllTrustManager; public class LdapUserService extends GitblitUserService {
public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
-
- private IStoredSettings settings;
+ private IStoredSettings settings;
+
public LdapUserService() {
super();
}
@@ -60,7 +61,7 @@ public class LdapUserService extends GitblitUserService { @Override
public void setup(IStoredSettings settings) {
this.settings = settings;
- String file = settings.getString(Keys.realm.ldap.backingUserService, "users.conf");
+ String file = settings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
File realmFile = GitBlit.getFileOrFolder(file);
serviceImpl = createUserService(realmFile);
@@ -155,9 +156,19 @@ public class LdapUserService extends GitblitUserService { public boolean supportsTeamMembershipChanges() {
return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
}
+
+ @Override
+ protected AccountType getAccountType() {
+ return AccountType.LDAP;
+ }
@Override
public UserModel authenticate(String username, char[] password) {
+ if (isLocalAccount(username)) {
+ // local account, bypass LDAP authentication
+ return super.authenticate(username, password);
+ }
+
String simpleUsername = getSimpleUsername(username);
LDAPConnection ldapConnection = getLdapConnection();
@@ -239,7 +250,8 @@ public class LdapUserService extends GitblitUserService { setAdminAttribute(user);
// Don't want visibility into the real password, make up a dummy
- user.password = "StoredInLDAP";
+ user.password = ExternalAccount;
+ user.accountType = getAccountType();
// Get full name Attribute
String displayName = settings.getString(Keys.realm.ldap.displayName, "");
diff --git a/src/com/gitblit/PagesServlet.java b/src/com/gitblit/PagesServlet.java index ad9276b4..91f25b70 100644 --- a/src/com/gitblit/PagesServlet.java +++ b/src/com/gitblit/PagesServlet.java @@ -170,7 +170,7 @@ public class PagesServlet extends HttpServlet { content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes(
Constants.ENCODING);
} else {
- content = JGitUtils.getByteContent(r, tree, resource);
+ content = JGitUtils.getByteContent(r, tree, resource, false);
}
response.setContentType(contentType);
} catch (Exception e) {
diff --git a/src/com/gitblit/RedmineUserService.java b/src/com/gitblit/RedmineUserService.java index b890f21b..9d571e37 100644 --- a/src/com/gitblit/RedmineUserService.java +++ b/src/com/gitblit/RedmineUserService.java @@ -9,7 +9,9 @@ import org.apache.wicket.util.io.IOUtils; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccountType;
import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ConnectionUtils;
import com.gitblit.utils.StringUtils;
import com.google.gson.Gson;
@@ -45,7 +47,7 @@ public class RedmineUserService extends GitblitUserService { public void setup(IStoredSettings settings) {
this.settings = settings;
- String file = settings.getString(Keys.realm.redmine.backingUserService, "users.conf");
+ String file = settings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
File realmFile = GitBlit.getFileOrFolder(file);
serviceImpl = createUserService(realmFile);
@@ -71,54 +73,110 @@ public class RedmineUserService extends GitblitUserService { public boolean supportsTeamMembershipChanges() {
return false;
}
+
+ @Override
+ protected AccountType getAccountType() {
+ return AccountType.REDMINE;
+ }
@Override
public UserModel authenticate(String username, char[] password) {
- String urlText = this.settings.getString(Keys.realm.redmine.url, "");
- if (!urlText.endsWith("/")) {
- urlText.concat("/");
- }
- String apiKey = String.valueOf(password);
+ if (isLocalAccount(username)) {
+ // local account, bypass Redmine authentication
+ return super.authenticate(username, password);
+ }
+ String jsonString = null;
try {
- String jsonString = getCurrentUserAsJson(urlText, apiKey);
-
- RedmineCurrent current = new Gson().fromJson(jsonString, RedmineCurrent.class);
- String login = current.user.login;
-
- boolean canAdmin = true;
- // non admin user can not get login name
- if (StringUtils.isEmpty(login)) {
- canAdmin = false;
- login = current.user.mail;
- }
-
- UserModel userModel = new UserModel(login);
- userModel.canAdmin = canAdmin;
- userModel.displayName = current.user.firstname + " " + current.user.lastname;
- userModel.emailAddress = current.user.mail;
- userModel.cookie = StringUtils.getSHA1(userModel.username + new String(password));
-
- return userModel;
- } catch (IOException e) {
- logger.error("authenticate", e);
+ // first attempt by username/password
+ jsonString = getCurrentUserAsJson(username, password);
+ } catch (Exception e1) {
+ logger.warn("Failed to authenticate via username/password against Redmine");
+ try {
+ // second attempt is by apikey
+ jsonString = getCurrentUserAsJson(null, password);
+ username = null;
+ } catch (Exception e2) {
+ logger.error("Failed to authenticate via apikey against Redmine", e2);
+ return null;
+ }
+ }
+
+ if (StringUtils.isEmpty(jsonString)) {
+ logger.error("Received empty authentication response from Redmine");
+ return null;
+ }
+
+ RedmineCurrent current = null;
+ try {
+ current = new Gson().fromJson(jsonString, RedmineCurrent.class);
+ } catch (Exception e) {
+ logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
+ return null;
+ }
+
+ if (StringUtils.isEmpty(username)) {
+ // if the username has been reset because of apikey authentication
+ // then use the email address of the user. this is the original
+ // behavior as contributed by github/mallowlabs
+ username = current.user.mail;
+ }
+
+ UserModel user = getUserModel(username);
+ if (user == null) // create user object for new authenticated user
+ user = new UserModel(username.toLowerCase());
+
+ // create a user cookie
+ if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
+ user.cookie = StringUtils.getSHA1(user.username + new String(password));
}
- return null;
+
+ // update user attributes from Redmine
+ user.accountType = getAccountType();
+ user.displayName = current.user.firstname + " " + current.user.lastname;
+ user.emailAddress = current.user.mail;
+ user.password = ExternalAccount;
+ if (!StringUtils.isEmpty(current.user.login)) {
+ // only admin users can get login name
+ // evidently this is an undocumented behavior of Redmine
+ user.canAdmin = true;
+ }
+
+ // TODO consider Redmine group mapping for team membership
+ // http://www.redmine.org/projects/redmine/wiki/Rest_Users
+
+ // push the changes to the backing user service
+ super.updateUserModel(user);
+
+ return user;
}
- private String getCurrentUserAsJson(String url, String apiKey) throws IOException {
+ private String getCurrentUserAsJson(String username, char [] password) throws IOException {
if (testingJson != null) { // for testing
return testingJson;
}
- String apiUrl = url + "users/current.json?key=" + apiKey;
- HttpURLConnection http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
+ String url = this.settings.getString(Keys.realm.redmine.url, "");
+ if (!url.endsWith("/")) {
+ url.concat("/");
+ }
+ HttpURLConnection http;
+ if (username == null) {
+ // apikey authentication
+ String apiKey = String.valueOf(password);
+ String apiUrl = url + "users/current.json?key=" + apiKey;
+ http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
+ } else {
+ // username/password BASIC authentication
+ String apiUrl = url + "users/current.json";
+ http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
+ }
http.setRequestMethod("GET");
http.connect();
InputStreamReader reader = new InputStreamReader(http.getInputStream());
return IOUtils.toString(reader);
}
-
+
/**
* set json response. do NOT invoke from production code.
* @param json json
@@ -126,5 +184,4 @@ public class RedmineUserService extends GitblitUserService { public void setTestingCurrentUserAsJson(String json) {
this.testingJson = json;
}
-
}
diff --git a/src/com/gitblit/RobotsTxtServlet.java b/src/com/gitblit/RobotsTxtServlet.java index c142be0d..d66ebf43 100644 --- a/src/com/gitblit/RobotsTxtServlet.java +++ b/src/com/gitblit/RobotsTxtServlet.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import com.gitblit.utils.FileUtils;
-import com.gitblit.utils.StringUtils;
/**
* Handles requests for robots.txt
@@ -55,13 +54,10 @@ public class RobotsTxtServlet extends HttpServlet { protected void processRequest(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
java.io.IOException {
- String robotstxt = GitBlit.getString(Keys.web.robots.txt, null);
+ File file = GitBlit.getFileOrFolder(Keys.web.robots.txt, null);
String content = "";
- if (!StringUtils.isEmpty(robotstxt)) {
- File robotsfile = new File(robotstxt);
- if (robotsfile.exists()) {
- content = FileUtils.readContent(robotsfile, "\n");
- }
+ if (file.exists()) {
+ content = FileUtils.readContent(file, "\n");
}
response.getWriter().append(content);
}
diff --git a/src/com/gitblit/authority/GitblitAuthority.java b/src/com/gitblit/authority/GitblitAuthority.java index 909831fe..c3d81848 100644 --- a/src/com/gitblit/authority/GitblitAuthority.java +++ b/src/com/gitblit/authority/GitblitAuthority.java @@ -138,6 +138,21 @@ public class GitblitAuthority extends JFrame implements X509Log { private JButton newSSLCertificate;
public static void main(String... args) {
+ // filter out the baseFolder parameter
+ String folder = "data";
+ for (int i = 0; i< args.length; i++) {
+ String arg = args[i];
+ if (arg.equals("--baseFolder")) {
+ if (i + 1 == args.length) {
+ System.out.println("Invalid --baseFolder parameter!");
+ System.exit(-1);
+ } else if (args[i + 1] != ".") {
+ folder = args[i+1];
+ }
+ break;
+ }
+ }
+ final String baseFolder = folder;
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
@@ -145,7 +160,7 @@ public class GitblitAuthority extends JFrame implements X509Log { } catch (Exception e) {
}
GitblitAuthority authority = new GitblitAuthority();
- authority.initialize();
+ authority.initialize(baseFolder);
authority.setLocationRelativeTo(null);
authority.setVisible(true);
}
@@ -158,7 +173,7 @@ public class GitblitAuthority extends JFrame implements X509Log { defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel);
}
- public void initialize() {
+ public void initialize(String baseFolder) {
setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
setTitle("Gitblit Certificate Authority v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")");
setContentPane(getUI());
@@ -174,10 +189,10 @@ public class GitblitAuthority extends JFrame implements X509Log { }
});
- setSizeAndPosition();
-
- File folder = new File(System.getProperty("user.dir"));
+ File folder = new File(baseFolder).getAbsoluteFile();
load(folder);
+
+ setSizeAndPosition();
}
private void setSizeAndPosition() {
@@ -230,7 +245,7 @@ public class GitblitAuthority extends JFrame implements X509Log { }
private StoredConfig getConfig() throws IOException, ConfigInvalidException {
- File configFile = new File(System.getProperty("user.dir"), X509Utils.CA_CONFIG);
+ File configFile = new File(folder, X509Utils.CA_CONFIG);
FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
config.load();
return config;
@@ -243,30 +258,31 @@ public class GitblitAuthority extends JFrame implements X509Log { }
gitblitSettings = new FileSettings(file.getAbsolutePath());
mail = new MailExecutor(gitblitSettings);
- String us = gitblitSettings.getString(Keys.realm.userService, "users.conf");
+ String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();
IUserService service = null;
if (!ext.equals("conf") && !ext.equals("properties")) {
if (us.equals("com.gitblit.LdapUserService")) {
- us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "users.conf");
+ us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
} else if (us.equals("com.gitblit.LdapUserService")) {
- us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "users.conf");
+ us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
}
}
if (us.endsWith(".conf")) {
- service = new ConfigUserService(new File(us));
+ service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
} else {
throw new RuntimeException("Unsupported user service: " + us);
}
- service = new ConfigUserService(new File(us));
+ service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
return service;
}
private void load(File folder) {
this.folder = folder;
this.userService = loadUsers(folder);
+ System.out.println(Constants.baseFolder$ + " set to " + folder);
if (userService == null) {
JOptionPane.showMessageDialog(this, MessageFormat.format("Sorry, {0} doesn't look like a Gitblit GO installation.", folder));
} else {
diff --git a/src/com/gitblit/build/Build.java b/src/com/gitblit/build/Build.java index 19d80e78..3a9ed751 100644 --- a/src/com/gitblit/build/Build.java +++ b/src/com/gitblit/build/Build.java @@ -109,6 +109,20 @@ public class Build { downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.RUNTIME);
downloadFromApache(MavenObject.XZ, BuildType.RUNTIME);
+ //needed for selenium ui tests
+ downloadFromApacheToExtSelenium(MavenObject.SEL_API, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_FF, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_JAVA, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_REMOTE, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_SUPPORT, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.GUAVA, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.JSON, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_EXEC, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCLIENT, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCORE, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPMIME, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_LOGGING, BuildType.RUNTIME);
+
downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
}
@@ -148,6 +162,20 @@ public class Build { downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.COMPILETIME);
downloadFromApache(MavenObject.XZ, BuildType.COMPILETIME);
+ //needed for selenium ui tests
+ downloadFromApacheToExtSelenium(MavenObject.SEL_API, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_FF, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_JAVA, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_REMOTE, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_SUPPORT, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.GUAVA, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.JSON, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_EXEC, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCLIENT, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCORE, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPMIME, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_LOGGING, BuildType.COMPILETIME);
+
downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
@@ -217,7 +245,7 @@ public class Build { Properties properties = new Properties();
FileInputStream is = null;
try {
- is = new FileInputStream(Constants.PROPERTIES_FILE);
+ is = new FileInputStream(new File("distrib", Constants.PROPERTIES_FILE));
properties.load(is);
} catch (Throwable t) {
t.printStackTrace();
@@ -398,7 +426,7 @@ public class Build { * the maven object to download.
* @return
*/
- private static List<File> downloadFromMaven(String mavenRoot, MavenObject mo, BuildType type) {
+ private static List<File> downloadFromMaven(String mavenRoot, MavenObject mo, BuildType type, String targetFolder) {
List<File> downloads = new ArrayList<File>();
String[] jars = { "" };
if (BuildType.RUNTIME.equals(type)) {
@@ -407,9 +435,9 @@ public class Build { jars = new String[] { "-sources" };
}
for (String jar : jars) {
- File targetFile = mo.getLocalFile("ext", jar);
+ File targetFile = mo.getLocalFile(targetFolder, jar);
if ("-sources".equals(jar)) {
- File relocated = new File("ext/src", targetFile.getName());
+ File relocated = new File(targetFolder+"/src", targetFile.getName());
if (targetFile.exists()) {
// move -sources jar to ext/src folder
targetFile.renameTo(relocated);
@@ -502,6 +530,31 @@ public class Build { return downloads;
}
+ /**
+ * Download a file from the official Apache Maven repository.
+ *
+ * @param mo
+ * the maven object to download.
+ * @return
+ */
+ private static List<File> downloadFromApacheToExtSelenium(MavenObject mo,
+ BuildType type) {
+ return downloadFromMaven("http://repo1.maven.org/maven2/", mo, type,
+ "ext/seleniumhq");
+ }
+
+ /**
+ * Download a file from the official Apache Maven repository.
+ *
+ * @param mo
+ * the maven object to download.
+ * @return
+ */
+ private static List<File> downloadFromMaven(String mavenRoot,
+ MavenObject mo, BuildType type) {
+ return downloadFromMaven(mavenRoot, mo, type, "ext");
+ }
+
private static void removeObsoleteArtifacts(final MavenObject mo, final BuildType type, File folder) {
File [] removals = folder.listFiles(new FilenameFilter() {
@Override
@@ -675,17 +728,17 @@ public class Build { "");
public static final MavenObject JGIT = new MavenObject(
- "JGit", "org/eclipse/jgit", "org.eclipse.jgit", "2.1.0.201209190230-r",
+ "JGit", "org/eclipse/jgit", "org.eclipse.jgit", "2.2.0.201212191850-r",
1600000, 1565000, 3460000,
- "5e7296d21645a479a1054fc96f3ec8469cede137",
- "5f492aaeae1beda2a31d1efa182f5d34e76d7b77",
+ "97d0761b9dd618d1f9f6c16c35c3ddf045ba536c",
+ "08dcf9546f4d61e1b8a50df5da5513006023b64b",
"");
public static final MavenObject JGIT_HTTP = new MavenObject(
- "JGit", "org/eclipse/jgit", "org.eclipse.jgit.http.server", "2.1.0.201209190230-r",
+ "JGit", "org/eclipse/jgit", "org.eclipse.jgit.http.server", "2.2.0.201212191850-r",
68000, 62000, 110000,
- "0bd9e5801c246d6f8ad9268d18c45ca9915f9a50",
- "210c434c38ddcf2126af250018d5845ea41ff502",
+ "8ad4fc4fb9529d645249bb46ad7e54d98436cb65",
+ "3385cf294957d1d34c1270b468853aea347b36ca",
"");
public static final MavenObject JSCH = new MavenObject(
@@ -796,6 +849,59 @@ public class Build { "ecff5cb8b1189514c9d1d8d68eb77ac372e000c9",
"f95e32a5d2dd8da643c4419814415b9704312993", "");
+ public static final MavenObject SEL_JAVA = new MavenObject(
+ "selenium-java", "org/seleniumhq/selenium", "selenium-java",
+ "2.28.0", 984098, 0, 0,
+ "7606286989ac9cb942cc206d975ffe187c18d605", "4ede08d293dc153989a337cd0d31d26421433af5", "");
+
+ public static final MavenObject SEL_API = new MavenObject(
+ "selenium-api", "org/seleniumhq/selenium", "selenium-api",
+ "2.28.0", 984098, 0, 0,
+ "c4044c40fff65cd25135a5f443638a2b1ccaeac5", "35fc6ec0804ae32b16a56627e69bdcb69995c515", "");
+
+ public static final MavenObject SEL_REMOTE = new MavenObject(
+ "selenium-remote-driver", "org/seleniumhq/selenium",
+ "selenium-remote-driver", "2.28.0", 984098, 0, 0,
+ "c67f97cd94e02afec92b0ac881844febb4fc90be", "51a9c30de3c8c203cb7a474a10842443005a5fb4", "");
+ public static final MavenObject SEL_SUPPORT = new MavenObject(
+ "selenium-support", "org/seleniumhq/selenium",
+ "selenium-support", "2.28.0", 984098, 0, 0,
+ "caf68d6310425f583bc592c08e43066b35eb94f6", "ce3831a601f5f50fda2f4604decde409b6c735a7", "");
+ public static final MavenObject SEL_FF = new MavenObject(
+ "selenium-firefox-driver", "org/seleniumhq/selenium",
+ "selenium-firefox-driver", "2.28.0", 984098, 0, 0,
+ "a7c34e45dba39e65467b900aa67611aaa039692d", "aa8cd5fb49ca75a53d5b143406ea3d81ab3eddfd", "");
+
+ public static final MavenObject GUAVA = new MavenObject("guava",
+ "com/google/guava", "guava", "12.0", 984098, 0, 0,
+ "5bc66dd95b79db1e437eb08adba124a3e4088dc0", "f8b98e61865bed3c39b978ee3bf5c7fb990c4032", "");
+
+ public static final MavenObject JSON = new MavenObject("json",
+ "org/json", "json", "20080701", 984098, 0, 0,
+ "d652f102185530c93b66158b1859f35d45687258", "71bd54221e701df9d112bf9ba2918e13b0671f3a", "");
+
+ public static final MavenObject COMMONS_EXEC = new MavenObject(
+ "commons-exec", "org/apache/commons", "commons-exec", "1.1",
+ 984098, 0, 0, "07dfdf16fade726000564386825ed6d911a44ba1", "f60bea898e18b308099862e8634d589b06a8b0be",
+ "");
+
+ public static final MavenObject HTTPCORE = new MavenObject("httpcore",
+ "org/apache/httpcomponents", "httpcore", "4.2.1", 984098, 0, 0,
+ "2d503272bf0a8b5f92d64db78b4ba9abbaccc6fd", "3f6caf5334fa83607b82e2f32dd128a9d8a0ea5e", "");
+
+ public static final MavenObject HTTPMIME = new MavenObject("httpmime",
+ "org/apache/httpcomponents", "httpmime", "4.2.1", 984098, 0, 0,
+ "7c772bace9aa31a728c39a88c6ff66a7cd177e89", "", "4e453843ae47f1c2d70e2eb2c13c037de4b614c4");
+
+ public static final MavenObject HTTPCLIENT = new MavenObject(
+ "httpclient", "org/apache/httpcomponents", "httpclient",
+ "4.2.1", 984098, 0, 0,
+ "b69bd03af60bf487b3ae1209a644ecac587bf6fc", "6b27312b9c28b59aaeb6c21f3490045690c703d3", "");
+ public static final MavenObject COMMONS_LOGGING = new MavenObject(
+ "commons-logging", "commons-logging", "commons-logging",
+ "1.1.1", 984098, 0, 0,
+ "5043bfebc3db072ed80fbd362e7caf00e885d8ae", "f3f156cbff0e0fb0d64bfce31a352cce4a33bc19", "");
+
public final String name;
public final String group;
public final String artifact;
diff --git a/src/com/gitblit/build/BuildWebXml.java b/src/com/gitblit/build/BuildWebXml.java index 4fcc6e97..49a12ab2 100644 --- a/src/com/gitblit/build/BuildWebXml.java +++ b/src/com/gitblit/build/BuildWebXml.java @@ -60,44 +60,44 @@ public class BuildWebXml { }
private static void generateWebXml(Params params) throws Exception {
+ StringBuilder parameters = new StringBuilder();
// Read the current Gitblit properties
- BufferedReader propertiesReader = new BufferedReader(new FileReader(new File(
- params.propertiesFile)));
-
- Vector<Setting> settings = new Vector<Setting>();
- List<String> comments = new ArrayList<String>();
- String line = null;
- while ((line = propertiesReader.readLine()) != null) {
- if (line.length() == 0) {
- comments.clear();
- } else {
- if (line.charAt(0) == '#') {
- if (line.length() > 1) {
- comments.add(line.substring(1).trim());
- }
+ if (params.propertiesFile != null) {
+ BufferedReader propertiesReader = new BufferedReader(new FileReader(new File(
+ params.propertiesFile)));
+
+ Vector<Setting> settings = new Vector<Setting>();
+ List<String> comments = new ArrayList<String>();
+ String line = null;
+ while ((line = propertiesReader.readLine()) != null) {
+ if (line.length() == 0) {
+ comments.clear();
} else {
- String[] kvp = line.split("=", 2);
- String key = kvp[0].trim();
- if (!skipKey(key)) {
- Setting s = new Setting(key, kvp[1].trim(), comments);
- settings.add(s);
+ if (line.charAt(0) == '#') {
+ if (line.length() > 1) {
+ comments.add(line.substring(1).trim());
+ }
+ } else {
+ String[] kvp = line.split("=", 2);
+ String key = kvp[0].trim();
+ if (!skipKey(key)) {
+ Setting s = new Setting(key, kvp[1].trim(), comments);
+ settings.add(s);
+ }
+ comments.clear();
}
- comments.clear();
}
}
- }
- propertiesReader.close();
+ propertiesReader.close();
- StringBuilder parameters = new StringBuilder();
-
- for (Setting setting : settings) {
- for (String comment : setting.comments) {
- parameters.append(MessageFormat.format(COMMENT_PATTERN, comment));
+ for (Setting setting : settings) {
+ for (String comment : setting.comments) {
+ parameters.append(MessageFormat.format(COMMENT_PATTERN, comment));
+ }
+ parameters.append(MessageFormat.format(PARAM_PATTERN, setting.name,
+ StringUtils.escapeForHtml(setting.value, false)));
}
- parameters.append(MessageFormat.format(PARAM_PATTERN, setting.name,
- StringUtils.escapeForHtml(setting.value, false)));
}
-
// Read the prototype web.xml file
File webxml = new File(params.sourceFile);
char[] buffer = new char[(int) webxml.length()];
@@ -150,11 +150,11 @@ public class BuildWebXml { @Parameter(names = { "--sourceFile" }, description = "Source web.xml file", required = true)
public String sourceFile;
- @Parameter(names = { "--propertiesFile" }, description = "Properties settings file", required = true)
+ @Parameter(names = { "--propertiesFile" }, description = "Properties settings file")
public String propertiesFile;
@Parameter(names = { "--destinationFile" }, description = "Destination web.xml file", required = true)
public String destinationFile;
-
+
}
}
diff --git a/src/com/gitblit/client/EditRepositoryDialog.java b/src/com/gitblit/client/EditRepositoryDialog.java index 6f9ed525..8851de43 100644 --- a/src/com/gitblit/client/EditRepositoryDialog.java +++ b/src/com/gitblit/client/EditRepositoryDialog.java @@ -38,7 +38,6 @@ import java.util.Set; import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
-import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
@@ -117,7 +116,7 @@ public class EditRepositoryDialog extends JDialog { private JComboBox federationStrategy;
- private JComboBox ownerField;
+ private JPalette<String> ownersPalette;
private JComboBox headRefField;
@@ -126,7 +125,7 @@ public class EditRepositoryDialog extends JDialog { private JTextField gcThreshold;
private JComboBox maxActivityCommits;
-
+
private RegistrantPermissionsPanel usersPalette;
private JPalette<String> setsPalette;
@@ -207,7 +206,7 @@ public class EditRepositoryDialog extends JDialog { gcThreshold = new JTextField(8);
gcThreshold.setText(anRepository.gcThreshold);
- ownerField = new JComboBox();
+ ownersPalette = new JPalette<String>(true);
useTickets = new JCheckBox(Translation.get("gb.useTicketsDescription"),
anRepository.useTickets);
@@ -334,10 +333,10 @@ public class EditRepositoryDialog extends JDialog { usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
- JPanel northFieldsPanel = new JPanel(new GridLayout(0, 1, 0, 5));
- northFieldsPanel.add(newFieldPanel(Translation.get("gb.owner"), ownerField));
+ JPanel northFieldsPanel = new JPanel(new BorderLayout(0, 5));
+ northFieldsPanel.add(newFieldPanel(Translation.get("gb.owners"), ownersPalette), BorderLayout.NORTH);
northFieldsPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
- accessRestriction), BorderLayout.NORTH);
+ accessRestriction), BorderLayout.CENTER);
JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
northAccessPanel.add(northFieldsPanel, BorderLayout.NORTH);
@@ -556,8 +555,8 @@ public class EditRepositoryDialog extends JDialog { repository.name = rname;
repository.description = descriptionField.getText();
- repository.owner = ownerField.getSelectedItem() == null ? null
- : ownerField.getSelectedItem().toString();
+ repository.owners.clear();
+ repository.owners.addAll(ownersPalette.getSelections());
repository.HEAD = headRefField.getSelectedItem() == null ? null
: headRefField.getSelectedItem().toString();
repository.gcPeriod = (Integer) gcPeriod.getSelectedItem();
@@ -629,11 +628,8 @@ public class EditRepositoryDialog extends JDialog { this.allowNamed.setSelected(!authenticated);
}
- public void setUsers(String owner, List<String> all, List<RegistrantAccessPermission> permissions) {
- ownerField.setModel(new DefaultComboBoxModel(all.toArray()));
- if (!StringUtils.isEmpty(owner)) {
- ownerField.setSelectedItem(owner);
- }
+ public void setUsers(List<String> owners, List<String> all, List<RegistrantAccessPermission> permissions) {
+ ownersPalette.setObjects(all, owners);
usersPalette.setObjects(all, permissions);
}
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java index 1101cd60..cc7d58a6 100644 --- a/src/com/gitblit/client/GitblitClient.java +++ b/src/com/gitblit/client/GitblitClient.java @@ -162,7 +162,7 @@ public class GitblitClient implements Serializable { }
public boolean isOwner(RepositoryModel model) {
- return account != null && account.equalsIgnoreCase(model.owner);
+ return model.isOwner(account);
}
public String getURL(String action, String repository, String objectId) {
diff --git a/src/com/gitblit/client/IndicatorsRenderer.java b/src/com/gitblit/client/IndicatorsRenderer.java index 59ce6dd1..44b39d01 100644 --- a/src/com/gitblit/client/IndicatorsRenderer.java +++ b/src/com/gitblit/client/IndicatorsRenderer.java @@ -55,6 +55,8 @@ public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Ser private final ImageIcon federatedIcon;
private final ImageIcon forkIcon;
+
+ private final ImageIcon sparkleshareIcon;
public IndicatorsRenderer() {
super(new FlowLayout(FlowLayout.RIGHT, 1, 0));
@@ -67,6 +69,7 @@ public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Ser frozenIcon = new ImageIcon(getClass().getResource("/cold_16x16.png"));
federatedIcon = new ImageIcon(getClass().getResource("/federated_16x16.png"));
forkIcon = new ImageIcon(getClass().getResource("/commit_divide_16x16.png"));
+ sparkleshareIcon = new ImageIcon(getClass().getResource("/star_16x16.png"));
}
@Override
@@ -80,6 +83,11 @@ public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Ser if (value instanceof RepositoryModel) {
StringBuilder tooltip = new StringBuilder();
RepositoryModel model = (RepositoryModel) value;
+ if (model.isSparkleshared()) {
+ JLabel icon = new JLabel(sparkleshareIcon);
+ tooltip.append(Translation.get("gb.isSparkleshared")).append("<br/>");
+ add(icon);
+ }
if (model.isFork()) {
JLabel icon = new JLabel(forkIcon);
tooltip.append(Translation.get("gb.isFork")).append("<br/>");
diff --git a/src/com/gitblit/client/JPalette.java b/src/com/gitblit/client/JPalette.java index 4ead099e..a0c2b258 100644 --- a/src/com/gitblit/client/JPalette.java +++ b/src/com/gitblit/client/JPalette.java @@ -144,7 +144,7 @@ public class JPalette<T> extends JPanel { table.getColumn(table.getColumnName(0)).setCellRenderer(nameRenderer);
JScrollPane jsp = new JScrollPane(table);
- jsp.setPreferredSize(new Dimension(225, 175));
+ jsp.setPreferredSize(new Dimension(225, 160));
JPanel panel = new JPanel(new BorderLayout());
JLabel jlabel = new JLabel(label);
jlabel.setFont(jlabel.getFont().deriveFont(Font.BOLD));
diff --git a/src/com/gitblit/client/RepositoriesPanel.java b/src/com/gitblit/client/RepositoriesPanel.java index 769d33b8..64bde9b8 100644 --- a/src/com/gitblit/client/RepositoriesPanel.java +++ b/src/com/gitblit/client/RepositoriesPanel.java @@ -49,8 +49,8 @@ import javax.swing.table.TableRowSorter; import com.gitblit.Constants;
import com.gitblit.Constants.RpcRequest;
import com.gitblit.Keys;
-import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.FeedModel;
+import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.StringUtils;
@@ -453,7 +453,7 @@ public abstract class RepositoriesPanel extends JPanel { dialog.setLocationRelativeTo(RepositoriesPanel.this);
List<String> usernames = gitblit.getUsernames();
List<RegistrantAccessPermission> members = gitblit.getUserAccessPermissions(repository);
- dialog.setUsers(repository.owner, usernames, members);
+ dialog.setUsers(new ArrayList<String>(repository.owners), usernames, members);
dialog.setTeams(gitblit.getTeamnames(), gitblit.getTeamAccessPermissions(repository));
dialog.setRepositories(gitblit.getRepositories());
dialog.setFederationSets(gitblit.getFederationSets(), repository.federationSets);
diff --git a/src/com/gitblit/client/RepositoriesTableModel.java b/src/com/gitblit/client/RepositoriesTableModel.java index c3eaf6e5..6b295a4b 100644 --- a/src/com/gitblit/client/RepositoriesTableModel.java +++ b/src/com/gitblit/client/RepositoriesTableModel.java @@ -23,6 +23,7 @@ import java.util.List; import javax.swing.table.AbstractTableModel;
import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.ArrayUtils;
/**
* Table model of a list of repositories.
@@ -111,7 +112,7 @@ public class RepositoriesTableModel extends AbstractTableModel { case Description:
return model.description;
case Owner:
- return model.owner;
+ return ArrayUtils.toString(model.owners);
case Indicators:
return model;
case Last_Change:
diff --git a/src/com/gitblit/client/UsersPanel.java b/src/com/gitblit/client/UsersPanel.java index e14c0010..c53a5791 100644 --- a/src/com/gitblit/client/UsersPanel.java +++ b/src/com/gitblit/client/UsersPanel.java @@ -112,8 +112,8 @@ public abstract class UsersPanel extends JPanel { String name = table.getColumnName(UsersTableModel.Columns.Name.ordinal());
table.getColumn(name).setCellRenderer(nameRenderer);
- int w = 125;
- name = table.getColumnName(UsersTableModel.Columns.AccessLevel.ordinal());
+ int w = 130;
+ name = table.getColumnName(UsersTableModel.Columns.Type.ordinal());
table.getColumn(name).setMinWidth(w);
table.getColumn(name).setMaxWidth(w);
name = table.getColumnName(UsersTableModel.Columns.Teams.ordinal());
diff --git a/src/com/gitblit/client/UsersTableModel.java b/src/com/gitblit/client/UsersTableModel.java index b8ce45d4..439d5afb 100644 --- a/src/com/gitblit/client/UsersTableModel.java +++ b/src/com/gitblit/client/UsersTableModel.java @@ -36,7 +36,7 @@ public class UsersTableModel extends AbstractTableModel { List<UserModel> list;
enum Columns {
- Name, Display_Name, AccessLevel, Teams, Repositories;
+ Name, Display_Name, Type, Teams, Repositories;
@Override
public String toString() {
@@ -71,8 +71,8 @@ public class UsersTableModel extends AbstractTableModel { return Translation.get("gb.name");
case Display_Name:
return Translation.get("gb.displayName");
- case AccessLevel:
- return Translation.get("gb.accessLevel");
+ case Type:
+ return Translation.get("gb.type");
case Teams:
return Translation.get("gb.teamMemberships");
case Repositories:
@@ -101,11 +101,18 @@ public class UsersTableModel extends AbstractTableModel { return model.username;
case Display_Name:
return model.displayName;
- case AccessLevel:
+ case Type:
+ StringBuilder sb = new StringBuilder();
+ if (model.accountType != null) {
+ sb.append(model.accountType.name());
+ }
if (model.canAdmin()) {
- return "administrator";
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append("admin");
}
- return "";
+ return sb.toString();
case Teams:
return (model.teams == null || model.teams.size() == 0) ? "" : String
.valueOf(model.teams.size());
diff --git a/src/com/gitblit/fanout/FanoutClient.java b/src/com/gitblit/fanout/FanoutClient.java new file mode 100644 index 00000000..b9ace4be --- /dev/null +++ b/src/com/gitblit/fanout/FanoutClient.java @@ -0,0 +1,413 @@ +/*
+ * Copyright 2013 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.fanout;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Fanout client class.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutClient implements Runnable {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutClient.class);
+
+ private final int clientTimeout = 500;
+ private final int reconnectTimeout = 2000;
+ private final String host;
+ private final int port;
+ private final List<FanoutListener> listeners;
+
+ private String id;
+ private volatile Selector selector;
+ private volatile SocketChannel socketCh;
+ private Thread clientThread;
+
+ private final AtomicBoolean isConnected;
+ private final AtomicBoolean isRunning;
+ private final AtomicBoolean isAutomaticReconnect;
+ private final ByteBuffer writeBuffer;
+ private final ByteBuffer readBuffer;
+ private final CharsetDecoder decoder;
+
+ private final Set<String> subscriptions;
+ private boolean resubscribe;
+
+ public interface FanoutListener {
+ public void pong(Date timestamp);
+ public void announcement(String channel, String message);
+ }
+
+ public static class FanoutAdapter implements FanoutListener {
+ public void pong(Date timestamp) { }
+ public void announcement(String channel, String message) { }
+ }
+
+ public static void main(String args[]) throws Exception {
+ FanoutClient client = new FanoutClient("localhost", 2000);
+ client.addListener(new FanoutAdapter() {
+
+ @Override
+ public void pong(Date timestamp) {
+ System.out.println("Pong. " + timestamp);
+ }
+
+ @Override
+ public void announcement(String channel, String message) {
+ System.out.println(MessageFormat.format("Here ye, Here ye. {0} says {1}", channel, message));
+ }
+ });
+ client.start();
+
+ Thread.sleep(5000);
+ client.ping();
+ client.subscribe("james");
+ client.announce("james", "12345");
+ client.subscribe("c52f99d16eb5627877ae957df7ce1be102783bd5");
+
+ while (true) {
+ Thread.sleep(10000);
+ client.ping();
+ }
+ }
+
+ public FanoutClient(String host, int port) {
+ this.host = host;
+ this.port = port;
+ readBuffer = ByteBuffer.allocateDirect(FanoutConstants.BUFFER_LENGTH);
+ writeBuffer = ByteBuffer.allocateDirect(FanoutConstants.BUFFER_LENGTH);
+ decoder = Charset.forName(FanoutConstants.CHARSET).newDecoder();
+ listeners = Collections.synchronizedList(new ArrayList<FanoutListener>());
+ subscriptions = new LinkedHashSet<String>();
+ isRunning = new AtomicBoolean(false);
+ isConnected = new AtomicBoolean(false);
+ isAutomaticReconnect = new AtomicBoolean(true);
+ }
+
+ public void addListener(FanoutListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(FanoutListener listener) {
+ listeners.remove(listener);
+ }
+
+ public boolean isAutomaticReconnect() {
+ return isAutomaticReconnect.get();
+ }
+
+ public void setAutomaticReconnect(boolean value) {
+ isAutomaticReconnect.set(value);
+ }
+
+ public void ping() {
+ confirmConnection();
+ write("ping");
+ }
+
+ public void status() {
+ confirmConnection();
+ write("status");
+ }
+
+ public void subscribe(String channel) {
+ confirmConnection();
+ if (subscriptions.add(channel)) {
+ write("subscribe " + channel);
+ }
+ }
+
+ public void unsubscribe(String channel) {
+ confirmConnection();
+ if (subscriptions.remove(channel)) {
+ write("unsubscribe " + channel);
+ }
+ }
+
+ public void announce(String channel, String message) {
+ confirmConnection();
+ write("announce " + channel + " " + message);
+ }
+
+ private void confirmConnection() {
+ if (!isConnected()) {
+ throw new RuntimeException("Fanout client is disconnected!");
+ }
+ }
+
+ public boolean isConnected() {
+ return isRunning.get() && socketCh != null && isConnected.get();
+ }
+
+ /**
+ * Start client connection and return immediately.
+ */
+ public void start() {
+ if (isRunning.get()) {
+ logger.warn("Fanout client is already running");
+ return;
+ }
+ clientThread = new Thread(this, "Fanout client");
+ clientThread.start();
+ }
+
+ /**
+ * Start client connection and wait until it has connected.
+ */
+ public void startSynchronously() {
+ start();
+ while (!isConnected()) {
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ /**
+ * Stops client connection. This method returns when the connection has
+ * been completely shutdown.
+ */
+ public void stop() {
+ if (!isRunning.get()) {
+ logger.warn("Fanout client is not running");
+ return;
+ }
+ isRunning.set(false);
+ try {
+ if (clientThread != null) {
+ clientThread.join();
+ clientThread = null;
+ }
+ } catch (InterruptedException e1) {
+ }
+ }
+
+ @Override
+ public void run() {
+ resetState();
+
+ isRunning.set(true);
+ while (isRunning.get()) {
+ // (re)connect
+ if (socketCh == null) {
+ try {
+ InetAddress addr = InetAddress.getByName(host);
+ socketCh = SocketChannel.open(new InetSocketAddress(addr, port));
+ socketCh.configureBlocking(false);
+ selector = Selector.open();
+ id = FanoutConstants.getLocalSocketId(socketCh.socket());
+ socketCh.register(selector, SelectionKey.OP_READ);
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("failed to open client connection to {0}:{1,number,0}", host, port), e);
+ try {
+ Thread.sleep(reconnectTimeout);
+ } catch (InterruptedException x) {
+ }
+ continue;
+ }
+ }
+
+ // read/write
+ try {
+ selector.select(clientTimeout);
+
+ Iterator<SelectionKey> i = selector.selectedKeys().iterator();
+ while (i.hasNext()) {
+ SelectionKey key = i.next();
+ i.remove();
+
+ if (key.isReadable()) {
+ // read message
+ String content = read();
+ String[] lines = content.split("\n");
+ for (String reply : lines) {
+ logger.trace(MessageFormat.format("fanout client {0} received: {1}", id, reply));
+ if (!processReply(reply)) {
+ logger.error(MessageFormat.format("fanout client {0} received unknown message", id));
+ }
+ }
+ } else if (key.isWritable()) {
+ // resubscribe
+ if (resubscribe) {
+ resubscribe = false;
+ logger.info(MessageFormat.format("fanout client {0} re-subscribing to {1} channels", id, subscriptions.size()));
+ for (String subscription : subscriptions) {
+ write("subscribe " + subscription);
+ }
+ }
+ socketCh.register(selector, SelectionKey.OP_READ);
+ }
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout client {0} error: {1}", id, e.getMessage()));
+ closeChannel();
+ if (!isAutomaticReconnect.get()) {
+ isRunning.set(false);
+ continue;
+ }
+ }
+ }
+
+ closeChannel();
+ resetState();
+ }
+
+ protected void resetState() {
+ readBuffer.clear();
+ writeBuffer.clear();
+ isRunning.set(false);
+ isConnected.set(false);
+ }
+
+ private void closeChannel() {
+ try {
+ if (socketCh != null) {
+ socketCh.close();
+ socketCh = null;
+ selector.close();
+ selector = null;
+ isConnected.set(false);
+ }
+ } catch (IOException x) {
+ }
+ }
+
+ protected boolean processReply(String reply) {
+ String[] fields = reply.split("!", 2);
+ if (fields.length == 1) {
+ try {
+ long time = Long.parseLong(fields[0]);
+ Date date = new Date(time);
+ firePong(date);
+ } catch (Exception e) {
+ }
+ return true;
+ } else if (fields.length == 2) {
+ String channel = fields[0];
+ String message = fields[1];
+ if (FanoutConstants.CH_DEBUG.equals(channel)) {
+ // debug messages are for internal use
+ if (FanoutConstants.MSG_CONNECTED.equals(message)) {
+ isConnected.set(true);
+ resubscribe = subscriptions.size() > 0;
+ if (resubscribe) {
+ try {
+ // register for async resubscribe
+ socketCh.register(selector, SelectionKey.OP_WRITE);
+ } catch (Exception e) {
+ logger.error("an error occurred", e);
+ }
+ }
+ }
+ logger.debug(MessageFormat.format("fanout client {0} < {1}", id, reply));
+ } else {
+ fireAnnouncement(channel, message);
+ }
+ return true;
+ } else {
+ // unknown message
+ return false;
+ }
+ }
+
+ protected void firePong(Date timestamp) {
+ logger.info(MessageFormat.format("fanout client {0} < pong {1,date,yyyy-MM-dd HH:mm:ss}", id, timestamp));
+ for (FanoutListener listener : listeners) {
+ try {
+ listener.pong(timestamp);
+ } catch (Throwable t) {
+ logger.error("FanoutListener threw an exception!", t);
+ }
+ }
+ }
+ protected void fireAnnouncement(String channel, String message) {
+ logger.info(MessageFormat.format("fanout client {0} < announcement {1} {2}", id, channel, message));
+ for (FanoutListener listener : listeners) {
+ try {
+ listener.announcement(channel, message);
+ } catch (Throwable t) {
+ logger.error("FanoutListener threw an exception!", t);
+ }
+ }
+ }
+
+ protected synchronized String read() throws IOException {
+ readBuffer.clear();
+ long len = socketCh.read(readBuffer);
+
+ if (len == -1) {
+ logger.error(MessageFormat.format("fanout client {0} lost connection to {1}:{2,number,0}, end of stream", id, host, port));
+ socketCh.close();
+ return null;
+ } else {
+ readBuffer.flip();
+ String content = decoder.decode(readBuffer).toString();
+ readBuffer.clear();
+ return content;
+ }
+ }
+
+ protected synchronized boolean write(String message) {
+ try {
+ logger.info(MessageFormat.format("fanout client {0} > {1}", id, message));
+ byte [] bytes = message.getBytes(FanoutConstants.CHARSET);
+ writeBuffer.clear();
+ writeBuffer.put(bytes);
+ if (bytes[bytes.length - 1] != 0xa) {
+ writeBuffer.put((byte) 0xa);
+ }
+ writeBuffer.flip();
+
+ // loop until write buffer has been completely sent
+ long written = 0;
+ long toWrite = writeBuffer.remaining();
+ while (written != toWrite) {
+ written += socketCh.write(writeBuffer);
+ try {
+ Thread.sleep(10);
+ } catch (Exception x) {
+ }
+ }
+ return true;
+ } catch (IOException e) {
+ logger.error("fanout client {0} error: {1}", id, e.getMessage());
+ }
+ return false;
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutConstants.java b/src/com/gitblit/fanout/FanoutConstants.java new file mode 100644 index 00000000..6e6964c9 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutConstants.java @@ -0,0 +1,36 @@ +/*
+ * Copyright 2013 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.fanout;
+
+import java.net.Socket;
+
+public class FanoutConstants {
+
+ public final static String CHARSET = "ISO-8859-1";
+ public final static int BUFFER_LENGTH = 512;
+ public final static String CH_ALL = "all";
+ public final static String CH_DEBUG = "debug";
+ public final static String MSG_CONNECTED = "connected...";
+ public final static String MSG_BUSY = "busy";
+
+ public static String getRemoteSocketId(Socket socket) {
+ return socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
+ }
+
+ public static String getLocalSocketId(Socket socket) {
+ return socket.getInetAddress().getHostAddress() + ":" + socket.getLocalPort();
+ }
+}
diff --git a/src/com/gitblit/fanout/FanoutNioService.java b/src/com/gitblit/fanout/FanoutNioService.java new file mode 100644 index 00000000..65d022ab --- /dev/null +++ b/src/com/gitblit/fanout/FanoutNioService.java @@ -0,0 +1,332 @@ +/*
+ * Copyright 2013 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.fanout;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A single-thread NIO implementation of https://github.com/travisghansen/fanout
+ *
+ * This implementation uses channels and selectors, which are the Java analog of
+ * the Linux epoll mechanism used in the original fanout C code.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutNioService extends FanoutService {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutNioService.class);
+
+ private volatile ServerSocketChannel serviceCh;
+ private volatile Selector selector;
+
+ public static void main(String[] args) throws Exception {
+ FanoutNioService pubsub = new FanoutNioService(null, DEFAULT_PORT);
+ pubsub.setStrictRequestTermination(false);
+ pubsub.setAllowAllChannelAnnouncements(false);
+ pubsub.start();
+ }
+
+ /**
+ * Create a single-threaded fanout service.
+ *
+ * @param host
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutNioService(int port) {
+ this(null, port);
+ }
+
+ /**
+ * Create a single-threaded fanout service.
+ *
+ * @param bindInterface
+ * the ip address to bind for the service, may be null
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutNioService(String bindInterface, int port) {
+ super(bindInterface, port, "Fanout nio service");
+ }
+
+ @Override
+ protected boolean isConnected() {
+ return serviceCh != null;
+ }
+
+ @Override
+ protected boolean connect() {
+ if (serviceCh == null) {
+ try {
+ serviceCh = ServerSocketChannel.open();
+ serviceCh.configureBlocking(false);
+ serviceCh.socket().setReuseAddress(true);
+ serviceCh.socket().bind(host == null ? new InetSocketAddress(port) : new InetSocketAddress(host, port));
+ selector = Selector.open();
+ serviceCh.register(selector, SelectionKey.OP_ACCEPT);
+ logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
+ name, host == null ? "0.0.0.0" : host, port));
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to open {0} on {1}:{2,number,0}",
+ name, name, host == null ? "0.0.0.0" : host, port), e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void disconnect() {
+ try {
+ if (serviceCh != null) {
+ // close all active client connections
+ Map<String, SocketChannel> clients = getCurrentClientSockets();
+ for (Map.Entry<String, SocketChannel> client : clients.entrySet()) {
+ closeClientSocket(client.getKey(), client.getValue());
+ }
+
+ // close service socket channel
+ logger.debug(MessageFormat.format("closing {0} socket channel", name));
+ serviceCh.socket().close();
+ serviceCh.close();
+ serviceCh = null;
+ selector.close();
+ selector = null;
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to disconnect {0}", name), e);
+ }
+ }
+
+ @Override
+ protected void listen() throws IOException {
+ while (selector.select(serviceTimeout) > 0) {
+ Set<SelectionKey> keys = selector.selectedKeys();
+ Iterator<SelectionKey> keyItr = keys.iterator();
+ while (keyItr.hasNext()) {
+ SelectionKey key = (SelectionKey) keyItr.next();
+ if (key.isAcceptable()) {
+ // new fanout client connection
+ ServerSocketChannel sch = (ServerSocketChannel) key.channel();
+ try {
+ SocketChannel ch = sch.accept();
+ ch.configureBlocking(false);
+ configureClientSocket(ch.socket());
+
+ FanoutNioConnection connection = new FanoutNioConnection(ch);
+ addConnection(connection);
+
+ // register to send the queued message
+ ch.register(selector, SelectionKey.OP_WRITE, connection);
+ } catch (IOException e) {
+ logger.error("error accepting fanout connection", e);
+ }
+ } else if (key.isReadable()) {
+ // read fanout client request
+ SocketChannel ch = (SocketChannel) key.channel();
+ FanoutNioConnection connection = (FanoutNioConnection) key.attachment();
+ try {
+ connection.read(ch, isStrictRequestTermination());
+ int replies = 0;
+ Iterator<String> reqItr = connection.requestQueue.iterator();
+ while (reqItr.hasNext()) {
+ String req = reqItr.next();
+ String reply = processRequest(connection, req);
+ reqItr.remove();
+ if (reply != null) {
+ replies++;
+ }
+ }
+
+ if (replies > 0) {
+ // register to send the replies to requests
+ ch.register(selector, SelectionKey.OP_WRITE, connection);
+ } else {
+ // re-register for next read
+ ch.register(selector, SelectionKey.OP_READ, connection);
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout connection {0} error: {1}", connection.id, e.getMessage()));
+ removeConnection(connection);
+ closeClientSocket(connection.id, ch);
+ }
+ } else if (key.isWritable()) {
+ // asynchronous reply to fanout client request
+ SocketChannel ch = (SocketChannel) key.channel();
+ FanoutNioConnection connection = (FanoutNioConnection) key.attachment();
+ try {
+ connection.write(ch);
+
+ if (hasConnection(connection)) {
+ // register for next read
+ ch.register(selector, SelectionKey.OP_READ, connection);
+ } else {
+ // Connection was rejected due to load or
+ // some other reason. Close it.
+ closeClientSocket(connection.id, ch);
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout connection {0}: {1}", connection.id, e.getMessage()));
+ removeConnection(connection);
+ closeClientSocket(connection.id, ch);
+ }
+ }
+ keyItr.remove();
+ }
+ }
+ }
+
+ protected void closeClientSocket(String id, SocketChannel ch) {
+ try {
+ ch.close();
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout connection {0}", id), e);
+ }
+ }
+
+ protected void broadcast(Collection<FanoutServiceConnection> connections, String channel, String message) {
+ super.broadcast(connections, channel, message);
+
+ // register queued write
+ Map<String, SocketChannel> sockets = getCurrentClientSockets();
+ for (FanoutServiceConnection connection : connections) {
+ SocketChannel ch = sockets.get(connection.id);
+ if (ch == null) {
+ logger.warn(MessageFormat.format("fanout connection {0} has been disconnected", connection.id));
+ removeConnection(connection);
+ continue;
+ }
+ try {
+ ch.register(selector, SelectionKey.OP_WRITE, connection);
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to register write op for fanout connection {0}", connection.id));
+ }
+ }
+ }
+
+ protected Map<String, SocketChannel> getCurrentClientSockets() {
+ Map<String, SocketChannel> sockets = new HashMap<String, SocketChannel>();
+ for (SelectionKey key : selector.keys()) {
+ if (key.channel() instanceof SocketChannel) {
+ SocketChannel ch = (SocketChannel) key.channel();
+ String id = FanoutConstants.getRemoteSocketId(ch.socket());
+ sockets.put(id, ch);
+ }
+ }
+ return sockets;
+ }
+
+ /**
+ * FanoutNioConnection handles reading/writing messages from a remote fanout
+ * connection.
+ *
+ * @author James Moger
+ *
+ */
+ static class FanoutNioConnection extends FanoutServiceConnection {
+ final ByteBuffer readBuffer;
+ final ByteBuffer writeBuffer;
+ final List<String> requestQueue;
+ final List<String> replyQueue;
+ final CharsetDecoder decoder;
+
+ FanoutNioConnection(SocketChannel ch) {
+ super(ch.socket());
+ readBuffer = ByteBuffer.allocate(FanoutConstants.BUFFER_LENGTH);
+ writeBuffer = ByteBuffer.allocate(FanoutConstants.BUFFER_LENGTH);
+ requestQueue = new ArrayList<String>();
+ replyQueue = new ArrayList<String>();
+ decoder = Charset.forName(FanoutConstants.CHARSET).newDecoder();
+ }
+
+ protected void read(SocketChannel ch, boolean strictRequestTermination) throws CharacterCodingException, IOException {
+ long bytesRead = 0;
+ readBuffer.clear();
+ bytesRead = ch.read(readBuffer);
+ readBuffer.flip();
+ if (bytesRead == -1) {
+ throw new IOException("lost client connection, end of stream");
+ }
+ if (readBuffer.limit() == 0) {
+ return;
+ }
+ CharBuffer cbuf = decoder.decode(readBuffer);
+ String req = cbuf.toString();
+ String [] lines = req.split(strictRequestTermination ? "\n" : "\n|\r");
+ requestQueue.addAll(Arrays.asList(lines));
+ }
+
+ protected void write(SocketChannel ch) throws IOException {
+ Iterator<String> itr = replyQueue.iterator();
+ while (itr.hasNext()) {
+ String reply = itr.next();
+ writeBuffer.clear();
+ logger.debug(MessageFormat.format("fanout reply to {0}: {1}", id, reply));
+ byte [] bytes = reply.getBytes(FanoutConstants.CHARSET);
+ writeBuffer.put(bytes);
+ if (bytes[bytes.length - 1] != 0xa) {
+ writeBuffer.put((byte) 0xa);
+ }
+ writeBuffer.flip();
+
+ // loop until write buffer has been completely sent
+ int written = 0;
+ int toWrite = writeBuffer.remaining();
+ while (written != toWrite) {
+ written += ch.write(writeBuffer);
+ try {
+ Thread.sleep(10);
+ } catch (Exception x) {
+ }
+ }
+ itr.remove();
+ }
+ writeBuffer.clear();
+ }
+
+ @Override
+ protected void reply(String content) throws IOException {
+ // queue the reply
+ // replies are transmitted asynchronously from the requests
+ replyQueue.add(content);
+ }
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutService.java b/src/com/gitblit/fanout/FanoutService.java new file mode 100644 index 00000000..cbfd8a24 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutService.java @@ -0,0 +1,563 @@ +/*
+ * Copyright 2013 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.fanout;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class for Fanout service implementations.
+ *
+ * Subclass implementations can be used as a Sparkleshare PubSub notification
+ * server. This allows Sparkleshare to be used in conjunction with Gitblit
+ * behind a corporate firewall that restricts or prohibits client internet access
+ * to the default Sparkleshare PubSub server: notifications.sparkleshare.org
+ *
+ * @author James Moger
+ *
+ */
+public abstract class FanoutService implements Runnable {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutService.class);
+
+ public final static int DEFAULT_PORT = 17000;
+
+ protected final static int serviceTimeout = 5000;
+
+ protected final String host;
+ protected final int port;
+ protected final String name;
+
+ private Thread serviceThread;
+
+ private final Map<String, FanoutServiceConnection> connections;
+ private final Map<String, Set<FanoutServiceConnection>> subscriptions;
+
+ protected final AtomicBoolean isRunning;
+ private final AtomicBoolean strictRequestTermination;
+ private final AtomicBoolean allowAllChannelAnnouncements;
+ private final AtomicInteger concurrentConnectionLimit;
+
+ private final Date bootDate;
+ private final AtomicLong rejectedConnectionCount;
+ private final AtomicInteger peakConnectionCount;
+ private final AtomicLong totalConnections;
+ private final AtomicLong totalAnnouncements;
+ private final AtomicLong totalMessages;
+ private final AtomicLong totalSubscribes;
+ private final AtomicLong totalUnsubscribes;
+ private final AtomicLong totalPings;
+
+ protected FanoutService(String host, int port, String name) {
+ this.host = host;
+ this.port = port;
+ this.name = name;
+
+ connections = new ConcurrentHashMap<String, FanoutServiceConnection>();
+ subscriptions = new ConcurrentHashMap<String, Set<FanoutServiceConnection>>();
+ subscriptions.put(FanoutConstants.CH_ALL, new ConcurrentSkipListSet<FanoutServiceConnection>());
+
+ isRunning = new AtomicBoolean(false);
+ strictRequestTermination = new AtomicBoolean(false);
+ allowAllChannelAnnouncements = new AtomicBoolean(false);
+ concurrentConnectionLimit = new AtomicInteger(0);
+
+ bootDate = new Date();
+ rejectedConnectionCount = new AtomicLong(0);
+ peakConnectionCount = new AtomicInteger(0);
+ totalConnections = new AtomicLong(0);
+ totalAnnouncements = new AtomicLong(0);
+ totalMessages = new AtomicLong(0);
+ totalSubscribes = new AtomicLong(0);
+ totalUnsubscribes = new AtomicLong(0);
+ totalPings = new AtomicLong(0);
+ }
+
+ /*
+ * Abstract methods
+ */
+
+ protected abstract boolean isConnected();
+
+ protected abstract boolean connect();
+
+ protected abstract void listen() throws IOException;
+
+ protected abstract void disconnect();
+
+ /**
+ * Returns true if the service requires \n request termination.
+ *
+ * @return true if request requires \n termination
+ */
+ public boolean isStrictRequestTermination() {
+ return strictRequestTermination.get();
+ }
+
+ /**
+ * Control the termination of fanout requests. If true, fanout requests must
+ * be terminated with \n. If false, fanout requests may be terminated with
+ * \n, \r, \r\n, or \n\r. This is useful for debugging with a telnet client.
+ *
+ * @param isStrictTermination
+ */
+ public void setStrictRequestTermination(boolean isStrictTermination) {
+ strictRequestTermination.set(isStrictTermination);
+ }
+
+ /**
+ * Returns the maximum allowable concurrent fanout connections.
+ *
+ * @return the maximum allowable concurrent connection count
+ */
+ public int getConcurrentConnectionLimit() {
+ return concurrentConnectionLimit.get();
+ }
+
+ /**
+ * Sets the maximum allowable concurrent fanout connection count.
+ *
+ * @param value
+ */
+ public void setConcurrentConnectionLimit(int value) {
+ concurrentConnectionLimit.set(value);
+ }
+
+ /**
+ * Returns true if connections are allowed to announce on the all channel.
+ *
+ * @return true if connections are allowed to announce on the all channel
+ */
+ public boolean allowAllChannelAnnouncements() {
+ return allowAllChannelAnnouncements.get();
+ }
+
+ /**
+ * Allows/prohibits connections from announcing on the ALL channel.
+ *
+ * @param value
+ */
+ public void setAllowAllChannelAnnouncements(boolean value) {
+ allowAllChannelAnnouncements.set(value);
+ }
+
+ /**
+ * Returns the current connections
+ *
+ * @param channel
+ * @return map of current connections keyed by their id
+ */
+ public Map<String, FanoutServiceConnection> getCurrentConnections() {
+ return connections;
+ }
+
+ /**
+ * Returns all subscriptions
+ *
+ * @return map of current subscriptions keyed by channel name
+ */
+ public Map<String, Set<FanoutServiceConnection>> getCurrentSubscriptions() {
+ return subscriptions;
+ }
+
+ /**
+ * Returns the subscriptions for the specified channel
+ *
+ * @param channel
+ * @return set of subscribed connections for the specified channel
+ */
+ public Set<FanoutServiceConnection> getCurrentSubscriptions(String channel) {
+ return subscriptions.get(channel);
+ }
+
+ /**
+ * Returns the runtime statistics object for this service.
+ *
+ * @return stats
+ */
+ public FanoutStats getStatistics() {
+ FanoutStats stats = new FanoutStats();
+
+ // settings
+ stats.allowAllChannelAnnouncements = allowAllChannelAnnouncements();
+ stats.concurrentConnectionLimit = getConcurrentConnectionLimit();
+ stats.strictRequestTermination = isStrictRequestTermination();
+
+ // runtime stats
+ stats.bootDate = bootDate;
+ stats.rejectedConnectionCount = rejectedConnectionCount.get();
+ stats.peakConnectionCount = peakConnectionCount.get();
+ stats.totalConnections = totalConnections.get();
+ stats.totalAnnouncements = totalAnnouncements.get();
+ stats.totalMessages = totalMessages.get();
+ stats.totalSubscribes = totalSubscribes.get();
+ stats.totalUnsubscribes = totalUnsubscribes.get();
+ stats.totalPings = totalPings.get();
+ stats.currentConnections = connections.size();
+ stats.currentChannels = subscriptions.size();
+ stats.currentSubscriptions = subscriptions.size() * connections.size();
+ return stats;
+ }
+
+ /**
+ * Returns true if the service is ready.
+ *
+ * @return true, if the service is ready
+ */
+ public boolean isReady() {
+ if (isRunning.get()) {
+ return isConnected();
+ }
+ return false;
+ }
+
+ /**
+ * Start the Fanout service thread and immediatel return.
+ *
+ */
+ public void start() {
+ if (isRunning.get()) {
+ logger.warn(MessageFormat.format("{0} is already running", name));
+ return;
+ }
+ serviceThread = new Thread(this);
+ serviceThread.setName(MessageFormat.format("{0} {1}:{2,number,0}", name, host == null ? "all" : host, port));
+ serviceThread.start();
+ }
+
+ /**
+ * Start the Fanout service thread and wait until it is accepting connections.
+ *
+ */
+ public void startSynchronously() {
+ start();
+ while (!isReady()) {
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ /**
+ * Stop the Fanout service. This method returns when the service has been
+ * completely shutdown.
+ */
+ public void stop() {
+ if (!isRunning.get()) {
+ logger.warn(MessageFormat.format("{0} is not running", name));
+ return;
+ }
+ logger.info(MessageFormat.format("stopping {0}...", name));
+ isRunning.set(false);
+ try {
+ if (serviceThread != null) {
+ serviceThread.join();
+ serviceThread = null;
+ }
+ } catch (InterruptedException e1) {
+ logger.error("", e1);
+ }
+ logger.info(MessageFormat.format("stopped {0}", name));
+ }
+
+ /**
+ * Main execution method of the service
+ */
+ @Override
+ public final void run() {
+ disconnect();
+ resetState();
+ isRunning.set(true);
+ while (isRunning.get()) {
+ if (connect()) {
+ try {
+ listen();
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("error processing {0}", name), e);
+ isRunning.set(false);
+ }
+ } else {
+ try {
+ Thread.sleep(serviceTimeout);
+ } catch (InterruptedException x) {
+ }
+ }
+ }
+ disconnect();
+ resetState();
+ }
+
+ protected void resetState() {
+ // reset state data
+ connections.clear();
+ subscriptions.clear();
+ rejectedConnectionCount.set(0);
+ peakConnectionCount.set(0);
+ totalConnections.set(0);
+ totalAnnouncements.set(0);
+ totalMessages.set(0);
+ totalSubscribes.set(0);
+ totalUnsubscribes.set(0);
+ totalPings.set(0);
+ }
+
+ /**
+ * Configure the client connection socket.
+ *
+ * @param socket
+ * @throws SocketException
+ */
+ protected void configureClientSocket(Socket socket) throws SocketException {
+ socket.setKeepAlive(true);
+ socket.setSoLinger(true, 0); // immediately discard any remaining data
+ }
+
+ /**
+ * Add the connection to the connections map.
+ *
+ * @param connection
+ * @return false if the connection was rejected due to too many concurrent
+ * connections
+ */
+ protected boolean addConnection(FanoutServiceConnection connection) {
+ int limit = getConcurrentConnectionLimit();
+ if (limit > 0 && connections.size() > limit) {
+ logger.info(MessageFormat.format("hit {0,number,0} connection limit, rejecting fanout connection", concurrentConnectionLimit));
+ increment(rejectedConnectionCount);
+ connection.busy();
+ return false;
+ }
+
+ // add the connection to our map
+ connections.put(connection.id, connection);
+
+ // track peak number of concurrent connections
+ if (connections.size() > peakConnectionCount.get()) {
+ peakConnectionCount.set(connections.size());
+ }
+
+ logger.info("fanout new connection " + connection.id);
+ connection.connected();
+ return true;
+ }
+
+ /**
+ * Remove the connection from the connections list and from subscriptions.
+ *
+ * @param connection
+ */
+ protected void removeConnection(FanoutServiceConnection connection) {
+ connections.remove(connection.id);
+ Iterator<Map.Entry<String, Set<FanoutServiceConnection>>> itr = subscriptions.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry<String, Set<FanoutServiceConnection>> entry = itr.next();
+ Set<FanoutServiceConnection> subscriptions = entry.getValue();
+ subscriptions.remove(connection);
+ if (!FanoutConstants.CH_ALL.equals(entry.getKey())) {
+ if (subscriptions.size() == 0) {
+ itr.remove();
+ logger.info(MessageFormat.format("fanout remove channel {0}, no subscribers", entry.getKey()));
+ }
+ }
+ }
+ logger.info(MessageFormat.format("fanout connection {0} removed", connection.id));
+ }
+
+ /**
+ * Tests to see if the connection is being monitored by the service.
+ *
+ * @param connection
+ * @return true if the service is monitoring the connection
+ */
+ protected boolean hasConnection(FanoutServiceConnection connection) {
+ return connections.containsKey(connection.id);
+ }
+
+ /**
+ * Reply to a connection on the specified channel.
+ *
+ * @param connection
+ * @param channel
+ * @param message
+ * @return the reply
+ */
+ protected String reply(FanoutServiceConnection connection, String channel, String message) {
+ if (channel != null && channel.length() > 0) {
+ increment(totalMessages);
+ }
+ return connection.reply(channel, message);
+ }
+
+ /**
+ * Service method to broadcast a message to all connections.
+ *
+ * @param message
+ */
+ public void broadcastAll(String message) {
+ broadcast(connections.values(), FanoutConstants.CH_ALL, message);
+ increment(totalAnnouncements);
+ }
+
+ /**
+ * Service method to broadcast a message to connections subscribed to the
+ * channel.
+ *
+ * @param message
+ */
+ public void broadcast(String channel, String message) {
+ List<FanoutServiceConnection> connections = new ArrayList<FanoutServiceConnection>(subscriptions.get(channel));
+ broadcast(connections, channel, message);
+ increment(totalAnnouncements);
+ }
+
+ /**
+ * Broadcast a message to connections subscribed to the specified channel.
+ *
+ * @param connections
+ * @param channel
+ * @param message
+ */
+ protected void broadcast(Collection<FanoutServiceConnection> connections, String channel, String message) {
+ for (FanoutServiceConnection connection : connections) {
+ reply(connection, channel, message);
+ }
+ }
+
+ /**
+ * Process an incoming Fanout request.
+ *
+ * @param connection
+ * @param req
+ * @return the reply to the request, may be null
+ */
+ protected String processRequest(FanoutServiceConnection connection, String req) {
+ logger.info(MessageFormat.format("fanout request from {0}: {1}", connection.id, req));
+ String[] fields = req.split(" ", 3);
+ String action = fields[0];
+ String channel = fields.length >= 2 ? fields[1] : null;
+ String message = fields.length >= 3 ? fields[2] : null;
+ try {
+ return processRequest(connection, action, channel, message);
+ } catch (IllegalArgumentException e) {
+ // invalid action
+ logger.error(MessageFormat.format("fanout connection {0} requested invalid action {1}", connection.id, action));
+ logger.error(asHexArray(req));
+ }
+ return null;
+ }
+
+ /**
+ * Process the Fanout request.
+ *
+ * @param connection
+ * @param action
+ * @param channel
+ * @param message
+ * @return the reply to the request, may be null
+ * @throws IllegalArgumentException
+ */
+ protected String processRequest(FanoutServiceConnection connection, String action, String channel, String message) throws IllegalArgumentException {
+ if ("ping".equals(action)) {
+ // ping
+ increment(totalPings);
+ return reply(connection, null, "" + System.currentTimeMillis());
+ } else if ("info".equals(action)) {
+ // info
+ String info = getStatistics().info();
+ return reply(connection, null, info);
+ } else if ("announce".equals(action)) {
+ // announcement
+ if (!allowAllChannelAnnouncements.get() && FanoutConstants.CH_ALL.equals(channel)) {
+ // prohibiting connection-sourced all announcements
+ logger.warn(MessageFormat.format("fanout connection {0} attempted to announce {1} on ALL channel", connection.id, message));
+ } else if ("debug".equals(channel)) {
+ // prohibiting connection-sourced debug announcements
+ logger.warn(MessageFormat.format("fanout connection {0} attempted to announce {1} on DEBUG channel", connection.id, message));
+ } else {
+ // acceptable announcement
+ List<FanoutServiceConnection> connections = new ArrayList<FanoutServiceConnection>(subscriptions.get(channel));
+ connections.remove(connection); // remove announcer
+ broadcast(connections, channel, message);
+ increment(totalAnnouncements);
+ }
+ } else if ("subscribe".equals(action)) {
+ // subscribe
+ if (!subscriptions.containsKey(channel)) {
+ logger.info(MessageFormat.format("fanout new channel {0}", channel));
+ subscriptions.put(channel, new ConcurrentSkipListSet<FanoutServiceConnection>());
+ }
+ subscriptions.get(channel).add(connection);
+ logger.debug(MessageFormat.format("fanout connection {0} subscribed to channel {1}", connection.id, channel));
+ increment(totalSubscribes);
+ } else if ("unsubscribe".equals(action)) {
+ // unsubscribe
+ if (subscriptions.containsKey(channel)) {
+ subscriptions.get(channel).remove(connection);
+ if (subscriptions.get(channel).size() == 0) {
+ subscriptions.remove(channel);
+ }
+ increment(totalUnsubscribes);
+ }
+ } else {
+ // invalid action
+ throw new IllegalArgumentException(action);
+ }
+ return null;
+ }
+
+ private String asHexArray(String req) {
+ StringBuilder sb = new StringBuilder();
+ for (char c : req.toCharArray()) {
+ sb.append(Integer.toHexString(c)).append(' ');
+ }
+ return "[ " + sb.toString().trim() + " ]";
+ }
+
+ /**
+ * Increment a long and prevent negative rollover.
+ *
+ * @param counter
+ */
+ private void increment(AtomicLong counter) {
+ long v = counter.incrementAndGet();
+ if (v < 0) {
+ counter.set(0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutServiceConnection.java b/src/com/gitblit/fanout/FanoutServiceConnection.java new file mode 100644 index 00000000..f7f2c959 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutServiceConnection.java @@ -0,0 +1,105 @@ +/*
+ * Copyright 2013 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.fanout;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * FanoutServiceConnection handles reading/writing messages from a remote fanout
+ * connection.
+ *
+ * @author James Moger
+ *
+ */
+public abstract class FanoutServiceConnection implements Comparable<FanoutServiceConnection> {
+
+ private static final Logger logger = LoggerFactory.getLogger(FanoutServiceConnection.class);
+
+ public final String id;
+
+ protected FanoutServiceConnection(Socket socket) {
+ this.id = FanoutConstants.getRemoteSocketId(socket);
+ }
+
+ protected abstract void reply(String content) throws IOException;
+
+ /**
+ * Send the connection a debug channel connected message.
+ *
+ * @param message
+ */
+ protected void connected() {
+ reply(FanoutConstants.CH_DEBUG, FanoutConstants.MSG_CONNECTED);
+ }
+
+ /**
+ * Send the connection a debug channel busy message.
+ *
+ * @param message
+ */
+ protected void busy() {
+ reply(FanoutConstants.CH_DEBUG, FanoutConstants.MSG_BUSY);
+ }
+
+ /**
+ * Send the connection a message for the specified channel.
+ *
+ * @param channel
+ * @param message
+ * @return the reply
+ */
+ protected String reply(String channel, String message) {
+ String content;
+ if (channel != null) {
+ content = channel + "!" + message;
+ } else {
+ content = message;
+ }
+ try {
+ reply(content);
+ } catch (Exception e) {
+ logger.error("failed to reply to fanout connection " + id, e);
+ }
+ return content;
+ }
+
+ @Override
+ public int compareTo(FanoutServiceConnection c) {
+ return id.compareTo(c.id);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof FanoutServiceConnection) {
+ return id.equals(((FanoutServiceConnection) o).id);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return id;
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutSocketService.java b/src/com/gitblit/fanout/FanoutSocketService.java new file mode 100644 index 00000000..07c18f90 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutSocketService.java @@ -0,0 +1,234 @@ +/*
+ * Copyright 2013 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.fanout;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.text.MessageFormat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A multi-threaded socket implementation of https://github.com/travisghansen/fanout
+ *
+ * This implementation creates a master acceptor thread which accepts incoming
+ * fanout connections and then spawns a daemon thread for each accepted connection.
+ * If there are 100 concurrent fanout connections, there are 101 threads.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutSocketService extends FanoutService {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutSocketService.class);
+
+ private volatile ServerSocket serviceSocket;
+
+ public static void main(String[] args) throws Exception {
+ FanoutSocketService pubsub = new FanoutSocketService(null, DEFAULT_PORT);
+ pubsub.setStrictRequestTermination(false);
+ pubsub.setAllowAllChannelAnnouncements(false);
+ pubsub.start();
+ }
+
+ /**
+ * Create a multi-threaded fanout service.
+ *
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutSocketService(int port) {
+ this(null, port);
+ }
+
+ /**
+ * Create a multi-threaded fanout service.
+ *
+ * @param bindInterface
+ * the ip address to bind for the service, may be null
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutSocketService(String bindInterface, int port) {
+ super(bindInterface, port, "Fanout socket service");
+ }
+
+ @Override
+ protected boolean isConnected() {
+ return serviceSocket != null;
+ }
+
+ @Override
+ protected boolean connect() {
+ if (serviceSocket == null) {
+ try {
+ serviceSocket = new ServerSocket();
+ serviceSocket.setReuseAddress(true);
+ serviceSocket.setSoTimeout(serviceTimeout);
+ serviceSocket.bind(host == null ? new InetSocketAddress(port) : new InetSocketAddress(host, port));
+ logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
+ name, host == null ? "0.0.0.0" : host, serviceSocket.getLocalPort()));
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to open {0} on {1}:{2,number,0}",
+ name, host == null ? "0.0.0.0" : host, port), e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void disconnect() {
+ try {
+ if (serviceSocket != null) {
+ logger.debug(MessageFormat.format("closing {0} server socket", name));
+ serviceSocket.close();
+ serviceSocket = null;
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to disconnect {0}", name), e);
+ }
+ }
+
+ /**
+ * This accepts incoming fanout connections and spawns connection threads.
+ */
+ @Override
+ protected void listen() throws IOException {
+ try {
+ Socket socket;
+ socket = serviceSocket.accept();
+ configureClientSocket(socket);
+
+ FanoutSocketConnection connection = new FanoutSocketConnection(socket);
+
+ if (addConnection(connection)) {
+ // spawn connection daemon thread
+ Thread connectionThread = new Thread(connection);
+ connectionThread.setDaemon(true);
+ connectionThread.setName("Fanout " + connection.id);
+ connectionThread.start();
+ } else {
+ // synchronously close the connection and remove it
+ removeConnection(connection);
+ connection.closeConnection();
+ connection = null;
+ }
+ } catch (SocketTimeoutException e) {
+ // ignore accept timeout exceptions
+ }
+ }
+
+ /**
+ * FanoutSocketConnection handles reading/writing messages from a remote fanout
+ * connection.
+ *
+ * @author James Moger
+ *
+ */
+ class FanoutSocketConnection extends FanoutServiceConnection implements Runnable {
+ Socket socket;
+
+ FanoutSocketConnection(Socket socket) {
+ super(socket);
+ this.socket = socket;
+ }
+
+ /**
+ * Connection thread read/write method.
+ */
+ @Override
+ public void run() {
+ try {
+ StringBuilder sb = new StringBuilder();
+ BufferedInputStream is = new BufferedInputStream(socket.getInputStream());
+ byte[] buffer = new byte[FanoutConstants.BUFFER_LENGTH];
+ int len = 0;
+ while (true) {
+ while (is.available() > 0) {
+ len = is.read(buffer);
+ for (int i = 0; i < len; i++) {
+ byte b = buffer[i];
+ if (b == 0xa || (!isStrictRequestTermination() && b == 0xd)) {
+ String req = sb.toString();
+ sb.setLength(0);
+ if (req.length() > 0) {
+ // ignore empty request strings
+ processRequest(this, req);
+ }
+ } else {
+ sb.append((char) b);
+ }
+ }
+ }
+
+ if (!isRunning.get()) {
+ // service has stopped, terminate client connection
+ break;
+ } else {
+ Thread.sleep(500);
+ }
+ }
+ } catch (Throwable t) {
+ if (t instanceof SocketException) {
+ logger.error(MessageFormat.format("fanout connection {0}: {1}", id, t.getMessage()));
+ } else if (t instanceof SocketTimeoutException) {
+ logger.error(MessageFormat.format("fanout connection {0}: {1}", id, t.getMessage()));
+ } else {
+ logger.error(MessageFormat.format("exception while handling fanout connection {0}", id), t);
+ }
+ } finally {
+ closeConnection();
+ }
+
+ logger.info(MessageFormat.format("thread for fanout connection {0} is finished", id));
+ }
+
+ @Override
+ protected void reply(String content) throws IOException {
+ // synchronously send reply
+ logger.debug(MessageFormat.format("fanout reply to {0}: {1}", id, content));
+ OutputStream os = socket.getOutputStream();
+ byte [] bytes = content.getBytes(FanoutConstants.CHARSET);
+ os.write(bytes);
+ if (bytes[bytes.length - 1] != 0xa) {
+ os.write(0xa);
+ }
+ os.flush();
+ }
+
+ protected void closeConnection() {
+ // close the connection socket
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ socket = null;
+
+ // remove this connection from the service
+ removeConnection(this);
+ }
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutStats.java b/src/com/gitblit/fanout/FanoutStats.java new file mode 100644 index 00000000..b06884d3 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutStats.java @@ -0,0 +1,98 @@ +/*
+ * Copyright 2013 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.fanout;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Date;
+
+/**
+ * Encapsulates the runtime stats of a fanout service.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutStats implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public long concurrentConnectionLimit;
+ public boolean allowAllChannelAnnouncements;
+ public boolean strictRequestTermination;
+
+ public Date bootDate;
+ public long rejectedConnectionCount;
+ public int peakConnectionCount;
+ public long currentChannels;
+ public long currentSubscriptions;
+ public long currentConnections;
+ public long totalConnections;
+ public long totalAnnouncements;
+ public long totalMessages;
+ public long totalSubscribes;
+ public long totalUnsubscribes;
+ public long totalPings;
+
+ public String info() {
+ int i = 0;
+ StringBuilder sb = new StringBuilder();
+ sb.append(infoStr(i++, "boot date"));
+ sb.append(infoStr(i++, "strict request termination"));
+ sb.append(infoStr(i++, "allow connection \"all\" announcements"));
+ sb.append(infoInt(i++, "concurrent connection limit"));
+ sb.append(infoInt(i++, "concurrent limit rejected connections"));
+ sb.append(infoInt(i++, "peak connections"));
+ sb.append(infoInt(i++, "current connections"));
+ sb.append(infoInt(i++, "current channels"));
+ sb.append(infoInt(i++, "current subscriptions"));
+ sb.append(infoInt(i++, "user-requested subscriptions"));
+ sb.append(infoInt(i++, "total connections"));
+ sb.append(infoInt(i++, "total announcements"));
+ sb.append(infoInt(i++, "total messages"));
+ sb.append(infoInt(i++, "total subscribes"));
+ sb.append(infoInt(i++, "total unsubscribes"));
+ sb.append(infoInt(i++, "total pings"));
+ String template = sb.toString();
+
+ String info = MessageFormat.format(template,
+ bootDate.toString(),
+ Boolean.toString(strictRequestTermination),
+ Boolean.toString(allowAllChannelAnnouncements),
+ concurrentConnectionLimit,
+ rejectedConnectionCount,
+ peakConnectionCount,
+ currentConnections,
+ currentChannels,
+ currentSubscriptions,
+ currentSubscriptions == 0 ? 0 : (currentSubscriptions - currentConnections),
+ totalConnections,
+ totalAnnouncements,
+ totalMessages,
+ totalSubscribes,
+ totalUnsubscribes,
+ totalPings);
+ return info;
+ }
+
+ private String infoStr(int index, String label) {
+ return label + ": {" + index + "}\n";
+ }
+
+ private String infoInt(int index, String label) {
+ return label + ": {" + index + ",number,0}\n";
+ }
+
+}
diff --git a/src/com/gitblit/models/Activity.java b/src/com/gitblit/models/Activity.java index 771c8a1a..59405c7f 100644 --- a/src/com/gitblit/models/Activity.java +++ b/src/com/gitblit/models/Activity.java @@ -25,9 +25,9 @@ import java.util.List; import java.util.Map;
import java.util.Set;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
+import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
/**
@@ -93,8 +93,7 @@ public class Activity implements Serializable, Comparable<Activity> { }
repositoryMetrics.get(repository).count++;
- String author = commit.getAuthorIdent().getEmailAddress()
- .toLowerCase();
+ String author = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();
if (!authorMetrics.containsKey(author)) {
authorMetrics.put(author, new Metric(author));
}
@@ -127,86 +126,4 @@ public class Activity implements Serializable, Comparable<Activity> { // reverse chronological order
return o.startDate.compareTo(startDate);
}
-
- /**
- * Model class to represent a RevCommit, it's source repository, and the
- * branch. This class is used by the activity page.
- *
- * @author James Moger
- */
- public static class RepositoryCommit implements Serializable, Comparable<RepositoryCommit> {
-
- private static final long serialVersionUID = 1L;
-
- public final String repository;
-
- public final String branch;
-
- private final RevCommit commit;
-
- private List<RefModel> refs;
-
- public RepositoryCommit(String repository, String branch, RevCommit commit) {
- this.repository = repository;
- this.branch = branch;
- this.commit = commit;
- }
-
- public void setRefs(List<RefModel> refs) {
- this.refs = refs;
- }
-
- public List<RefModel> getRefs() {
- return refs;
- }
-
- public String getName() {
- return commit.getName();
- }
-
- public String getShortName() {
- return commit.getName().substring(0, 8);
- }
-
- public String getShortMessage() {
- return commit.getShortMessage();
- }
-
- public int getParentCount() {
- return commit.getParentCount();
- }
-
- public PersonIdent getAuthorIdent() {
- return commit.getAuthorIdent();
- }
-
- public PersonIdent getCommitterIdent() {
- return commit.getCommitterIdent();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof RepositoryCommit) {
- RepositoryCommit commit = (RepositoryCommit) o;
- return repository.equals(commit.repository) && getName().equals(commit.getName());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return (repository + commit).hashCode();
- }
-
- @Override
- public int compareTo(RepositoryCommit o) {
- // reverse-chronological order
- if (commit.getCommitTime() > o.commit.getCommitTime()) {
- return -1;
- } else if (commit.getCommitTime() < o.commit.getCommitTime()) {
- return 1;
- }
- return 0;
- }
- }
}
diff --git a/src/com/gitblit/models/ProjectModel.java b/src/com/gitblit/models/ProjectModel.java index 189a409b..9e5d5233 100644 --- a/src/com/gitblit/models/ProjectModel.java +++ b/src/com/gitblit/models/ProjectModel.java @@ -39,6 +39,8 @@ public class ProjectModel implements Serializable, Comparable<ProjectModel> { public String description;
public final Set<String> repositories = new HashSet<String>();
+ public String projectMarkdown;
+ public String repositoriesMarkdown;
public Date lastChange;
public final boolean isRoot;
diff --git a/src/com/gitblit/models/PushLogEntry.java b/src/com/gitblit/models/PushLogEntry.java new file mode 100644 index 00000000..f625c2a3 --- /dev/null +++ b/src/com/gitblit/models/PushLogEntry.java @@ -0,0 +1,208 @@ +/*
+ * Copyright 2013 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.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * Model class to represent a push into a repository.
+ *
+ * @author James Moger
+ */
+public class PushLogEntry implements Serializable, Comparable<PushLogEntry> {
+
+ private static final long serialVersionUID = 1L;
+
+ public final String repository;
+
+ public final Date date;
+
+ public final UserModel user;
+
+ private final Set<RepositoryCommit> commits;
+
+ private final Map<String, ReceiveCommand.Type> refUpdates;
+
+ /**
+ * Constructor for specified duration of push from start date.
+ *
+ * @param repository
+ * the repository that received the push
+ * @param date
+ * the date of the push
+ * @param user
+ * the user who pushed
+ */
+ public PushLogEntry(String repository, Date date, UserModel user) {
+ this.repository = repository;
+ this.date = date;
+ this.user = user;
+ this.commits = new LinkedHashSet<RepositoryCommit>();
+ this.refUpdates = new HashMap<String, ReceiveCommand.Type>();
+ }
+
+ /**
+ * Tracks the change type for the specified ref.
+ *
+ * @param ref
+ * @param type
+ */
+ public void updateRef(String ref, ReceiveCommand.Type type) {
+ if (!refUpdates.containsKey(ref)) {
+ refUpdates.put(ref, type);
+ }
+ }
+
+ /**
+ * Adds a commit to the push entry object as long as the commit is not a
+ * duplicate.
+ *
+ * @param branch
+ * @param commit
+ * @return a RepositoryCommit, if one was added. Null if this is duplicate
+ * commit
+ */
+ public RepositoryCommit addCommit(String branch, RevCommit commit) {
+ RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
+ if (commits.add(commitModel)) {
+ return commitModel;
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if this push contains a non-fastforward ref update.
+ *
+ * @return true if this is a non-fastforward push
+ */
+ public boolean isNonFastForward() {
+ for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
+ if (ReceiveCommand.Type.UPDATE_NONFASTFORWARD.equals(entry.getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the list of branches changed by the push.
+ *
+ * @return a list of branches
+ */
+ public List<String> getChangedBranches() {
+ return getChangedRefs(Constants.R_HEADS);
+ }
+
+ /**
+ * Returns the list of tags changed by the push.
+ *
+ * @return a list of tags
+ */
+ public List<String> getChangedTags() {
+ return getChangedRefs(Constants.R_TAGS);
+ }
+
+ /**
+ * Gets the changed refs in the push.
+ *
+ * @param baseRef
+ * @return the changed refs
+ */
+ protected List<String> getChangedRefs(String baseRef) {
+ Set<String> refs = new HashSet<String>();
+ for (String ref : refUpdates.keySet()) {
+ if (baseRef == null || ref.startsWith(baseRef)) {
+ refs.add(ref);
+ }
+ }
+ List<String> list = new ArrayList<String>(refs);
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * The total number of commits in the push.
+ *
+ * @return the number of commits in the push
+ */
+ public int getCommitCount() {
+ return commits.size();
+ }
+
+ /**
+ * Returns all commits in the push.
+ *
+ * @return a list of commits
+ */
+ public List<RepositoryCommit> getCommits() {
+ List<RepositoryCommit> list = new ArrayList<RepositoryCommit>(commits);
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * Returns all commits that belong to a particular ref
+ *
+ * @param ref
+ * @return a list of commits
+ */
+ public List<RepositoryCommit> getCommits(String ref) {
+ List<RepositoryCommit> list = new ArrayList<RepositoryCommit>();
+ for (RepositoryCommit commit : commits) {
+ if (commit.branch.equals(ref)) {
+ list.add(commit);
+ }
+ }
+ Collections.sort(list);
+ return list;
+ }
+
+ @Override
+ public int compareTo(PushLogEntry o) {
+ // reverse chronological order
+ return o.date.compareTo(date);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1} pushed {2,number,0} commit{3} to {4} ",
+ date, user.getDisplayName(), commits.size(), commits.size() == 1 ? "":"s", repository));
+ for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
+ String ref = entry.getKey();
+ ReceiveCommand.Type type = entry.getValue();
+ sb.append("\n ").append(ref).append(' ').append(type.name()).append('\n');
+ for (RepositoryCommit commit : getCommits(ref)) {
+ sb.append(" ").append(commit.toString()).append('\n');
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/com/gitblit/models/RepositoryCommit.java b/src/com/gitblit/models/RepositoryCommit.java new file mode 100644 index 00000000..e68e8613 --- /dev/null +++ b/src/com/gitblit/models/RepositoryCommit.java @@ -0,0 +1,112 @@ +/* + * 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.text.MessageFormat; +import java.util.List; + +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; + +/** + * Model class to represent a RevCommit, it's source repository, and the branch. + * This class is used by the activity page. + * + * @author James Moger + */ +public class RepositoryCommit implements Serializable, Comparable<RepositoryCommit> { + + private static final long serialVersionUID = 1L; + + public final String repository; + + public final String branch; + + private final RevCommit commit; + + private List<RefModel> refs; + + public RepositoryCommit(String repository, String branch, RevCommit commit) { + this.repository = repository; + this.branch = branch; + this.commit = commit; + } + + public void setRefs(List<RefModel> refs) { + this.refs = refs; + } + + public List<RefModel> getRefs() { + return refs; + } + + public String getName() { + return commit.getName(); + } + + public String getShortName() { + return commit.getName().substring(0, 8); + } + + public String getShortMessage() { + return commit.getShortMessage(); + } + + public int getParentCount() { + return commit.getParentCount(); + } + + public PersonIdent getAuthorIdent() { + return commit.getAuthorIdent(); + } + + public PersonIdent getCommitterIdent() { + return commit.getCommitterIdent(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof RepositoryCommit) { + RepositoryCommit commit = (RepositoryCommit) o; + return repository.equals(commit.repository) && getName().equals(commit.getName()); + } + return false; + } + + @Override + public int hashCode() { + return (repository + commit).hashCode(); + } + + @Override + public int compareTo(RepositoryCommit o) { + // reverse-chronological order + if (commit.getCommitTime() > o.commit.getCommitTime()) { + return -1; + } else if (commit.getCommitTime() < o.commit.getCommitTime()) { + return 1; + } + return 0; + } + + @Override + public String toString() { + return MessageFormat.format("{0} {1} {2,date,yyyy-MM-dd HH:mm} {3} {4}", + getShortName(), branch, getCommitterIdent().getWhen(), getAuthorIdent().getName(), + getShortMessage()); + } +}
\ No newline at end of file diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java index 5be33a2d..a2dab3c5 100644 --- a/src/com/gitblit/models/RepositoryModel.java +++ b/src/com/gitblit/models/RepositoryModel.java @@ -17,6 +17,7 @@ package com.gitblit.models; import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -43,7 +44,7 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel // field names are reflectively mapped in EditRepository page
public String name;
public String description;
- public String owner;
+ public List<String> owners;
public Date lastChange;
public boolean hasCommits;
public boolean showRemoteBranches;
@@ -82,6 +83,7 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel public transient boolean isCollectingGarbage;
public Date lastGC;
+ public String sparkleshareId;
public RepositoryModel() {
this("", "", "", new Date(0));
@@ -90,13 +92,15 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel public RepositoryModel(String name, String description, String owner, Date lastchange) {
this.name = name;
this.description = description;
- this.owner = owner;
this.lastChange = lastchange;
this.accessRestriction = AccessRestrictionType.NONE;
this.authorizationControl = AuthorizationControl.NAMED;
this.federationSets = new ArrayList<String>();
this.federationStrategy = FederationStrategy.FEDERATE_THIS;
this.projectPath = StringUtils.getFirstPathElement(name);
+ this.owners = new ArrayList<String>();
+
+ addOwner(owner);
}
public List<String> getLocalBranches() {
@@ -161,7 +165,10 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel }
public boolean isOwner(String username) {
- return owner != null && username != null && owner.equalsIgnoreCase(username);
+ if (StringUtils.isEmpty(username) || ArrayUtils.isEmpty(owners)) {
+ return false;
+ }
+ return owners.contains(username.toLowerCase());
}
public boolean isPersonalRepository() {
@@ -176,6 +183,10 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel return !accessRestriction.atLeast(AccessRestrictionType.VIEW);
}
+ public boolean isSparkleshared() {
+ return !StringUtils.isEmpty(sparkleshareId);
+ }
+
public RepositoryModel cloneAs(String cloneName) {
RepositoryModel clone = new RepositoryModel();
clone.originRepository = name;
@@ -193,6 +204,40 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel clone.useTickets = useTickets;
clone.skipSizeCalculation = skipSizeCalculation;
clone.skipSummaryMetrics = skipSummaryMetrics;
+ clone.sparkleshareId = sparkleshareId;
return clone;
}
-}
\ No newline at end of file +
+ public void addOwner(String username) {
+ if (!StringUtils.isEmpty(username)) {
+ String name = username.toLowerCase();
+ // a set would be more efficient, but this complicates JSON
+ // deserialization so we enforce uniqueness with an arraylist
+ if (!owners.contains(name)) {
+ owners.add(name);
+ }
+ }
+ }
+
+ public void removeOwner(String username) {
+ if (!StringUtils.isEmpty(username)) {
+ owners.remove(username.toLowerCase());
+ }
+ }
+
+ public void addOwners(Collection<String> usernames) {
+ if (!ArrayUtils.isEmpty(usernames)) {
+ for (String username : usernames) {
+ addOwner(username);
+ }
+ }
+ }
+
+ public void removeOwners(Collection<String> usernames) {
+ if (!ArrayUtils.isEmpty(owners)) {
+ for (String username : usernames) {
+ removeOwner(username);
+ }
+ }
+ }
+} diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java index ac67ff78..bec011d9 100644 --- a/src/com/gitblit/models/UserModel.java +++ b/src/com/gitblit/models/UserModel.java @@ -29,6 +29,7 @@ import java.util.TreeSet; import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AccountType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
@@ -73,15 +74,22 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel> // non-persisted fields
public boolean isAuthenticated;
+ public AccountType accountType;
public UserModel(String username) {
this.username = username;
this.isAuthenticated = true;
+ this.accountType = AccountType.LOCAL;
}
private UserModel() {
this.username = "$anonymous";
this.isAuthenticated = false;
+ this.accountType = AccountType.LOCAL;
+ }
+
+ public boolean isLocalAccount() {
+ return accountType.isLocal();
}
/**
@@ -100,8 +108,7 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel> @Deprecated
@Unused
public boolean canAccessRepository(RepositoryModel repository) {
- boolean isOwner = !StringUtils.isEmpty(repository.owner)
- && repository.owner.equals(username);
+ boolean isOwner = repository.isOwner(username);
boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl);
return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase())
|| hasTeamAccess(repository.name) || allowAuthenticated;
diff --git a/src/com/gitblit/utils/ActivityUtils.java b/src/com/gitblit/utils/ActivityUtils.java index ef3a55e7..732fdeb1 100644 --- a/src/com/gitblit/utils/ActivityUtils.java +++ b/src/com/gitblit/utils/ActivityUtils.java @@ -36,9 +36,9 @@ import org.eclipse.jgit.revwalk.RevCommit; import com.gitblit.GitBlit;
import com.gitblit.models.Activity;
-import com.gitblit.models.Activity.RepositoryCommit;
import com.gitblit.models.GravatarProfile;
import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryCommit;
import com.gitblit.models.RepositoryModel;
import com.google.gson.reflect.TypeToken;
diff --git a/src/com/gitblit/utils/ArrayUtils.java b/src/com/gitblit/utils/ArrayUtils.java index 41d110a3..65834673 100644 --- a/src/com/gitblit/utils/ArrayUtils.java +++ b/src/com/gitblit/utils/ArrayUtils.java @@ -15,7 +15,9 @@ */
package com.gitblit.utils;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
/**
@@ -41,4 +43,32 @@ public class ArrayUtils { public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.size() == 0;
}
+
+ public static String toString(Collection<?> collection) {
+ if (isEmpty(collection)) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (Object o : collection) {
+ sb.append(o.toString()).append(", ");
+ }
+ // trim trailing comma-space
+ sb.setLength(sb.length() - 2);
+ return sb.toString();
+ }
+
+ public static Collection<String> fromString(String value) {
+ if (StringUtils.isEmpty(value)) {
+ value = "";
+ }
+ List<String> list = new ArrayList<String>();
+ String [] values = value.split(",|;");
+ for (String v : values) {
+ String string = v.trim();
+ if (!StringUtils.isEmpty(string)) {
+ list.add(string);
+ }
+ }
+ return list;
+ }
}
diff --git a/src/com/gitblit/utils/FileUtils.java b/src/com/gitblit/utils/FileUtils.java index 0b8aeb4a..a21b5128 100644 --- a/src/com/gitblit/utils/FileUtils.java +++ b/src/com/gitblit/utils/FileUtils.java @@ -176,19 +176,17 @@ public class FileUtils { public static long folderSize(File directory) {
if (directory == null || !directory.exists()) {
return -1;
- }
- if (directory.isFile()) {
- return directory.length();
- }
- long length = 0;
- for (File file : directory.listFiles()) {
- if (file.isFile()) {
- length += file.length();
- } else {
+ }
+ if (directory.isDirectory()) {
+ long length = 0;
+ for (File file : directory.listFiles()) {
length += folderSize(file);
}
+ return length;
+ } else if (directory.isFile()) {
+ return directory.length();
}
- return length;
+ return 0;
}
/**
@@ -276,4 +274,19 @@ public class FileUtils { return path.getAbsoluteFile();
}
}
+
+ public static File resolveParameter(String parameter, File aFolder, String path) {
+ if (aFolder == null) {
+ // strip any parameter reference
+ path = path.replace(parameter, "").trim();
+ if (path.length() > 0 && path.charAt(0) == '/') {
+ // strip leading /
+ path = path.substring(1);
+ }
+ } else if (path.contains(parameter)) {
+ // replace parameter with path
+ path = path.replace(parameter, aFolder.getAbsolutePath());
+ }
+ return new File(path);
+ }
}
diff --git a/src/com/gitblit/utils/IssueUtils.java b/src/com/gitblit/utils/IssueUtils.java index 7b24ccf7..dd09235b 100644 --- a/src/com/gitblit/utils/IssueUtils.java +++ b/src/com/gitblit/utils/IssueUtils.java @@ -76,9 +76,9 @@ public class IssueUtils { public abstract boolean accept(IssueModel issue);
}
- public static final String GB_ISSUES = "refs/heads/gb-issues";
+ public static final String GB_ISSUES = "refs/gitblit/issues";
- static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
+ static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);
/**
* Log an error message and exception.
@@ -111,7 +111,13 @@ public class IssueUtils { * @return a refmodel for the gb-issues branch or null
*/
public static RefModel getIssuesBranch(Repository repository) {
- return JGitUtils.getBranch(repository, "gb-issues");
+ List<RefModel> refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT);
+ for (RefModel ref : refs) {
+ if (ref.reference.getName().equals(GB_ISSUES)) {
+ return ref;
+ }
+ }
+ return null;
}
/**
@@ -374,7 +380,7 @@ public class IssueUtils { String issuePath = getIssuePath(issueId);
RevTree tree = JGitUtils.getCommit(repository, GB_ISSUES).getTree();
byte[] content = JGitUtils
- .getByteContent(repository, tree, issuePath + "/" + attachment.id);
+ .getByteContent(repository, tree, issuePath + "/" + attachment.id, false);
attachment.content = content;
attachment.size = content.length;
return attachment;
@@ -394,7 +400,7 @@ public class IssueUtils { public static IssueModel createIssue(Repository repository, Change change) {
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
- JGitUtils.createOrphanBranch(repository, "gb-issues", null);
+ JGitUtils.createOrphanBranch(repository, GB_ISSUES, null);
}
if (StringUtils.isEmpty(change.author)) {
@@ -471,7 +477,7 @@ public class IssueUtils { RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
- throw new RuntimeException("gb-issues branch does not exist!");
+ throw new RuntimeException(GB_ISSUES + " does not exist!");
}
if (StringUtils.isEmpty(issueId)) {
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java index beaa27da..1f2ae943 100644 --- a/src/com/gitblit/utils/JGitUtils.java +++ b/src/com/gitblit/utils/JGitUtils.java @@ -537,7 +537,7 @@ public class JGitUtils { * @param path
* @return content as a byte []
*/
- public static byte[] getByteContent(Repository repository, RevTree tree, final String path) {
+ public static byte[] getByteContent(Repository repository, RevTree tree, final String path, boolean throwError) {
RevWalk rw = new RevWalk(repository);
TreeWalk tw = new TreeWalk(repository);
tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
@@ -572,7 +572,9 @@ public class JGitUtils { }
}
} catch (Throwable t) {
- error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
+ if (throwError) {
+ error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
+ }
} finally {
rw.dispose();
tw.release();
@@ -591,7 +593,7 @@ public class JGitUtils { * @return UTF-8 string content
*/
public static String getStringContent(Repository repository, RevTree tree, String blobPath, String... charsets) {
- byte[] content = getByteContent(repository, tree, blobPath);
+ byte[] content = getByteContent(repository, tree, blobPath, true);
if (content == null) {
return null;
}
@@ -741,11 +743,7 @@ public class JGitUtils { df.setDetectRenames(true);
List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
for (DiffEntry diff : diffs) {
- String objectId = null;
- if (FileMode.GITLINK.equals(diff.getNewMode())) {
- objectId = diff.getNewId().name();
- }
-
+ String objectId = diff.getNewId().name();
if (diff.getChangeType().equals(ChangeType.DELETE)) {
list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
.getNewMode().getBits(), objectId, commit.getId().getName(), diff
@@ -1457,6 +1455,20 @@ public class JGitUtils { int maxCount) {
return getRefs(repository, Constants.R_NOTES, fullName, maxCount);
}
+
+ /**
+ * Returns the list of refs in the specified base ref. If repository does
+ * not exist or is empty, an empty list is returned.
+ *
+ * @param repository
+ * @param fullName
+ * if true, /refs/yadayadayada is returned. If false,
+ * yadayadayada is returned.
+ * @return list of refs
+ */
+ public static List<RefModel> getRefs(Repository repository, String baseRef) {
+ return getRefs(repository, baseRef, true, -1);
+ }
/**
* Returns a list of references in the repository matching "refs". If the
@@ -1570,7 +1582,7 @@ public class JGitUtils { */
public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {
List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();
- byte [] blob = getByteContent(repository, tree, ".gitmodules");
+ byte [] blob = getByteContent(repository, tree, ".gitmodules", false);
if (blob == null) {
return list;
}
@@ -1604,6 +1616,32 @@ public class JGitUtils { }
return null;
}
+
+ public static String getSubmoduleCommitId(Repository repository, String path, RevCommit commit) {
+ String commitId = null;
+ RevWalk rw = new RevWalk(repository);
+ TreeWalk tw = new TreeWalk(repository);
+ tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
+ try {
+ tw.reset(commit.getTree());
+ while (tw.next()) {
+ if (tw.isSubtree() && !path.equals(tw.getPathString())) {
+ tw.enterSubtree();
+ continue;
+ }
+ if (FileMode.GITLINK == tw.getFileMode(0)) {
+ commitId = tw.getObjectId(0).getName();
+ break;
+ }
+ }
+ } catch (Throwable t) {
+ error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
+ } finally {
+ rw.dispose();
+ tw.release();
+ }
+ return commitId;
+ }
/**
* Returns the list of notes entered about the commit from the refs/notes
@@ -1720,4 +1758,18 @@ public class JGitUtils { }
return success;
}
+
+ /**
+ * Reads the sparkleshare id, if present, from the repository.
+ *
+ * @param repository
+ * @return an id or null
+ */
+ public static String getSparkleshareId(Repository repository) {
+ byte[] content = getByteContent(repository, null, ".sparkleshare", false);
+ if (content == null) {
+ return null;
+ }
+ return StringUtils.decodeString(content);
+ }
}
diff --git a/src/com/gitblit/utils/MetricUtils.java b/src/com/gitblit/utils/MetricUtils.java index e9e1fa52..26e4581c 100644 --- a/src/com/gitblit/utils/MetricUtils.java +++ b/src/com/gitblit/utils/MetricUtils.java @@ -210,6 +210,7 @@ public class MetricUtils { p = rev.getAuthorIdent().getEmailAddress().toLowerCase();
}
}
+ p = p.replace('\n',' ').replace('\r', ' ').trim();
if (!metricMap.containsKey(p)) {
metricMap.put(p, new Metric(p));
}
diff --git a/src/com/gitblit/utils/PushLogUtils.java b/src/com/gitblit/utils/PushLogUtils.java new file mode 100644 index 00000000..665533b3 --- /dev/null +++ b/src/com/gitblit/utils/PushLogUtils.java @@ -0,0 +1,344 @@ +/* + * Copyright 2013 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.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.models.PathModel.PathChangeModel; +import com.gitblit.models.PushLogEntry; +import com.gitblit.models.RefModel; +import com.gitblit.models.UserModel; + +/** + * Utility class for maintaining a pushlog within a git repository on an + * orphan branch. + * + * @author James Moger + * + */ +public class PushLogUtils { + + public static final String GB_PUSHES = "refs/gitblit/pushes"; + + static final Logger LOGGER = LoggerFactory.getLogger(PushLogUtils.class); + + /** + * Log an error message and exception. + * + * @param t + * @param repository + * if repository is not null it MUST be the {0} parameter in the + * pattern. + * @param pattern + * @param objects + */ + private static void error(Throwable t, Repository repository, String pattern, Object... objects) { + List<Object> parameters = new ArrayList<Object>(); + if (objects != null && objects.length > 0) { + for (Object o : objects) { + parameters.add(o); + } + } + if (repository != null) { + parameters.add(0, repository.getDirectory().getAbsolutePath()); + } + LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t); + } + + /** + * Returns a RefModel for the gb-pushes branch in the repository. If the + * branch can not be found, null is returned. + * + * @param repository + * @return a refmodel for the gb-pushes branch or null + */ + public static RefModel getPushLogBranch(Repository repository) { + List<RefModel> refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT); + for (RefModel ref : refs) { + if (ref.reference.getName().equals(GB_PUSHES)) { + return ref; + } + } + return null; + } + + /** + * Updates a push log. + * + * @param user + * @param repository + * @param commands + * @return true, if the update was successful + */ + public static boolean updatePushLog(UserModel user, Repository repository, + Collection<ReceiveCommand> commands) { + RefModel pushlogBranch = getPushLogBranch(repository); + if (pushlogBranch == null) { + JGitUtils.createOrphanBranch(repository, GB_PUSHES, null); + } + + boolean success = false; + String message = "push"; + + try { + ObjectId headId = repository.resolve(GB_PUSHES + "^{commit}"); + ObjectInserter odi = repository.newObjectInserter(); + try { + // Create the in-memory index of the push log entry + DirCache index = createIndex(repository, headId, commands); + ObjectId indexTreeId = index.writeTree(odi); + + PersonIdent ident = new PersonIdent(user.getDisplayName(), + user.emailAddress == null ? user.username:user.emailAddress); + + // Create a commit object + CommitBuilder commit = new CommitBuilder(); + commit.setAuthor(ident); + commit.setCommitter(ident); + commit.setEncoding(Constants.CHARACTER_ENCODING); + commit.setMessage(message); + commit.setParentId(headId); + commit.setTreeId(indexTreeId); + + // Insert the commit into the repository + ObjectId commitId = odi.insert(commit); + odi.flush(); + + RevWalk revWalk = new RevWalk(repository); + try { + RevCommit revCommit = revWalk.parseCommit(commitId); + RefUpdate ru = repository.updateRef(GB_PUSHES); + ru.setNewObjectId(commitId); + ru.setExpectedOldObjectId(headId); + ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false); + Result rc = ru.forceUpdate(); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + success = true; + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, + ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, GB_PUSHES, commitId.toString(), + rc)); + } + } finally { + revWalk.release(); + } + } finally { + odi.release(); + } + } catch (Throwable t) { + error(t, repository, "Failed to commit pushlog entry to {0}"); + } + return success; + } + + /** + * Creates an in-memory index of the push log entry. + * + * @param repo + * @param headId + * @param commands + * @return an in-memory index + * @throws IOException + */ + private static DirCache createIndex(Repository repo, ObjectId headId, + Collection<ReceiveCommand> commands) throws IOException { + + DirCache inCoreIndex = DirCache.newInCore(); + DirCacheBuilder dcBuilder = inCoreIndex.builder(); + ObjectInserter inserter = repo.newObjectInserter(); + + long now = System.currentTimeMillis(); + Set<String> ignorePaths = new TreeSet<String>(); + try { + // add receive commands to the temporary index + for (ReceiveCommand command : commands) { + // use the ref names as the path names + String path = command.getRefName(); + ignorePaths.add(path); + + StringBuilder change = new StringBuilder(); + change.append(command.getType().name()).append(' '); + switch (command.getType()) { + case CREATE: + change.append(ObjectId.zeroId().getName()); + change.append(' '); + change.append(command.getNewId().getName()); + break; + case UPDATE: + case UPDATE_NONFASTFORWARD: + change.append(command.getOldId().getName()); + change.append(' '); + change.append(command.getNewId().getName()); + break; + case DELETE: + change = null; + break; + } + if (change == null) { + // ref deleted + continue; + } + String content = change.toString(); + + // create an index entry for this attachment + final DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setLength(content.length()); + dcEntry.setLastModified(now); + dcEntry.setFileMode(FileMode.REGULAR_FILE); + + // insert object + dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))); + + // add to temporary in-core index + dcBuilder.add(dcEntry); + } + + // Traverse HEAD to add all other paths + TreeWalk treeWalk = new TreeWalk(repo); + int hIdx = -1; + if (headId != null) + hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId)); + treeWalk.setRecursive(true); + + while (treeWalk.next()) { + String path = treeWalk.getPathString(); + CanonicalTreeParser hTree = null; + if (hIdx != -1) + hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); + if (!ignorePaths.contains(path)) { + // add entries from HEAD for all other paths + if (hTree != null) { + // create a new DirCacheEntry with data retrieved from + // HEAD + final DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setObjectId(hTree.getEntryObjectId()); + dcEntry.setFileMode(hTree.getEntryFileMode()); + + // add to temporary in-core index + dcBuilder.add(dcEntry); + } + } + } + + // release the treewalk + treeWalk.release(); + + // finish temporary in-core index used for this commit + dcBuilder.finish(); + } finally { + inserter.release(); + } + return inCoreIndex; + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository) { + return getPushLog(repositoryName, repository, null, -1); + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, int maxCount) { + return getPushLog(repositoryName, repository, null, maxCount); + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, Date minimumDate) { + return getPushLog(repositoryName, repository, minimumDate, -1); + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, Date minimumDate, int maxCount) { + List<PushLogEntry> list = new ArrayList<PushLogEntry>(); + RefModel ref = getPushLogBranch(repository); + if (ref == null) { + return list; + } + List<RevCommit> pushes; + if (minimumDate == null) { + pushes = JGitUtils.getRevLog(repository, GB_PUSHES, 0, maxCount); + } else { + pushes = JGitUtils.getRevLog(repository, GB_PUSHES, minimumDate); + } + for (RevCommit push : pushes) { + if (push.getAuthorIdent().getName().equalsIgnoreCase("gitblit")) { + // skip gitblit/internal commits + continue; + } + Date date = push.getAuthorIdent().getWhen(); + UserModel user = new UserModel(push.getAuthorIdent().getEmailAddress()); + user.displayName = push.getAuthorIdent().getName(); + PushLogEntry log = new PushLogEntry(repositoryName, date, user); + list.add(log); + List<PathChangeModel> changedRefs = JGitUtils.getFilesInCommit(repository, push); + for (PathChangeModel change : changedRefs) { + switch (change.changeType) { + case DELETE: + log.updateRef(change.path, ReceiveCommand.Type.DELETE); + break; + case ADD: + log.updateRef(change.path, ReceiveCommand.Type.CREATE); + default: + String content = JGitUtils.getStringContent(repository, push.getTree(), change.path); + String [] fields = content.split(" "); + log.updateRef(change.path, ReceiveCommand.Type.valueOf(fields[0])); + String oldId = fields[1]; + String newId = fields[2]; + List<RevCommit> pushedCommits = JGitUtils.getRevLog(repository, oldId, newId); + for (RevCommit pushedCommit : pushedCommits) { + log.addCommit(change.path, pushedCommit); + } + } + } + } + Collections.sort(list); + return list; + } +} diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java index 86840048..86823db5 100644 --- a/src/com/gitblit/utils/StringUtils.java +++ b/src/com/gitblit/utils/StringUtils.java @@ -719,4 +719,18 @@ public class StringUtils { Matcher m = p.matcher(input);
return m.matches();
}
+
+ /**
+ * Removes new line and carriage return chars from a string.
+ * If input value is null an empty string is returned.
+ *
+ * @param input
+ * @return a sanitized or empty string
+ */
+ public static String removeNewlines(String input) {
+ if (input == null) {
+ return "";
+ }
+ return input.replace('\n',' ').replace('\r', ' ').trim();
+ }
}
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index 16f76411..a993f9f1 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -440,4 +440,6 @@ gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certif gb.validity = validity
gb.siteName = site name
gb.siteNameDescription = short, descriptive name of your server
-gb.excludeFromActivity = exclude from activity page
\ No newline at end of file +gb.excludeFromActivity = exclude from activity page
+gb.isSparkleshared = repository is Sparkleshared
+gb.owners = owners
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_es.properties b/src/com/gitblit/wicket/GitBlitWebApp_es.properties index d83fcef3..64c9ca13 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp_es.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp_es.properties @@ -163,7 +163,7 @@ gb.manage = Administrar gb.lastLogin = \u00DAltimo acceso
gb.skipSizeCalculation = Saltar comprobaciones de tama\u00F1o
gb.skipSizeCalculationDescription = No calcular el tama\u00F1o del repositorio (Reduce tiempo de carga de la p\u00E1gina)
-gb.skipSummaryMetrics = Saltar el resumen de estad\u00EDsticas
+gb.skipSummaryMetrics = Saltar resumen de estad\u00EDsticas
gb.skipSummaryMetricsDescription = No calcular estad\u00EDsticas (Reduce tiempo de carga de la p\u00E1gina)
gb.accessLevel = Nivel de acceso
gb.default = Predeterminado
@@ -341,8 +341,103 @@ gb.isFork = Es bifurcado gb.canCreate = Puede crear
gb.canCreateDescription = Puede crear repositorios personales
gb.illegalPersonalRepositoryLocation = Tu repositorio personal debe estar ubicado en \"{0}\"
-gb.verifyCommitter = Acreditar consignador
-gb.verifyCommitterDescription = Require que la acreditaci\u00F3n del consignador coincida con la de la cuenta del usuario en Gitblt (es necesario "--no-ff" al empujar para que consignador se acredite)
+gb.verifyCommitter = Consignador acreditado
+gb.verifyCommitterDescription = Require que la acreditaci\u00F3n del consignador coincida con la de la cuenta del usuario en Gitblt
+gb.verifyCommitterNote = es obligatorio "--no-ff" al empujar para que el consignador se acredite
gb.repositoryPermissions = Permisos del repositorio
gb.userPermissions = Permisos de usuarios
gb.teamPermissions = Permisos de equipos
+gb.add = A\u00F1adir
+gb.noPermission = BORRAR ESTE PERMISO
+gb.excludePermission = {0} (excluir)
+gb.viewPermission = {0} (ver)
+gb.clonePermission = {0} (clonar)
+gb.pushPermission = {0} (empujar)
+gb.createPermission = {0} (empujar, ref creaci\u00F3n)
+gb.deletePermission = {0} (empujar, ref creaci\u00F3n+borrado)
+gb.rewindPermission = {0} (empujar, ref creaci\u00F3n+borrado+supresi\u00F3n)
+gb.permission = Permisos
+gb.regexPermission = Estos permisos se ajustan desde la expresi\u00F3n regulare \"{0}\"
+gb.accessDenied = Acceso denegado
+gb.busyCollectingGarbage = Perd\u00F3n, Gitblit est\u00E1 ocupado quitando basura de {0}
+gb.gcPeriod = Periodo para GC
+gb.gcPeriodDescription = Duraci\u00F3n entre periodos de limpieza
+gb.gcThreshold = L\u00EDmites para GC
+gb.gcThresholdDescription = Tama\u00F1o m\u00EDnimo total de objetos sueltos para activar la recolecci\u00F3n inmediata de basura
+gb.ownerPermission = Propietario del repositorio
+gb.administrator = Admin
+gb.administratorPermission = Administrador de Gitblit
+gb.team = Equipo
+gb.teamPermission = Permisos ajustados para \"{0}\" mienbros de equipo
+gb.missing = \u00A1Omitido!
+gb.missingPermission = \u00A1Falta el repositorio de este permiso!
+gb.mutable = Alterables
+gb.specified = Espec\u00EDficos
+gb.effective = Efectivos
+gb.organizationalUnit = Unidad de organizaci\u00F3n
+gb.organization = Organizaci\u00F3n
+gb.locality = Localidad
+gb.stateProvince = Estado o provincia
+gb.countryCode = C\u00F3digo postal
+gb.properties = Propiedades
+gb.issued = Publicado
+gb.expires = Expira
+gb.expired = Expirado
+gb.expiring = Concluido
+gb.revoked = Revocado
+gb.serialNumber = N\u00FAmero de serie
+gb.certificates = Certificados
+gb.newCertificate = Nuevo certificado
+gb.revokeCertificate = Revocar certificado
+gb.sendEmail = Enviar correo
+gb.passwordHint = Recordatorio de contrase\u00F1a
+gb.ok = ok
+gb.invalidExpirationDate = \u00A1La fecha de expiraci\u00F3n no es v\u00E1lida!
+gb.passwordHintRequired = \u00A1Se requiere una pista para la contrase\u00F1a!
+gb.viewCertificate = Ver certificado
+gb.subject = Asunto
+gb.issuer = Emisor
+gb.validFrom = V\u00E1lido desde
+gb.validUntil = V\u00E1lido hasta
+gb.publicKey = Clave p\u00FAblica
+gb.signatureAlgorithm = Algoritmo de firma
+gb.sha1FingerPrint = Huella digital SHA-1
+gb.md5FingerPrint = Huella digital MD5
+gb.reason = Motivo
+gb.revokeCertificateReason = Por favor, selecciona un motivo por el que revocas el certificado
+gb.unspecified = Sin especificar
+gb.keyCompromise = Clave de compromiso
+gb.caCompromise = Compromiso CA
+gb.affiliationChanged = Afiliaci\u00F3n cambiada
+gb.superseded = Sustituida
+gb.cessationOfOperation = Cese de operaci\u00F3n
+gb.privilegeWithdrawn = Privilegios retirados
+gb.time.inMinutes = en {0} mints
+gb.time.inHours = en {0} horas
+gb.time.inDays = en {0} d\u00EDas
+gb.hostname = Nombre de host
+gb.hostnameRequired = Por favor, introduzca un nombre de host
+gb.newSSLCertificate = Nuevo certificado SSL del servidor
+gb.newCertificateDefaults = Nuevo certificado predeterminado
+gb.duration = Duraci\u00F3n
+gb.certificateRevoked = El cretificado {0,n\u00FAmero,0} ha sido revocado
+gb.clientCertificateGenerated = Nuevo certificado de cliente generado correctamente para {0}
+gb.sslCertificateGenerated = Nuevo certificado de SSL generado correctamente para {0}
+gb.newClientCertificateMessage = AVISO:\nLa 'contrase\u00F1a' no es la contrase\u00F1a del usuario, es la contrase\u00F1a para proteger el almac\u00E9n de claves del usuario. Esta contrase\u00F1a no se guarda por lo que tambi\u00E9n debe introducirse una "pista" que ser\u00E1 incluida en las instrucciones LEEME del usuario.
+gb.certificate = Certificado
+gb.emailCertificateBundle = Correo del cliente para el paquete del certificado
+gb.pleaseGenerateClientCertificate = Por favor, genera un certificado de cliente para {0}
+gb.clientCertificateBundleSent = Paquete de certificado de cliente {0} enviado
+gb.enterKeystorePassword = Por favor, introduzca la contrase\u00F1a del almac\u00E9n de claves de Gitblit
+gb.warning = Advertencia
+gb.jceWarning = Tu entorno de trabajo JAVA no contiene los archivos \"JCE Unlimited Strength Jurisdiction Policy\".\nEsto limita la longitud de la contrase\u00F1a que puedes usuar para cifrar el almac\u00E9n de claves a 7 caracteres.\nEstos archivos opcionales puedes descargarlos desde Oracle.\n\n\u00BFQuieres continuar y generar la infraestructura de certificados de todos modos?\n\nSi respondes No tu navegador te dirigir\u00E1 a la p\u00E1gina de descarga de Oracle para que pueda descargar dichos archivos.
+gb.maxActivityCommits = Actividad m\u00E1xima de consignas
+gb.maxActivityCommitsDescription = N\u00FAmero m\u00E1ximo de consignas a incluir en la p\u00E1gina de actividad
+gb.noMaximum = Sin m\u00E1ximos
+gb.attributes = Atributos
+gb.serveCertificate = Servidor https con este certificado
+gb.sslCertificateGeneratedRestart = Certificado SSL generado correctamente para {0}.\nDebes reiniciar Gitblit para usar el nuevo certificado.\n\nSi lo has iniciado con la opci\u00F3n '--alias' deber\u00E1s ajustar dicha opci\u00F3n a ''--alias {0}''.
+gb.validity = Vigencia
+gb.siteName = Nombre del sitio
+gb.siteNameDescription = Nombre corto y descriptivo de tu servidor
+gb.excludeFromActivity = Excluir de la p\u00E1gina de actividad
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties b/src/com/gitblit/wicket/GitBlitWebApp_ko.properties index 9582ef85..18eda26c 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp_ko.properties @@ -237,7 +237,7 @@ gb.passwordChanged = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uBCC0\uACBD \uC131\uACF5. gb.passwordChangeAborted = \uD328\uC2A4\uC6CC\uB4DC \uBCC0\uACBD \uCDE8\uC18C\uB428. gb.pleaseSetRepositoryName = \uC800\uC7A5\uC18C \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694! gb.illegalLeadingSlash = \uC800\uC7A5\uC18C \uC774\uB984 \uB610\uB294 \uD3F4\uB354\uB294 (/) \uB85C \uC2DC\uC791\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. -gb.illegalRelativeSlash = \uC0C1\uB300 \uACBD\uB85C \uC9C0\uC815 (../) \uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +gb.illegalRelativeSlash = \uC0C1\uB300 \uACBD\uB85C \uC9C0\uC815 (../) \uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.. gb.illegalCharacterRepositoryName = \uBB38\uC790 ''{0}'' \uC800\uC7A5\uC18C \uC774\uB984\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694! gb.selectAccessRestriction = \uC811\uC18D \uAD8C\uD55C\uC744 \uC120\uD0DD\uD558\uC138\uC694! gb.selectFederationStrategy = \uD398\uB354\uB808\uC774\uC158 \uC815\uCC45\uC744 \uC120\uD0DD\uD558\uC138\uC694! @@ -287,7 +287,7 @@ gb.none = none gb.line = \uB77C\uC778 gb.content = \uB0B4\uC6A9 gb.empty = empty -gb.inherited = inherited +gb.inherited = \uC0C1\uC18D gb.deleteRepository = \"{0}\" \uC800\uC7A5\uC18C\uB97C \uC0AD\uC81C\uD560\uAE4C\uC694? gb.repositoryDeleted = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C\uB428. gb.repositoryDeleteFailed = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C \uC2E4\uD328! @@ -315,3 +315,129 @@ gb.allowAuthenticatedDescription = \uBAA8\uB4E0 \uC778\uC99D\uB41C \uC720\uC800\ gb.allowNamedDescription = \uC774\uB984\uC73C\uB85C \uC720\uC800\uB098 \uD300\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC gb.markdownFailure = \uB9C8\uD06C\uB2E4\uC6B4 \uCEE8\uD150\uD2B8 \uD30C\uC2F1 \uC624\uB958! gb.clearCache = \uCE90\uC2DC \uC9C0\uC6B0\uAE30 +gb.projects = \uD504\uB85C\uC81D\uD2B8\uB4E4 +gb.project = \uD504\uB85C\uC81D\uD2B8 +gb.allProjects = \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8 +gb.copyToClipboard = \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC +gb.fork = \uD3EC\uD06C +gb.forks = \uD3EC\uD06C +gb.forkRepository = fork {0}? +gb.repositoryForked = {0} \uD3EC\uD06C\uB428 +gb.repositoryForkFailed= \uD3EC\uD06C\uC2E4\uD328 +gb.personalRepositories = \uAC1C\uC778 \uC800\uC7A5\uC18C +gb.allowForks = \uD3EC\uD06C \uD5C8\uC6A9 +gb.allowForksDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC70 \uD3EC\uD06C \uD5C8\uC6A9 +gb.forkedFrom = forked from +gb.canFork = \uD3EC\uD06C \uAC00\uB2A5 +gb.canForkDescription = \uD5C8\uC6A9\uB41C \uC800\uC7A5\uC18C\uB97C \uAC1C\uC778 \uC800\uC7A5\uC18C\uC5D0 \uD3EC\uD06C\uD560 \uC218 \uC788\uC74C +gb.myFork = \uB0B4 \uD3EC\uD06C \uBCF4\uAE30 +gb.forksProhibited = \uD3EC\uD06C \uCC28\uB2E8\uB428 +gb.forksProhibitedWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uD3EC\uD06C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC74C +gb.noForks = {0} \uB294 \uD3EC\uD06C \uC5C6\uC74C +gb.forkNotAuthorized = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. {0} \uD3EC\uD06C\uC5D0 \uC811\uC18D \uC778\uC99D\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. +gb.forkInProgress = \uD504\uD06C \uC9C4\uD589 \uC911 +gb.preparingFork = \uD3EC\uD06C \uC900\uBE44 \uC911... +gb.isFork = \uD3EC\uD06C\uD55C +gb.canCreate = \uC0DD\uC131 \uAC00\uB2A5 +gb.canCreateDescription = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4 \uC218 \uC788\uC74C +gb.illegalPersonalRepositoryLocation = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB294 \uBC18\uB4DC\uC2DC \"{0}\" \uC5D0 \uC704\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4. +gb.verifyCommitter = \uCEE4\uBBF8\uD130 \uD655\uC778 +gb.verifyCommitterDescription = \uCEE4\uBBF8\uD130 ID \uB294 Gitblit ID \uC640 \uB9E4\uCE58\uB418\uC5B4\uC57C \uD568 +gb.verifyCommitterNote = \uBAA8\uB4E0 \uBA38\uC9C0\uB294 \uCEE4\uBBF8\uD130 ID \uB97C \uC801\uC6A9\uD558\uAE30 \uC704\uD574 "--no-ff" \uC635\uC158 \uD544\uC694 +gb.repositoryPermissions = \uC800\uC7A5\uC18C \uAD8C\uD55C +gb.userPermissions = \uC720\uC800 \uAD8C\uD55C +gb.teamPermissions = \uD300 \uAD8C\uD55C +gb.add = \uCD94\uAC00 +gb.noPermission = \uC774 \uAD8C\uD55C \uC0AD\uC81C +gb.excludePermission = {0} (\uC81C\uC678) +gb.viewPermission = {0} (\uBCF4\uAE30) +gb.clonePermission = {0} (\uD074\uB860) +gb.pushPermission = {0} (\uD478\uC2DC) +gb.createPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131) +gb.deletePermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C) +gb.rewindPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C+\uB418\uB3CC\uB9AC\uAE30) +gb.permission = \uAD8C\uD55C +gb.regexPermission = \uC774 \uAD8C\uD55C\uC740 \uC815\uADDC\uC2DD \"{0}\" \uB85C\uBD80\uD130 \uC124\uC815\uB428 +gb.accessDenied = \uC811\uC18D \uAC70\uBD80 +gb.busyCollectingGarbage = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. Gitblit \uC740 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158 \uC911\uC785\uB2C8\uB2E4. {0} +gb.gcPeriod = GC \uC8FC\uAE30 +gb.gcPeriodDescription = \uAC00\uBE44\uC9C0 \uD074\uB809\uC158\uAC04\uC758 \uC2DC\uAC04 \uAC04\uACA9 +gb.gcThreshold = GC \uAE30\uC900\uC810 +gb.gcThresholdDescription = \uC870\uAE30 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158\uC744 \uBC1C\uC0DD\uC2DC\uD0A4\uAE30 \uC704\uD55C \uC624\uBE0C\uC81D\uD2B8\uB4E4\uC758 \uCD5C\uC18C \uC804\uCCB4 \uD06C\uAE30 +gb.ownerPermission = \uC800\uC7A5\uC18C \uC624\uB108 +gb.administrator = \uAD00\uB9AC\uC790 +gb.administratorPermission = Gitblit \uAD00\uB9AC\uC790 +gb.team = \uD300 +gb.teamPermission = \"{0}\" \uD300 \uBA64\uBC84\uC5D0 \uAD8C\uD55C \uC124\uC815\uB428 +gb.missing = \uB204\uB77D! +gb.missingPermission = \uC774 \uAD8C\uD55C\uC744 \uC704\uD55C \uC800\uC7A5\uC18C \uB204\uB77D! +gb.mutable = \uAC00\uBCC0 +gb.specified = \uC9C0\uC815\uB41C +gb.effective = \uD6A8\uACFC\uC801 +gb.organizationalUnit = \uC870\uC9C1 +gb.organization = \uAE30\uAD00 +gb.locality = \uC704\uCE58 +gb.stateProvince = \uB3C4 \uB610\uB294 \uC8FC +gb.countryCode = \uAD6D\uAC00\uCF54\uB4DC +gb.properties = \uC18D\uC131 +gb.issued = \uBC1C\uAE09\uB428 +gb.expires = \uB9CC\uB8CC +gb.expired = \uB9CC\uB8CC\uB428 +gb.expiring = \uB9CC\uB8CC\uC911 +gb.revoked = \uD3D0\uAE30\uB428 +gb.serialNumber = \uC77C\uB828\uBC88\uD638 +gb.certificates = \uC778\uC99D\uC11C +gb.newCertificate = \uC0C8 \uC778\uC99D\uC11C +gb.revokeCertificate = \uC778\uC99D\uC11C \uD3D0\uAE30 +gb.sendEmail = \uBA54\uC77C \uBCF4\uB0B4\uAE30 +gb.passwordHint = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 +gb.ok = ok +gb.invalidExpirationDate = \uB9D0\uB8CC\uC77C\uC790 \uC624\uB958! +gb.passwordHintRequired = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 \uD544\uC218! +gb.viewCertificate = \uC778\uC99D\uC11C \uBCF4\uAE30 +gb.subject = \uC774\uB984 +gb.issuer = \uBC1C\uAE09\uC790 +gb.validFrom = \uC720\uD6A8\uAE30\uAC04 (\uC2DC\uC791) +gb.validUntil = \uC720\uD6A8\uAE30\uAC04 (\uB05D) +gb.publicKey = \uACF5\uAC1C\uD0A4 +gb.signatureAlgorithm = \uC11C\uBA85 \uC54C\uACE0\uB9AC\uC998 +gb.sha1FingerPrint = SHA-1 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998 +gb.md5FingerPrint = MD5 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998 +gb.reason = \uC774\uC720 +gb.revokeCertificateReason = \uC778\uC99D\uC11C \uD574\uC9C0\uC774\uC720\uB97C \uC120\uD0DD\uD558\uC138\uC694 +gb.unspecified = \uD45C\uC2DC\uD558\uC9C0 \uC54A\uC74C +gb.keyCompromise = \uD0A4 \uC190\uC0C1 +gb.caCompromise = CA \uC190\uC0C1 +gb.affiliationChanged = \uAD00\uACC4 \uBCC0\uACBD\uB428 +gb.superseded = \uB300\uCCB4\uB428 +gb.cessationOfOperation = \uC6B4\uC601 \uC911\uC9C0 +gb.privilegeWithdrawn = \uAD8C\uD55C \uCCA0\uD68C\uB428 +gb.time.inMinutes = {0} \uBD84 +gb.time.inHours = {0} \uC2DC\uAC04 +gb.time.inDays = {0} \uC77C +gb.hostname = \uD638\uC2A4\uD2B8\uBA85 +gb.hostnameRequired = \uD638\uC2A4\uD2B8\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694 +gb.newSSLCertificate = \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C +gb.newCertificateDefaults = \uC0C8 \uC778\uC99D\uC11C \uAE30\uBCF8 +gb.duration = \uAE30\uAC04 +gb.certificateRevoked = \uC778\uC99D\uC11C {0,number,0} \uB294 \uD3D0\uAE30\uB418\uC5C8\uC2B5\uB2C8\uB2E4 +gb.clientCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5 +gb.sslCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uC11C\uBC84 SSL \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5 +gb.newClientCertificateMessage = \uB178\uD2B8:\n'\uD328\uC2A4\uC6CC\uB4DC' \uB294 \uC720\uC800\uC758 \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uC544\uB2C8\uB77C \uC720\uC800\uC758 \uD0A4\uC2A4\uD1A0\uC5B4 \uB97C \uBCF4\uD638\uD558\uAE30 \uC704\uD55C \uAC83\uC785\uB2C8\uB2E4. \uC774 \uD328\uC2A4\uC6CC\uB4DC\uB294 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uC0AC\uC6A9\uC790 README \uC9C0\uCE68\uC5D0 \uD3EC\uD568\uB420 '\uD78C\uD2B8' \uB97C \uBC18\uB4DC\uC2DC \uC785\uB825\uD574\uC57C \uD569\uB2C8\uB2E4. +gb.certificate = \uC778\uC99D\uC11C +gb.emailCertificateBundle = \uC774\uBA54\uC77C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 +gb.pleaseGenerateClientCertificate = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C\uB97C \uC0DD\uC131\uD558\uC138\uC694 +gb.clientCertificateBundleSent = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 \uBC1C\uC1A1\uB428 +gb.enterKeystorePassword = Gitblit \uD0A4\uC2A4\uD1A0\uC5B4 \uD328\uC2A4\uC6CC\uB4DC\uB97C \uC785\uB825\uD558\uC138\uC694 +gb.warning = \uACBD\uACE0 +gb.jceWarning = \uC790\uBC14 \uC2E4\uD589\uD658\uACBD\uC5D0 \"JCE Unlimited Strength Jurisdiction Policy\" \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uC774\uAC83\uC740 \uD0A4\uC800\uC7A5\uC18C \uC554\uD638\uD654\uC5D0 \uC0AC\uC6A9\uB418\uB294 \uD328\uC2A4\uC6CC\uB4DC\uC758 \uAE38\uC774\uB294 7\uC790\uB85C \uC81C\uD55C\uD569\uB2C8\uB2E4.\n\uC774 \uC815\uCC45 \uD30C\uC77C\uC740 Oracle \uC5D0\uC11C \uC120\uD0DD\uC801\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD574\uC57C \uD569\uB2C8\uB2E4.\n\n\uBB34\uC2DC\uD558\uACE0 \uC778\uC99D\uC11C \uC778\uD504\uB77C\uB97C \uC0DD\uC131\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?\n\n\uC544\uB2C8\uC624(No) \uB77C\uACE0 \uB2F5\uD558\uBA74 \uC815\uCC45\uD30C\uC77C\uC744 \uB2E4\uC6B4\uBC1B\uC744 \uC218 \uC788\uB294 Oracle \uB2E4\uC6B4\uB85C\uB4DC \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC548\uB0B4\uD560 \uAC83\uC785\uB2C8\uB2E4. +gb.maxActivityCommits = \uCD5C\uB300 \uC561\uD2F0\uBE44\uD2F0 \uCEE4\uBC0B +gb.maxActivityCommitsDescription = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0 \uD45C\uC2DC\uD560 \uCD5C\uB300 \uCEE4\uBC0B \uC218 +gb.noMaximum = \uBB34\uC81C\uD55C +gb.attributes = \uC18D\uC131 +gb.serveCertificate = \uC774 \uC778\uC99D\uC11C\uB85C https \uC81C\uACF5 +gb.sslCertificateGeneratedRestart = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C\uB97C \uC131\uACF5\uC801\uC73C\uB85C \uC0DD\uC131\uD558\uC600\uC2B5\uB2C8\uB2E4. \n\uC0C8 \uC778\uC99D\uC11C\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 Gitblit \uC744 \uC7AC\uC2DC\uC791 \uD574\uC57C \uD569\uB2C8\uB2E4.\n\n'--alias' \uD30C\uB77C\uBBF8\uD130\uB85C \uC2E4\uD589\uD55C\uB2E4\uBA74 ''--alias {0}'' \uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4. +gb.validity = \uC720\uD6A8\uC131 +gb.siteName = \uC0AC\uC774\uD2B8 \uC774\uB984 +gb.siteNameDescription = \uC11C\uBC84\uC758 \uC9E6\uC740 \uC124\uBA85\uC774 \uD3EC\uD568\uB41C \uC774\uB984 +gb.excludeFromActivity = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0\uC11C \uC81C\uC678
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_nl.properties b/src/com/gitblit/wicket/GitBlitWebApp_nl.properties new file mode 100644 index 00000000..5471ad8a --- /dev/null +++ b/src/com/gitblit/wicket/GitBlitWebApp_nl.properties @@ -0,0 +1,443 @@ +gb.repository = repositorie
+gb.owner = eigenaar
+gb.description = omschrijving
+gb.lastChange = laatste wijziging
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = auteur
+gb.committer = committer
+gb.commit = commit
+gb.tree = tree
+gb.parent = parent
+gb.url = URL
+gb.history = historie
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = toegewezen
+gb.ticketOpenDate = open datum
+gb.ticketState = status
+gb.ticketComments = commentaar
+gb.view = view
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = meer commits...
+gb.allTags = alle tags...
+gb.allBranches = alle branches...
+gb.summary = samenvatting
+gb.ticket = ticket
+gb.newRepository = nieuwe repositorie
+gb.newUser = nieuwe gebruiker
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = eerste
+gb.pagePrevious = vorige
+gb.pageNext = volgende
+gb.head = HEAD
+gb.blame = blame
+gb.login = aanmelden
+gb.logout = afmelden
+gb.username = gebruikersnaam
+gb.password = wachtwoord
+gb.tagger = tagger
+gb.moreHistory = meer historie...
+gb.difftocurrent = diff naar current
+gb.search = zoeken
+gb.searchForAuthor = Zoeken naar commits authored door
+gb.searchForCommitter = Zoeken naar commits committed door
+gb.addition = additie
+gb.modification = wijziging
+gb.deletion = verwijdering
+gb.rename = hernoem
+gb.metrics = metrieken
+gb.stats = stats
+gb.markdown = markdown
+gb.changedFiles = gewijzigde bestanden
+gb.filesAdded = {0} bestanden toegevoegd
+gb.filesModified = {0} bestanden gewijzigd
+gb.filesDeleted = {0} bestanden verwijderd
+gb.filesCopied = {0} bestanden gekopieerd
+gb.filesRenamed = {0} bestanden hernoemd
+gb.missingUsername = Ontbrekende Gebruikersnaam
+gb.edit = edit
+gb.searchTypeTooltip = Selecteer Zoek Type
+gb.searchTooltip = Zoek {0}
+gb.delete = verwijder
+gb.docs = docs
+gb.accessRestriction = toegangsbeperking
+gb.name = naam
+gb.enableTickets = enable tickets
+gb.enableDocs = enable docs
+gb.save = opslaan
+gb.showRemoteBranches = toon remote branches
+gb.editUsers = wijzig gebruikers
+gb.confirmPassword = bevestig wachtwoord
+gb.restrictedRepositories = restricted repositories
+gb.canAdmin = kan beheren
+gb.notRestricted = anoniem view, clone, & push
+gb.pushRestricted = geauthenticeerde push
+gb.cloneRestricted = geauthenticeerde clone & push
+gb.viewRestricted = geauthenticeerde view, clone, & push
+gb.useTicketsDescription = readonly, gedistribueerde Ticgit issues
+gb.useDocsDescription = enumereer Markdown documentatie in repositorie
+gb.showRemoteBranchesDescription = toon remote branches
+gb.canAdminDescription = kan Gitblit server beheren
+gb.permittedUsers = toegestane gebruikers
+gb.isFrozen = is bevroren
+gb.isFrozenDescription = weiger push operaties
+gb.zip = zip
+gb.showReadme = toon readme
+gb.showReadmeDescription = toon een \"readme\" Markdown bestand in de samenvattingspagina
+gb.nameDescription = gebruik '/' voor het groeperen van repositories. bijv. libraries/mycoollib.git
+gb.ownerDescription = de eigenaar mag repository instellingen wijzigen
+gb.blob = blob
+gb.commitActivityTrend = commit activiteit trend
+gb.commitActivityDOW = commit activiteit per dag van de week
+gb.commitActivityAuthors = primaire auteurs op basis van commit activiteit
+gb.feed = feed
+gb.cancel = afbreken
+gb.changePassword = wijzig wachtwoord
+gb.isFederated = is gefedereerd
+gb.federateThis = federeer deze repositorie
+gb.federateOrigin = federeer deze origin
+gb.excludeFromFederation = uitsluiten van federatie
+gb.excludeFromFederationDescription = sluit gefedereerde Gitblit instances uit van het pullen van dit account
+gb.tokens = federatie tokens
+gb.tokenAllDescription = alle repositories, gebruikers, & instellingen
+gb.tokenUnrDescription = alle repositories & gebruikers
+gb.tokenJurDescription = alle repositories
+gb.federatedRepositoryDefinitions = repositorie definities
+gb.federatedUserDefinitions = gebruikersdefinities
+gb.federatedSettingDefinitions = instellingendefinities
+gb.proposals = federatie voorstellen
+gb.received = ontvangen
+gb.type = type
+gb.token = token
+gb.repositories = repositories
+gb.proposal = voorstel
+gb.frequency = frequentie
+gb.folder = map
+gb.lastPull = laatste pull
+gb.nextPull = volgende pull
+gb.inclusions = inclusies
+gb.exclusions = exclusies
+gb.registration = registratie
+gb.registrations = federatie registraties
+gb.sendProposal = voorstel
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = wijzig de ref waar HEAD naar linkt naar bijv. refs/heads/master
+gb.federationStrategy = federatie strategie
+gb.federationRegistration = federatie registratie
+gb.federationResults = federatie pull resultaten
+gb.federationSets = federatie sets
+gb.message = melding
+gb.myUrlDescription = de publiek toegankelijke url voor uw Gitblit instantie
+gb.destinationUrl = zend naar
+gb.destinationUrlDescription = de url van de Gitblit instantie voor het verzenden van uw voorstel
+gb.users = gebruikers
+gb.federation = federatie
+gb.error = fout
+gb.refresh = ververs
+gb.browse = blader
+gb.clone = clone
+gb.filter = filter
+gb.create = maak
+gb.servers = servers
+gb.recent = recent
+gb.available = beschikbaar
+gb.selected = geselecteerd
+gb.size = grootte
+gb.downloading = downloading
+gb.loading = laden
+gb.starting = starten
+gb.general = algemeen
+gb.settings = instellingen
+gb.manage = beheer
+gb.lastLogin = laatste login
+gb.skipSizeCalculation = geen berekening van de omvang
+gb.skipSizeCalculationDescription = geen berekening van de repositoriegrootte (beperkt laadtijd pagina)
+gb.skipSummaryMetrics = geen metrieken samenvatting
+gb.skipSummaryMetricsDescription = geen berekening van metrieken op de samenvattingspagina (beperkt laadtijd pagina)
+gb.accessLevel = toegangsniveau
+gb.default = standaard
+gb.setDefault = instellen als standaard
+gb.since = sinds
+gb.status = status
+gb.bootDate = boot datum
+gb.servletContainer = servlet container
+gb.heapMaximum = maximum heap
+gb.heapAllocated = toegewezen heap
+gb.heapUsed = gebruikte heap
+gb.free = beschikbaar
+gb.version = versie
+gb.releaseDate = release datum
+gb.date = datum
+gb.activity = activiteit
+gb.subscribe = aboneer
+gb.branch = branch
+gb.maxHits = max hits
+gb.recentActivity = recente activiteit
+gb.recentActivityStats = laatste {0} dagen / {1} commits door {2} auteurs
+gb.recentActivityNone = laatste {0} dagen / geen
+gb.dailyActivity = dagelijkse activiteit
+gb.activeRepositories = actieve repositories
+gb.activeAuthors = actieve auteurs
+gb.commits = commits
+gb.teams = teams
+gb.teamName = teamnaam
+gb.teamMembers = teamleden
+gb.teamMemberships = teamlidmaatschappen
+gb.newTeam = nieuw team
+gb.permittedTeams = toegestane teams
+gb.emptyRepository = lege repositorie
+gb.repositoryUrl = repositorie url
+gb.mailingLists = mailing lijsten
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = custom velden
+gb.customFieldsDescription = custom velden beschikbaar voor Groovy hooks
+gb.accessPermissions = toegangsrechten
+gb.filters = filters
+gb.generalDescription = algemene instellingen
+gb.accessPermissionsDescription = beperk toegang voor gebruikers en teams
+gb.accessPermissionsForUserDescription = stel teamlidmaatschappen in of geef toegang tot specifieke besloten repositories
+gb.accessPermissionsForTeamDescription = stel teamlidmaatschappen in en geef toegang tot specifieke besloten repositories
+gb.federationRepositoryDescription = deel deze repositorie met andere Gitblit servers
+gb.hookScriptsDescription = run Groovy scripts bij pushes naar deze Gitblit server
+gb.reset = reset
+gb.pages = paginas
+gb.workingCopy = werkkopie
+gb.workingCopyWarning = deze repositorie heeft een werkkopie en kan geen pushes ontvangen
+gb.query = query
+gb.queryHelp = Standaard query syntax wordt ondersteund.<p/><p/>Zie aub <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> voor informatie.
+gb.queryResults = resultaten {0} - {1} ({2} hits)
+gb.noHits = geen hits
+gb.authored = authored
+gb.committed = committed
+gb.indexedBranches = geïndexeerde branches
+gb.indexedBranchesDescription = kies de branches voor opname in uw Lucene index
+gb.noIndexedRepositoriesWarning = geen van uw repositories is geconfigureerd voor Lucene indexering
+gb.undefinedQueryWarning = query is niet gedefinieerd!
+gb.noSelectedRepositoriesWarning = kies aub één of meerdere repositories!
+gb.luceneDisabled = Lucene indexering staat uit
+gb.failedtoRead = Lezen is mislukt
+gb.isNotValidFile = is geen valide bestand
+gb.failedToReadMessage = Het lezen van de standaard boodschap van {0} is mislukt!
+gb.passwordsDoNotMatch = Wachtwoorden komen niet overeen!
+gb.passwordTooShort = Wachtwoord is te kort. Minimum lengte is {0} karakters.
+gb.passwordChanged = Wachtwoord succesvol gewijzigd.
+gb.passwordChangeAborted = Wijziging wachtwoord afgebroken.
+gb.pleaseSetRepositoryName = Vul aub een repositorie naam in!
+gb.illegalLeadingSlash = Leidende root folder referenties (/) zijn niet toegestaan.
+gb.illegalRelativeSlash = Relatieve folder referenties (../) zijn niet toegestaan.
+gb.illegalCharacterRepositoryName = Illegaal karakter ''{0}'' in repositorie naam!
+gb.selectAccessRestriction = Stel aub een toegangsbeperking in!
+gb.selectFederationStrategy = Selecteer aub een federatie strategie!
+gb.pleaseSetTeamName = Vul aub een teamnaam in!
+gb.teamNameUnavailable = Teamnaam ''{0}'' is niet beschikbaar.
+gb.teamMustSpecifyRepository = Een team moet minimaal één repositorie specificeren.
+gb.teamCreated = Nieuw team ''{0}'' successvol aangemaakt.
+gb.pleaseSetUsername = Vul aub een gebruikersnaam in!
+gb.usernameUnavailable = Gebruikersnaam ''{0}'' is niet beschikbaar.
+gb.combinedMd5Rename = Gitblit is geconfigureerd voor combined-md5 wachtwoord hashing. U moet een nieuw wachtwoord opgeven bij het hernoemen van een account.
+gb.userCreated = Nieuwe gebruiker ''{0}'' succesvol aangemaakt.
+gb.couldNotFindFederationRegistration = Kon de federatie registratie niet vinden!
+gb.failedToFindGravatarProfile = Kon het Gravatar profiel voor {0} niet vinden
+gb.branchStats = {0} commits en {1} tags in {2}
+gb.repositoryNotSpecified = Repositorie niet gespecificeerd!
+gb.repositoryNotSpecifiedFor = Repositorie niet gespecificeerd voor {0}!
+gb.canNotLoadRepository = Kan repositorie niet laden
+gb.commitIsNull = Commit is null
+gb.unauthorizedAccessForRepository = Niet toegestane toegang tot repositorie
+gb.failedToFindCommit = Het vinden van commit \"{0}\" in {1} voor {2} pagina is mislukt!
+gb.couldNotFindFederationProposal = Kon federatievoorstel niet vinden!
+gb.invalidUsernameOrPassword = Onjuiste gebruikersnaam of wachtwoord!
+gb.OneProposalToReview = Er is 1 federatie voorstel dat wacht op review.
+gb.nFederationProposalsToReview = Er zijn {0} federatie verzoeken die wachten op review.
+gb.couldNotFindTag = Kon tag {0} niet vinden
+gb.couldNotCreateFederationProposal = Kon geen federatie voorstel maken!
+gb.pleaseSetGitblitUrl = Vul aub uw Gitblit url in!
+gb.pleaseSetDestinationUrl = Vul aub een bestemmings-url in voor uw voorstel!
+gb.proposalReceived = Voorstel correct ontvangen door {0}.
+gb.noGitblitFound = Sorry, {0} kon geen Gitblit instance vinden op {1}.
+gb.noProposals = Sorry, {0} accepteert geen voorstellen op dit moment.
+gb.noFederation = Sorry, {0} is niet geconfigureerd voor het federeren met een Gitblit instance.
+gb.proposalFailed = Sorry, {0} ontving geen voorstelgegevens!
+gb.proposalError = Sorry, {0} rapporteert dat een onverwachte fout is opgetreden!
+gb.failedToSendProposal = Voorstel verzenden is niet gelukt!
+gb.userServiceDoesNotPermitAddUser = {0} staat het toevoegen van een gebruikersaccount niet toe!
+gb.userServiceDoesNotPermitPasswordChanges = {0} staat wachtwoord wijzigingen niet toe!
+gb.displayName = display naam
+gb.emailAddress = emailadres
+gb.errorAdminLoginRequired = Aanmelden vereist voor beheerwerk
+gb.errorOnlyAdminMayCreateRepository = Alleen een beheerder kan een repositorie maken
+gb.errorOnlyAdminOrOwnerMayEditRepository = Alleen een beheerder of de eigenaar kan een repositorie wijzigen
+gb.errorAdministrationDisabled = Beheer is uitgeschakeld
+gb.lastNDays = laatste {0} dagen
+gb.completeGravatarProfile = Completeer profiel op Gravatar.com
+gb.none = geen
+gb.line = regel
+gb.content = inhoud
+gb.empty = leeg
+gb.inherited = geërfd
+gb.deleteRepository = Verwijder repositorie \"{0}\"?
+gb.repositoryDeleted = Repositorie ''{0}'' verwijderd.
+gb.repositoryDeleteFailed = Verwijdering van repositorie ''{0}'' mislukt!
+gb.deleteUser = Verwijder gebruiker \"{0}\"?
+gb.userDeleted = Gebruiker ''{0}'' verwijderd.
+gb.userDeleteFailed = Verwijdering van gebruiker ''{0}'' mislukt!
+gb.time.justNow = net
+gb.time.today = vandaag
+gb.time.yesterday = gisteren
+gb.time.minsAgo = {0} minuten geleden
+gb.time.hoursAgo = {0} uren geleden
+gb.time.daysAgo = {0} dagen geleden
+gb.time.weeksAgo = {0} weken geleden
+gb.time.monthsAgo = {0} maanden geleden
+gb.time.oneYearAgo = 1 jaar geleden
+gb.time.yearsAgo = {0} jaren geleden
+gb.duration.oneDay = 1 dag
+gb.duration.days = {0} dagen
+gb.duration.oneMonth = 1 maand
+gb.duration.months = {0} maanden
+gb.duration.oneYear = 1 jaar
+gb.duration.years = {0} jaren
+gb.authorizationControl = authorisatiebeheer
+gb.allowAuthenticatedDescription = ken RW+ rechten toe aan alle geautoriseerde gebruikers
+gb.allowNamedDescription = ken verfijnde rechten toe aan genoemde gebruikers of teams
+gb.markdownFailure = Het parsen van Markdown content is mislukt!
+gb.clearCache = maak cache leeg
+gb.projects = projecten
+gb.project = project
+gb.allProjects = alle projecten
+gb.copyToClipboard = kopieer naar clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = {0} is geforked
+gb.repositoryForkFailed= fork is mislukt
+gb.personalRepositories = personlijke repositories
+gb.allowForks = sta forks toe
+gb.allowForksDescription = sta geauthoriseerde gebruikers toe om deze repositorie te forken
+gb.forkedFrom = geforked vanaf
+gb.canFork = kan geforked worden
+gb.canForkDescription = kan geauthoriseerde repositories forken naar persoonlijke repositories
+gb.myFork = toon mijn fork
+gb.forksProhibited = forks niet toegestaan
+gb.forksProhibitedWarning = deze repositorie staat forken niet toe
+gb.noForks = {0} heeft geen forks
+gb.forkNotAuthorized = sorry, u bent niet geautoriseerd voor het forken van {0}
+gb.forkInProgress = bezig met forken
+gb.preparingFork = bezig met het maken van uw fork...
+gb.isFork = is een fork
+gb.canCreate = mag maken
+gb.canCreateDescription = mag persoonlijke repositories maken
+gb.illegalPersonalRepositoryLocation = uw persoonlijke repositorie moet te vinden zijn op \"{0}\"
+gb.verifyCommitter = controleer committer
+gb.verifyCommitterDescription = vereis dat committer identiteit overeen komt met pushing Gitblt gebruikersaccount
+gb.verifyCommitterNote = alle merges vereisen "--no-ff" om committer identiteit af te dwingen
+gb.repositoryPermissions = repository rechten
+gb.userPermissions = gebruikersrechten
+gb.teamPermissions = teamrechten
+gb.add = toevoegen
+gb.noPermission = VERWIJDER DIT RECHT
+gb.excludePermission = {0} (exclude)
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creëer)
+gb.deletePermission = {0} (push, ref creëer+verwijdering)
+gb.rewindPermission = {0} (push, ref creëer+verwijdering+rewind)
+gb.permission = recht
+gb.regexPermission = dit recht is gezet vanaf de reguliere expressie \"{0}\"
+gb.accessDenied = toegang geweigerd
+gb.busyCollectingGarbage = sorry, Gitblit is bezig met opruimen in {0}
+gb.gcPeriod = opruim periode
+gb.gcPeriodDescription = tijdsduur tussen opruimacties
+gb.gcThreshold = opruim drempel
+gb.gcThresholdDescription = minimum totaalomvang van losse objecten voor het starten van opruimactie
+gb.ownerPermission = repositorie eigenaar
+gb.administrator = beheer
+gb.administratorPermission = Gitblit beheerder
+gb.team = team
+gb.teamPermission = permissie ingesteld via \"{0}\" teamlidmaatschap
+gb.missing = ontbrekend!
+gb.missingPermission = de repositorie voor deze permissie ontbreekt!
+gb.mutable = te wijzigen
+gb.specified = gespecificeerd
+gb.effective = geldig
+gb.organizationalUnit = organisatie eenheid
+gb.organization = organisatie
+gb.locality = localiteit
+gb.stateProvince = staat of provincie
+gb.countryCode = landcode
+gb.properties = eigenschappen
+gb.issued = uitgegeven
+gb.expires = verloopt op
+gb.expired = verlopen
+gb.expiring = verloopt
+gb.revoked = ingetrokken
+gb.serialNumber = serie nummer
+gb.certificates = certificaten
+gb.newCertificate = nieuwe certificaten
+gb.revokeCertificate = trek certificaat in
+gb.sendEmail = zend email
+gb.passwordHint = wachtwoord hint
+gb.ok = ok
+gb.invalidExpirationDate = ongeldige verloopdatum!
+gb.passwordHintRequired = wachtwoord hint vereist!
+gb.viewCertificate = toon certificaat
+gb.subject = onderwerp
+gb.issuer = issuer
+gb.validFrom = geldig vanaf
+gb.validUntil = geldig tot
+gb.publicKey = publieke sleutel
+gb.signatureAlgorithm = signature algoritme
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.md5FingerPrint = MD5 Fingerprint
+gb.reason = reden
+gb.revokeCertificateReason = Kies aub een reden voor het intrekken van het certificaat
+gb.unspecified = niet gespecificeerd
+gb.keyCompromise = sleutel gecompromitteerd
+gb.caCompromise = CA gecompromitteerd
+gb.affiliationChanged = affiliatie gewijzigd
+gb.superseded = opgevolgd
+gb.cessationOfOperation = gestaakt
+gb.privilegeWithdrawn = privilege ingetrokken
+gb.time.inMinutes = in {0} minuten
+gb.time.inHours = in {0} uren
+gb.time.inDays = in {0} dagen
+gb.hostname = hostnaam
+gb.hostnameRequired = Vul aub een hostnaam in
+gb.newSSLCertificate = nieuw server SSL certificaat
+gb.newCertificateDefaults = nieuw certificaat defaults
+gb.duration = duur
+gb.certificateRevoked = Certificaat {0,number,0} is ingetrokken
+gb.clientCertificateGenerated = Nieuw client certificaat voor {0} succesvol gegenereerd
+gb.sslCertificateGenerated = Nieuw server SSL certificaat voor {0} succesvol gegenereerd
+gb.newClientCertificateMessage = MERK OP:\nHet 'wachtwoord' is niet het wachtwoord van de gebruiker. Het is het wachtwoord voor het afschermen van de sleutelring van de gebruiker. Dit wachtwoord wordt niet opgeslagen dus moet u ook een 'hint' invullen die zal worden opgenomen in de README instructies van de gebruiker.
+gb.certificate = certificaat
+gb.emailCertificateBundle = email client certificaat bundel
+gb.pleaseGenerateClientCertificate = Genereer aub een client certificaat voor {0}
+gb.clientCertificateBundleSent = Client certificaat bundel voor {0} verzonden
+gb.enterKeystorePassword = Vul aub het Gitblit keystore wachtwoord in
+gb.warning = waarschuwing
+gb.jceWarning = Uw Java Runtime Environment heeft geen \"JCE Unlimited Strength Jurisdiction Policy\" bestanden.\nDit zal de lengte van wachtwoorden voor het eventueel versleutelen van uw keystores beperken tot 7 karakters.\nDeze policy bestanden zijn een optionele download van Oracle.\n\nWilt u toch doorgaan en de certificaat infrastructuur genereren?\n\nNee antwoorden zal uw browser doorsturen naar de downloadpagina van Oracle zodat u de policybestanden kunt downloaden.
+gb.maxActivityCommits = maximum activiteit commits
+gb.maxActivityCommitsDescription = maximum aantal commits om bij te dragen aan de Activiteitspagina
+gb.noMaximum = geen maximum
+gb.attributes = attributen
+gb.serveCertificate = gebruik deze certificaten voor https
+gb.sslCertificateGeneratedRestart = Nieuwe SSL certificaten voor {0} succesvol gegenereerd.\nU dient Gitblit te herstarten om de nieuwe certificaten te gebruiken.\n\nAls u opstart met de '--alias' parameter moet u die wijzigen naar ''--alias {0}''.
+gb.validity = geldigheid
+gb.siteName = site naam
+gb.siteNameDescription = korte, verduidelijkende naam van deze server
+gb.excludeFromActivity = sluit uit van activiteitspagina
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties b/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties new file mode 100644 index 00000000..469d2055 --- /dev/null +++ b/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties @@ -0,0 +1,442 @@ +gb.repository = repositório
+gb.owner = proprietário
+gb.description = descrição
+gb.lastChange = última alteração
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = autor
+gb.committer = committer
+gb.commit = commit
+gb.tree = árvore
+gb.parent = parent
+gb.url = URL
+gb.history = histórico
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = atribuído
+gb.ticketOpenDate = data da abertura
+gb.ticketState = estado
+gb.ticketComments = comentários
+gb.view = visualizar
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = mais commits...
+gb.allTags = todas as tags...
+gb.allBranches = todos os branches...
+gb.summary = resumo
+gb.ticket = ticket
+gb.newRepository = novo repositório
+gb.newUser = novo usuário
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = primeira
+gb.pagePrevious anterior
+gb.pageNext = próxima
+gb.head = HEAD
+gb.blame = blame
+gb.login = login
+gb.logout = logout
+gb.username = username
+gb.password = password
+gb.tagger = tagger
+gb.moreHistory = mais histórico...
+gb.difftocurrent = diff para a atual
+gb.search = pesquisar
+gb.searchForAuthor = Procurar por commits cujo autor é
+gb.searchForCommitter = Procurar por commits commitados por é
+gb.addition = adicionados
+gb.modification = modificados
+gb.deletion = apagados
+gb.rename = renomear
+gb.metrics = métricas
+gb.stats = estatísticas
+gb.markdown = markdown
+gb.changedFiles = arquivos alterados
+gb.filesAdded = {0} arquivos adicionados
+gb.filesModified = {0} arquivos modificados
+gb.filesDeleted = {0} arquivos deletados
+gb.filesCopied = {0} arquivos copiados
+gb.filesRenamed = {0} arquivos renomeados
+gb.missingUsername = Username desconhecido
+gb.edit = editar
+gb.searchTypeTooltip = Selecione o Tipo de Pesquisa
+gb.searchTooltip = Pesquisar {0}
+gb.delete = deletar
+gb.docs = documentos
+gb.accessRestriction = restrição de acesso
+gb.name = nome
+gb.enableTickets = ativar tickets
+gb.enableDocs = ativar documentação
+gb.save = salvar
+gb.showRemoteBranches = mostrar branches remotos
+gb.editUsers = editar usuários
+gb.confirmPassword = confirmar password
+gb.restrictedRepositories = repositórios restritos
+gb.canAdmin = pode administrar
+gb.notRestricted = visualização anônima, clone, & push
+gb.pushRestricted = push autênticado
+gb.cloneRestricted = clone & push autênticados
+gb.viewRestricted = view, clone, & push autênticados
+gb.useTicketsDescription = somente leitura, issues do Ticgit distribuídos
+gb.useDocsDescription = enumerar documentação Markdown no repositório
+gb.showRemoteBranchesDescription = mostrar branches remotos
+gb.canAdminDescription = pode administrar o server Gitblit
+gb.permittedUsers = usuários autorizados
+gb.isFrozen = congelar
+gb.isFrozenDescription = proibir fazer push
+gb.zip = zip
+gb.showReadme = mostrar readme
+gb.showReadmeDescription = mostrar um arquivo \"leia-me\" na página de resumo
+gb.nameDescription = usar '/' para agrupar repositórios. e.g. libraries/mycoollib.git
+gb.ownerDescription = o proprietário pode editar configurações do repositório
+gb.blob = blob
+gb.commitActivityTrend = tendência dos commits
+gb.commitActivityDOW = commits diários
+gb.commitActivityAuthors = principais committers
+gb.feed = feed
+gb.cancel = cancelar
+gb.changePassword = alterar password
+gb.isFederated = está federado
+gb.federateThis = federar este repositório
+gb.federateOrigin = federar o origin
+gb.excludeFromFederation = excluir da federação
+gb.excludeFromFederationDescription = bloquear instâncias federadas do GitBlit de fazer pull desta conta
+gb.tokens = tokens de federação
+gb.tokenAllDescription = todos repositórios, usuários & configurações
+gb.tokenUnrDescription = todos repositórios & usuários
+gb.tokenJurDescription = todos repositórios
+gb.federatedRepositoryDefinitions = definições de repositório
+gb.federatedUserDefinitions = definições de usuários
+gb.federatedSettingDefinitions = definições de configurações
+gb.proposals = propostas de federações
+gb.received = recebidos
+gb.type = tipo
+gb.token = token
+gb.repositories = repositórios
+gb.proposal = propostas
+gb.frequency = frequência
+gb.folder = pasta
+gb.lastPull = último pull
+gb.nextPull = próximo pull
+gb.inclusions = inclusões
+gb.exclusions = excluões
+gb.registration = cadastro
+gb.registrations = cadastro de federações
+gb.sendProposal = enviar proposta
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = alterar a ref o qual a HEAD aponta. e.g. refs/heads/master
+gb.federationStrategy = estratégia de federação
+gb.federationRegistration = cadastro de federações
+gb.federationResults = resultados dos pulls de federações
+gb.federationSets = ajustes de federações
+gb.message = mensagem
+gb.myUrlDescription = a url de acesso público para a instância Gitblit
+gb.destinationUrl = enviar para
+gb.destinationUrlDescription = a url da intância do Gitblit para enviar sua proposta
+gb.users = usuários
+gb.federation = federação
+gb.error = erro
+gb.refresh = atualizar
+gb.browse = navegar
+gb.clone = clonar
+gb.filter = filtrar
+gb.create = criar
+gb.servers = servidores
+gb.recent = recente
+gb.available = disponível
+gb.selected = selecionado
+gb.size = tamanho
+gb.downloading = downloading
+gb.loading = loading
+gb.starting = inciando
+gb.general = geral
+gb.settings = configurações
+gb.manage = administrar
+gb.lastLogin = último login
+gb.skipSizeCalculation = ignorar cálculo do tamanho
+gb.skipSizeCalculationDescription = não calcular o tamanho do repositório (reduz o tempo de load da página)
+gb.skipSummaryMetrics = ignorar resumo das métricas
+gb.skipSummaryMetricsDescription = não calcular métricas na página de resumo
+gb.accessLevel = acesso
+gb.default = default
+gb.setDefault = tornar default
+gb.since = desde
+gb.status = status
+gb.bootDate = data do boot
+gb.servletContainer = servlet container
+gb.heapMaximum = heap máximo
+gb.heapAllocated = alocar heap
+gb.heapUsed = usar heap
+gb.free = free
+gb.version = versão
+gb.releaseDate = data de release
+gb.date = data
+gb.activity = atividade
+gb.subscribe = inscrever
+gb.branch = branch
+gb.maxHits = hits máximos
+gb.recentActivity = atividade recente
+gb.recentActivityStats = últimos {0} dias / {1} commits por {2} autores
+gb.recentActivityNone = últimos {0} dias / nenhum
+gb.dailyActivity = atividade diária
+gb.activeRepositories = repositórios ativos
+gb.activeAuthors = autores ativos
+gb.commits = commits
+gb.teams = equipes
+gb.teamName = nome da equipe
+gb.teamMembers = membros
+gb.teamMemberships = filiações em equipes
+gb.newTeam = nova equipe
+gb.permittedTeams = equipes permitidas
+gb.emptyRepository = repositório vazio
+gb.repositoryUrl = url do repositório
+gb.mailingLists = listas de e-mails
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = campos customizados
+gb.customFieldsDescription = campos customizados disponíveis para Groovy hooks
+gb.accessPermissions = permissões de acesso
+gb.filters = filtros
+gb.generalDescription = configurações comuns
+gb.accessPermissionsDescription = restringir acesso por usuários e equipes
+gb.accessPermissionsForUserDescription = ajustar filiações em equipes ou garantir acesso a repositórios específicos
+gb.accessPermissionsForTeamDescription = ajustar membros da equipe e garantir acesso a repositórios específicos
+gb.federationRepositoryDescription = compartilhar este repositório com outros servidores Gitblit
+gb.hookScriptsDescription = rodar scripts Groovy nos pushes pushes para este servidor Gitblit
+gb.reset = reset
+gb.pages = páginas
+gb.workingCopy = working copy
+gb.workingCopyWarning = this repository has a working copy and can not receive pushes
+gb.query = query
+gb.queryHelp = Standard query syntax é suportada.<p/><p/>Por favor veja <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> para mais detalhes.
+gb.queryResults = resultados {0} - {1} ({2} hits)
+gb.noHits = sem hits
+gb.authored = foi autor do
+gb.committed = committed
+gb.indexedBranches = branches indexados
+gb.indexedBranchesDescription = selecione os branches para incluir nos seus índices Lucene
+gb.noIndexedRepositoriesWarning = nenhum dos seus repositórios foram configurados para indexação do Lucene
+gb.undefinedQueryWarning = a query não foi definida!
+gb.noSelectedRepositoriesWarning = por favor selecione um ou mais repositórios!
+gb.luceneDisabled = indexação do Lucene está desabilitada
+gb.failedtoRead = leitura falhou
+gb.isNotValidFile = não é um arquivo válido
+gb.failedToReadMessage = Falhou em ler mensagens default de {0}!
+gb.passwordsDoNotMatch = Passwords não conferem!
+gb.passwordTooShort = Password é muito curto. Tamanho mínimo são {0} caracteres.
+gb.passwordChanged = Password alterado com sucesso.
+gb.passwordChangeAborted = alteração do Password foi abortada.
+gb.pleaseSetRepositoryName = Por favor ajuste o nome do repositório!
+gb.illegalLeadingSlash = Referências a diretórios raiz começando com (/) são proibidas.
+gb.illegalRelativeSlash = Referências a diretórios relativos (../) são proibidas.
+gb.illegalCharacterRepositoryName = Caractere ilegal ''{0}'' no nome do repositório!
+gb.selectAccessRestriction = Please select access restriction!
+gb.selectFederationStrategy = Por favor selecione a estratégia de federação!
+gb.pleaseSetTeamName = Por favor insira um nome de equipe!
+gb.teamNameUnavailable = O nome de equipe ''{0}'' está indisponível.
+gb.teamMustSpecifyRepository = Uma equipe deve especificar pelo menos um repositório.
+gb.teamCreated = Nova equipe ''{0}'' criada com sucesso.
+gb.pleaseSetUsername = Por favor entre com um username!
+gb.usernameUnavailable = Username ''{0}'' está indisponível.
+gb.combinedMd5Rename = Gitblit está configurado para usar um hash combinado-md5. Você deve inserir um novo password ao renamear a conta.
+gb.userCreated = Novo usuário ''{0}'' criado com sucesso.
+gb.couldNotFindFederationRegistration = Não foi possível localizar o registro da federação!
+gb.failedToFindGravatarProfile = Falhou em localizar um perfil Gravatar para {0}
+gb.branchStats = {0} commits e {1} tags em {2}
+gb.repositoryNotSpecified = Repositório não específicado!
+gb.repositoryNotSpecifiedFor = Repositório não específicado para {0}!
+gb.canNotLoadRepository = Não foi possível carregar o repositório
+gb.commitIsNull = Commit está nulo
+gb.unauthorizedAccessForRepository = Acesso não autorizado para o repositório
+gb.failedToFindCommit = Não foi possível achar o commit \"{0}\" em {1} para {2} página!
+gb.couldNotFindFederationProposal = Não foi possível localizar propostas de federação!
+gb.invalidUsernameOrPassword = username ou password inválido!
+gb.OneProposalToReview = Existe uma proposta de federação aguardando revisão.
+gb.nFederationProposalsToReview = Existem {0} propostas de federação aguardando revisão.
+gb.couldNotFindTag = Não foi possível localizar a tag {0}
+gb.couldNotCreateFederationProposal = Não foi possível criar uma proposta de federation!
+gb.pleaseSetGitblitUrl = Por favor insira sua url do Gitblit!
+gb.pleaseSetDestinationUrl = Por favor insira a url de destino para sua proposta!
+gb.proposalReceived = Proposta recebida com sucesso por {0}.
+gb.noGitblitFound = Desculpe, {0} não localizou uma instância do Gitblit em {1}.
+gb.noProposals = Desculpe, {0} não está aceitando propostas agora.
+gb.noFederation = Desculpe, {0} não está configurado com nenhuma intância do Gitblit.
+gb.proposalFailed = Desculpe, {0} não recebeu nenhum dado de proposta!
+gb.proposalError = Desculpe, {0} reportou que um erro inesperado ocorreu!
+gb.failedToSendProposal = Não foi possível enviar a proposta!
+gb.userServiceDoesNotPermitAddUser = {0} não permite adicionar uma conta de usuário!
+gb.userServiceDoesNotPermitPasswordChanges = {0} não permite alterações no password!
+gb.displayName = nome
+gb.emailAddress = e-mail
+gb.errorAdminLoginRequired = Administração requer um login
+gb.errorOnlyAdminMayCreateRepository = Somente umadministrador pode criar um repositório
+gb.errorOnlyAdminOrOwnerMayEditRepository = Somente umadministrador pode editar um repositório
+gb.errorAdministrationDisabled = Administração está desabilitada
+gb.lastNDays = últimos {0} dias
+gb.completeGravatarProfile = Profile completo em Gravatar.com
+gb.none = nenhum
+gb.line = linha
+gb.content = conteúdo
+gb.empty = vazio
+gb.inherited = herdado
+gb.deleteRepository = Deletar repositório \"{0}\"?
+gb.repositoryDeleted = Repositório ''{0}'' deletado.
+gb.repositoryDeleteFailed = Não foi possível apagar o repositório ''{0}''!
+gb.deleteUser = Deletar usuário \"{0}\"?
+gb.userDeleted = Usuário ''{0}'' deletado.
+gb.userDeleteFailed = Não foi possível apagar o usuário ''{0}''!
+gb.time.justNow = agora mesmo
+gb.time.today = hoje
+gb.time.yesterday = ontem
+gb.time.minsAgo = há {0} minutos
+gb.time.hoursAgo = há {0} horas
+gb.time.daysAgo = há {0} dias
+gb.time.weeksAgo = há {0} semanas
+gb.time.monthsAgo = há {0} meses
+gb.time.oneYearAgo = há 1 ano
+gb.time.yearsAgo = há {0} anos
+gb.duration.oneDay = 1 dia
+gb.duration.days = {0} dias
+gb.duration.oneMonth = 1 mês
+gb.duration.months = {0} meses
+gb.duration.oneYear = 1 ano
+gb.duration.years = {0} anos
+gb.authorizationControl = controle de autorização
+gb.allowAuthenticatedDescription = conceder permissão RW+ para todos os usuários autênticados
+gb.allowNamedDescription = conceder permissões refinadas para usuários escolhidos ou equipes
+gb.markdownFailure = Não foi possível converter conteúdo Markdown!
+gb.clearCache = limpar o cache
+gb.projects = projetos
+gb.project = projeto
+gb.allProjects = todos projetos
+gb.copyToClipboard = copiar para o clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = fork feito em {0}
+gb.repositoryForkFailed= não foi possível fazer fork
+gb.personalRepositories = repositórios pessoais
+gb.allowForks = permitir forks
+gb.allowForksDescription = permitir usuários autorizados a fazer fork deste repositório
+gb.forkedFrom = forked de
+gb.canFork = pode fazer fork
+gb.canForkDescription = pode fazer fork de repositórios autorizados para repositórios pessoais
+gb.myFork = visualizar meu fork
+gb.forksProhibited = forks proibidos
+gb.forksProhibitedWarning = este repositório proíbe forks
+gb.noForks = {0} não possui forks
+gb.forkNotAuthorized = desculpe, você não está autorizado a fazer fork de {0}
+gb.forkInProgress = fork em progresso
+gb.preparingFork = preparando seu fork...
+gb.isFork = é fork
+gb.canCreate = pode criar
+gb.canCreateDescription = pode criar repositórios pessoais
+gb.illegalPersonalRepositoryLocation = seu repositório pessoal deve estar localizado em \"{0}\"
+gb.verifyCommitter = verificar committer
+gb.verifyCommitterDescription = requer a identidade do committer para combinar com uma conta do Gitblt
+gb.verifyCommitterNote = todos os merges requerem "--no-ff" para impor a identidade do committer
+gb.repositoryPermissions = permissões de repositório
+gb.userPermissions = permissões de usuário
+gb.teamPermissions = permissões de equipe
+gb.add = add
+gb.noPermission = APAGAR ESTA PERMISSÃO
+gb.excludePermission = {0} (excluir)
+gb.viewPermission = {0} (visualizar)
+gb.clonePermission = {0} (clonar)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = permissão
+gb.regexPermission = esta permissão foi configurada através da expressão regular \"{0}\"
+gb.accessDenied = acesso negado
+gb.busyCollectingGarbage = desculpe, o Gitblit está ocupado coletando lixo em {0}
+gb.gcPeriod = período do GC
+gb.gcPeriodDescription = duração entre as coletas de lixo
+gb.gcThreshold = limite do GC
+gb.gcThresholdDescription = tamanho total mínimo de objetos \"soltos\" que ativam a coleta de lixo
+gb.ownerPermission = proprietário do repositório
+gb.administrator = administrador
+gb.administratorPermission = administrador do Gitblit
+gb.team = equipe
+gb.teamPermission = permissão concedida pela filiação a equipe \"{0}\"
+gb.missing = faltando!
+gb.missingPermission = o repositório para esta permissão está faltando!
+gb.mutable = mutável
+gb.specified = específico
+gb.effective = efetivo
+gb.organizationalUnit = unidade organizacional
+gb.organization = organização
+gb.locality = localidade
+gb.stateProvince = estado ou província
+gb.countryCode = código do país
+gb.properties = propriedades
+gb.issued = emitido
+gb.expires = expira
+gb.expired = expirado
+gb.expiring = expirando
+gb.revoked = revogado
+gb.serialNumber = número serial
+gb.certificates = certificados
+gb.newCertificate = novo certificado
+gb.revokeCertificate = revogar certificado
+gb.sendEmail = enviar email
+gb.passwordHint = dica de password
+gb.ok = ok
+gb.invalidExpirationDate = data de expiração inválida!
+gb.passwordHintRequired = dica de password requerida!
+gb.viewCertificate = visualizar certificado
+gb.subject = assunto
+gb.issuer = emissor
+gb.validFrom = válido a partir de
+gb.validUntil = válido até
+gb.publicKey = chave pública
+gb.signatureAlgorithm = algoritmo de assinatura
+gb.sha1FingerPrint = digital SHA-1
+gb.md5FingerPrint = digital MD5
+gb.reason = razão
+gb.revokeCertificateReason = Por selecione a razão da revogação do certificado
+gb.unspecified = não específico
+gb.keyCompromise = comprometimento de chave
+gb.caCompromise = compromisso CA
+gb.affiliationChanged = afiliação foi alterada
+gb.superseded = substituídas
+gb.cessationOfOperation = cessação de funcionamento
+gb.privilegeWithdrawn = privilégio retirado
+gb.time.inMinutes = em {0} minutos
+gb.time.inHours = em {0} horas
+gb.time.inDays = em {0} dias
+gb.hostname = hostname
+gb.hostnameRequired = Por favor insira um hostname
+gb.newSSLCertificate = novo servidor de certificado SSL
+gb.newCertificateDefaults = novos padrões de certificação
+gb.duration = duração
+gb.certificateRevoked = Certificado {0, número, 0} foi revogado
+gb.clientCertificateGenerated = Novo certificado cliente para {0} foi gerado com sucesso
+gb.sslCertificateGenerated = Novo servidor de certificado SSL gerado com sucesso para {0}
+gb.newClientCertificateMessage = OBSERVAÇÃO:\nO 'password' não é o password do usuário mas sim o password usado para proteger a keystore. Este password não será salvo então você também inserir uma dica que será incluída nas instruções de LEIA-ME do usuário.
+gb.certificate = certificado
+gb.emailCertificateBundle = pacote certificado de cliente de email
+gb.pleaseGenerateClientCertificate = Por favor gere um certificado cliente para {0}
+gb.clientCertificateBundleSent = Pacote de certificado de cliente para {0} enviada
+gb.enterKeystorePassword = Por favor insira uma chave para keystore do Gitblit
+gb.warning = warning
+gb.jceWarning = Seu Java Runtime Environment não tem os arquivos \"JCE Unlimited Strength Jurisdiction Policy\".\nIsto irá limitar o tamanho dos passwords que você usará para encriptar suas keystores para 7 caracteres.\nEstes arquivos de políticas são um download opcional da Oracle.\n\nVocê gostaria de continuar e gerar os certificados de infraestrutura de qualquer forma?\n\nRespondendo "Não" irá redirecionar o seu browser para a página de downloads da Oracle, de onde você poderá fazer download desses arquivos.
+gb.maxActivityCommits = limitar exibição de commits
+gb.maxActivityCommitsDescription = quantidade máxima de commits para contribuir para a página de atividade
+gb.noMaximum = ilimitado
+gb.attributes = atributos
+gb.serveCertificate = servir https com este certificado
+gb.sslCertificateGeneratedRestart = Novo certificado SSL de servidor gerado com sucesso para {0}.\nVocê deve reiniciar o Gitblit para usar o novo certificado.\n\nSe você estiver executando com o parâmetro '--alias', você precisará alterá-lo para ''--alias {0}''.
+gb.validity = validade
+gb.siteName = nome do site
+gb.siteNameDescription = breve mas um nome descritivo para seu servidor
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties b/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties new file mode 100644 index 00000000..b5715850 --- /dev/null +++ b/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties @@ -0,0 +1,444 @@ +gb.repository = \u7248\u672c\u5e93 +gb.owner = \u7ba1\u7406\u5458 +gb.description = \u63cf\u8ff0 +gb.lastChange = \u6700\u8fd1\u4fee\u6539 +gb.refs = refs +gb.tag = \u6807\u7b7e +gb.tags = \u6807\u7b7e +gb.author = \u7528\u6237 +gb.committer = \u63d0\u4ea4\u8005 +gb.commit = \u63d0\u4ea4 +gb.tree = \u76ee\u5f55 +gb.parent = parent +gb.url = URL +gb.history = \u5386\u53f2\u4fe1\u606f +gb.raw = raw +gb.object = object +gb.ticketId = ticket id +gb.ticketAssigned = assigned +gb.ticketOpenDate = \u5f00\u542f\u65e5\u671f +gb.ticketState = \u72b6\u6001 +gb.ticketComments = \u8bc4\u8bba +gb.view = \u67e5\u770b +gb.local = \u672c\u5730 +gb.remote = \u8fdc\u7a0b +gb.branches = \u5206\u652f +gb.patch = patch +gb.diff = \u5bf9\u6bd4 +gb.log = \u65e5\u5fd7 +gb.moreLogs = \u66f4\u591a\u63d0\u4ea4... +gb.allTags = \u6240\u6709\u6807\u7b7e... +gb.allBranches = \u6240\u6709\u5206\u652f... +gb.summary = \u6982\u51b5 +gb.ticket = ticket +gb.newRepository = \u521b\u5efa\u7248\u672c\u5e93 +gb.newUser = \u6dfb\u52a0\u7528\u6237 +gb.commitdiff = \u5bf9\u6bd4\u63d0\u4ea4\u7684\u5185\u5bb9 +gb.tickets = tickets +gb.pageFirst = \u9996\u9875 +gb.pagePrevious = \u524d\u4e00\u9875 +gb.pageNext = \u4e0b\u4e00\u9875 +gb.head = HEAD +gb.blame = blame +gb.login = \u767b\u5f55 +gb.logout = \u6ce8\u9500 +gb.username = \u7528\u6237\u540d +gb.password = \u5bc6\u7801 +gb.tagger = \u6807\u8bb0\u8005 +gb.moreHistory = \u66f4\u591a\u7684\u5386\u53f2\u4fe1\u606f... +gb.difftocurrent = \u5bf9\u6bd4\u5f53\u524d +gb.search = \u641c\u7d22 +gb.searchForAuthor = \u6309\u4f5c\u8005\u641c\u7d22 commits +gb.searchForCommitter = \u6309\u63d0\u4ea4\u8005\u641c\u7d22 commits +gb.addition = \u6dfb\u52a0 +gb.modification = \u4fee\u6539 +gb.deletion = \u5220\u9664 +gb.rename = \u91cd\u547d\u540d +gb.metrics = metrics +gb.stats = \u7edf\u8ba1 +gb.markdown = markdown +gb.changedFiles = \u5df2\u4fee\u6539\u6587\u4ef6 +gb.filesAdded = {0}\u4e2a\u6587\u4ef6\u5df2\u6dfb\u52a0 +gb.filesModified = {0}\u4e2a\u6587\u4ef6\u5df2\u4fee\u6539 +gb.filesDeleted = {0}\u4e2a\u6587\u4ef6\u5df2\u5220\u9664 +gb.filesCopied = {0} \u6587\u4ef6\u5df2\u590d\u5236 +gb.filesRenamed = {0} \u6587\u4ef6\u5df2\u91cd\u547d\u540d +gb.missingUsername = \u7528\u6237\u540d\u4e0d\u5b58\u5728 +gb.edit = \u7f16\u8f91 +gb.searchTypeTooltip = \u9009\u62e9\u641c\u7d22\u7c7b\u578b +gb.searchTooltip = \u641c\u7d22 {0} +gb.delete = \u5220\u9664 +gb.docs = \u6587\u6863 +gb.accessRestriction = \u8bbf\u95ee\u9650\u5236 +gb.name = \u540d\u79f0 +gb.enableTickets = \u5141\u8bb8 tickets +gb.enableDocs = \u5141\u8bb8\u6587\u6863 +gb.save = \u4fdd\u5b58 +gb.showRemoteBranches = \u663e\u793a\u8fdc\u7a0b\u5206\u652f +gb.editUsers = \u7f16\u8f91\u7528\u6237 +gb.confirmPassword = \u786e\u8ba4\u5bc6\u7801 +gb.restrictedRepositories = \u7248\u672c\u5e93\u8bbe\u7f6e +gb.canAdmin = \u7ba1\u7406\u6743\u9650 +gb.notRestricted = anonymous view, clone, & push +gb.pushRestricted = authenticated push +gb.cloneRestricted = authenticated clone & push +gb.viewRestricted = authenticated view, clone, & push +gb.useTicketsDescription = distributed Ticgit issues +gb.useDocsDescription = \u5217\u51fa\u7248\u672c\u5e93\u5185\u6240\u6709 Markdown \u6587\u6863 +gb.showRemoteBranchesDescription = \u663e\u793a\u8fdc\u7a0b\u5206\u652f +gb.canAdminDescription = Gitblit \u670d\u52a1\u5668\u7ba1\u7406\u5458 +gb.permittedUsers = \u5141\u8bb8\u7528\u6237 +gb.isFrozen = \u88ab\u51bb\u7ed3 +gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001\u64cd\u4f5c +gb.zip = zip +gb.showReadme = \u663e\u793areadme +gb.showReadmeDescription = \u5728\u6982\u51b5\u9875\u9762\u663e\u793a \\"readme\\" Markdown \u6587\u4ef6 +gb.nameDescription = \u4f7f\u7528 '/' \u5bf9\u7248\u672c\u5e93\u8fdb\u884c\u5206\u7ec4 \u4f8b\u5982. libraries/mycoollib.git +gb.ownerDescription = \u521b\u5efa\u8005\u53ef\u4ee5\u7f16\u8f91\u7248\u672c\u5e93\u5c5e\u6027 +gb.blob = blob +gb.commitActivityTrend = commit \u6d3b\u52a8\u8d8b\u52bf +gb.commitActivityDOW = \u6bcf\u5468 commit \u6d3b\u52a8 +gb.commitActivityAuthors = commit \u6d3b\u52a8\u4e3b\u8981\u7528\u6237 +gb.feed = feed +gb.cancel = \u53d6\u6d88 +gb.changePassword = \u4fee\u6539\u5bc6\u7801 +gb.isFederated = is federated +gb.federateThis = federate this repository +gb.federateOrigin = federate the origin +gb.excludeFromFederation = exclude from federation +gb.excludeFromFederationDescription = \u7981\u6b62\u5df2 federated \u7684 Gitblit \u5b9e\u4f8b\u4ece\u672c\u8d26\u6237\u62c9\u53d6 +gb.tokens = federation tokens +gb.tokenAllDescription = all repositories, users, & settings +gb.tokenUnrDescription = all repositories & users +gb.tokenJurDescription = all repositories +gb.federatedRepositoryDefinitions = \u7248\u672c\u5e93\u5b9a\u4e49 +gb.federatedUserDefinitions = \u7528\u6237\u5b9a\u4e49 +gb.federatedSettingDefinitions = \u8bbe\u7f6e\u5b9a\u4e49 +gb.proposals = federation proposals +gb.received = \u5df2\u63a5\u53d7 +gb.type = type +gb.token = token +gb.repositories = \u7248\u672c\u5e93 +gb.proposal = proposal +gb.frequency = \u9891\u7387 +gb.folder = \u6587\u4ef6\u5939 +gb.lastPull = \u4e0a\u4e00\u6b21\u62c9\u53d6 +gb.nextPull = \u4e0b\u4e00\u6b21\u62c9\u53d6 +gb.inclusions = \u5305\u542b\u5185\u5bb9 +gb.exclusions = \u4f8b\u5916 +gb.registration = \u6ce8\u518c +gb.registrations = federation \u6ce8\u518c +gb.sendProposal = propose +gb.status = \u72b6\u6001 +gb.origin = origin +gb.headRef = \u9ed8\u8ba4\u5206\u652f (HEAD) +gb.headRefDescription = \u4fee\u6539 HEAD \u6240\u6307\u5411\u7684 ref\u3002 \u4f8b\u5982: refs/heads/master +gb.federationStrategy = federation \u7b56\u7565 +gb.federationRegistration = federation \u6ce8\u518c +gb.federationResults = federation \u62c9\u53d6\u7ed3\u679c +gb.federationSets = federation \u96c6 +gb.message = \u6d88\u606f +gb.myUrlDescription = \u60a8\u7684 Gitblit \u5b9e\u4f8b\u7684\u516c\u5171\u8bbf\u95ee\u7f51\u5740 +gb.destinationUrl = \u53d1\u9001\u81f3 +gb.destinationUrlDescription = \u4f60\u6240\u8981\u53d1\u9001proposal\u7684 Gitblit \u5b9e\u4f8b\u7f51\u5740 +gb.users = \u7528\u6237 +gb.federation = federation +gb.error = \u9519\u8bef +gb.refresh = \u5237\u65b0 +gb.browse = \u6d4f\u89c8 +gb.clone = \u514b\u9686 +gb.filter = \u8fc7\u6ee4 +gb.create = \u521b\u5efa +gb.servers = \u670d\u52a1\u5668 +gb.recent = \u6700\u8fd1 +gb.available = \u53ef\u7528 +gb.selected = \u5df2\u9009\u4e2d +gb.size = \u5927\u5c0f +gb.downloading = \u4e0b\u8f7d\u4e2d +gb.loading = \u8f7d\u5165\u4e2d +gb.starting = \u542f\u52a8\u4e2d +gb.general = \u5e38\u89c4 +gb.settings = \u8bbe\u7f6e +gb.manage = \u7ba1\u7406 +gb.lastLogin = \u4e0a\u6b21\u767b\u5f55 +gb.skipSizeCalculation = \u5ffd\u7565\u5927\u5c0f\u4f30\u8ba1 +gb.skipSizeCalculationDescription = \u4e0d\u8ba1\u7b97\u7248\u672c\u5e93\u5927\u5c0f\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09 +gb.skipSummaryMetrics = \u5ffd\u7565\u6982\u51b5\u5904 metrics +gb.skipSummaryMetricsDescription = \u6982\u51b5\u9875\u9762\u4e0d\u8ba1\u7b97metrics\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09 +gb.accessLevel = \u8bbf\u95ee\u7ea7\u522b +gb.default = \u9ed8\u8ba4 +gb.setDefault = \u9ed8\u8ba4\u8bbe\u7f6e +gb.since = \u81ea\u4ece +gb.status = \u72b6\u6001 +gb.bootDate = \u542f\u52a8\u65e5\u671f +gb.servletContainer = servlet container +gb.heapMaximum = \u6700\u5927\u5806 +gb.heapAllocated = \u5df2\u5206\u914d\u5806 +gb.heapUsed = \u5df2\u4f7f\u7528\u5806 +gb.free = \u7a7a\u95f2 +gb.version = \u7248\u672c +gb.releaseDate = \u53d1\u884c\u65e5\u671f +gb.date = \u65e5\u671f +gb.activity = \u6d3b\u52a8 +gb.subscribe = \u8ba2\u9605 +gb.branch = \u5206\u652f +gb.maxHits = \u6700\u5927\u547d\u4e2d\u6570 +gb.recentActivity = \u6700\u8fd1\u6d3b\u52a8 +gb.recentActivityStats = \u6700\u8fd1{0}\u5929 / {2}\u4f4d\u7528\u6237\u505a\u4e86{1}\u6b21\u63d0\u4ea4 +gb.recentActivityNone = \u6700\u8fd1{0}\u5929 / \u6ca1\u6709\u6d3b\u52a8 +gb.dailyActivity = \u65e5\u5e38\u6d3b\u52a8 +gb.activeRepositories = \u6d3b\u8dc3\u7684\u7248\u672c\u5e93 +gb.activeAuthors = \u6d3b\u8dc3\u7528\u6237 +gb.commits = \u63d0\u4ea4\u6b21\u6570 +gb.teams = \u56e2\u961f +gb.teamName = \u56e2\u961f\u540d\u79f0 +gb.teamMembers = \u56e2\u961f\u6210\u5458 +gb.teamMemberships = \u56e2\u961f\u6210\u5458 +gb.newTeam = \u6dfb\u52a0\u56e2\u961f +gb.permittedTeams = \u5141\u8bb8\u56e2\u961f +gb.emptyRepository = \u7a7a\u7248\u672c\u5e93 +gb.repositoryUrl = \u7248\u672c\u5e93\u5730\u5740 +gb.mailingLists = \u90ae\u4ef6\u5217\u8868 +gb.preReceiveScripts = pre-receive \u811a\u672c +gb.postReceiveScripts = post-receive \u811a\u672c +gb.hookScripts = hook \u811a\u672c +gb.customFields = \u81ea\u5b9a\u4e49\u57df +gb.customFieldsDescription = Groovy\u811a\u672c\u652f\u6301\u7684\u81ea\u5b9a\u4e49\u57df +gb.accessPermissions = \u8bbf\u95ee\u6743\u9650 +gb.filters = \u8fc7\u6ee4 +gb.generalDescription = \u4e00\u822c\u8bbe\u7f6e +gb.accessPermissionsDescription = \u6309\u7167\u7528\u6237\u548c\u56e2\u961f\u9650\u5236\u8bbf\u95ee +gb.accessPermissionsForUserDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u6216\u8005\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650 +gb.accessPermissionsForTeamDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u5e76\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650 +gb.federationRepositoryDescription = \u4e0e\u5176\u4ed6Gitblit\u670d\u52a1\u5668\u5206\u4eab\u7248\u672c\u5e93 +gb.hookScriptsDescription = \u5728\u670d\u52a1\u5668\u4e0a\u8fd0\u884cGroovy\u811a\u672c +gb.reset = \u91cd\u7f6e +gb.pages = \u9875\u9762 +gb.workingCopy = \u5de5\u4f5c\u526f\u672c +gb.workingCopyWarning = \u6b64\u7248\u672c\u5e93\u5b58\u5728\u4e00\u4efd\u5de5\u4f5c\u526f\u672c\uff0c\u65e0\u6cd5\u8fdb\u884c\u63a8\u9001 +gb.query = \u67e5\u8be2 +gb.queryHelp = \u652f\u6301\u6807\u51c6\u67e5\u8be2\u683c\u5f0f.<p/><p/>\u8bf7\u67e5\u770b <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene \u67e5\u8be2\u5904\u7406\u5668\u683c\u5f0f</a> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u5185\u5bb9\u3002 +gb.queryResults = \u7ed3\u679c {0} - {1} ({2} \u6b21\u547d\u4e2d) +gb.noHits = \u672a\u547d\u4e2d +gb.authored = authored +gb.committed = committed +gb.indexedBranches = \u5df2\u7d22\u5f15\u5206\u652f +gb.indexedBranchesDescription = \u9009\u62e9\u8981\u653e\u5165\u4f60\u7684 Lucene \u7d22\u5f15\u7684\u5206\u652f +gb.noIndexedRepositoriesWarning = \u60a8\u7684\u6240\u6709\u7248\u672c\u5e93\u90fd\u6ca1\u6709\u7ecf\u8fc7Lucene\u7d22\u5f15 +gb.undefinedQueryWarning = \u67e5\u8be2\u672a\u5b9a\u4e49! +gb.noSelectedRepositoriesWarning = \u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u7248\u672c\u5e93! +gb.luceneDisabled = Lucene\u7d22\u5f15\u5df2\u88ab\u7981\u6b62 +gb.failedtoRead = \u8bfb\u53d6\u5931\u8d25 +gb.isNotValidFile = \u4e0d\u662f\u5408\u6cd5\u6587\u4ef6 +gb.failedToReadMessage = \u5728 {0} \u4e2d\u8bfb\u53d6\u9ed8\u8ba4\u6d88\u606f\u5931\u8d25! +gb.passwordsDoNotMatch = \u5bc6\u7801\u4e0d\u5339\u914d! +gb.passwordTooShort = \u5bc6\u7801\u957f\u5ea6\u592a\u77ed\u3002\u6700\u77ed\u957f\u5ea6 {0} \u4e2a\u5b57\u7b26\u3002 +gb.passwordChanged = \u5bc6\u7801\u4fee\u6539\u6210\u529f\u3002 +gb.passwordChangeAborted = \u5bc6\u7801\u4fee\u6539\u7ec8\u6b62 +gb.pleaseSetRepositoryName = \u8bf7\u8bbe\u7f6e\u4e00\u4e2a\u7248\u672c\u5e93\u540d\u79f0! +gb.illegalLeadingSlash = \u7981\u6b62\u4f7f\u7528\u6839\u76ee\u5f55\u5f15\u7528 (/) \u3002 +gb.illegalRelativeSlash = \u76f8\u5bf9\u6587\u4ef6\u5939\u8def\u5f84(../)\u7981\u6b62\u4f7f\u7528 +gb.illegalCharacterRepositoryName = \u7248\u672c\u5e93\u4e2d\u542b\u6709\u4e0d\u5408\u6cd5\u5b57\u7b26 ''{0}'' ! +gb.selectAccessRestriction = \u8bf7\u9009\u62e9\u8bbf\u95ee\u6743\u9650\uff01 +gb.selectFederationStrategy = \u8bf7\u9009\u62e9federation\u7b56\u7565! +gb.pleaseSetTeamName = \u8bf7\u8f93\u5165\u4e00\u4e2a\u56e2\u961f\u540d\u79f0\uff01 +gb.teamNameUnavailable = \u56e2\u961f\u540d ''{0}'' \u4e0d\u5408\u6cd5. +gb.teamMustSpecifyRepository = \u56e2\u961f\u5fc5\u987b\u62e5\u6709\u81f3\u5c11\u4e00\u4e2a\u7248\u672c\u5e93\u3002 +gb.teamCreated = \u6210\u529f\u521b\u5efa\u65b0\u56e2\u961f ''{0}'' . +gb.pleaseSetUsername = \u8bf7\u8f93\u5165\u7528\u6237\u540d\uff01 +gb.usernameUnavailable = \u7528\u6237\u540d ''{0}'' \u4e0d\u53ef\u7528.. +gb.combinedMd5Rename = Gitblit\u91c7\u7528\u6df7\u5408md5\u5bc6\u7801\u54c8\u5e0c\u3002\u56e0\u6b64\u5fc5\u987b\u5728\u4fee\u6539\u7528\u6237\u540d\u540e\u4fee\u6539\u5bc6\u7801\u3002 +gb.userCreated = \u6210\u529f\u521b\u5efa\u65b0\u7528\u6237 \\"{0}\\"\u3002 +gb.couldNotFindFederationRegistration = \u65e0\u6cd5\u627e\u5230federation registration! +gb.failedToFindGravatarProfile = \u52a0\u8f7d {0} \u7684Gravatar\u4fe1\u606f\u5931\u8d25 +gb.branchStats = {0} \u4e2a\u63d0\u4ea4\u548c {1} \u4e2a\u6807\u7b7e\u5728 {2} \u5185 +gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5e93! +gb.repositoryNotSpecifiedFor = \u6ca1\u6709\u4e3a {0} \u8bbe\u7f6e\u7248\u672c\u5e93! +gb.canNotLoadRepository = \u65e0\u6cd5\u8f7d\u5165\u7248\u672c\u5e93 +gb.commitIsNull = \u63d0\u4ea4\u5185\u5bb9\u4e3a\u7a7a +gb.unauthorizedAccessForRepository = \u672a\u6388\u6743\u8bbf\u95ee\u7248\u672c\u5e93 +gb.failedToFindCommit = \u5728 {1} \u4e2d {2} \u4e2a\u9875\u9762\u5185\u67e5\u627e\u63d0\u4ea4 \\"{0}\\"\u5931\u8d25! +gb.couldNotFindFederationProposal = \u65e0\u6cd5\u627e\u5230federation proposal! +gb.invalidUsernameOrPassword = \u7528\u6237\u540d\u6216\u8005\u5bc6\u7801\u9519\u8bef\uff01 +gb.OneProposalToReview = 1\u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5\u3002 +gb.nFederationProposalsToReview = {0} \u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5 +gb.couldNotFindTag = \u65e0\u6cd5\u627e\u5230\u6807\u7b7e {0} +gb.couldNotCreateFederationProposal = \u65e0\u6cd5\u521b\u5efafederation proposal! +gb.pleaseSetGitblitUrl = \u8bf7\u8f93\u5165\u4f60\u7684Gitblit\u7f51\u5740! +gb.pleaseSetDestinationUrl = \u8bf7\u4e3a\u4f60\u7684proposal\u8f93\u5165\u4e00\u4e2a\u76ee\u6807\u5730\u5740! +gb.proposalReceived = \u6210\u529f\u4ece {0} \u63a5\u6536Proposal. +gb.noGitblitFound = \u62b1\u6b49, {0} \u65e0\u6cd5\u5728{1} \u4e2d\u627e\u5230Gitblit\u5b9e\u4f8b\u3002 +gb.noProposals = \u62b1\u6b49, {0} \u5f53\u524d\u4e0d\u63a5\u53d7proposals\u3002 +gb.noFederation = \u62b1\u6b49, {0} \u6ca1\u6709\u4e0e\u4efb\u4f55Gitblit\u5b9e\u4f8b\u8bbe\u7f6efederate\u3002. +gb.proposalFailed = \u62b1\u6b49, {0} \u65e0\u6cd5\u63a5\u53d7\u4efb\u4f55proposal\u6570\u636e! +gb.proposalError = \u62b1\u6b49\uff0c{0} \u62a5\u544a\u4e2d\u53d1\u73b0\u672a\u9884\u671f\u7684\u9519\u8bef\uff01 +gb.failedToSendProposal = \u53d1\u9001proposal\u5931\u8d25! +gb.userServiceDoesNotPermitAddUser = {0} \u4e0d\u5141\u8bb8\u6dfb\u52a0\u7528\u6237! +gb.userServiceDoesNotPermitPasswordChanges = {0} \u4e0d\u5141\u8bb8\u8fdb\u884c\u5bc6\u7801\u4fee\u6539! +gb.displayName = \u663e\u793a\u540d\u79f0 +gb.emailAddress = \u90ae\u7bb1 +gb.errorAdminLoginRequired = \u9700\u8981\u7ba1\u7406\u5458\u767b\u9646 +gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u5458\u624d\u53ef\u4ee5\u521b\u5efa\u7248\u672c\u5e93 +gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u5458\u6216\u8005\u6240\u6709\u8005\u624d\u53ef\u4ee5\u7f16\u8f91\u4ee3\u7801\u5e93 +gb.errorAdministrationDisabled = \u7ba1\u7406\u6743\u9650\u88ab\u7981\u6b62\u3002 +gb.lastNDays = \u6700\u8fd1 {0} \u5929 +gb.completeGravatarProfile = \u5728Gravatar.com\u4e0a\u5b8c\u6210\u4e2a\u4eba\u8bbe\u5b9a +gb.none = \u65e0 +gb.line = \u884c +gb.content = \u5185\u5bb9 +gb.empty = \u7a7a\u767d\u7248\u672c\u5e93 +gb.inherited = \u7ee7\u627f +gb.deleteRepository = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \uff1f +gb.repositoryDeleted = \u7248\u672c\u5e93 ''{0}'' \u5df2\u5220\u9664\u3002 +gb.repositoryDeleteFailed = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \u5931\u8d25\uff01 +gb.deleteUser = \u5220\u9664\u7528\u6237 \\"{0}\\" \uff1f +gb.userDeleted = \u7528\u6237 ''{0}'' \u5df2\u5220\u9664\uff01 +gb.userDeleteFailed = \u5220\u9664\u7528\u6237''{0}''\u5931\u8d25\uff01 +gb.time.justNow = \u521a\u521a +gb.time.today = \u4eca\u5929 +gb.time.yesterday = \u6628\u5929 +gb.time.minsAgo = {0} \u5206\u949f\u4ee5\u524d +gb.time.hoursAgo = {0} \u5c0f\u65f6\u4ee5\u524d +gb.time.daysAgo = {0} \u5929\u4ee5\u524d +gb.time.weeksAgo = {0} \u5468\u4ee5\u524d +gb.time.monthsAgo = {0} \u4e2a\u6708\u4ee5\u524d +gb.time.oneYearAgo = 1 \u5e74\u4ee5\u524d +gb.time.yearsAgo = {0} \u5e74\u4ee5\u524d +gb.duration.oneDay = 1 \u5929 +gb.duration.days = {0} \u5929 +gb.duration.oneMonth = 1 \u6708 +gb.duration.months = {0} \u6708 +gb.duration.oneYear = 1 \u5e74 +gb.duration.years = {0} \u5e74 +gb.authorizationControl = \u6388\u6743\u63a7\u5236 +gb.allowAuthenticatedDescription = \u6388\u4e88\u6240\u6709\u8ba4\u8bc1\u7528\u6237\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650 +gb.allowNamedDescription = \u6388\u4e88\u6307\u5b9a\u540d\u79f0\u7684\u7528\u6237\u6216\u56e2\u961f\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650 +gb.markdownFailure = \u8bfb\u53d6 Markdown \u5185\u5bb9\u5931\u8d25\uff01 +gb.clearCache = \u6e05\u9664\u7f13\u5b58 +gb.projects = \u9879\u76ee +gb.project = \u9879\u76ee +gb.allProjects = \u6240\u6709\u9879\u76ee +gb.copyToClipboard = \u590d\u5236\u5230\u526a\u8d34\u677f +gb.fork = \u6d3e\u751f +gb.forks = \u6d3e\u751f +gb.forkRepository = \u6d3e\u751f {0} ? +gb.repositoryForked = {0} \u5df2\u88ab\u6d3e\u751f +gb.repositoryForkFailed = \u6d3e\u751f\u5931\u8d25 +gb.personalRepositories = \u79c1\u4eba\u7248\u672c\u5e93 +gb.allowForks = \u5141\u8bb8\u6d3e\u751f +gb.allowForksDescription = \u5141\u8bb8\u8ba4\u8bc1\u7528\u6237\u6d3e\u751f\u6b64\u7248\u672c\u5e93 +gb.forkedFrom = \u6d3e\u751f\u81ea +gb.canFork = \u5141\u8bb8\u6d3e\u751f +gb.canForkDescription = \u5141\u8bb8\u6d3e\u751f\u8ba4\u8bc1\u7248\u672c\u5e93\u5230\u79c1\u4eba\u7248\u672c\u5e93 +gb.myFork = \u67e5\u770b\u6211\u7684\u6d3e\u751f +gb.forksProhibited = \u7981\u6b62\u6d3e\u751f +gb.forksProhibitedWarning = \u5f53\u524d\u7248\u672c\u5e93\u7981\u6b62\u6d3e\u751f +gb.noForks = {0} \u6ca1\u6709\u6d3e\u751f +gb.forkNotAuthorized = \u62b1\u6b49\uff0c\u4f60\u65e0\u6743\u6d3e\u751f {0} +gb.forkInProgress = \u6b63\u5728\u6d3e\u751f +gb.preparingFork = \u6b63\u5728\u4e3a\u60a8\u51c6\u5907\u6d3e\u751f... +gb.isFork = \u5df2\u6d3e\u751f +gb.canCreate = \u5141\u8bb8\u521b\u5efa +gb.canCreateDescription = \u5141\u8bb8\u521b\u5efa\u79c1\u4eba\u7248\u672c\u5e93 +gb.illegalPersonalRepositoryLocation = \u60a8\u7684\u79c1\u4eba\u7248\u672c\u5e93\u5fc5\u987b\u4f4d\u4e8e \\"{0}\\" +gb.verifyCommitter = \u9a8c\u8bc1\u63d0\u4ea4\u8005 +gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7684\u8eab\u4efd\u4e0e Gitblit \u7528\u6237\u8eab\u4efd\u76f8\u7b26 +gb.verifyCommitterNote = \u6240\u6709\u5408\u5e76\u9009\u9879\u9700\u8981\u4f7f\u7528 \\"--no-ff\\" \u6765\u6267\u884c\u63d0\u4ea4\u8005\u9a8c\u8bc1 +gb.repositoryPermissions = \u7248\u672c\u5e93\u6743\u9650 +gb.userPermissions = \u7528\u6237\u6743\u9650 +gb.teamPermissions = \u56e2\u961f\u6743\u9650 +gb.add = \u6dfb\u52a0 +gb.noPermission = \u5220\u9664\u6b64\u6743\u9650 +gb.excludePermission = {0} (exclude) +gb.viewPermission = {0} (view) +gb.clonePermission = {0} (clone) +gb.pushPermission = {0} (push) +gb.createPermission = {0} (push, ref creation) +gb.deletePermission = {0} (push, ref creation+deletion) +gb.rewindPermission = {0} (push, ref creation+deletion+rewind) +gb.permission = \u6743\u9650 +gb.regexPermission = \u6b64\u6743\u9650\u662f\u901a\u8fc7\u6b63\u5219\u8868\u8fbe\u5f0f \\"{0}\\" \u8bbe\u7f6e +gb.accessDenied = \u8bbf\u95ee\u88ab\u62d2\u7edd +gb.busyCollectingGarbage = \u62b1\u6b49\uff0cGitblit\u6b63\u5728 {0} \u5185\u6e05\u7406\u5783\u573e +gb.gcPeriod = GC \u65f6\u95f4 +gb.gcPeriodDescription = \u5783\u573e\u6e05\u7406\u7684\u6301\u7eed\u65f6\u95f4 +gb.gcThreshold = GC \u9600\u503c +gb.gcThresholdDescription = \u6fc0\u53d1\u5783\u573e\u6e05\u7406\u7684\u6700\u5c0f objects \u5927\u5c0f +gb.ownerPermission = \u7248\u672c\u5e93\u521b\u5efa\u8005 +gb.administrator = \u7ba1\u7406\u5458 +gb.administratorPermission = Gitblit \u7ba1\u7406\u5458 +gb.team = \u56e2\u961f +gb.teamPermission = \u901a\u8fc7 \\"{0}\\" \u56e2\u961f\u6210\u5458\u8bbe\u7f6e\u6743\u9650 +gb.missing = \u4e0d\u5b58\u5728! +gb.missingPermission = \u6b64\u6743\u9650\u7684\u7248\u672c\u5e93\u4e0d\u5b58\u5728! +gb.mutable = mutable +gb.specified = specified +gb.effective = effective +gb.organizationalUnit = \u7ec4\u7ec7\u90e8\u5206 +gb.organization = \u7ec4\u7ec7 +gb.locality = \u5730\u533a +gb.stateProvince = \u5dde\u6216\u7701 +gb.countryCode = \u56fd\u5bb6\u4ee3\u7801 +gb.properties = \u5c5e\u6027 +gb.issued = issued +gb.expires = \u5230\u671f +gb.expired = \u5df2\u5230\u671f +gb.expiring = \u5373\u5c06\u8fc7\u671f +gb.revoked = \u5df2\u64a4\u9500 +gb.serialNumber = \u5e8f\u5217\u53f7 +gb.certificates = \u8bc1\u4e66 +gb.newCertificate = \u521b\u5efa\u8bc1\u4e66 +gb.revokeCertificate = \u64a4\u9500\u8bc1\u4e66 +gb.sendEmail = \u53d1\u9001\u90ae\u4ef6 +gb.passwordHint = \u5bc6\u7801\u63d0\u793a +gb.ok = \u786e\u5b9a +gb.invalidExpirationDate = \u65e0\u6548\u7684\u8fc7\u671f\u65f6\u95f4! +gb.passwordHintRequired = \u9700\u8981\u586b\u5199\u5bc6\u7801\u63d0\u793a! +gb.viewCertificate = \u67e5\u770b\u8bc1\u4e66 +gb.subject = \u4e3b\u9898 +gb.issuer = \u63d0\u4ea4\u8005 +gb.validFrom = \u6709\u6548\u671f\u5f00\u59cb\u81ea +gb.validUntil = \u6709\u6548\u671f\u622a\u6b62\u4e8e +gb.publicKey = \u516c\u94a5 +gb.signatureAlgorithm = \u7b7e\u540d\u7b97\u6cd5 +gb.sha1FingerPrint = SHA-1 \u6307\u7eb9\u7b97\u6cd5 +gb.md5FingerPrint = MD5 \u6307\u7eb9\u7b97\u6cd5 +gb.reason = \u7406\u7531 +gb.revokeCertificateReason = \u8bf7\u9009\u62e9\u64a4\u9500\u8bc1\u4e66\u7684\u7406\u7531 +gb.unspecified = \u672a\u6307\u5b9a +gb.keyCompromise = key compromise +gb.caCompromise = CA compromise +gb.affiliationChanged = \u96b6\u5c5e\u5173\u7cfb\u5df2\u4fee\u6539 +gb.superseded = \u5df2\u53d6\u4ee3 +gb.cessationOfOperation = \u505c\u6b62\u64cd\u4f5c +gb.privilegeWithdrawn = \u7279\u6743\u5df2\u64a4\u56de +gb.time.inMinutes = {0} \u5206\u949f\u4e4b\u5185 +gb.time.inHours = {0} \u5c0f\u65f6\u4e4b\u5185 +gb.time.inDays = {0} \u5929\u4e4b\u5185 +gb.hostname = hostname +gb.hostnameRequired = \u8bf7\u8f93\u5165 hostname +gb.newSSLCertificate = \u521b\u5efa\u670d\u52a1\u5668 SSL \u8bc1\u4e66 +gb.newCertificateDefaults = \u521b\u5efa\u8bc1\u4e66\u9ed8\u8ba4\u8bbe\u7f6e +gb.duration = \u6301\u7eed\u65f6\u95f4 +gb.certificateRevoked = \u8bc1\u4e66 {0,number,0} \u5df2\u88ab\u64a4\u9500 +gb.clientCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u5ba2\u6237\u7aef\u8bc1\u4e66 +gb.sslCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u670d\u52a1\u5668 SSL \u8bc1\u4e66 +gb.newClientCertificateMessage = \u6ce8\u610f:\\n\u6b64\u5bc6\u7801\u5e76\u975e\u7528\u6237\u5bc6\u7801, \u8fd9\u662f\u4fdd\u5b58\u7528\u6237 keystore \u7684\u5bc6\u7801\u3002 \u7531\u4e8e\u672c\u5bc6\u7801\u672a\u5b58\u50a8\uff0c\u56e0\u6b64\u4f60\u5fc5\u987b\u4e00\u4e2a\u5bc6\u7801\u63d0\u793a\uff0c\u8fd9\u4e2a\u63d0\u793a\u4f1a\u8bb0\u5f55\u5728\u7528\u6237\u7684 README \u6587\u6863\u5185\u3002 +gb.certificate = \u8bc1\u4e66 +gb.emailCertificateBundle = \u53d1\u9001\u5ba2\u6237\u7aef\u8bc1\u4e66 +gb.pleaseGenerateClientCertificate = \u8bf7\u4e3a {0} \u751f\u6210\u4e00\u4e2a\u5ba2\u6237\u7aef\u8bc1\u4e66 +gb.clientCertificateBundleSent = {0} \u7684\u5ba2\u6237\u7aef\u8bc1\u4e66\u5df2\u53d1\u9001 +gb.enterKeystorePassword = \u8bf7\u8f93\u5165 Gitblit keystore \u5bc6\u7801 +gb.warning = \u8b66\u544a +gb.jceWarning = \u60a8\u7684 JAVA \u8fd0\u884c\u73af\u5883\u4e0d\u5305\u542b \\"JCE Unlimited Strength Jurisdiction Policy\\" \u6587\u4ef6\u3002\\n\u8fd9\u5c06\u5bfc\u81f4\u60a8\u6700\u591a\u53ea\u80fd\u75287\u4e2a\u5b57\u7b26\u7684\u5bc6\u7801\u4fdd\u62a4\u60a8\u7684 keystore\u3002 \\n\u8fd9\u4e9b\u662f\u4e00\u4e9b\u53ef\u9009\u4e0b\u8f7d\u7684\u653f\u7b56\u6587\u4ef6\u3002\\n\\n\u4f60\u662f\u5426\u8981\u7ee7\u7eed\u751f\u6210\u8bc1\u4e66\uff1f\\n\\n\u9009\u62e9\u5426\u7684\u8bdd\uff0c\u5c06\u4f1a\u6253\u5f00\u4e00\u4e2a\u6d4f\u89c8\u5668\u754c\u9762\u4f9b\u60a8\u4e0b\u8f7d\u76f8\u5173\u6587\u4ef6\u3002 +gb.maxActivityCommits = \u6700\u5927\u6d3b\u52a8\u63d0\u4ea4\u6570 +gb.maxActivityCommitsDescription = \u6d3b\u52a8\u9875\u9762\u663e\u793a\u7684\u6700\u5927\u63d0\u4ea4\u6570 +gb.noMaximum = \u65e0\u4e0a\u9650 +gb.attributes = \u5c5e\u6027 +gb.serveCertificate = \u4f7f\u7528\u6b64\u8bc1\u4e66\u63d0\u4f9b https \u652f\u6301 +gb.sslCertificateGeneratedRestart = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684 SSL \u8bc1\u4e66.\\n\u4f60\u5fc5\u987b\u91cd\u65b0\u542f\u52a8 Gitblit \u4ee5\u4f7f\u7528\u6b64\u8bc1\u4e66\u3002\\n\\n\u5982\u679c\u60a8\u4f7f\u7528 '--alias' \u53c2\u6570\u542f\u52a8\uff0c\u4f60\u5fc5\u987b\u4e5f\u8981\u8bbe\u7f6e ''--alias {0}''\u3002 +gb.validity = \u5408\u6cd5\u6027 +gb.siteName = \u7f51\u7ad9\u540d\u79f0 +gb.siteNameDescription = \u60a8\u7684\u670d\u52a1\u5668\u7684\u7b80\u8981\u63cf\u8ff0 +gb.excludeFromActivity = \u4ece\u6d3b\u52a8\u9875\u9762\u6392\u9664 +gb.isSparkleshared = repository is Sparkleshared
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java index d1ee2710..c733c992 100644 --- a/src/com/gitblit/wicket/pages/BasePage.java +++ b/src/com/gitblit/wicket/pages/BasePage.java @@ -98,6 +98,10 @@ public abstract class BasePage extends WebPage { return GitBlitWebSession.get().getLocale().getLanguage();
}
+ protected String getCountryCode() {
+ return GitBlitWebSession.get().getLocale().getCountry().toLowerCase();
+ }
+
protected TimeUtils getTimeUtils() {
if (timeUtils == null) {
ResourceBundle bundle;
@@ -132,7 +136,10 @@ public abstract class BasePage extends WebPage { private void login() {
GitBlitWebSession session = GitBlitWebSession.get();
if (session.isLoggedIn() && !session.isSessionInvalidated()) {
- // already have a session
+ // already have a session, refresh usermodel to pick up
+ // any changes to permissions or roles (issue-186)
+ UserModel user = GitBlit.self().getUserModel(session.getUser().username);
+ session.setUser(user);
return;
}
@@ -429,7 +436,7 @@ public abstract class BasePage extends WebPage { GitBlitWebSession session = GitBlitWebSession.get();
if (session.isLoggedIn()) {
UserModel user = session.getUser();
- boolean editCredentials = GitBlit.self().supportsCredentialChanges();
+ boolean editCredentials = GitBlit.self().supportsCredentialChanges(user);
boolean standardLogin = session.authenticationType.isStandard();
// username, logout, and change password
diff --git a/src/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/com/gitblit/wicket/pages/ChangePasswordPage.java index 5e663006..c4014208 100644 --- a/src/com/gitblit/wicket/pages/ChangePasswordPage.java +++ b/src/com/gitblit/wicket/pages/ChangePasswordPage.java @@ -51,12 +51,13 @@ public class ChangePasswordPage extends RootSubPage { throw new RestartResponseException(getApplication().getHomePage());
}
- if (!GitBlit.self().supportsCredentialChanges()) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ if (!GitBlit.self().supportsCredentialChanges(user)) {
error(MessageFormat.format(getString("gb.userServiceDoesNotPermitPasswordChanges"),
- GitBlit.getString(Keys.realm.userService, "users.conf")), true);
+ GitBlit.getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
}
- setupPage(getString("gb.changePassword"), GitBlitWebSession.get().getUsername());
+ setupPage(getString("gb.changePassword"), user.username);
StatelessForm<Void> form = new StatelessForm<Void>("passwordForm") {
diff --git a/src/com/gitblit/wicket/pages/CommitDiffPage.java b/src/com/gitblit/wicket/pages/CommitDiffPage.java index 585ce8e3..3ad70742 100644 --- a/src/com/gitblit/wicket/pages/CommitDiffPage.java +++ b/src/com/gitblit/wicket/pages/CommitDiffPage.java @@ -15,14 +15,14 @@ */ package com.gitblit.wicket.pages; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; -import java.text.MessageFormat; - import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.data.DataView; import org.apache.wicket.markup.repeater.data.ListDataProvider; @@ -150,15 +150,12 @@ public class CommitDiffPage extends RepositoryPage { // quick links if (entry.isSubmodule()) { // submodule - item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils - .newPathParameter(submodulePath, entry.objectId, entry.path)).setEnabled(false)); + item.add(new ExternalLink("patch", "").setEnabled(false)); item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils .newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule)); - item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils - .newPathParameter(submodulePath, entry.objectId, entry.path)).setEnabled(false)); + item.add(new ExternalLink("blame", "").setEnabled(false)); item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils - .newPathParameter(submodulePath, entry.objectId, entry.path)) - .setEnabled(hasSubmodule)); + .newPathParameter(repositoryName, entry.commitId, entry.path))); } else { // tree or blob item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java index 17621ad0..c5a24c8d 100644 --- a/src/com/gitblit/wicket/pages/CommitPage.java +++ b/src/com/gitblit/wicket/pages/CommitPage.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
@@ -184,16 +185,14 @@ public class CommitPage extends RepositoryPage { if (entry.isSubmodule()) {
// submodule
item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
- .newPathParameter(submodulePath, entry.objectId, entry.path))
- .setEnabled(false));
+ .newPathParameter(repositoryName, entry.commitId, entry.path))
+ .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
- item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
- .newPathParameter(submodulePath, entry.objectId, entry.path))
- .setEnabled(false));
+ item.add(new ExternalLink("blame", "").setEnabled(false));
item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
- .newPathParameter(submodulePath, entry.objectId, entry.path))
- .setEnabled(hasSubmodule));
+ .newPathParameter(repositoryName, entry.commitId, entry.path))
+ .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
} else {
// tree or blob
item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html index 60893f44..7fc0de23 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html @@ -50,7 +50,7 @@ <div class="tab-pane" id="permissions">
<table class="plain">
<tbody class="settings">
- <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select class="span2" wicket:id="owner" tabindex="15" /> <span class="help-inline"><wicket:message key="gb.ownerDescription"></wicket:message></span></td></tr>
+ <tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="15" /> </td></tr>
<tr><th colspan="2"><hr/></th></tr>
<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="16" /></td></tr>
<tr><th colspan="2"><hr/></th></tr>
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java index a071b69e..d68d6550 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java @@ -94,7 +94,7 @@ public class EditRepositoryPage extends RootSubPage { // personal create permissions, inject personal repository path
model.name = user.getPersonalPath() + "/";
model.projectPath = user.getPersonalPath();
- model.owner = user.username;
+ model.addOwner(user.username);
// personal repositories are private by default
model.accessRestriction = AccessRestrictionType.VIEW;
model.authorizationControl = AuthorizationControl.NAMED;
@@ -164,6 +164,12 @@ public class EditRepositoryPage extends RootSubPage { final RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams",
RegistrantType.TEAM, GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
+ // owners palette
+ List<String> owners = new ArrayList<String>(repositoryModel.owners);
+ List<String> persons = GitBlit.self().getAllUsernames();
+ final Palette<String> ownersPalette = new Palette<String>("owners", new ListModel<String>(owners), new CollectionModel<String>(
+ persons), new StringChoiceRenderer(), 12, true);
+
// indexed local branches palette
List<String> allLocalBranches = new ArrayList<String>();
allLocalBranches.add(Constants.DEFAULT_BRANCH);
@@ -326,6 +332,13 @@ public class EditRepositoryPage extends RootSubPage { }
repositoryModel.indexedBranches = indexedBranches;
+ // owners
+ repositoryModel.owners.clear();
+ Iterator<String> owners = ownersPalette.getSelectedChoices();
+ while (owners.hasNext()) {
+ repositoryModel.addOwner(owners.next());
+ }
+
// pre-receive scripts
List<String> preReceiveScripts = new ArrayList<String>();
Iterator<String> pres = preReceivePalette.getSelectedChoices();
@@ -377,8 +390,7 @@ public class EditRepositoryPage extends RootSubPage { // field names reflective match RepositoryModel fields
form.add(new TextField<String>("name").setEnabled(allowEditName));
form.add(new TextField<String>("description"));
- form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames())
- .setEnabled(GitBlitWebSession.get().canAdmin() && !repositoryModel.isPersonalRepository()));
+ form.add(ownersPalette);
form.add(new CheckBox("allowForks").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer());
@@ -559,7 +571,7 @@ public class EditRepositoryPage extends RootSubPage { isAdmin = true;
return;
} else {
- if (!model.owner.equalsIgnoreCase(user.username)) {
+ if (!model.isOwner(user.username)) {
// User is not an Admin nor Owner
error(getString("gb.errorOnlyAdminOrOwnerMayEditRepository"), true);
}
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.java b/src/com/gitblit/wicket/pages/EditTeamPage.java index 1991c02a..8344d387 100644 --- a/src/com/gitblit/wicket/pages/EditTeamPage.java +++ b/src/com/gitblit/wicket/pages/EditTeamPage.java @@ -212,7 +212,7 @@ public class EditTeamPage extends RootSubPage { form.add(new SimpleAttributeModifier("autocomplete", "off"));
// not all user services support manipulating team memberships
- boolean editMemberships = GitBlit.self().supportsTeamMembershipChanges();
+ boolean editMemberships = GitBlit.self().supportsTeamMembershipChanges(null);
// field names reflective match TeamModel fields
form.add(new TextField<String>("name"));
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java index 7a01fb68..c060f237 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/com/gitblit/wicket/pages/EditUserPage.java @@ -55,9 +55,9 @@ public class EditUserPage extends RootSubPage { public EditUserPage() {
// create constructor
super();
- if (!GitBlit.self().supportsCredentialChanges()) {
+ if (!GitBlit.self().supportsAddUser()) {
error(MessageFormat.format(getString("gb.userServiceDoesNotPermitAddUser"),
- GitBlit.getString(Keys.realm.userService, "users.conf")), true);
+ GitBlit.getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
}
isCreate = true;
setupPage(new UserModel(""));
@@ -134,7 +134,7 @@ public class EditUserPage extends RootSubPage { }
boolean rename = !StringUtils.isEmpty(oldName)
&& !oldName.equalsIgnoreCase(username);
- if (GitBlit.self().supportsCredentialChanges()) {
+ if (GitBlit.self().supportsCredentialChanges(userModel)) {
if (!userModel.password.equals(confirmPassword.getObject())) {
error(getString("gb.passwordsDoNotMatch"));
return;
@@ -210,16 +210,16 @@ public class EditUserPage extends RootSubPage { form.add(new SimpleAttributeModifier("autocomplete", "off"));
// not all user services support manipulating username and password
- boolean editCredentials = GitBlit.self().supportsCredentialChanges();
+ boolean editCredentials = GitBlit.self().supportsCredentialChanges(userModel);
// not all user services support manipulating display name
- boolean editDisplayName = GitBlit.self().supportsDisplayNameChanges();
+ boolean editDisplayName = GitBlit.self().supportsDisplayNameChanges(userModel);
// not all user services support manipulating email address
- boolean editEmailAddress = GitBlit.self().supportsEmailAddressChanges();
+ boolean editEmailAddress = GitBlit.self().supportsEmailAddressChanges(userModel);
// not all user services support manipulating team memberships
- boolean editTeams = GitBlit.self().supportsTeamMembershipChanges();
+ boolean editTeams = GitBlit.self().supportsTeamMembershipChanges(userModel);
// field names reflective match UserModel fields
form.add(new TextField<String>("username").setEnabled(editCredentials));
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html new file mode 100644 index 00000000..a8ee2e25 --- /dev/null +++ b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html @@ -0,0 +1,53 @@ +<!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="nl"
+ lang="nl">
+
+<body>
+<wicket:extend>
+
+ <h2>Empty Repository</h2>
+ <p></p>
+ <div class="row">
+ <div class="span10">
+ <div class="alert alert-success">
+ <span wicket:id="repository" style="font-weight: bold;">[repository]</span> is een lege repositorie en kan niet bekeken worden door Gitblit.
+ <p></p>
+ Push aub een paar commitsome commits naar <span wicket:id="pushurl"></span>
+ <p></p>
+ <hr/>
+ Nadat u een paar commits gepushed hebt kunt u deze pagina <b>verversen</b> om de repository te bekijken.
+ </div>
+ </div>
+ </div>
+
+ <h3>Git Command-Line Syntax</h3>
+ <span style="padding-bottom:5px;">Als u geen lokale Git repositorie heeft, kunt u deze repository clonen, er een paar bestanden naar committen en deze commits teug pushen naar Gitblit.</span>
+ <p></p>
+ <pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+ <p></p>
+ <span style="padding-bottom:5px;">Als u al een lokale Git repositorie heeft met commits kunt u deze repository als een remote toevoegen en er naar toe pushen.</span>
+ <p></p>
+ <pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+ <p></p>
+ <h3>Learn Git</h3>
+ Als u niet goed weet wat u met deze informatie aan moet raden we aan om het <a href="http://book.git-scm.com">Git Community Book</a> of <a href="http://progit.org/book" target="_blank">Pro Git</a> te bestuderen voor een betere begrip van hoe u Git kunt gebruiken.
+ <p></p>
+ <h4>Open Source Git Clients</h4>
+ <ul>
+ <li><a href="http://git-scm.com">Git</a> - de officiele, command-line Git</li>
+ <li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Windows bestandsverkenner ingetratie (officiele command-line Git is wel nodig)</li>
+ <li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git voor de Eclipse IDE (gebaseerd op JGit, zoals Gitblit)</li>
+ <li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend voor Git met Windows Explorer en Visual Studio integratie</li>
+ <li><a href="http://gitx.laullon.com/">GitX (L)</a> - een Mac OS X Git client</li>
+ </ul>
+ <p></p>
+ <h4>Commercial/Closed-Source Git Clients</h4>
+ <ul>
+ <li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Een Java Git, Mercurial, en SVN client applicatie (officiele command-line Git is wel nodig)</li>
+ <li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Een gratis Mac Client voor Git, Mercurial, en SVN</li>
+ </ul>
+</wicket:extend>
+</body>
+</html>
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html new file mode 100644 index 00000000..351ef879 --- /dev/null +++ b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html @@ -0,0 +1,53 @@ +<!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="pt-br"
+ lang="pt-br">
+
+<body>
+<wicket:extend>
+
+ <h2>Repositório Vazio</h2>
+ <p></p>
+ <div class="row">
+ <div class="span10">
+ <div class="alert alert-success">
+ <span wicket:id="repository" style="font-weight: bold;">[repository]</span> é um repositório vazio e não pode ser visualizado pelo Gitblit.
+ <p></p>
+ Por favor faça o push de alguns commits para <span wicket:id="pushurl"></span>
+ <p></p>
+ <hr/>
+ Depois de ter feito push você poderá <b>atualizar</b> esta página para visualizar seu repositório.
+ </div>
+ </div>
+ </div>
+
+ <h3>Sintaxe dos comandos do Git</h3>
+ <span style="padding-bottom:5px;">Se você ainda não tem um repositório local do Git, então você deve primeiro clonar este repositório, fazer commit de alguns arquivos e então fazer push desses commits para o Gitblit.</span>
+ <p></p>
+ <pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+ <p></p>
+ <span style="padding-bottom:5px;">Se você já tem um repositório Git local com alguns commits, então você deve adicionar este repositório como uma referência remota e então fazer push.</span>
+ <p></p>
+ <pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+ <p></p>
+ <h3>Aprenda Git</h3>
+ Se você estiver com dúvidas sobre como ultilizar essas informações, uma sugestão seria dar uma olhada no livro <a href="http://book.git-scm.com">Git Community Book</a> ou <a href="http://progit.org/book" target="_blank">Pro Git</a> para entender melhor como usar o Git.
+ <p></p>
+ <h4>Alguns clients do Git que são Open Source</h4>
+ <ul>
+ <li><a href="http://git-scm.com">Git</a> - o Git oficial através de linhas de comando</li>
+ <li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Faz integração do Explorer do Windows com o Git (por isso requer o Git Oficial)</li>
+ <li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git para a IDE Eclipse (baseada no JGit, como o Gitblit)</li>
+ <li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - Interface (em C#) para o Git cuja a caracterÃstica é a integração com o Windows Explorer e o Visual Studio</li>
+ <li><a href="http://gitx.laullon.com/">GitX (L)</a> - um Cliente do Git para Mac OS X</li>
+ </ul>
+ <p></p>
+ <h4>Clients do Git proprietários ou com Código Fechado</h4>
+ <ul>
+ <li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Aplicação Client (em Java) para Git, Mercurial, e SVN (por isso requer o Git Oficial)</li>
+ <li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Client gratuito para o Mac que suporta Git, Mercurial e SVN</li>
+ </ul>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html new file mode 100644 index 00000000..4b21800e --- /dev/null +++ b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html @@ -0,0 +1,55 @@ +<!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="zh-CN" + lang="zh-CN"> + +<body> +<wicket:extend> + + <h2>空版本库</h2> + <p></p> + <div class="row"> + <div class="span10"> + <div class="alert alert-success"> + <span wicket:id="repository" style="font-weight: bold;">[repository]</span> 版本库目å‰ä¸ºç©ºã€‚ + Gitblit æ— æ³•æŸ¥çœ‹ã€‚ + <p></p> + 请往æ¤ç½‘å€è¿›è¡ŒæŽ¨é€ <span wicket:id="pushurl"></span> + <p></p> + <hr/> + å½“ä½ æŽ¨é€å®Œæ¯•åŽä½ å¯ä»¥ <b>刷新</b> æ¤é¡µé¢é‡æ–°æŸ¥çœ‹æ‚¨çš„版本库。 + </div> + </div> + </div> + + <h3>Git å‘½ä»¤è¡Œæ ¼å¼</h3> + <span style="padding-bottom:5px;">如果您没有本地 Git 版本库, 您å¯ä»¥å…‹éš†æ¤ç‰ˆæœ¬åº“, æ交一些文件, 然åŽå°†æ‚¨çš„æ交推é€å›žGitblit。</span> + <p></p> + <pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre> + <p></p> + <span style="padding-bottom:5px;">如果您已ç»æœ‰ä¸€ä¸ªæœ¬åœ°çš„æ交过的版本库, 那么您å¯ä»¥å°†æ¤ç‰ˆæœ¬åº“åŠ ä¸ºè¿œç¨‹ + 版本库,并进行推é€ã€‚</span> + <p></p> + <pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre> + <p></p> + <h3>å¦ä¹ Git</h3> + 如果您ä¸æ˜Žç™½è¿™äº›ä¿¡æ¯ä»€ä¹ˆæ„æ€, 您å¯ä»¥å‚考 <a href="http://book.git-scm.com">Git Community Book</a> 或者 <a href="http://progit.org/book" target="_blank">Pro Git</a> åŽ»æ›´åŠ æ·±å…¥çš„å¦ä¹ Git 的用法。 + <p></p> + <h4>å¼€æº Git 客户端</h4> + <ul> + <li><a href="http://git-scm.com">Git</a> - 官方, 命令行版本 Git</li> + <li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - 与 Windows 资æºç®¡ç†å™¨é›†æˆ (需è¦å®˜æ–¹, 命令行 Git 的支æŒ)</li> + <li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git for the Eclipse IDE (基于 JGit, 类似 Gitblit)</li> + <li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# 版本的 Git å‰ç«¯ï¼Œä¸Ž Windows 资æºç®¡ç†å™¨å’Œ Visual Studio 集æˆ</li> + <li><a href="http://gitx.laullon.com/">GitX (L)</a> - Mac OS X Git 客户端</li> + </ul> + <p></p> + <h4>商业/é—æº Git 客户端</h4> + <ul> + <li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Java ç‰ˆæœ¬çš„æ”¯æŒ Git, Mercurial å’Œ SVN 客户端应用 (需è¦å®˜æ–¹, 命令行 Git 的支æŒ)</li> + <li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - å…费的 Mac Git Mercurial ä»¥åŠ SVN 客户端, Mercurial, and SVN</li> + </ul> +</wicket:extend> +</body> +</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/ForksPage.java b/src/com/gitblit/wicket/pages/ForksPage.java index 6155f3ed..cc483878 100644 --- a/src/com/gitblit/wicket/pages/ForksPage.java +++ b/src/com/gitblit/wicket/pages/ForksPage.java @@ -58,7 +58,7 @@ public class ForksPage extends RepositoryPage { if (repository.isPersonalRepository()) {
UserModel user = GitBlit.self().getUserModel(repository.projectPath.substring(1));
- PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress);
+ PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
item.add(new GravatarImage("anAvatar", ident, 20));
if (pageRepository.equals(repository)) {
// do not link to self
diff --git a/src/com/gitblit/wicket/pages/ProjectPage.java b/src/com/gitblit/wicket/pages/ProjectPage.java index bc546dfc..7eba0331 100644 --- a/src/com/gitblit/wicket/pages/ProjectPage.java +++ b/src/com/gitblit/wicket/pages/ProjectPage.java @@ -15,9 +15,6 @@ */
package com.gitblit.wicket.pages;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -37,7 +34,6 @@ import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Constants;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
@@ -46,7 +42,6 @@ import com.gitblit.models.Activity; import com.gitblit.models.Metric;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
import com.gitblit.utils.ActivityUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
@@ -111,23 +106,14 @@ public class ProjectPage extends RootPage { add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(project.getDisplayName(),
null), feedLink));
- final String projectPath;
- if (project.isRoot) {
- projectPath = "";
- } else {
- projectPath = projectName + "/";
- }
-
// project markdown message
- File pmkd = new File(GitBlit.getRepositoriesFolder(), projectPath + "project.mkd");
- String pmessage = readMarkdown(projectName, pmkd);
+ String pmessage = transformMarkdown(project.projectMarkdown);
Component projectMessage = new Label("projectMessage", pmessage)
.setEscapeModelStrings(false).setVisible(pmessage.length() > 0);
add(projectMessage);
// markdown message above repositories list
- File rmkd = new File(GitBlit.getRepositoriesFolder(), projectPath + "repositories.mkd");
- String rmessage = readMarkdown(projectName, rmkd);
+ String rmessage = transformMarkdown(project.repositoriesMarkdown);
Component repositoriesMessage = new Label("repositoriesMessage", rmessage)
.setEscapeModelStrings(false).setVisible(rmessage.length() > 0);
add(repositoriesMessage);
@@ -300,8 +286,8 @@ public class ProjectPage extends RootPage { @Override
protected List<ProjectModel> getProjectModels() {
if (projectModels.isEmpty()) {
- final UserModel user = GitBlitWebSession.get().getUser();
- List<ProjectModel> projects = GitBlit.self().getProjectModels(user, false);
+ List<RepositoryModel> repositories = getRepositoryModels();
+ List<ProjectModel> projects = GitBlit.self().getProjectModels(repositories, false);
projectModels.addAll(projects);
}
return projectModels;
@@ -352,20 +338,15 @@ public class ProjectPage extends RootPage { }
return menu;
}
-
-
- private String readMarkdown(String projectName, File projectMessage) {
+
+ private String transformMarkdown(String markdown) {
String message = "";
- if (projectMessage.exists()) {
+ if (!StringUtils.isEmpty(markdown)) {
// Read user-supplied message
try {
- FileInputStream fis = new FileInputStream(projectMessage);
- InputStreamReader reader = new InputStreamReader(fis,
- Constants.CHARACTER_ENCODING);
- message = MarkdownUtils.transformMarkdown(reader);
- reader.close();
+ message = MarkdownUtils.transformMarkdown(markdown);
} catch (Throwable t) {
- message = getString("gb.failedToRead") + " " + projectMessage;
+ message = getString("gb.failedToRead") + " " + markdown;
warn(message, t);
}
}
diff --git a/src/com/gitblit/wicket/pages/ProjectsPage.java b/src/com/gitblit/wicket/pages/ProjectsPage.java index 7161d0f3..7f0b002e 100644 --- a/src/com/gitblit/wicket/pages/ProjectsPage.java +++ b/src/com/gitblit/wicket/pages/ProjectsPage.java @@ -36,7 +36,6 @@ import org.eclipse.jgit.lib.Constants; import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.ProjectModel;
-import com.gitblit.models.UserModel;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
@@ -48,8 +47,6 @@ import com.gitblit.wicket.panels.LinkPanel; public class ProjectsPage extends RootPage {
- List<ProjectModel> projectModels = new ArrayList<ProjectModel>();
-
public ProjectsPage() {
super();
setup(null);
@@ -67,9 +64,7 @@ public class ProjectsPage extends RootPage { @Override
protected List<ProjectModel> getProjectModels() {
- final UserModel user = GitBlitWebSession.get().getUser();
- List<ProjectModel> projects = GitBlit.self().getProjectModels(user, false);
- return projects;
+ return GitBlit.self().getProjectModels(getRepositoryModels(), false);
}
private void setup(PageParameters params) {
@@ -194,39 +189,47 @@ public class ProjectsPage extends RootPage { }
private String readDefaultMarkdown(String file) {
- String content = readDefaultMarkdown(file, getLanguageCode());
- if (StringUtils.isEmpty(content)) {
- content = readDefaultMarkdown(file, null);
- }
- return content;
- }
+ String base = file.substring(0, file.lastIndexOf('.'));
+ String ext = file.substring(file.lastIndexOf('.'));
+ String lc = getLanguageCode();
+ String cc = getCountryCode();
- private String readDefaultMarkdown(String file, String lc) {
+ // try to read file_en-us.ext, file_en.ext, file.ext
+ List<String> files = new ArrayList<String>();
if (!StringUtils.isEmpty(lc)) {
- // convert to file_lc.mkd
- file = file.substring(0, file.lastIndexOf('.')) + "_" + lc
- + file.substring(file.lastIndexOf('.'));
+ if (!StringUtils.isEmpty(cc)) {
+ files.add(base + "_" + lc + "-" + cc + ext);
+ files.add(base + "_" + lc + "_" + cc + ext);
+ }
+ files.add(base + "_" + lc + ext);
}
- String message;
- try {
- ContextRelativeResource res = WicketUtils.getResource(file);
- InputStream is = res.getResourceStream().getInputStream();
- InputStreamReader reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
- message = MarkdownUtils.transformMarkdown(reader);
- reader.close();
- } catch (ResourceStreamNotFoundException t) {
- if (lc == null) {
- // could not find default language resource
+ files.add(file);
+
+ for (String name : files) {
+ String message;
+ InputStreamReader reader = null;
+ try {
+ ContextRelativeResource res = WicketUtils.getResource(name);
+ InputStream is = res.getResourceStream().getInputStream();
+ reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
+ message = MarkdownUtils.transformMarkdown(reader);
+ reader.close();
+ return message;
+ } catch (ResourceStreamNotFoundException t) {
+ continue;
+ } catch (Throwable t) {
message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
error(message, t, false);
- } else {
- // ignore so we can try default language resource
- message = null;
- }
- } catch (Throwable t) {
- message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
- error(message, t, false);
+ return message;
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (Exception e) {
+ }
+ }
+ }
}
- return message;
+ return MessageFormat.format(getString("gb.failedToReadMessage"), file);
}
}
diff --git a/src/com/gitblit/wicket/pages/RawPage.java b/src/com/gitblit/wicket/pages/RawPage.java index 7f6ed139..28e8bae2 100644 --- a/src/com/gitblit/wicket/pages/RawPage.java +++ b/src/com/gitblit/wicket/pages/RawPage.java @@ -109,7 +109,7 @@ public class RawPage extends WebPage { switch (type) {
case 2:
// image blobs
- byte[] image = JGitUtils.getByteContent(r, commit.getTree(), blobPath);
+ byte[] image = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
response.setContentType("image/" + extension.toLowerCase());
response.setContentLength(image.length);
try {
@@ -120,7 +120,7 @@ public class RawPage extends WebPage { break;
case 3:
// binary blobs (download)
- byte[] binary = JGitUtils.getByteContent(r, commit.getTree(), blobPath);
+ byte[] binary = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
response.setContentLength(binary.length);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java index 97c6aa23..4bce77f5 100644 --- a/src/com/gitblit/wicket/pages/RepositoriesPage.java +++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java @@ -20,6 +20,7 @@ import java.io.FileInputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Component;
@@ -118,7 +119,7 @@ public class RepositoriesPage extends RootPage { } else {
// Read user-supplied message
if (!StringUtils.isEmpty(messageSource)) {
- File file = new File(messageSource);
+ File file = GitBlit.getFileOrFolder(messageSource);
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
@@ -139,37 +140,47 @@ public class RepositoriesPage extends RootPage { }
private String readDefaultMarkdown(String file) {
- String content = readDefaultMarkdown(file, getLanguageCode());
- if (StringUtils.isEmpty(content)) {
- content = readDefaultMarkdown(file, null);
- }
- return content;
- }
-
- private String readDefaultMarkdown(String file, String lc) {
+ String base = file.substring(0, file.lastIndexOf('.'));
+ String ext = file.substring(file.lastIndexOf('.'));
+ String lc = getLanguageCode();
+ String cc = getCountryCode();
+
+ // try to read file_en-us.ext, file_en.ext, file.ext
+ List<String> files = new ArrayList<String>();
if (!StringUtils.isEmpty(lc)) {
- // convert to file_lc.mkd
- file = file.substring(0, file.lastIndexOf('.')) + "_" + lc + file.substring(file.lastIndexOf('.'));
+ if (!StringUtils.isEmpty(cc)) {
+ files.add(base + "_" + lc + "-" + cc + ext);
+ files.add(base + "_" + lc + "_" + cc + ext);
+ }
+ files.add(base + "_" + lc + ext);
}
- String message;
- try {
- InputStream is = GitBlit.self().getResourceAsStream(file);
- InputStreamReader reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
- message = MarkdownUtils.transformMarkdown(reader);
- reader.close();
- } catch (ResourceStreamNotFoundException t) {
- if (lc == null) {
- // could not find default language resource
+ files.add(file);
+
+ for (String name : files) {
+ String message;
+ InputStreamReader reader = null;
+ try {
+ ContextRelativeResource res = WicketUtils.getResource(name);
+ InputStream is = res.getResourceStream().getInputStream();
+ reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
+ message = MarkdownUtils.transformMarkdown(reader);
+ reader.close();
+ return message;
+ } catch (ResourceStreamNotFoundException t) {
+ continue;
+ } catch (Throwable t) {
message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
error(message, t, false);
- } else {
- // ignore so we can try default language resource
- message = null;
- }
- } catch (Throwable t) {
- message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
- error(message, t, false);
+ return message;
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (Exception e) {
+ }
+ }
+ }
}
- return message;
+ return MessageFormat.format(getString("gb.failedToReadMessage"), file);
}
}
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.html b/src/com/gitblit/wicket/pages/RepositoryPage.html index 63a894da..d49f0188 100644 --- a/src/com/gitblit/wicket/pages/RepositoryPage.html +++ b/src/com/gitblit/wicket/pages/RepositoryPage.html @@ -52,7 +52,7 @@ </div>
</div>
<div class="span7">
- <div><span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span> <span class="hidden-phone"><span wicket:id="pageName">[page name]</span></span></div>
+ <div><span class="project" wicket:id="projectTitle">[project title]</span>/<img wicket:id="repositoryIcon" style="padding-left: 10px;"></img><span class="repository" wicket:id="repositoryName">[repository name]</span> <span class="hidden-phone"><span wicket:id="pageName">[page name]</span></span></div>
<span wicket:id="originRepository">[origin repository]</span>
</div>
</div>
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java index b4e1a0e3..a477b741 100644 --- a/src/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/com/gitblit/wicket/pages/RepositoryPage.java @@ -184,7 +184,7 @@ public abstract class RepositoryPage extends BasePage { showAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
}
isOwner = GitBlitWebSession.get().isLoggedIn()
- && (model.owner != null && model.owner.equalsIgnoreCase(GitBlitWebSession.get()
+ && (model.isOwner(GitBlitWebSession.get()
.getUsername()));
if (showAdmin || isOwner) {
pages.put("edit", new PageRegistration("gb.edit", EditRepositoryPage.class, params));
@@ -246,6 +246,14 @@ public abstract class RepositoryPage extends BasePage { }
}
+ // show sparkleshare folder icon
+ if (model.isSparkleshared()) {
+ add(WicketUtils.newImage("repositoryIcon", "folder_star_32x32.png",
+ getString("gb.isSparkleshared")));
+ } else {
+ add(WicketUtils.newClearPixel("repositoryIcon").setVisible(false));
+ }
+
if (getRepositoryModel().isBare) {
add(new Label("workingCopyIndicator").setVisible(false));
} else {
@@ -326,7 +334,7 @@ public abstract class RepositoryPage extends BasePage { RepositoryModel model = GitBlit.self().getRepositoryModel(
GitBlitWebSession.get().getUser(), repositoryName);
if (model == null) {
- if (GitBlit.self().hasRepository(repositoryName)) {
+ if (GitBlit.self().hasRepository(repositoryName, true)) {
// has repository, but unauthorized
authenticationError(getString("gb.unauthorizedAccessForRepository") + " " + repositoryName);
} else {
@@ -360,10 +368,6 @@ public abstract class RepositoryPage extends BasePage { return submodules;
}
- protected Map<String, SubmoduleModel> getSubmodules() {
- return submodules;
- }
-
protected SubmoduleModel getSubmodule(String path) {
SubmoduleModel model = submodules.get(path);
if (model == null) {
@@ -450,6 +454,8 @@ public abstract class RepositoryPage extends BasePage { Constants.SearchType searchType) {
String name = identity == null ? "" : identity.getName();
String address = identity == null ? "" : identity.getEmailAddress();
+ name = StringUtils.removeNewlines(name);
+ address = StringUtils.removeNewlines(address);
boolean showEmail = GitBlit.getBoolean(Keys.web.showEmailAddresses, false);
if (!showEmail || StringUtils.isEmpty(name) || StringUtils.isEmpty(address)) {
String value = name;
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.html b/src/com/gitblit/wicket/pages/SummaryPage.html index 45ffddfb..3e85df99 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.html +++ b/src/com/gitblit/wicket/pages/SummaryPage.html @@ -16,7 +16,7 @@ <div class="hidden-phone" style="padding-bottom: 10px;">
<table class="plain">
<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr>
- <tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr>
+ <tr><th><wicket:message key="gb.owners">[owner]</wicket:message></th><td><span wicket:id="repositoryOwners"><span wicket:id="owner"></span><span wicket:id="comma"></span></span></td></tr>
<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>
<tr><th style="vertical-align:top;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message> <img style="vertical-align: top;padding-left:3px;" wicket:id="accessRestrictionIcon" /></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span><div wicket:id="otherUrls"></div></td></tr>
@@ -44,7 +44,11 @@ <div style="border:1px solid #ddd;border-radius: 0 0 3px 3px;padding: 20px;">
<div wicket:id="readmeContent" class="markdown"></div>
</div>
- </wicket:fragment>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="ownersFragment">
+
+ </wicket:fragment>
</wicket:extend>
</body>
</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java index 8df2cebc..bd40a1b7 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.java +++ b/src/com/gitblit/wicket/pages/SummaryPage.java @@ -27,6 +27,9 @@ import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.wicketstuff.googlecharts.Chart;
@@ -82,18 +85,29 @@ public class SummaryPage extends RepositoryPage { // repository description
add(new Label("repositoryDescription", getRepositoryModel().description));
- String owner = getRepositoryModel().owner;
- if (StringUtils.isEmpty(owner)) {
- add(new Label("repositoryOwner").setVisible(false));
- } else {
- UserModel ownerModel = GitBlit.self().getUserModel(owner);
- if (ownerModel != null) {
- add(new LinkPanel("repositoryOwner", null, ownerModel.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(owner)));
- } else {
- add(new Label("repositoryOwner", owner));
+
+ // owner links
+ final List<String> owners = new ArrayList<String>(getRepositoryModel().owners);
+ ListDataProvider<String> ownersDp = new ListDataProvider<String>(owners);
+ DataView<String> ownersView = new DataView<String>("repositoryOwners", ownersDp) {
+ private static final long serialVersionUID = 1L;
+ int counter = 0;
+ public void populateItem(final Item<String> item) {
+ UserModel ownerModel = GitBlit.self().getUserModel(item.getModelObject());
+ if (ownerModel != null) {
+ item.add(new LinkPanel("owner", null, ownerModel.getDisplayName(), UserPage.class,
+ WicketUtils.newUsernameParameter(ownerModel.username)).setRenderBodyOnly(true));
+ } else {
+ item.add(new Label("owner").setVisible(false));
+ }
+ counter++;
+ item.add(new Label("comma", ",").setVisible(counter < owners.size()));
+ item.setRenderBodyOnly(true);
}
- }
-
+ };
+ ownersView.setRenderBodyOnly(true);
+ add(ownersView);
+
add(WicketUtils.createTimestampLabel("repositoryLastChange",
JGitUtils.getLastChange(r), getTimeZone(), getTimeUtils()));
if (metricsTotal == null) {
diff --git a/src/com/gitblit/wicket/pages/TreePage.java b/src/com/gitblit/wicket/pages/TreePage.java index b8ce7b48..bc27f0c2 100644 --- a/src/com/gitblit/wicket/pages/TreePage.java +++ b/src/com/gitblit/wicket/pages/TreePage.java @@ -137,8 +137,8 @@ public class TreePage extends RepositoryPage { WicketUtils.newPathParameter(submodulePath, submoduleId,
"")).setEnabled(hasSubmodule));
links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
- WicketUtils.newPathParameter(submodulePath, submoduleId,
- "")).setEnabled(hasSubmodule));
+ WicketUtils.newPathParameter(repositoryName, entry.commitId,
+ entry.path)));
links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
submodulePath, submoduleId, "").setEnabled(hasSubmodule));
item.add(links);
diff --git a/src/com/gitblit/wicket/pages/UserPage.java b/src/com/gitblit/wicket/pages/UserPage.java index d3e93c61..f4331dd1 100644 --- a/src/com/gitblit/wicket/pages/UserPage.java +++ b/src/com/gitblit/wicket/pages/UserPage.java @@ -97,7 +97,7 @@ public class UserPage extends RootPage { email.setRenderBodyOnly(true);
add(email.setVisible(GitBlit.getBoolean(Keys.web.showEmailAddresses, true) && !StringUtils.isEmpty(user.emailAddress)));
- PersonIdent person = new PersonIdent(user.getDisplayName(), user.emailAddress);
+ PersonIdent person = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
add(new GravatarImage("gravatar", person, 210));
UserModel sessionUser = GitBlitWebSession.get().getUser();
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.java b/src/com/gitblit/wicket/panels/ActivityPanel.java index 6caee3e7..669c36be 100644 --- a/src/com/gitblit/wicket/panels/ActivityPanel.java +++ b/src/com/gitblit/wicket/panels/ActivityPanel.java @@ -27,7 +27,7 @@ import com.gitblit.Constants; import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.Activity;
-import com.gitblit.models.Activity.RepositoryCommit;
+import com.gitblit.models.RepositoryCommit;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.CommitDiffPage;
diff --git a/src/com/gitblit/wicket/panels/HistoryPanel.java b/src/com/gitblit/wicket/panels/HistoryPanel.java index 0f586031..e5878635 100644 --- a/src/com/gitblit/wicket/panels/HistoryPanel.java +++ b/src/com/gitblit/wicket/panels/HistoryPanel.java @@ -15,10 +15,14 @@ */
package com.gitblit.wicket.panels;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
@@ -38,6 +42,7 @@ import com.gitblit.Constants; import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.PathModel;
+import com.gitblit.models.SubmoduleModel;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.utils.JGitUtils;
@@ -69,6 +74,11 @@ public class HistoryPanel extends BasePanel { RevCommit commit = JGitUtils.getCommit(r, objectId);
List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, commit);
+ Map<String, SubmoduleModel> submodules = new HashMap<String, SubmoduleModel>();
+ for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {
+ submodules.put(model.path, model);
+ }
+
PathModel matchingPath = null;
for (PathModel p : paths) {
if (p.path.equals(path)) {
@@ -99,7 +109,20 @@ public class HistoryPanel extends BasePanel { }
final boolean isTree = matchingPath == null ? true : matchingPath.isTree();
+ final boolean isSubmodule = matchingPath == null ? true : matchingPath.isSubmodule();
+ // submodule
+ SubmoduleModel submodule = getSubmodule(submodules, repositoryName, matchingPath.path);
+ final String submodulePath;
+ final boolean hasSubmodule;
+ if (submodule != null) {
+ submodulePath = submodule.gitblitPath;
+ hasSubmodule = submodule.hasSubmodule;
+ } else {
+ submodulePath = "";
+ hasSubmodule = false;
+ }
+
final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs);
List<RevCommit> commits;
if (pageResults) {
@@ -179,6 +202,23 @@ public class HistoryPanel extends BasePanel { links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
WicketUtils.newObjectParameter(repositoryName, entry.getName())));
item.add(links);
+ } else if (isSubmodule) {
+ // submodule
+ item.add(new Label("hashLabel", submodulePath + "@"));
+ Repository repository = GitBlit.self().getRepository(repositoryName);
+ String submoduleId = JGitUtils.getSubmoduleCommitId(repository, path, entry);
+ repository.close();
+ LinkPanel commitHash = new LinkPanel("hashLink", null, submoduleId.substring(0, hashLen),
+ TreePage.class, WicketUtils.newObjectParameter(
+ submodulePath, submoduleId));
+ WicketUtils.setCssClass(commitHash, "shortsha1");
+ WicketUtils.setHtmlTooltip(commitHash, submoduleId);
+ item.add(commitHash.setEnabled(hasSubmodule));
+
+ Fragment links = new Fragment("historyLinks", "treeLinks", this);
+ links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
+ WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+ item.add(links);
} else {
// commit
item.add(new Label("hashLabel", getString("gb.blob") + "@"));
@@ -230,4 +270,66 @@ public class HistoryPanel extends BasePanel { public boolean hasMore() {
return hasMore;
}
+
+ protected SubmoduleModel getSubmodule(Map<String, SubmoduleModel> submodules, String repositoryName, String path) {
+ SubmoduleModel model = submodules.get(path);
+ if (model == null) {
+ // undefined submodule?!
+ model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);
+ model.hasSubmodule = false;
+ model.gitblitPath = model.name;
+ return model;
+ } else {
+ // extract the repository name from the clone url
+ List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);
+ String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));
+
+ // determine the current path for constructing paths relative
+ // to the current repository
+ String currentPath = "";
+ if (repositoryName.indexOf('/') > -1) {
+ currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);
+ }
+
+ // try to locate the submodule repository
+ // prefer bare to non-bare names
+ List<String> candidates = new ArrayList<String>();
+
+ // relative
+ candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));
+ candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+ // relative, no subfolder
+ if (submoduleName.lastIndexOf('/') > -1) {
+ String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+ candidates.add(currentPath + StringUtils.stripDotGit(name));
+ candidates.add(currentPath + candidates.get(candidates.size() - 1) + ".git");
+ }
+
+ // absolute
+ candidates.add(StringUtils.stripDotGit(submoduleName));
+ candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+ // absolute, no subfolder
+ if (submoduleName.lastIndexOf('/') > -1) {
+ String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+ candidates.add(StringUtils.stripDotGit(name));
+ candidates.add(candidates.get(candidates.size() - 1) + ".git");
+ }
+
+ // create a unique, ordered set of candidate paths
+ Set<String> paths = new LinkedHashSet<String>(candidates);
+ for (String candidate : paths) {
+ if (GitBlit.self().hasRepository(candidate)) {
+ model.hasSubmodule = true;
+ model.gitblitPath = candidate;
+ return model;
+ }
+ }
+
+ // we do not have a copy of the submodule, but we need a path
+ model.gitblitPath = candidates.get(0);
+ return model;
+ }
+ }
}
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html index 46781536..9b621d5a 100644 --- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html +++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html @@ -38,6 +38,7 @@ <div class="pull-right" style="text-align:right;padding-right:15px;">
<span wicket:id="repositoryLinks"></span>
<div>
+ <img class="inlineIcon" wicket:id="sparkleshareIcon" />
<img class="inlineIcon" wicket:id="frozenIcon" />
<img class="inlineIcon" wicket:id="federatedIcon" />
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java index 50f0d52d..7b4ee9f0 100644 --- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java +++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java @@ -87,6 +87,12 @@ public class ProjectRepositoryPanel extends BasePanel { add(forkFrag);
}
+ if (entry.isSparkleshared()) {
+ add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png", localizer.getString("gb.isSparkleshared", parent)));
+ } else {
+ add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
+ }
+
add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp).setVisible(entry.useTickets));
add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp).setVisible(entry.useDocs));
@@ -121,16 +127,24 @@ public class ProjectRepositoryPanel extends BasePanel { add(WicketUtils.newBlankImage("accessRestrictionIcon"));
}
- if (StringUtils.isEmpty(entry.owner)) {
+ if (ArrayUtils.isEmpty(entry.owners)) {
add(new Label("repositoryOwner").setVisible(false));
} else {
- UserModel ownerModel = GitBlit.self().getUserModel(entry.owner);
- String owner = entry.owner;
- if (ownerModel != null) {
- owner = ownerModel.getDisplayName();
+ String owner = "";
+ for (String username : entry.owners) {
+ UserModel ownerModel = GitBlit.self().getUserModel(username);
+
+ if (ownerModel != null) {
+ owner = ownerModel.getDisplayName();
+ }
+ }
+ if (entry.owners.size() > 1) {
+ owner += ", ...";
}
- add(new Label("repositoryOwner", owner + " (" +
+ Label ownerLabel = (new Label("repositoryOwner", owner + " (" +
localizer.getString("gb.owner", parent) + ")"));
+ WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
+ add(ownerLabel);
}
UserModel user = GitBlitWebSession.get().getUser();
diff --git a/src/com/gitblit/wicket/panels/RefsPanel.java b/src/com/gitblit/wicket/panels/RefsPanel.java index b4676427..3ba22c0b 100644 --- a/src/com/gitblit/wicket/panels/RefsPanel.java +++ b/src/com/gitblit/wicket/panels/RefsPanel.java @@ -129,8 +129,14 @@ public class RefsPanel extends Panel { name = name.substring(Constants.R_TAGS.length());
cssClass = "tagRef";
} else if (name.startsWith(Constants.R_NOTES)) {
+ // codereview refs
linkClass = CommitPage.class;
cssClass = "otherRef";
+ } else if (name.startsWith(com.gitblit.Constants.R_GITBLIT)) {
+ // gitblit refs
+ linkClass = LogPage.class;
+ cssClass = "otherRef";
+ name = name.substring(com.gitblit.Constants.R_GITBLIT.length());
}
Component c = new LinkPanel("refName", null, name, linkClass,
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java index 689ee571..4156cd19 100644 --- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java +++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java @@ -138,10 +138,10 @@ public class RegistrantPermissionsPanel extends BasePanel { } } else if (RegistrantType.USER.equals(entry.registrantType)) { // user - PersonIdent ident = new PersonIdent(entry.registrant, null); + PersonIdent ident = new PersonIdent(entry.registrant, ""); UserModel user = GitBlit.self().getUserModel(entry.registrant); if (user != null) { - ident = new PersonIdent(user.getDisplayName(), user.emailAddress); + ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress); } Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this); diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html index 42f9f1f2..81a4c6eb 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.html +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html @@ -89,7 +89,7 @@ <td class="left" style="padding-left:3px;" ><b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
<td class="hidden-phone"><span class="list" wicket:id="repositoryDescription">[repository description]</span></td>
<td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
- <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="forkIcon" /><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 class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="forkIcon" /><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 class="hidden-phone" 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 d3b8ddbe..726af61d 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java @@ -49,6 +49,7 @@ import com.gitblit.SyndicationServlet; import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
@@ -123,22 +124,18 @@ public class RepositoriesPanel extends BasePanel { if (rootRepositories.size() > 0) {
// inject the root repositories at the top of the page
- String rootPath = GitBlit.getString(Keys.web.repositoryRootGroupName, " ");
- roots.add(0, rootPath);
- groups.put(rootPath, rootRepositories);
+ roots.add(0, "");
+ groups.put("", rootRepositories);
}
- Map<String, ProjectModel> projects = new HashMap<String, ProjectModel>();
- for (ProjectModel project : GitBlit.self().getProjectModels(user, true)) {
- projects.put(project.name, project);
- }
List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
for (String root : roots) {
List<RepositoryModel> subModels = groups.get(root);
- GroupRepositoryModel group = new GroupRepositoryModel(root, subModels.size());
- if (projects.containsKey(root)) {
- group.title = projects.get(root).title;
- group.description = projects.get(root).description;
+ ProjectModel project = GitBlit.self().getProjectModel(root);
+ GroupRepositoryModel group = new GroupRepositoryModel(project.name, subModels.size());
+ if (project != null) {
+ group.title = project.title;
+ group.description = project.description;
}
groupedModels.add(group);
Collections.sort(subModels);
@@ -237,6 +234,13 @@ public class RepositoriesPanel extends BasePanel { .setEscapeModelStrings(false));
}
+ if (entry.isSparkleshared()) {
+ row.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png",
+ getString("gb.isSparkleshared")));
+ } else {
+ row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
+ }
+
if (entry.isFork()) {
row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
getString("gb.isFork")));
@@ -291,14 +295,23 @@ public class RepositoriesPanel extends BasePanel { row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
}
- String owner = entry.owner;
- if (!StringUtils.isEmpty(owner)) {
- UserModel ownerModel = GitBlit.self().getUserModel(owner);
- if (ownerModel != null) {
- owner = ownerModel.getDisplayName();
+ String owner = "";
+ if (!ArrayUtils.isEmpty(entry.owners)) {
+ // display first owner
+ for (String username : entry.owners) {
+ UserModel ownerModel = GitBlit.self().getUserModel(username);
+ if (ownerModel != null) {
+ owner = ownerModel.getDisplayName();
+ break;
+ }
+ }
+ if (entry.owners.size() > 1) {
+ owner += ", ...";
}
}
- row.add(new Label("repositoryOwner", owner));
+ Label ownerLabel = new Label("repositoryOwner", owner);
+ WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
+ row.add(ownerLabel);
String lastChange;
if (entry.lastChange.getTime() == 0) {
@@ -519,10 +532,12 @@ public class RepositoriesPanel extends BasePanel { Collections.sort(list, new Comparator<RepositoryModel>() {
@Override
public int compare(RepositoryModel o1, RepositoryModel o2) {
+ String own1 = ArrayUtils.toString(o1.owners);
+ String own2 = ArrayUtils.toString(o2.owners);
if (asc) {
- return o1.owner.compareTo(o2.owner);
+ return own1.compareTo(own2);
}
- return o2.owner.compareTo(o1.owner);
+ return own2.compareTo(own1);
}
});
} else if (prop.equals(SortBy.description.name())) {
diff --git a/src/com/gitblit/wicket/panels/TeamsPanel.java b/src/com/gitblit/wicket/panels/TeamsPanel.java index cc37c519..b76388b3 100644 --- a/src/com/gitblit/wicket/panels/TeamsPanel.java +++ b/src/com/gitblit/wicket/panels/TeamsPanel.java @@ -40,7 +40,7 @@ public class TeamsPanel extends BasePanel { Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
adminLinks.add(new BookmarkablePageLink<Void>("newTeam", EditTeamPage.class));
- add(adminLinks.setVisible(showAdmin && GitBlit.self().supportsTeamMembershipChanges()));
+ add(adminLinks.setVisible(showAdmin && GitBlit.self().supportsTeamMembershipChanges(null)));
final List<TeamModel> teams = GitBlit.self().getAllTeams();
DataView<TeamModel> teamsView = new DataView<TeamModel>("teamRow",
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.html b/src/com/gitblit/wicket/panels/UsersPanel.html index aed985c1..80159610 100644 --- a/src/com/gitblit/wicket/panels/UsersPanel.html +++ b/src/com/gitblit/wicket/panels/UsersPanel.html @@ -17,7 +17,7 @@ </th>
<th class="hidden-phone hidden-tablet left"><wicket:message key="gb.displayName">[display name]</wicket:message></th>
<th class="hidden-phone hidden-tablet left"><wicket:message key="gb.emailAddress">[email address]</wicket:message></th>
- <th class="hidden-phone" style="width:120px;"><wicket:message key="gb.accessLevel">[access level]</wicket:message></th>
+ <th class="hidden-phone" style="width:140px;"><wicket:message key="gb.type">[type]</wicket:message></th>
<th class="hidden-phone" style="width:140px;"><wicket:message key="gb.teamMemberships">[team memberships]</wicket:message></th>
<th class="hidden-phone" style="width:100px;"><wicket:message key="gb.repositories">[repositories]</wicket:message></th>
<th style="width:80px;" class="right"></th>
@@ -27,7 +27,7 @@ <td class="left" ><span class="list" wicket:id="username">[username]</span></td>
<td class="hidden-phone hidden-tablet left" ><span class="list" wicket:id="displayName">[display name]</span></td>
<td class="hidden-phone hidden-tablet left" ><span class="list" wicket:id="emailAddress">[email address]</span></td>
- <td class="hidden-phone left" ><span class="list" wicket:id="accesslevel">[access level]</span></td>
+ <td class="hidden-phone left" ><span style="font-size: 0.8em;" wicket:id="accountType">[account type]</span></td>
<td class="hidden-phone left" ><span class="list" wicket:id="teams">[team memberships]</span></td>
<td class="hidden-phone left" ><span class="list" wicket:id="repositories">[repositories]</span></td>
<td class="rightAlign"><span wicket:id="userLinks"></span></td>
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.java b/src/com/gitblit/wicket/panels/UsersPanel.java index 46c502e5..f5b95e20 100644 --- a/src/com/gitblit/wicket/panels/UsersPanel.java +++ b/src/com/gitblit/wicket/panels/UsersPanel.java @@ -41,7 +41,7 @@ public class UsersPanel extends BasePanel { Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class)
- .setVisible(GitBlit.self().supportsCredentialChanges()));
+ .setVisible(GitBlit.self().supportsAddUser()));
add(adminLinks.setVisible(showAdmin));
final List<UserModel> users = GitBlit.self().getAllUsers();
@@ -81,7 +81,7 @@ public class UsersPanel extends BasePanel { item.add(editLink);
}
- item.add(new Label("accesslevel", entry.canAdmin() ? "administrator" : ""));
+ item.add(new Label("accountType", entry.accountType.name() + (entry.canAdmin() ? ", admin":"")));
item.add(new Label("teams", entry.teams.size() > 0 ? ("" + entry.teams.size()) : ""));
item.add(new Label("repositories",
entry.permissions.size() > 0 ? ("" + entry.permissions.size()) : ""));
|