Merge pull request #1160 from fzs/sshLdapAuthenticator

LDAP SSH key manager
diff --git a/build.moxie b/build.moxie
index e1dbdd5..e84ab40 100644
--- a/build.moxie
+++ b/build.moxie
@@ -10,7 +10,7 @@
 description: pure Java Git solution
 groupId: com.gitblit
 artifactId: gitblit
-version: 1.8.0
+version: 1.8.1-SNAPSHOT
 inceptionYear: 2011
 
 # Current stable release
diff --git a/releases.moxie b/releases.moxie
index 554028a..be1200e 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -1,4 +1,22 @@
 #
+# ${project.version} release
+#
+r30: {
+    title: ${project.name} ${project.version} released
+    id: ${project.version}
+    date: ${project.buildDate}
+    note: ~
+    html: ~
+    text: ~
+    security: ~
+    fixes: ~
+    changes: ~
+    additions: ~
+    dependencyChanges: ~
+    contributors: ~
+}
+
+#
 # 1.8.0 release
 #
 r29: {
@@ -1832,6 +1850,6 @@
 	- James Moger
 }
 
-snapshot: ~
+snapshot: &r30
 release: &r29
 releases: &r[1..29]
diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties
index b9d77fe..4b12ba2 100644
--- a/src/main/distrib/data/defaults.properties
+++ b/src/main/distrib/data/defaults.properties
@@ -567,6 +567,21 @@
 # SINCE 1.4.0
 tickets.requireApproval = false
 
+# Default setting to control how patchsets are merged to the integration branch.
+# Valid values: 
+# MERGE_ALWAYS       - Always merge with a merge commit. Every ticket will show up as a branch,
+#                       even if it could have been fast-forward merged. This is the default.
+# MERGE_IF_NECESSARY - If possible, fast-forward the integration branch,
+#                       if not, merge with a merge commit.
+# FAST_FORWARD_ONLY  - Only merge when a fast-forward is possible. This produces a strictly
+#                       linear history of the integration branch.
+#
+# This setting can be overriden per-repository.
+#
+# RESTART REQUIRED
+# SINCE 1.9.0
+tickets.mergeType = MERGE_ALWAYS
+
 # The case-insensitive regular expression used to identify and close tickets on
 # push to the integration branch for commits that are NOT already referenced as
 # a patchset tip.
diff --git a/src/main/distrib/data/groovy/sendmail-html.groovy b/src/main/distrib/data/groovy/sendmail-html.groovy
index 2692556..305c640 100644
--- a/src/main/distrib/data/groovy/sendmail-html.groovy
+++ b/src/main/distrib/data/groovy/sendmail-html.groovy
@@ -336,7 +336,7 @@
         }

         builder.td() {

             mkp.yield header.oldPath

-			mkp.yieldUnescaped "<b> -&rt; </b>"

+			mkp.yieldUnescaped "<b> -&gt; </b>"

 			a(href:blobDiffUrl(id, header.newPath),  header.newPath)

         }

     }

diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java
index 6d7230f..025b1d8 100644
--- a/src/main/java/com/gitblit/ConfigUserService.java
+++ b/src/main/java/com/gitblit/ConfigUserService.java
@@ -898,7 +898,7 @@
 					user.countryCode = config.getString(USER, username, COUNTRYCODE);

 					user.cookie = config.getString(USER, username, COOKIE);

 					if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {

-						user.cookie = StringUtils.getSHA1(user.username + user.password);

+						user.cookie = user.createCookie();

 					}

 

 					// preferences

diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
index 6232552..ab503bd 100644
--- a/src/main/java/com/gitblit/Constants.java
+++ b/src/main/java/com/gitblit/Constants.java
@@ -62,6 +62,12 @@
 	public static final String GIT_PATH = "/git/";

 	

 	public static final String REGEX_SHA256 = "[a-fA-F0-9]{64}";

+	

+	/**

+	 * This regular expression is used when searching for "mentions" in tickets

+	 * (when someone writes @thisOtherUser)

+	 */

+	public static final String REGEX_TICKET_MENTION = "\\B@(?<user>[^\\s]+)\\b";

 

 	public static final String ZIP_PATH = "/zip/";

 

@@ -639,6 +645,37 @@
 		}

 	}

 

+	/**

+	 * The type of merge Gitblit will use when merging a ticket to the integration branch.

+	 * <p>

+	 * The default type is MERGE_ALWAYS.

+	 * <p>

+	 * This is modeled after the Gerrit SubmitType.

+	 */

+	public static enum MergeType {

+		/** Allows a merge only if it can be fast-forward merged into the integration branch. */

+		FAST_FORWARD_ONLY,

+		/** Uses a fast-forward merge if possible, other wise a merge commit is created. */

+		MERGE_IF_NECESSARY,

+		// Future REBASE_IF_NECESSARY,

+		/** Always merge with a merge commit, even when a fast-forward would be possible. */

+		MERGE_ALWAYS,

+		// Future? CHERRY_PICK

+		;

+

+		public static final MergeType DEFAULT_MERGE_TYPE = MERGE_ALWAYS;

+

+		public static MergeType fromName(String name) {

+			for (MergeType type : values()) {

+				if (type.name().equalsIgnoreCase(name)) {

+					return type;

+				}

+			}

+			return DEFAULT_MERGE_TYPE;

+		}

+	}

+

+

 	@Documented

 	@Retention(RetentionPolicy.RUNTIME)

 	public @interface Unused {

diff --git a/src/main/java/com/gitblit/GitBlitServer.java b/src/main/java/com/gitblit/GitBlitServer.java
index d56d9c0..6123a87 100644
--- a/src/main/java/com/gitblit/GitBlitServer.java
+++ b/src/main/java/com/gitblit/GitBlitServer.java
@@ -375,7 +375,8 @@
 		HashSessionManager sessionManager = new HashSessionManager();
 		sessionManager.setHttpOnly(true);
 		// Use secure cookies if only serving https
-		sessionManager.setSecureRequestOnly(params.port <= 0 && params.securePort > 0);
+		sessionManager.setSecureRequestOnly( (params.port <= 0 && params.securePort > 0) ||
+				(params.port > 0 && params.securePort > 0 && settings.getBoolean(Keys.server.redirectToHttpsPort, true)) );
 		rootContext.getSessionHandler().setSessionManager(sessionManager);
 
 		// Ensure there is a defined User Service
diff --git a/src/main/java/com/gitblit/auth/AuthenticationProvider.java b/src/main/java/com/gitblit/auth/AuthenticationProvider.java
index 0bfe235..e359fd7 100644
--- a/src/main/java/com/gitblit/auth/AuthenticationProvider.java
+++ b/src/main/java/com/gitblit/auth/AuthenticationProvider.java
@@ -78,10 +78,10 @@
 
 	public abstract AuthenticationType getAuthenticationType();
 
-	protected void setCookie(UserModel user, char [] password) {
+	protected void setCookie(UserModel user) {
 		// create a user cookie
-		if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
-			user.cookie = StringUtils.getSHA1(user.username + new String(password));
+		if (StringUtils.isEmpty(user.cookie)) {
+			user.cookie = user.createCookie();
 		}
 	}
 
diff --git a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
index 2cdabf6..3a6cb8e 100644
--- a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
@@ -196,7 +196,7 @@
                 }
 
                 // create a user cookie
-                setCookie(user, password);
+                setCookie(user);
 
                 // Set user attributes, hide password from backing user service.
                 user.password = Constants.EXTERNAL_ACCOUNT;
diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
index 7ea8f11..6a2cbde 100644
--- a/src/main/java/com/gitblit/auth/LdapAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
@@ -292,7 +292,7 @@
 							}
 
 							// create a user cookie
-							setCookie(user, password);
+							setCookie(user);
 
 							if (!supportsTeamMembershipChanges()) {
 								getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
diff --git a/src/main/java/com/gitblit/auth/PAMAuthProvider.java b/src/main/java/com/gitblit/auth/PAMAuthProvider.java
index 46f4dd6..b38d49d 100644
--- a/src/main/java/com/gitblit/auth/PAMAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/PAMAuthProvider.java
@@ -122,7 +122,7 @@
         }
 
         // create a user cookie
-        setCookie(user, password);
+        setCookie(user);
 
         // update user attributes from UnixUser
         user.accountType = getAccountType();
diff --git a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
index 27cece2..364aff0 100644
--- a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
@@ -139,7 +139,7 @@
         }
 
         // create a user cookie
-        setCookie(user, password);
+        setCookie(user);
 
         // update user attributes from Redmine
         user.accountType = getAccountType();
diff --git a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
index df033c2..79c3a0c 100644
--- a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
@@ -66,7 +66,7 @@
 					user = new UserModel(simpleUsername);
 				}
 
-				setCookie(user, password);
+				setCookie(user);
 				setUserAttributes(user, info);
 
 				updateUser(user);
diff --git a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
index aee5100..4c31fb1 100644
--- a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
@@ -153,7 +153,7 @@
         }
 
         // create a user cookie
-        setCookie(user, password);
+        setCookie(user);
 
         // update user attributes from Windows identity
         user.accountType = getAccountType();
diff --git a/src/main/java/com/gitblit/client/EditUserDialog.java b/src/main/java/com/gitblit/client/EditUserDialog.java
index 676916b..4b01ff0 100644
--- a/src/main/java/com/gitblit/client/EditUserDialog.java
+++ b/src/main/java/com/gitblit/client/EditUserDialog.java
@@ -330,7 +330,7 @@
 			}

 

 			// change the cookie

-			user.cookie = StringUtils.getSHA1(user.username + password);

+			user.cookie = user.createCookie();

 

 			String type = settings.get(Keys.realm.passwordStorage).getString("md5");

 			if (type.equalsIgnoreCase("md5")) {

diff --git a/src/main/java/com/gitblit/git/PatchsetReceivePack.java b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
index 33fa470..4a09139 100644
--- a/src/main/java/com/gitblit/git/PatchsetReceivePack.java
+++ b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
@@ -599,7 +599,7 @@
 		}

 

 		// ensure that the patchset can be cleanly merged right now

-		MergeStatus status = JGitUtils.canMerge(getRepository(), tipCommit.getName(), forBranch);

+		MergeStatus status = JGitUtils.canMerge(getRepository(), tipCommit.getName(), forBranch, repository.mergeType);

 		switch (status) {

 		case ALREADY_MERGED:

 			sendError("");

@@ -1279,6 +1279,7 @@
 				getRepository(),

 				patchset.tip,

 				ticket.mergeTo,

+				getRepositoryModel().mergeType,

 				committer,

 				message);

 

diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java
index 4978763..0a4d8ed 100644
--- a/src/main/java/com/gitblit/manager/AuthenticationManager.java
+++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java
@@ -608,6 +608,11 @@
 						userCookie = new Cookie(Constants.NAME, cookie);
 						// expire the cookie in 7 days
 						userCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(7));
+
+						// Set cookies HttpOnly so they are not accessible to JavaScript engines
+						userCookie.setHttpOnly(true);
+						// Set secure cookie if only HTTPS is used
+						userCookie.setSecure(httpsOnly());
 					}
 				}
 				String path = "/";
@@ -622,6 +627,15 @@
 		}
 	}
 
+
+	private boolean httpsOnly() {
+		int port = settings.getInteger(Keys.server.httpPort, 0);
+		int tlsPort = settings.getInteger(Keys.server.httpsPort, 0);
+		return  (port <= 0 && tlsPort > 0) ||
+				(port > 0 && tlsPort > 0 && settings.getBoolean(Keys.server.redirectToHttpsPort, true) );
+	}
+
+
 	/**
 	 * Logout a user.
 	 *
diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java
index e9bf5b8..2be6587 100644
--- a/src/main/java/com/gitblit/manager/RepositoryManager.java
+++ b/src/main/java/com/gitblit/manager/RepositoryManager.java
@@ -63,6 +63,7 @@
 import com.gitblit.Constants.AuthorizationControl;
 import com.gitblit.Constants.CommitMessageRenderer;
 import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.MergeType;
 import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.GitBlitException;
@@ -899,6 +900,7 @@
 			model.acceptNewTickets = getConfig(config, "acceptNewTickets", true);
 			model.requireApproval = getConfig(config, "requireApproval", settings.getBoolean(Keys.tickets.requireApproval, false));
 			model.mergeTo = getConfig(config, "mergeTo", null);
+			model.mergeType = MergeType.fromName(getConfig(config, "mergeType", settings.getString(Keys.tickets.mergeType, null)));
 			model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false);
 			model.incrementalPushTagPrefix = getConfig(config, "incrementalPushTagPrefix", null);
 			model.allowForks = getConfig(config, "allowForks", true);
@@ -1557,6 +1559,13 @@
 		if (!StringUtils.isEmpty(repository.mergeTo)) {
 			config.setString(Constants.CONFIG_GITBLIT, null, "mergeTo", repository.mergeTo);
 		}
+		if (repository.mergeType == null || repository.mergeType == MergeType.fromName(settings.getString(Keys.tickets.mergeType, null))) {
+			// use default
+			config.unset(Constants.CONFIG_GITBLIT, null, "mergeType");
+		} else {
+			// override default
+			config.setString(Constants.CONFIG_GITBLIT, null, "mergeType", repository.mergeType.name());
+		}
 		config.setBoolean(Constants.CONFIG_GITBLIT, null, "useIncrementalPushTags", repository.useIncrementalPushTags);
 		if (StringUtils.isEmpty(repository.incrementalPushTagPrefix) ||
 				repository.incrementalPushTagPrefix.equals(settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r"))) {
@@ -1952,39 +1961,47 @@
 	}
 
 	protected void configureCommitCache() {
-		int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14);
+		final int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14);
 		if (daysToCache <= 0) {
 			logger.info("Commit cache is disabled");
-		} else {
-			long start = System.nanoTime();
-			long repoCount = 0;
-			long commitCount = 0;
-			logger.info(MessageFormat.format("Preparing {0} day commit cache. please wait...", daysToCache));
-			CommitCache.instance().setCacheDays(daysToCache);
-			Date cutoff = CommitCache.instance().getCutoffDate();
-			for (String repositoryName : getRepositoryList()) {
-				RepositoryModel model = getRepositoryModel(repositoryName);
-				if (model != null && model.hasCommits && model.lastChange.after(cutoff)) {
-					repoCount++;
-					Repository repository = getRepository(repositoryName);
-					for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) {
-						if (!ref.getDate().after(cutoff)) {
-							// branch not recently updated
-							continue;
-						}
-						List<?> commits = CommitCache.instance().getCommits(repositoryName, repository, ref.getName());
-						if (commits.size() > 0) {
-							logger.info(MessageFormat.format("  cached {0} commits for {1}:{2}",
-									commits.size(), repositoryName, ref.getName()));
-							commitCount += commits.size();
-						}
-					}
-					repository.close();
-				}
-			}
-			logger.info(MessageFormat.format("built {0} day commit cache of {1} commits across {2} repositories in {3} msecs",
-					daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+			return;
 		}
+		logger.info(MessageFormat.format("Preparing {0} day commit cache...", daysToCache));
+		CommitCache.instance().setCacheDays(daysToCache);
+		Thread loader = new Thread() {
+			@Override
+			public void run() {
+				long start = System.nanoTime();
+				long repoCount = 0;
+				long commitCount = 0;
+				Date cutoff = CommitCache.instance().getCutoffDate();
+				for (String repositoryName : getRepositoryList()) {
+					RepositoryModel model = getRepositoryModel(repositoryName);
+					if (model != null && model.hasCommits && model.lastChange.after(cutoff)) {
+						repoCount++;
+						Repository repository = getRepository(repositoryName);
+						for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) {
+							if (!ref.getDate().after(cutoff)) {
+								// branch not recently updated
+								continue;
+							}
+							List<?> commits = CommitCache.instance().getCommits(repositoryName, repository, ref.getName());
+							if (commits.size() > 0) {
+								logger.info(MessageFormat.format("  cached {0} commits for {1}:{2}",
+										commits.size(), repositoryName, ref.getName()));
+								commitCount += commits.size();
+							}
+						}
+						repository.close();
+					}
+				}
+				logger.info(MessageFormat.format("built {0} day commit cache of {1} commits across {2} repositories in {3} msecs",
+						daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+			}
+		};
+		loader.setName("CommitCacheLoader");
+		loader.setDaemon(true);
+		loader.start();
 	}
 
 	protected void confirmWriteAccess() {
diff --git a/src/main/java/com/gitblit/models/RepositoryModel.java b/src/main/java/com/gitblit/models/RepositoryModel.java
index a81c622..67ee1c7 100644
--- a/src/main/java/com/gitblit/models/RepositoryModel.java
+++ b/src/main/java/com/gitblit/models/RepositoryModel.java
@@ -28,6 +28,7 @@
 import com.gitblit.Constants.AuthorizationControl;

 import com.gitblit.Constants.CommitMessageRenderer;

 import com.gitblit.Constants.FederationStrategy;

+import com.gitblit.Constants.MergeType;

 import com.gitblit.utils.ArrayUtils;

 import com.gitblit.utils.ModelUtils;

 import com.gitblit.utils.StringUtils;

@@ -89,6 +90,7 @@
 	public boolean acceptNewTickets;

 	public boolean requireApproval;
 	public String mergeTo;

+	public MergeType mergeType;

 

 	public transient boolean isCollectingGarbage;

 	public Date lastGC;

@@ -111,6 +113,7 @@
 		this.isBare = true;

 		this.acceptNewTickets = true;

 		this.acceptNewPatchsets = true;

+		this.mergeType = MergeType.DEFAULT_MERGE_TYPE;

 

 		addOwner(owner);

 	}

diff --git a/src/main/java/com/gitblit/models/TicketModel.java b/src/main/java/com/gitblit/models/TicketModel.java
index d534589..65e29dc 100644
--- a/src/main/java/com/gitblit/models/TicketModel.java
+++ b/src/main/java/com/gitblit/models/TicketModel.java
@@ -43,6 +43,8 @@
 
 import org.eclipse.jgit.util.RelativeDateFormatter;
 
+import com.gitblit.Constants;
+
 /**
  * The Gitblit Ticket model, its component classes, and enums.
  *
@@ -773,10 +775,10 @@
 			}
 			
 			try {
-				Pattern mentions = Pattern.compile("\\s@([A-Za-z0-9-_]+)");
+				Pattern mentions = Pattern.compile(Constants.REGEX_TICKET_MENTION);
 				Matcher m = mentions.matcher(text);
 				while (m.find()) {
-					String username = m.group(1);
+					String username = m.group("user");
 					plusList(Field.mentions, username);
 				}
 			} catch (Exception e) {
diff --git a/src/main/java/com/gitblit/models/UserModel.java b/src/main/java/com/gitblit/models/UserModel.java
index e152274..1d9e413 100644
--- a/src/main/java/com/gitblit/models/UserModel.java
+++ b/src/main/java/com/gitblit/models/UserModel.java
@@ -36,6 +36,7 @@
 import com.gitblit.Constants.RegistrantType;

 import com.gitblit.utils.ArrayUtils;

 import com.gitblit.utils.ModelUtils;

+import com.gitblit.utils.SecureRandom;

 import com.gitblit.utils.StringUtils;

 

 /**

@@ -52,6 +53,8 @@
 

 	public static final UserModel ANONYMOUS = new UserModel();

 

+	private static final SecureRandom RANDOM = new SecureRandom();

+

 	// field names are reflectively mapped in EditUser page

 	public String username;

 	public String password;

@@ -660,4 +663,8 @@
 		String projectPath = StringUtils.getFirstPathElement(repository);

 		return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase(getPersonalPath());

 	}

+

+	public String createCookie() {

+		return StringUtils.getSHA1(RANDOM.randomBytes(32));

+	}

 }

diff --git a/src/main/java/com/gitblit/service/MailService.java b/src/main/java/com/gitblit/service/MailService.java
index ec3a84c..58acc9c 100644
--- a/src/main/java/com/gitblit/service/MailService.java
+++ b/src/main/java/com/gitblit/service/MailService.java
@@ -17,6 +17,7 @@
 

 import java.io.File;

 import java.util.ArrayList;

+import java.util.Arrays;

 import java.util.Date;

 import java.util.List;

 import java.util.Properties;

@@ -31,6 +32,7 @@
 import javax.mail.Message;

 import javax.mail.MessagingException;

 import javax.mail.PasswordAuthentication;

+import javax.mail.SendFailedException;

 import javax.mail.Session;

 import javax.mail.Transport;

 import javax.mail.internet.InternetAddress;

@@ -272,9 +274,22 @@
 				while ((message = queue.poll()) != null) {

 					try {

 						if (settings.getBoolean(Keys.mail.debug, false)) {

-							logger.info("send: " + StringUtils.trimString(message.getSubject(), 60));

+							logger.info("send: '" + StringUtils.trimString(message.getSubject(), 60)

+									    + "' to:" + StringUtils.trimString(Arrays.toString(message.getAllRecipients()), 300));

 						}

 						Transport.send(message);

+					} catch (SendFailedException sfe) {

+						if (settings.getBoolean(Keys.mail.debug, false)) {

+							logger.error("Failed to send message: {}", sfe.getMessage());

+							logger.info("   Invalid addresses: {}", Arrays.toString(sfe.getInvalidAddresses()));

+							logger.info("   Valid sent addresses: {}", Arrays.toString(sfe.getValidSentAddresses()));

+							logger.info("   Valid unset addresses: {}", Arrays.toString(sfe.getValidUnsentAddresses()));

+							logger.info("", sfe);

+						}

+						else {

+							logger.error("Failed to send message: {}", sfe.getMessage(), sfe.getNextException());

+						}

+						failures.add(message);

 					} catch (Throwable e) {

 						logger.error("Failed to send message", e);

 						failures.add(message);

diff --git a/src/main/java/com/gitblit/tickets/TicketNotifier.java b/src/main/java/com/gitblit/tickets/TicketNotifier.java
index 8c7fe6d..b913db2 100644
--- a/src/main/java/com/gitblit/tickets/TicketNotifier.java
+++ b/src/main/java/com/gitblit/tickets/TicketNotifier.java
@@ -573,10 +573,10 @@
 		// cc users mentioned in last comment
 		Change lastChange = ticket.changes.get(ticket.changes.size() - 1);
 		if (lastChange.hasComment()) {
-			Pattern p = Pattern.compile("\\s@([A-Za-z0-9-_]+)");
+			Pattern p = Pattern.compile(Constants.REGEX_TICKET_MENTION);
 			Matcher m = p.matcher(lastChange.comment.text);
 			while (m.find()) {
-				String username = m.group();
+				String username = m.group("user");
 				ccs.add(username);
 			}
 		}
diff --git a/src/main/java/com/gitblit/utils/ArrayUtils.java b/src/main/java/com/gitblit/utils/ArrayUtils.java
index 1402ad5..b850ccc 100644
--- a/src/main/java/com/gitblit/utils/ArrayUtils.java
+++ b/src/main/java/com/gitblit/utils/ArrayUtils.java
@@ -42,7 +42,7 @@
 	}

 

 	public static boolean isEmpty(Collection<?> collection) {

-		return collection == null || collection.size() == 0;

+		return collection == null || collection.isEmpty();

 	}

 

 	public static String toString(Collection<?> collection) {

diff --git a/src/main/java/com/gitblit/utils/CommitCache.java b/src/main/java/com/gitblit/utils/CommitCache.java
index a3963f5..53b8de1 100644
--- a/src/main/java/com/gitblit/utils/CommitCache.java
+++ b/src/main/java/com/gitblit/utils/CommitCache.java
@@ -19,9 +19,9 @@
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.lib.ObjectId;
@@ -58,7 +58,7 @@
 	}
 
 	protected CommitCache() {
-		cache = new ConcurrentHashMap<String, ObjectCache<List<RepositoryCommit>>>();
+		cache = new HashMap<>();
 	}
 
 	/**
@@ -93,7 +93,9 @@
 	 *
 	 */
 	public void clear() {
-		cache.clear();
+		synchronized (cache) {
+			cache.clear();
+		}
 	}
 
 	/**
@@ -103,8 +105,11 @@
 	 */
 	public void clear(String repositoryName) {
 		String repoKey = repositoryName.toLowerCase();
-		ObjectCache<List<RepositoryCommit>> repoCache = cache.remove(repoKey);
-		if (repoCache != null) {
+		boolean hadEntries = false;
+		synchronized (cache) {
+			hadEntries = cache.remove(repoKey) != null;
+		}
+		if (hadEntries) {
 			logger.info(MessageFormat.format("{0} commit cache cleared", repositoryName));
 		}
 	}
@@ -117,13 +122,17 @@
 	 */
 	public void clear(String repositoryName, String branch) {
 		String repoKey = repositoryName.toLowerCase();
-		ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
-		if (repoCache != null) {
-			List<RepositoryCommit> commits = repoCache.remove(branch.toLowerCase());
-			if (!ArrayUtils.isEmpty(commits)) {
-				logger.info(MessageFormat.format("{0}:{1} commit cache cleared", repositoryName, branch));
+		boolean hadEntries = false;
+		synchronized (cache) {
+			ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
+			if (repoCache != null) {
+				List<RepositoryCommit> commits = repoCache.remove(branch.toLowerCase());
+				hadEntries = !ArrayUtils.isEmpty(commits);
 			}
 		}
+		if (hadEntries) {
+			logger.info(MessageFormat.format("{0}:{1} commit cache cleared", repositoryName, branch));
+		}
 	}
 
 	/**
@@ -156,49 +165,55 @@
 		if (cacheDays > 0 && (sinceDate.getTime() >= cacheCutoffDate.getTime())) {
 			// request fits within the cache window
 			String repoKey = repositoryName.toLowerCase();
-			if (!cache.containsKey(repoKey)) {
-				cache.put(repoKey, new ObjectCache<List<RepositoryCommit>>());
-			}
-
-			ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
 			String branchKey = branch.toLowerCase();
 
 			RevCommit tip = JGitUtils.getCommit(repository, branch);
 			Date tipDate = JGitUtils.getCommitDate(tip);
 
-			List<RepositoryCommit> commits;
-			if (!repoCache.hasCurrent(branchKey, tipDate)) {
-				commits = repoCache.getObject(branchKey);
-				if (ArrayUtils.isEmpty(commits)) {
-					// we don't have any cached commits for this branch, reload
-					commits = get(repositoryName, repository, branch, cacheCutoffDate);
-					repoCache.updateObject(branchKey, tipDate, commits);
-					logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
-							commits.size(), repositoryName, branch, cacheCutoffDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
-				} else {
-					// incrementally update cache since the last cached commit
-					ObjectId sinceCommit = commits.get(0).getId();
-					List<RepositoryCommit> incremental = get(repositoryName, repository, branch, sinceCommit);
-					logger.info(MessageFormat.format("incrementally added {0} commits to cache for {1}:{2} in {3} msecs",
-							incremental.size(), repositoryName, branch, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
-					incremental.addAll(commits);
-					repoCache.updateObject(branchKey, tipDate, incremental);
-					commits = incremental;
+			ObjectCache<List<RepositoryCommit>> repoCache;
+			synchronized (cache) {
+				repoCache = cache.get(repoKey);
+				if (repoCache == null) {
+					repoCache = new ObjectCache<>();
+					cache.put(repoKey, repoCache);
 				}
-			} else {
-				// cache is current
-				commits = repoCache.getObject(branchKey);
-				// evict older commits outside the cache window
-				commits = reduce(commits, cacheCutoffDate);
-				// update cache
-				repoCache.updateObject(branchKey, tipDate, commits);
 			}
+			synchronized (repoCache) {
+				List<RepositoryCommit> commits;
+				if (!repoCache.hasCurrent(branchKey, tipDate)) {
+					commits = repoCache.getObject(branchKey);
+					if (ArrayUtils.isEmpty(commits)) {
+						// we don't have any cached commits for this branch, reload
+						commits = get(repositoryName, repository, branch, cacheCutoffDate);
+						repoCache.updateObject(branchKey, tipDate, commits);
+						logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
+								commits.size(), repositoryName, branch, cacheCutoffDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+					} else {
+						// incrementally update cache since the last cached commit
+						ObjectId sinceCommit = commits.get(0).getId();
+						List<RepositoryCommit> incremental = get(repositoryName, repository, branch, sinceCommit);
+						logger.info(MessageFormat.format("incrementally added {0} commits to cache for {1}:{2} in {3} msecs",
+								incremental.size(), repositoryName, branch, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+						incremental.addAll(commits);
+						repoCache.updateObject(branchKey, tipDate, incremental);
+						commits = incremental;
+					}
+				} else {
+					// cache is current
+					commits = repoCache.getObject(branchKey);
+					// evict older commits outside the cache window
+					commits = reduce(commits, cacheCutoffDate);
+					// update cache
+					repoCache.updateObject(branchKey, tipDate, commits);
+				}
 
-			if (sinceDate.equals(cacheCutoffDate)) {
-				list = commits;
-			} else {
-				// reduce the commits to those since the specified date
-				list = reduce(commits, sinceDate);
+				if (sinceDate.equals(cacheCutoffDate)) {
+					// Mustn't hand out the cached list; that's not thread-safe
+					list = new ArrayList<>(commits);
+				} else {
+					// reduce the commits to those since the specified date
+					list = reduce(commits, sinceDate);
+				}
 			}
 			logger.debug(MessageFormat.format("retrieved {0} commits from cache of {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
 					list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
@@ -222,8 +237,9 @@
 	 */
 	protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, Date sinceDate) {
 		Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
-		List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
-		for (RevCommit commit : JGitUtils.getRevLog(repository, branch, sinceDate)) {
+		List<RevCommit> revLog = JGitUtils.getRevLog(repository, branch, sinceDate);
+		List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>(revLog.size());
+		for (RevCommit commit : revLog) {
 			RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
 			List<RefModel> commitRefs = allRefs.get(commitModel.getId());
 			commitModel.setRefs(commitRefs);
@@ -243,8 +259,9 @@
 	 */
 	protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, ObjectId sinceCommit) {
 		Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
-		List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
-		for (RevCommit commit : JGitUtils.getRevLog(repository, sinceCommit.getName(), branch)) {
+		List<RevCommit> revLog = JGitUtils.getRevLog(repository, sinceCommit.getName(), branch);
+		List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>(revLog.size());
+		for (RevCommit commit : revLog) {
 			RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
 			List<RefModel> commitRefs = allRefs.get(commitModel.getId());
 			commitModel.setRefs(commitRefs);
@@ -261,7 +278,7 @@
 	 * @return  a list of commits
 	 */
 	protected List<RepositoryCommit> reduce(List<RepositoryCommit> commits, Date sinceDate) {
-		List<RepositoryCommit> filtered = new ArrayList<RepositoryCommit>();
+		List<RepositoryCommit> filtered = new ArrayList<RepositoryCommit>(commits.size());
 		for (RepositoryCommit commit : commits) {
 			if (commit.getCommitDate().compareTo(sinceDate) >= 0) {
 				filtered.add(commit);
diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index a02fc3f..0eea1d6 100644
--- a/src/main/java/com/gitblit/utils/JGitUtils.java
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -99,6 +99,7 @@
 import org.slf4j.Logger;

 import org.slf4j.LoggerFactory;

 

+import com.gitblit.Constants.MergeType;

 import com.gitblit.GitBlitException;

 import com.gitblit.IStoredSettings;

 import com.gitblit.Keys;

@@ -2453,44 +2454,13 @@
 	 * @param repository

 	 * @param src

 	 * @param toBranch

+	 * @param mergeType

+	 *            Defines the integration strategy to use for merging.

 	 * @return true if we can merge without conflict

 	 */

-	public static MergeStatus canMerge(Repository repository, String src, String toBranch) {

-		RevWalk revWalk = null;

-		try {

-			revWalk = new RevWalk(repository);

-			ObjectId branchId = repository.resolve(toBranch);

-			if (branchId == null) {

-				return MergeStatus.MISSING_INTEGRATION_BRANCH;

-			}

-			ObjectId srcId = repository.resolve(src);

-			if (srcId == null) {

-				return MergeStatus.MISSING_SRC_BRANCH;

-			}

-			RevCommit branchTip = revWalk.lookupCommit(branchId);

-			RevCommit srcTip = revWalk.lookupCommit(srcId);

-			if (revWalk.isMergedInto(srcTip, branchTip)) {

-				// already merged

-				return MergeStatus.ALREADY_MERGED;

-			} else if (revWalk.isMergedInto(branchTip, srcTip)) {

-				// fast-forward

-				return MergeStatus.MERGEABLE;

-			}

-			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);

-			boolean canMerge = merger.merge(branchTip, srcTip);

-			if (canMerge) {

-				return MergeStatus.MERGEABLE;

-			}

-		} catch (NullPointerException e) {

-			LOGGER.error("Failed to determine canMerge", e);

-		} catch (IOException e) {

-			LOGGER.error("Failed to determine canMerge", e);

-		} finally {

-			if (revWalk != null) {

-				revWalk.close();

-			}

-		}

-		return MergeStatus.NOT_MERGEABLE;

+	public static MergeStatus canMerge(Repository repository, String src, String toBranch, MergeType mergeType) {

+		IntegrationStrategy strategy = IntegrationStrategyFactory.create(mergeType, repository, src, toBranch);

+		return strategy.canMerge();

 	}

 

 

@@ -2511,11 +2481,13 @@
 	 * @param repository

 	 * @param src

 	 * @param toBranch

+	 * @param mergeType

+	 *            Defines the integration strategy to use for merging.

 	 * @param committer

 	 * @param message

 	 * @return the merge result

 	 */

-	public static MergeResult merge(Repository repository, String src, String toBranch,

+	public static MergeResult merge(Repository repository, String src, String toBranch, MergeType mergeType,

 			PersonIdent committer, String message) {

 

 		if (!toBranch.startsWith(Constants.R_REFS)) {

@@ -2523,15 +2495,202 @@
 			toBranch = Constants.R_HEADS + toBranch;

 		}

 

-		RevWalk revWalk = null;

+		IntegrationStrategy strategy = IntegrationStrategyFactory.create(mergeType, repository, src, toBranch);

+		MergeResult mergeResult = strategy.merge(committer, message);

+

+		if (mergeResult.status != MergeStatus.MERGED) {

+			return mergeResult;

+		}

+

 		try {

-			revWalk = new RevWalk(repository);

-			RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));

-			RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));

-			if (revWalk.isMergedInto(srcTip, branchTip)) {

-				// already merged

-				return new MergeResult(MergeStatus.ALREADY_MERGED, null);

+			// Update the integration branch ref

+			RefUpdate mergeRefUpdate = repository.updateRef(toBranch);

+			mergeRefUpdate.setNewObjectId(strategy.getMergeCommit());

+			mergeRefUpdate.setRefLogMessage(strategy.getRefLogMessage(), false);

+			mergeRefUpdate.setExpectedOldObjectId(strategy.branchTip);

+			RefUpdate.Result rc = mergeRefUpdate.update();

+			switch (rc) {

+			case FAST_FORWARD:

+				// successful, clean merge

+				break;

+			default:

+				mergeResult = new MergeResult(MergeStatus.FAILED, null);

+				throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when {1} in {2}",

+						rc.name(), strategy.getOperationMessage(), repository.getDirectory()));

 			}

+		} catch (IOException e) {

+			LOGGER.error("Failed to merge", e);

+		}

+

+		return mergeResult;

+	}

+

+

+	private static abstract class IntegrationStrategy {

+		Repository repository;

+		String src;

+		String toBranch;

+

+		RevWalk revWalk;

+		RevCommit branchTip;

+		RevCommit srcTip;

+

+		RevCommit mergeCommit;

+		String refLogMessage;

+		String operationMessage;

+

+		RevCommit getMergeCommit() {

+			return mergeCommit;

+		}

+

+		String getRefLogMessage() {

+			return refLogMessage;

+		}

+

+		String getOperationMessage() {

+			return operationMessage;

+		}

+

+		IntegrationStrategy(Repository repository, String src, String toBranch) {

+			this.repository = repository;

+			this.src = src;

+			this.toBranch = toBranch;

+		}

+

+		void prepare() throws IOException {

+			if (revWalk == null) revWalk = new RevWalk(repository);

+			ObjectId branchId = repository.resolve(toBranch);

+			if (branchId != null) {

+				branchTip = revWalk.lookupCommit(branchId);

+			}

+			ObjectId srcId = repository.resolve(src);

+			if (srcId != null) {

+				srcTip = revWalk.lookupCommit(srcId);

+			}

+		}

+

+

+		abstract MergeStatus _canMerge() throws IOException;

+

+

+		MergeStatus canMerge() {

+			try {

+				prepare();

+				if (branchTip == null) {

+					return MergeStatus.MISSING_INTEGRATION_BRANCH;

+				}

+				if (srcTip == null) {

+					return MergeStatus.MISSING_SRC_BRANCH;

+				}

+				if (revWalk.isMergedInto(srcTip, branchTip)) {

+					// already merged

+					return MergeStatus.ALREADY_MERGED;

+				}

+				// determined by specific integration strategy

+				return _canMerge();

+

+			} catch (NullPointerException e) {

+				LOGGER.error("Failed to determine canMerge", e);

+			} catch (IOException e) {

+				LOGGER.error("Failed to determine canMerge", e);

+			} finally {

+				if (revWalk != null) {

+					revWalk.close();

+				}

+			}

+

+			return MergeStatus.NOT_MERGEABLE;

+		}

+

+

+		abstract MergeResult _merge(PersonIdent committer, String message) throws IOException;

+

+

+		MergeResult merge(PersonIdent committer, String message) {

+			try {

+				prepare();

+				if (revWalk.isMergedInto(srcTip, branchTip)) {

+					// already merged

+					return new MergeResult(MergeStatus.ALREADY_MERGED, null);

+				}

+				// determined by specific integration strategy

+				return _merge(committer, message);

+

+			} catch (IOException e) {

+				LOGGER.error("Failed to merge", e);

+			} finally {

+				if (revWalk != null) {

+					revWalk.close();

+				}

+			}

+

+			return new MergeResult(MergeStatus.FAILED, null);

+		}

+	}

+

+

+	private static class FastForwardOnly extends IntegrationStrategy {

+		FastForwardOnly(Repository repository, String src, String toBranch) {

+			super(repository, src, toBranch);

+		}

+

+		@Override

+		MergeStatus _canMerge() throws IOException {

+			if (revWalk.isMergedInto(branchTip, srcTip)) {

+				// fast-forward

+				return MergeStatus.MERGEABLE;

+			}

+

+			return MergeStatus.NOT_MERGEABLE;

+		}

+

+		@Override

+		MergeResult _merge(PersonIdent committer, String message) throws IOException {

+			if (! revWalk.isMergedInto(branchTip, srcTip)) {

+				// is not fast-forward

+				return new MergeResult(MergeStatus.FAILED, null);

+			}

+

+			mergeCommit = srcTip;

+			refLogMessage = "merge " + src + ": Fast-forward";

+			operationMessage = MessageFormat.format("fast-forwarding {0} to commit {1}", srcTip.getName(), branchTip.getName());

+

+			return new MergeResult(MergeStatus.MERGED, srcTip.getName());

+		}

+	}

+

+	private static class MergeIfNecessary extends IntegrationStrategy {

+		MergeIfNecessary(Repository repository, String src, String toBranch) {

+			super(repository, src, toBranch);

+		}

+

+		@Override

+		MergeStatus _canMerge() throws IOException {

+			if (revWalk.isMergedInto(branchTip, srcTip)) {

+				// fast-forward

+				return MergeStatus.MERGEABLE;

+			}

+

+			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);

+			boolean canMerge = merger.merge(branchTip, srcTip);

+			if (canMerge) {

+				return MergeStatus.MERGEABLE;

+			}

+

+			return MergeStatus.NOT_MERGEABLE;

+		}

+

+		@Override

+		MergeResult _merge(PersonIdent committer, String message) throws IOException {

+			if (revWalk.isMergedInto(branchTip, srcTip)) {

+				// fast-forward

+				mergeCommit = srcTip;

+				refLogMessage = "merge " + src + ": Fast-forward";

+				operationMessage = MessageFormat.format("fast-forwarding {0} to commit {1}", branchTip.getName(), srcTip.getName());

+

+				return new MergeResult(MergeStatus.MERGED, srcTip.getName());

+			}

+

 			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);

 			boolean merged = merger.merge(branchTip, srcTip);

 			if (merged) {

@@ -2555,20 +2714,9 @@
 					ObjectId mergeCommitId = odi.insert(commitBuilder);

 					odi.flush();

 

-					// set the merge ref to the merge commit

-					RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId);

-					RefUpdate mergeRefUpdate = repository.updateRef(toBranch);

-					mergeRefUpdate.setNewObjectId(mergeCommitId);

-					mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false);

-					RefUpdate.Result rc = mergeRefUpdate.update();

-					switch (rc) {

-					case FAST_FORWARD:

-						// successful, clean merge

-						break;

-					default:

-						throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}",

-								rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory()));

-					}

+					mergeCommit = revWalk.parseCommit(mergeCommitId);

+					refLogMessage = "commit: " + mergeCommit.getShortMessage();

+					operationMessage = MessageFormat.format("merging commit {0} into {1}", srcTip.getName(), branchTip.getName());

 

 					// return the merge commit id

 					return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());

@@ -2576,17 +2724,82 @@
 					odi.close();

 				}

 			}

-		} catch (IOException e) {

-			LOGGER.error("Failed to merge", e);

-		} finally {

-			if (revWalk != null) {

-				revWalk.close();

-			}

+			return new MergeResult(MergeStatus.FAILED, null);

 		}

-		return new MergeResult(MergeStatus.FAILED, null);

 	}

-	

-	

+

+	private static class MergeAlways extends IntegrationStrategy {

+		MergeAlways(Repository repository, String src, String toBranch) {

+			super(repository, src, toBranch);

+		}

+

+		@Override

+		MergeStatus _canMerge() throws IOException {

+			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);

+			boolean canMerge = merger.merge(branchTip, srcTip);

+			if (canMerge) {

+				return MergeStatus.MERGEABLE;

+			}

+

+			return MergeStatus.NOT_MERGEABLE;

+		}

+

+		@Override

+		MergeResult _merge(PersonIdent committer, String message) throws IOException {

+			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);

+			boolean merged = merger.merge(branchTip, srcTip);

+			if (merged) {

+				// create a merge commit and a reference to track the merge commit

+				ObjectId treeId = merger.getResultTreeId();

+				ObjectInserter odi = repository.newObjectInserter();

+				try {

+					// Create a commit object

+					CommitBuilder commitBuilder = new CommitBuilder();

+					commitBuilder.setCommitter(committer);

+					commitBuilder.setAuthor(committer);

+					commitBuilder.setEncoding(Constants.CHARSET);

+					if (StringUtils.isEmpty(message)) {

+						message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());

+					}

+					commitBuilder.setMessage(message);

+					commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());

+					commitBuilder.setTreeId(treeId);

+

+					// Insert the merge commit into the repository

+					ObjectId mergeCommitId = odi.insert(commitBuilder);

+					odi.flush();

+

+					mergeCommit = revWalk.parseCommit(mergeCommitId);

+					refLogMessage = "commit: " + mergeCommit.getShortMessage();

+					operationMessage = MessageFormat.format("merging commit {0} into {1}", srcTip.getName(), branchTip.getName());

+

+					// return the merge commit id

+					return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());

+				} finally {

+					odi.close();

+				}

+			}

+

+			return new MergeResult(MergeStatus.FAILED, null);

+		}

+	}

+

+

+	private static class IntegrationStrategyFactory {

+		static IntegrationStrategy create(MergeType mergeType, Repository repository, String src, String toBranch) {

+			switch(mergeType) {

+			case FAST_FORWARD_ONLY:

+				return new FastForwardOnly(repository, src, toBranch);

+			case MERGE_IF_NECESSARY:

+				return new MergeIfNecessary(repository, src, toBranch);

+			case MERGE_ALWAYS:

+				return new MergeAlways(repository, src, toBranch);

+			}

+			return null;

+		}

+	}

+

+

 	/**

 	 * Returns the LFS URL for the given oid 

 	 * Currently assumes that the Gitblit Filestore is used 

diff --git a/src/main/java/com/gitblit/utils/MarkdownUtils.java b/src/main/java/com/gitblit/utils/MarkdownUtils.java
index e0c9dd4..8371b3c 100644
--- a/src/main/java/com/gitblit/utils/MarkdownUtils.java
+++ b/src/main/java/com/gitblit/utils/MarkdownUtils.java
@@ -30,6 +30,7 @@
 import org.pegdown.PegDownProcessor;

 import org.pegdown.ast.RootNode;

 

+import com.gitblit.Constants;

 import com.gitblit.IStoredSettings;

 import com.gitblit.Keys;

 import com.gitblit.wicket.MarkupProcessor.WorkaroundHtmlSerializer;

@@ -137,8 +138,8 @@
 		String canonicalUrl = settings.getString(Keys.web.canonicalUrl, "https://localhost:8443");

 

 		// emphasize and link mentions

-		String mentionReplacement = String.format(" **[@$1](%1s/user/$1)**", canonicalUrl);

-		text = text.replaceAll("\\s@([A-Za-z0-9-_]+)", mentionReplacement);

+		String mentionReplacement = String.format("**[@${user}](%1s/user/${user})**", canonicalUrl);

+		text = text.replaceAll(Constants.REGEX_TICKET_MENTION, mentionReplacement);

 

 		// link ticket refs
 		String ticketReplacement = MessageFormat.format("$1[#$2]({0}/tickets?r={1}&h=$2)$3", canonicalUrl, repositoryName);
diff --git a/src/main/java/com/gitblit/utils/SecureRandom.java b/src/main/java/com/gitblit/utils/SecureRandom.java
new file mode 100644
index 0000000..119533d
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/SecureRandom.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 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;
+
+/**
+ * Wrapper class for java.security.SecureRandom, which will periodically reseed
+ * the PRNG in case an instance of the class has been running for a long time.
+ *
+ * @author Florian Zschocke
+ */
+public class SecureRandom {
+
+	/** Period (in ms) after which a new SecureRandom will be created in order to get a fresh random seed. */
+	private static final long RESEED_PERIOD = 24 * 60 * 60 * 1000; /* 24 hours */
+
+
+	private long last;
+	private java.security.SecureRandom random;
+
+
+
+	public SecureRandom() {
+		// Make sure the SecureRandom is seeded right from the start.
+		// This also lets any blocks during seeding occur at creation
+		// and prevents it from happening when getting next random bytes.
+		seed();
+	}
+
+
+
+	public byte[] randomBytes(int num) {
+		byte[] bytes = new byte[num];
+		nextBytes(bytes);
+		return bytes;
+	}
+
+
+	public void nextBytes(byte[] bytes) {
+		random.nextBytes(bytes);
+		reseed(false);
+	}
+
+
+	void reseed(boolean forced) {
+		long ts = System.currentTimeMillis();
+		if (forced || (ts - last) > RESEED_PERIOD) {
+			last = ts;
+			runReseed();
+		}
+	}
+
+
+
+	private void seed() {
+		random = new java.security.SecureRandom();
+		random.nextBytes(new byte[0]);
+		last = System.currentTimeMillis();
+	}
+
+
+	private void runReseed() {
+		// Have some other thread hit the penalty potentially incurred by reseeding,
+		// so that we can immediately return and not block the operation in progress.
+		new Thread() {
+			public void run() {
+				seed();
+			}
+		}.start();
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index a215b4d..b3cbef8 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -660,6 +660,7 @@
 gb.body = body
 gb.mergeSha = mergeSha
 gb.mergeTo = merge to
+gb.mergeType = merge type
 gb.labels = labels
 gb.reviewers = reviewers
 gb.voters = voters
@@ -671,6 +672,7 @@
 gb.serverDoesNotAcceptPatchsets = This server does not accept patchsets.
 gb.ticketIsClosed = This ticket is closed.
 gb.mergeToDescription = default integration branch for merging ticket patchsets
+gb.mergeTypeDescription = merge a ticket fast-forward only, if necessary, or always with a merge commit to the integration branch
 gb.anonymousCanNotPropose = Anonymous users can not propose patchsets.
 gb.youDoNotHaveClonePermission = You are not permitted to clone this repository.
 gb.myTickets = my tickets
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties
index f43b8f5..f71d67d 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties
@@ -476,7 +476,7 @@
 gb.pushedNewTag = push nieuwe tag
 gb.createdNewTag = nieuwe tag gemaakt
 gb.deletedTag = tag verwijderd
-gb.pushedNewBranch = push neuwe branch
+gb.pushedNewBranch = push nieuwe branch
 gb.createdNewBranch = nieuwe branch gemaakt
 gb.deletedBranch = branch verwijderd
 gb.createdNewPullRequest = pull verzoek gemaakt
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
index 16a9c86..bf2d2c3 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
@@ -1,772 +1,783 @@
 #!
-#! created/edited by Popeye version 0.54 (popeye.sourceforge.net)
+#! created/edited by Popeye version 0.55 (https://github.com/koppor/popeye)
 #! encoding:ISO-8859-1
-gb.about = \u95dc\u65bc
-gb.acceptNewPatchsets = \u5141\u8a31\u88dc\u4e01
-gb.acceptNewPatchsetsDescription = \u63a5\u53d7\u5230\u7248\u672c\u5009\u9032\u884c\u4fee\u88dc\u52d5\u4f5c
-gb.acceptNewTickets = \u5141\u8a31\u5efa\u7acb\u4efb\u52d9\u55ae
-gb.acceptNewTicketsDescription = \u5141\u8a31\u65b0\u589e"\u81ed\u87f2","\u512a\u5316","\u4efb\u52d9"\u5404\u985e\u578b\u4efb\u52d9\u55ae
-gb.accessDenied = \u62d2\u7d55\u5b58\u53d6
-gb.accessLevel = \u5b58\u53d6\u7b49\u7d1a
-gb.accessPermissions = \u5b58\u53d6\u6b0a\u9650
-gb.accessPermissionsDescription = restrict access by users and teams
-gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
-gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
-gb.accessPolicy = \u5b58\u53d6\u653f\u7b56
-gb.accessPolicyDescription = \u9078\u64c7\u7528\u4f86\u63a7\u5236\u6587\u4ef6\u5eab\u7684\u5b58\u53d6\u653f\u7b56\u4ee5\u53ca\u6b0a\u9650\u8a2d\u5b9a
-gb.accessRestriction = \u9650\u5236\u5b58\u53d6
-gb.accountPreferences = \u5e33\u865f\u8a2d\u5b9a
-gb.accountPreferencesDescription = \u8a2d\u5b9a\u5e33\u865f\u9810\u8a2d\u503c
-gb.action = \u52d5\u4f5c
-gb.active = \u6d3b\u52d5
-gb.activeAuthors = \u6d3b\u8e8d\u7528\u6236
-gb.activeRepositories = \u6d3b\u8e8d\u7248\u672c\u5eab
-gb.activity = \u6d3b\u52d5
-gb.add = \u65b0\u589e
-gb.addComment = \u65b0\u589e\u8a3b\u89e3
-gb.addedNCommits = {0}\u500b\u6a94\u6848\u63d0\u4ea4\u5b8c\u7562
-gb.addedOneCommit = \u63d0\u4ea41\u500b\u6a94\u6848
-gb.addition = addition
-gb.addSshKey = \u65b0\u589e SSH Key
-gb.administration = \u7ba1\u7406\u6b0a\u9650
-gb.administrator = \u7ba1\u7406\u54e1
-gb.administratorPermission = Gitblit \u7ba1\u7406\u54e1
-gb.affiliationChanged = affiliation changed
-gb.age = \u6642\u9593
-gb.all = \u5168\u90e8
-gb.allBranches = \u6240\u6709\u5206\u652f
-gb.allowAuthenticatedDescription = \u6279\u51c6 RW+ \u6b0a\u9650\u7d66\u4e88\u5c08\u6848\u6210\u54e1
-gb.allowForks = \u5141\u8a31\u5efa\u7acb\u5206\u652f(forks)
-gb.allowForksDescription = \u5141\u8a31\u5df2\u6388\u6b0a\u7684\u4f7f\u7528\u8005\u5f9e\u6587\u4ef6\u5eab\u5efa\u7acb\u5206\u652f(fork)
-gb.allowNamedDescription = grant fine-grained permissions to named users or teams
-gb.allProjects = \u5168\u90e8\u7fa4\u7d44
-gb.allTags = \u6240\u6709\u6a19\u7c64
-gb.anonymousCanNotPropose = \u533f\u540d\u8005\u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
-gb.anonymousPolicy = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push
-gb.anonymousPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u6587\u4ef6\u5230\u6587\u4ef6\u5eab
-gb.anonymousUser= \u533f\u540d\u72c0\u614b
-gb.any = \u4efb\u4f55
-gb.approve = \u901a\u904e
-gb.at = at
-gb.attributes = \u5c6c\u6027
-gb.authenticatedPushPolicy = Restrict Push (Authenticated)
-gb.authenticatedPushPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u6aa2\u8996\u8207\u8907\u88fd(clone).\u6240\u6709\u6587\u4ef6\u5eab\u6210\u54e1\u7686\u6709RW+\u8207\u63a8\u9001(push)\u529f\u80fd.
-gb.author = \u4f5c\u8005
-gb.authored = \u5df2\u6388\u6b0a
-gb.authorizationControl = \u6388\u6b0a\u7ba1\u63a7
-gb.available = \u53ef\u7528
-gb.blame = \u7a76\u67e5
-gb.blinkComparator = Blink comparator
-gb.blob = \u5340\u584a
-gb.body = body
-gb.bootDate = \u555f\u52d5\u65e5
-gb.branch = \u5206\u652f
-gb.branches = \u5206\u652f
-gb.branchStats = \u9019\u500b\u5206\u652f{2}\u6709{0}\u500b\u63d0\u4ea4\u4ee5\u53ca{1}\u500b\u6a19\u7c64
-gb.browse = \u700f\u89bd
-gb.bugTickets = \u81ed\u87f2
-gb.busyCollectingGarbage = \u62b1\u6b49,Gitblit\u6b63\u5728\u56de\u6536\u7cfb\u7d71\u8cc7\u6e90\u4e2d:{0}
-gb.byNAuthors = \u7d93\u7531{0}\u500b\u4f5c\u8005
-gb.byOneAuthor = \u7d93\u7531{0}
-gb.caCompromise = CA compromise
-gb.canAdmin = \u53ef\u7ba1\u7406
-gb.canAdminDescription = \u53ef\u7ba1\u7406Gitblit\u4f3a\u670d\u5668
-gb.cancel = \u53d6\u6d88
-gb.canCreate = \u53ef\u5efa\u7acb
-gb.canCreateDescription = \u80fd\u5920\u5efa\u7acb\u79c1\u4eba\u6587\u4ef6\u5eab
-gb.canFork = \u53ef\u5efa\u7acb\u5206\u652f(fork)
-gb.canForkDescription = \u53ef\u4ee5\u5efa\u7acb\u6587\u4ef6\u5eab\u5206\u652f(fork),\u4e26\u4e14\u8907\u88fd\u5230\u79c1\u4eba\u6587\u4ef6\u5eab\u4e2d
-gb.canNotLoadRepository = \u7121\u6cd5\u8f09\u5165\u7248\u672c\u5eab
-gb.canNotProposePatchset = \u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
-gb.certificate = \u8b49\u66f8
-gb.certificateRevoked = \u8b49\u66f8{0,number,0} \u5df2\u7d93\u88ab\u53d6\u6d88
-gb.certificates = \u8b49\u66f8
-gb.cessationOfOperation = cessation of operation
-gb.changedFiles = \u5df2\u8b8a\u66f4\u904e\u7684\u6a94\u6848
-gb.changedStatus = changed the status
-gb.changePassword = \u4fee\u6539\u5bc6\u78bc
-gb.checkout = \u6aa2\u51fa(checkout)
-gb.checkoutStep1 = Fetch the current patchset \u2014 run this from your project directory
-gb.checkoutStep2 = \u5c07\u8a72\u88dc\u4e01\u8f49\u51fa\u5230\u65b0\u7684\u5206\u652f\u7136\u5f8c\u7528\u4f86\u6aa2\u8996
-gb.checkoutViaCommandLine = \u4f7f\u7528\u6307\u4ee4Checkout
-gb.checkoutViaCommandLineNote = \u4f60\u53ef\u4ee5\u5f9e\u4f60\u6587\u4ef6\u5eab\u4e2dcheckout\u4e00\u4efd,\u7136\u5f8c\u9032\u884c\u6e2c\u8a66
-gb.clearCache = \u6e05\u9664\u5feb\u53d6
-gb.clientCertificateBundleSent = {0}\u7684\u7528\u6236\u8b49\u66f8\u5df2\u5bc4\u767c
-gb.clientCertificateGenerated = \u6210\u529f\u7522\u751f{0}\u7684\u65b0\u8b49\u66f8
-gb.clone = \u8907\u88fd(clone)
-gb.clonePermission = {0} \u8907\u88fd(clone)
-gb.clonePolicy = Restrict Clone & Push
-gb.clonePolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u770b\u6587\u4ef6\u5eab. \u4f46\u4f60\u80fd\u5920\u8907\u88fd(clone)\u8207\u63a8\u9001(push)
-gb.cloneRestricted = authenticated clone & push
-gb.closeBrowser = \u8acb\u95dc\u9589\u700f\u89bd\u5668\u7d50\u675f\u6b64\u767b\u5165\u968e\u6bb5
-gb.closed = \u95dc\u9589
-gb.closedMilestones = \u5df2\u95dc\u9589\u7684\u91cc\u7a0b\u7891(milestones)
-gb.combinedMd5Rename = Gitblit\u4f7f\u7528md5\u65b9\u5f0f\u5c07\u5bc6\u78bc\u7de8\u78bc(\u7121\u6cd5\u9084\u539f).\u4f60\u5fc5\u9808\u8f38\u5165\u65b0\u5bc6\u78bc.
-gb.comment = \u8a3b\u89e3
-gb.commented = \u5df2\u8a3b\u89e3
-gb.comments = \u8a3b\u89e3
-gb.commit = \u63d0\u4ea4
-gb.commitActivityAuthors = \u63d0\u4ea4\u6d3b\u8e8d\u7387(\u4f7f\u7528\u8005)
-gb.commitActivityDOW = \u6bcf(\u65e5)\u9031\u63d0\u4ea4
-gb.commitActivityTrend = \u63d0\u4ea4\u8da8\u52e2\u5716
-gb.commitdiff = \u63d0\u4ea4\u5dee\u7570
-gb.commitIsNull = \u63d0\u4ea4\u5167\u5bb9\u662f\u7a7a\u7684
-gb.commitMessageRenderer = \u63d0\u4ea4\u8a0a\u606f\u5448\u73fe\u65b9\u5f0f
-gb.commitMessageRendererDescription = \u63d0\u4ea4\u8a0a\u606f\u53ef\u4ee5\u4f7f\u7528\u6587\u5b57\u6216\u662f\u6a19\u8a18\u8a9e\u8a00(markup)\u5448\u73fe
-gb.commits = \u63d0\u4ea4
-gb.commitsInPatchsetN = \u88dc\u4e01 {0} \u7684\u63d0\u4ea4
-gb.commitsTo = {0} commits to
-gb.committed = \u5df2\u63d0\u4ea4
-gb.committer = \u78ba\u8a8d\u63d0\u4ea4\u8005
-gb.compare = \u6bd4\u5c0d
-gb.compareToMergeBase = \u6bd4\u5c0d\u5f8c,\u5408\u4f75\u5230\u4e3b\u8981\u5de5\u4f5c\u5340
-gb.compareToN = \u8207{0}\u9032\u884c\u6bd4\u5c0d
-gb.completeGravatarProfile = \u5b8c\u6210Gravator.com\u4e0a\u7684\u57fa\u672c\u8cc7\u6599\u8a2d\u5b9a
-gb.confirmPassword = \u78ba\u8a8d\u5bc6\u78bc
-gb.content = \u5167\u5bb9
-gb.copyToClipboard = \u8907\u88fd\u5230\u526a\u8cbc\u677f
-gb.couldNotCreateFederationProposal = \u7121\u6cd5\u5efa\u7acb\u4e32\u9023\u7684\u5408\u4f5c\u63d0\u6848
-gb.couldNotFindFederationProposal = \u641c\u5c0b\u4e0d\u5230\u8981\u6c42\u4e32\u9023\u7684\u63d0\u6848
-gb.couldNotFindFederationRegistration = \u627e\u4e0d\u5230\u4e32\u9023\u8a3b\u518a\u55ae
-gb.couldNotFindTag = \u627e\u4e0d\u5230\u6a19\u7c64{0}
-gb.countryCode = \u570b\u5bb6\u4ee3\u78bc
-gb.create = \u5efa\u7acb
-gb.createdBy = created by
-gb.createdNewBranch = \u5efa\u7acb\u65b0\u5206\u652f
-gb.createdNewPullRequest = created pull request
-gb.createdNewTag = \u5efa\u7acb\u65b0\u6a19\u7c64
-gb.createdThisTicket = \u5df2\u958b\u7acb\u7684\u4efb\u52d9\u55ae
-gb.createFirstTicket = \u6309\u6b64\u9996\u767c\u4efb\u52d9\u55ae
-gb.createPermission = {0} (push, ref creation)
-gb.createReadme = \u5efa\u7acbREADME\u6a94\u6848
-gb.customFields = custom fields
-gb.customFieldsDescription = custom fields available to Groovy hooks
-gb.dailyActivity = \u6bcf\u65e5\u6d3b\u52d5
-gb.dashboard = \u5100\u8868\u677f
-gb.date = \u65e5\u671f
-gb.default = \u9810\u8a2d
-gb.delete = \u522a\u9664
-gb.deletedBranch = deleted branch
-gb.deletedTag = \u522a\u9664\u6a19\u7c64
-gb.deleteMilestone = \u522a\u9664\u91cc\u7a0b\u7891"{0}"?
-gb.deletePermission = {0} (push, ref creation+deletion)
-gb.deleteRepository = \u522a\u9664\u7248\u672c\u5eab"{0}"?
-gb.deleteRepositoryDescription = \u7248\u672c\u5eab\u522a\u9664\u5c07\u7121\u6cd5\u9084\u539f
-gb.deleteRepositoryHeader = \u522a\u9664\u7248\u672c\u5eab
-gb.deleteUser = \u522a\u9664\u4f7f\u7528\u8005"{0}"?
-gb.deletion = \u522a\u9664
+gb.repository = \u7248\u672c\u5eab
+gb.owner = \u64c1\u6709\u8005
 gb.description = \u6982\u8ff0
+gb.lastChange = \u6700\u8fd1\u4fee\u6539
+gb.refs = \u6bd4\u5c0d
+gb.tag = \u6a19\u7c64
+gb.tags = \u6a19\u7c64
+gb.author = \u4f5c\u8005
+gb.committer = \u78ba\u8a8d\u8005
+gb.commit = \u63d0\u4ea4
+gb.age = \u6642\u9593
+gb.tree = \u76ee\u9304
+gb.parent = \u4e0a\u500b\u7248\u672c
+gb.url = URL
+gb.history = \u6b77\u53f2
+gb.raw = \u539f\u59cb
+gb.object = \u7269\u4ef6
+gb.ticketId = \u4efb\u52d9ID
+gb.ticketAssigned = \u5df2\u6307\u5b9a
+gb.ticketOpenDate = \u767c\u884c\u65e5
+gb.ticketComments = \u8a3b\u89e3
+gb.view = \u6aa2\u8996
+gb.local = \u672c\u5730\u7aef
+gb.remote = \u9060\u7aef
+gb.branches = \u5206\u652f
+gb.patch = \u4fee\u88dc\u6a94
+gb.diff = \u5dee\u7570
+gb.log = \u65e5\u8a8c
+gb.moreLogs = \u66f4\u591a\u63d0\u4ea4 ...
+gb.allTags = \u6240\u6709\u6a19\u7c64
+gb.allBranches = \u6240\u6709\u5206\u652f
+gb.summary = \u532f\u7e3d
+gb.ticket = \u4efb\u52d9
+gb.newRepository = \u5efa\u7acb\u7248\u672c\u5eab
+gb.newUser = \u5efa\u7acb\u4f7f\u7528\u8005
+gb.commitdiff = \u5dee\u7570
+gb.tickets = \u4efb\u52d9
+gb.pageFirst = \u7b2c\u4e00\u7b46
+gb.pagePrevious = \u4e0a\u4e00\u9801
+gb.pageNext = \u4e0b\u4e00\u9801
+gb.head = HEAD
+gb.blame = \u8a73\u67e5
+gb.login = \u767b\u5165
+gb.logout = \u767b\u51fa
+gb.username = \u4f7f\u7528\u8005\u540d\u7a31
+gb.password = \u5bc6\u78bc
+gb.tagger = \u6a19\u8a18\u8005
+gb.moreHistory = \u66f4\u591a\u6b77\u53f2\u7d00\u9304...
+gb.difftocurrent = \u6bd4\u5c0d\u5dee\u7570
+gb.search = \u641c\u5c0b
+gb.searchForAuthor = \u4f9d\u5be9\u6838\u8005\u641c\u5c0b\u63d0\u4ea4\u5167\u5bb9
+gb.searchForCommitter = \u4f9d\u63d0\u4ea4\u8005\u641c\u5c0b\u63d0\u4ea4\u5167\u5bb9
+gb.addition = \u589e\u52a0
+gb.modification = \u4fee\u6539
+gb.deletion = \u522a\u9664
+gb.rename = \u6539\u540d\u7a31
+gb.metrics = \u7d71\u8a08
+gb.stats = \u7d71\u8a08
+gb.markdown = markdown
+gb.changedFiles = \u5df2\u8b8a\u66f4\u904e\u7684\u6a94\u6848
+gb.filesAdded = \u65b0\u589e{0}\u500b\u6a94\u6848
+gb.filesModified = \u4fee\u6539{0}\u500b\u6a94\u6848
+gb.filesDeleted = \u522a\u9664{0}\u500b\u6a94\u6848
+gb.filesCopied = \u8907\u88fd{0}\u500b\u6a94\u6848
+gb.filesRenamed = \u4fee\u6539{0}\u500b\u6a94\u6848\u540d\u7a31
+gb.missingUsername = \u7121\u4f7f\u7528\u8005\u540d\u7a31
+gb.edit = \u7de8\u8f2f
+gb.searchTypeTooltip = \u9078\u64c7\u641c\u5c0b\u985e\u578b
+gb.searchTooltip = \u641c\u5c0b{0}
+gb.delete = \u522a\u9664
+gb.docs = \u6587\u4ef6
+gb.accessRestriction = \u9650\u5236\u5b58\u53d6
+gb.name = \u540d\u5b57
+gb.enableTickets = \u555f\u7528\u4efb\u52d9(Ticket)\u7cfb\u7d71
+gb.enableDocs = \u555f\u7528\u8aaa\u660e\u6587\u4ef6
+gb.save = \u5132\u5b58
+gb.showRemoteBranches = \u986f\u793a\u9060\u7aef\u5206\u652f
+gb.editUsers = \u4fee\u6539\u5e33\u865f
+gb.confirmPassword = \u78ba\u8a8d\u5bc6\u78bc
+gb.restrictedRepositories = \u53d7\u9650\u5236\u7684\u7248\u672c\u5eab
+gb.canAdmin = \u53ef\u7ba1\u7406
+gb.notRestricted = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push
+gb.pushRestricted = \u6709\u6388\u6b0a\u624d\u80fd\u63a8\u9001(push)
+gb.cloneRestricted = \u6709\u6388\u6b0a\u624d\u80fd\u8907\u88fd(clone)\u8207\u63a8\u9001(push)
+gb.viewRestricted = \u6709\u6388\u6b0a\u624d\u80fd\u6aa2\u8996(view),\u8907\u88fd(clone), \u8207\u63a8\u9001(push)
+gb.useTicketsDescription = readonly, distributed Ticgit issues
+gb.useDocsDescription = \u8a08\u7b97\u7248\u672c\u5eab\u88e1\u9762\u7684Markdown\u6a94\u6848
+gb.showRemoteBranchesDescription = \u986f\u793a\u9060\u7aef\u5206\u652f(branches)
+gb.canAdminDescription = \u53ef\u7ba1\u7406Gitblit\u4f3a\u670d\u5668
+gb.permittedUsers = \u5141\u8a31\u7528\u6236
+gb.isFrozen = \u51cd\u7d50\u63a5\u6536
+gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001(push)
+gb.zip = zip\u58d3\u7e2e\u6a94
+gb.showReadme = \u986f\u793areadme\u6587\u4ef6
+gb.showReadmeDescription = \u5728\u532f\u7e3d\u9801\u9762\u4e2d\u986f\u793a"readme"(markdown\u683c\u5f0f)
+gb.nameDescription = \u4f7f\u7528"/"\u505a\u70ba\u7248\u672c\u5eab\u7fa4\u7d44\u5206\u985e. \u5982: library/mycoolib.git
+gb.ownerDescription = \u64c1\u6709\u8005\u53ef\u4fee\u6539\u7248\u672c\u5eab\u8a2d\u5b9a\u503c
+gb.blob = \u5340\u584a
+gb.commitActivityTrend = \u63d0\u4ea4\u8da8\u52e2
+gb.commitActivityDOW = \u6bcf(\u65e5)\u9031\u63d0\u4ea4
+gb.commitActivityAuthors = \u63d0\u4ea4\u6d3b\u8e8d\u7387(\u4f7f\u7528\u8005)
+gb.feed = \u8cc7\u6599\u8a02\u95b1
+gb.cancel = \u53d6\u6d88
+gb.changePassword = \u4fee\u6539\u5bc6\u78bc
+gb.isFederated = \u5df2\u7d93federated
+gb.federateThis = \u8207\u6b64\u7248\u672c\u5eab federate
+gb.federateOrigin = federate the origin
+gb.excludeFromFederation = exclude from federation
+gb.excludeFromFederationDescription = \u7981\u6b62federated \u7684Gitblit\u4f3a\u670d\u5668
+gb.tokens = federation tokens
+gb.tokenAllDescription = \u6240\u6709\u7248\u672c\u5eab,\u4f7f\u7528\u8005\u8207\u8a2d\u5b9a
+gb.tokenUnrDescription = \u6240\u6709\u7248\u672c\u5eab\u8207\u4f7f\u7528\u8005
+gb.tokenJurDescription = \u6240\u6709\u7248\u672c\u5eab
+gb.federatedRepositoryDefinitions =  \u7248\u672c\u5eab\u5b9a\u7fa9
+gb.federatedUserDefinitions = \u4f7f\u7528\u8005\u5b9a\u7fa9
+gb.federatedSettingDefinitions = setting definitions
+gb.proposals = federation proposals
+gb.received = \u5df2\u63a5\u6536
+gb.type = \u985e\u578b
+gb.token = token
+gb.repositories = \u7248\u672c\u5eab
+gb.proposal = \u63d0\u6848
+gb.frequency = \u983b\u7387
+gb.folder = \u76ee\u9304
+gb.lastPull = \u4e0a\u6b21\u4e0b\u8f09(pull)
+gb.nextPull = \u4e0b\u4e00\u500b pull
+gb.inclusions = inclusions
+gb.exclusions = \u6392\u9664
+gb.registration = \u8a3b\u518a
+gb.registrations = federation registrations
+gb.sendProposal = \u63d0\u6848
+gb.status = \u72c0\u614b
+gb.origin = origin
+gb.headRef = \u9810\u8a2d\u5206\u652f(HEAD)
+gb.headRefDescription = \u9810\u8a2d\u5206\u652f\u5c07\u6703\u8907\u88fd\u4ee5\u53ca\u986f\u793a\u5230\u532f\u7e3d\u9801\u9762
+gb.federationStrategy = federation \u7b56\u7565
+gb.federationRegistration = federation registration
+gb.federationResults = federation pull results
+gb.federationSets = federation sets
+gb.message = \u8a0a\u606f
+gb.myUrlDescription = \u60a8Gitblit\u4f3a\u670d\u5668\u7684\u516c\u958bURL
 gb.destinationUrl = \u50b3\u9001
 gb.destinationUrlDescription = \u50b3\u9001Gitblit\u9023\u7d50\u5230\u4f60\u7684\u5c08\u6848(proposal)
-gb.diff = \u5dee\u7570
-gb.diffCopiedFile = File was copied from {0}
-gb.diffDeletedFile = \u6a94\u6848\u5df2\u522a\u9664
-gb.diffDeletedFileSkipped = (\u522a\u9664)
-gb.diffFileDiffTooLarge = \u6a94\u6848\u592a\u5927
-gb.diffNewFile = \u6bd4\u5c0d\u65b0\u6a94\u6848
-gb.diffRenamedFile = File was renamed from {0}
-gb.diffStat = \u65b0\u589e{0}\u5217\u8207\u522a\u9664{1}\u5217
-gb.difftocurrent = \u6bd4\u5c0d\u5dee\u7570
-gb.diffTruncated = Diff truncated after the above file
-gb.disableUser = \u505c\u7528\u5e33\u6236
-gb.disableUserDescription = \u8a72\u5e33\u6236\u7121\u6cd5\u4f7f\u7528
-gb.discussion = \u8a0e\u8ad6
-gb.displayName = \u986f\u793a\u7684\u540d\u7a31
-gb.displayNameDescription = \u5e0c\u671b\u986f\u793a\u7684\u540d\u7a31
-gb.docs = \u6a94\u6848\u5340
-gb.docsWelcome1 = \u4f60\u53ef\u4ee5\u4f7f\u7528\u6a94\u6848\u5340\u5efa\u7acb\u6587\u4ef6\u5eab\u7684\u6559\u5b78\u6a94\u6848
-gb.docsWelcome2 = \u63d0\u4ea4README.md \u6216 HOME.md\u5f8c,\u518d\u958b\u59cb\u65b0\u7684\u6587\u4ef6\u5eab
-gb.doesNotExistInTree = {0}\u4e26\u6c92\u6709\u5728\u76ee\u9304{1}\u88e1\u9762
-gb.download = \u4e0b\u8f09
-gb.downloading = \u4e0b\u8f09ing
-gb.due = due
-gb.duration = \u9031\u671f
-gb.duration.days = {0}\u5929
-gb.duration.months = {0}\u6708
-gb.duration.oneDay = 1\u5929
-gb.duration.oneMonth = 1\u6708
-gb.duration.oneYear = 1\u5e74
-gb.duration.years = {0}\u5e74
-gb.edit = \u7de8\u8f2f
-gb.editMilestone = \u4fee\u6539milestone
-gb.editTicket = \u4fee\u6539\u4efb\u52d9\u55ae
-gb.editUsers = \u4fee\u6539\u5e33\u865f
-gb.effective = \u6240\u6709\u6b0a\u9650
-gb.emailAddress = \u96fb\u5b50\u90f5\u4ef6
-gb.emailAddressDescription = \u7528\u4f86\u63a5\u6536\u901a\u77e5\u7684\u4e3b\u8981\u96fb\u5b50\u90f5\u4ef6
-gb.emailCertificateBundle = \u5bc4\u767c\u7528\u6236\u7aef\u8b49\u66f8
-gb.emailMeOnMyTicketChanges = \u6211\u7684\u4efb\u52d9\u55ae\u82e5\u6709\u8b8a\u66f4,\u8acb800\u91cc\u52a0\u6025(email)\u901a\u77e5\u6211
-gb.emailMeOnMyTicketChangesDescription  = \u6211\u8655\u7406\u904e\u7684\u4efb\u52d9\u55ae\u8acbemail\u901a\u77e5\u6211
-gb.empty = \u7a7a\u7684
-gb.emptyRepository = \u7a7a\u7684\u7248\u672c\u5eab
-gb.enableDocs = \u555f\u7528\u6a94\u6848\u5340
-gb.enableIncrementalPushTags = \u555f\u7528\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u529f\u80fd
-gb.enableTickets = \u555f\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71
-gb.enhancementTickets = \u512a\u5316
-gb.enterKeystorePassword = \u8acb\u8f38\u5165Gitblit\u7684keystore\u5c08\u7528\u5bc6\u78bc
+gb.users = \u4f7f\u7528\u8005
+gb.federation = federation
 gb.error = \u932f\u8aa4
-gb.errorAdministrationDisabled = \u7ba1\u7406\u6b0a\u9650\u5df2\u53d6\u6d88
+gb.refresh = \u91cd\u6574
+gb.browse = \u700f\u89bd
+gb.clone = \u8907\u88fd(clone)
+gb.filter = \u7be9\u9078
+gb.create = \u5efa\u7acb
+gb.servers = \u4f3a\u670d\u5668
+gb.recent = \u6700\u8fd1
+gb.available = \u53ef\u7528
+gb.selected = \u9078\u5b9a
+gb.size = \u5bb9\u91cf
+gb.downloading = \u4e0b\u8f09\u4e2d
+gb.loading = \u8f09\u5165
+gb.starting = \u555f\u52d5\u4e2d
+gb.general = \u4e00\u822c
+gb.settings = \u8a2d\u5b9a
+gb.manage = \u7ba1\u7406
+gb.lastLogin = \u6700\u8fd1\u767b\u5165
+gb.skipSizeCalculation = \u7565\u904e\u5bb9\u91cf\u8a08\u7b97
+gb.skipSizeCalculationDescription = \u4e0d\u8a08\u7b97\u7248\u672c\u5eab\u5bb9\u91cf(\u52a0\u5feb\u7db2\u9801\u8f09\u5165\u901f\u5ea6)
+gb.skipSummaryMetrics = \u7565\u904e\u91cf\u5316\u532f\u7e3d
+gb.skipSummaryMetricsDescription = \u4e0d\u8981\u8a08\u7b97\u91cf\u5316\u4e26\u4e14\u986f\u793a\u5728\u532f\u7e3d\u9801\u9762\u4e0a(\u52a0\u5feb\u901f\u5ea6)
+gb.accessLevel = \u5b58\u53d6\u7b49\u7d1a
+gb.default = \u9810\u8a2d
+gb.setDefault = \u8a2d\u70ba\u9810\u8a2d\u503c
+gb.since = \u5f9e
+gb.bootDate = \u555f\u52d5\u65e5
+gb.servletContainer = servlet\u5bb9\u5668
+gb.heapMaximum = \u6700\u5927\u5806\u7a4d(heap)
+gb.heapAllocated = \u5df2\u4f7f\u7528\u5806\u7a4d(Heap)
+gb.heapUsed = \u5df2\u4f7f\u7528\u7684\u5806\u7a4d(heap)
+gb.free = \u91cb\u653e
+gb.version = \u7248\u672c
+gb.releaseDate = \u767c\u8868\u65e5
+gb.date = \u65e5\u671f
+gb.activity = \u6d3b\u52d5
+gb.subscribe = \u8a02\u95b1
+gb.branch = \u5206\u652f
+gb.maxHits = \u6700\u5927\u9ede\u64ca
+gb.recentActivity = \u6700\u8fd1\u6d3b\u8e8d\u72c0\u6cc1
+gb.recentActivityStats = \u904e\u53bb{0}\u5929,\u4e00\u5171\u6709{2}\u4eba\u505a\u4e86{1}\u4efd\u63d0\u4ea4
+gb.recentActivityNone = \u904e\u53bb{0}\u5929/\u7121
+gb.dailyActivity = \u6bcf\u65e5\u6d3b\u52d5
+gb.activeRepositories = \u6d3b\u8e8d\u7248\u672c\u5eab
+gb.activeAuthors = \u6d3b\u8e8d\u7528\u6236
+gb.commits = \u63d0\u4ea4
+gb.teams = \u5718\u968a
+gb.teamName = \u5718\u968a\u540d\u7a31
+gb.teamMembers = \u5718\u968a\u6210\u54e1
+gb.teamMemberships = \u5718\u968a\u6210\u54e1(memberships)
+gb.newTeam = \u5efa\u7acb\u5718\u968a
+gb.permittedTeams = permitted teams
+gb.emptyRepository = \u7a7a\u7684\u7248\u672c\u5eab
+gb.repositoryUrl = \u7248\u672c\u5eab url
+gb.mailingLists = \u90f5\u4ef6\u540d\u55ae
+gb.preReceiveScripts = pre-receive \u8173\u672c
+gb.postReceiveScripts = post-receive\u8173\u672c
+gb.hookScripts = hook\u7684\u8173\u672c
+gb.customFields = custom fields
+gb.customFieldsDescription = custom fields available to Groovy hooks
+gb.accessPermissions = \u5b58\u53d6\u6b0a\u9650
+gb.filters = \u7be9\u9078
+gb.generalDescription = \u4e00\u822c\u8a2d\u5b9a
+gb.accessPermissionsDescription = restrict access by users and teams
+gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
+gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
+gb.federationRepositoryDescription = \u8207\u5176\u4ed6gitblit\u4f3a\u670d\u5668\u5206\u4eab\u4e00\u8d77\u4f7f\u7528\u9019\u500b\u7248\u672c\u5eab
+gb.hookScriptsDescription = \u7576\u63a8\u9001(push)\u81f3\u6b64Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u6642, \u57f7\u884cGroovy\u8173\u672c
+gb.reset = \u6e05\u9664
+gb.pages = \u6587\u4ef6
+gb.workingCopy = \u66ab\u5b58\u8907\u672c
+gb.workingCopyWarning = \u8a72\u7248\u672c\u5eab\u4ecd\u6709\u66ab\u5b58\u8907\u672c,\u56e0\u6b64\u7121\u6cd5\u63a5\u53d7\u63a8\u9001(push)
+gb.query = \u67e5\u8a62
+gb.queryHelp = \u652f\u63f4\u6a19\u6e96\u67e5\u8a62\u8a9e\u6cd5.<p/><p/>\u8a73\u60c5\u8acb\u53c3\u8003 <a target\ = "_new" href\ = "http\://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a>
+gb.queryResults = \u7d50\u679c {0} - {1} ({2} \u67e5\u8a62)
+gb.noHits = \u7121\u9ede\u64ca
+gb.authored = \u6388\u6b0a
+gb.committed = \u5df2\u63d0\u4ea4
+gb.indexedBranches = \u5206\u652f\u7d22\u5f15
+gb.indexedBranchesDescription = \u9078\u5b9a\u6b32\u57f7\u884cLucene\u7d22\u5f15\u529f\u80fd\u7684\u5206\u652f
+gb.noIndexedRepositoriesWarning = \u8ddf\u4f60\u76f8\u95dc\u7684\u7248\u672c\u5eab\u4e26\u6c92\u6709\u505aLucene\u7d22\u5f15
+gb.undefinedQueryWarning = \u672a\u8a2d\u5b9a\u67e5\u8a62\u689d\u4ef6
+gb.noSelectedRepositoriesWarning = \u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u500b\u7248\u672c\u5eab
+gb.luceneDisabled = \u505c\u7528Lucene\u7d22\u5f15\u529f\u80fd
+gb.failedtoRead = \u8b80\u53d6\u5931\u6557
+gb.isNotValidFile = \u4e0d\u662f\u6709\u6548\u6a94\u6848
+gb.failedToReadMessage = Failed to read default message from {0}\!
+gb.passwordsDoNotMatch = \u5bc6\u78bc\u4e0d\u76f8\u7b26
+gb.passwordTooShort = \u5bc6\u78bc\u904e\u77ed, \u6700\u5c11{0}\u500b\u5b57\u5143
+gb.passwordChanged = \u5bc6\u78bc\u8b8a\u66f4\u6210\u529f
+gb.passwordChangeAborted = \u53d6\u6d88\u5bc6\u78bc\u8b8a\u66f4
+gb.pleaseSetRepositoryName = \u8acb\u8a2d\u5b9a\u7248\u672c\u5eab\u540d\u7a31
+gb.illegalLeadingSlash = \u7981\u6b62\u6839\u76ee\u9304(/)
+gb.illegalRelativeSlash = \u7981\u6b62\u76f8\u5c0d\u76ee\u9304(../)
+gb.illegalCharacterRepositoryName = \u7248\u672c\u5eab\u540d\u7a31\u6709\u4e0d\u5408\u6cd5\u7684\u5b57\u5143"{0}"
+gb.selectAccessRestriction = Please select access restriction\!
+gb.selectFederationStrategy = Please select federation strategy\!
+gb.pleaseSetTeamName = \u8acb\u8f38\u5165\u5718\u968a\u540d\u7a31
+gb.teamNameUnavailable = \u5718\u968a"{0}"\u4e0d\u5b58\u5728.
+gb.teamMustSpecifyRepository = \u5718\u968a\u6700\u5c11\u8981\u6307\u5b9a\u4e00\u500b\u7248\u672c\u5eab
+gb.teamCreated = \u5718\u968a"{0}"\u65b0\u589e\u6210\u529f.
+gb.pleaseSetUsername = \u8acb\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31
+gb.usernameUnavailable = \u4f7f\u7528\u8005\u540d\u7a31"{0}"\u4e0d\u53ef\u7528
+gb.combinedMd5Rename = Gitblit\u4f7f\u7528md5\u65b9\u5f0f\u5c07\u5bc6\u78bc\u7de8\u78bc(\u7121\u6cd5\u9084\u539f).\u4f60\u5fc5\u9808\u8f38\u5165\u65b0\u5bc6\u78bc.
+gb.userCreated = \u6210\u529f\u5efa\u7acb\u65b0\u4f7f\u7528\u8005"{0}"
+gb.couldNotFindFederationRegistration = \u627e\u4e0d\u5230federation registration!
+gb.failedToFindGravatarProfile = \u7121\u6cd5\u627e\u5230\u5e33\u865f{0}\u7684Gravator\u8cc7\u6599
+gb.branchStats = \u9019\u500b\u5206\u652f{2}\u6709{0}\u500b\u63d0\u4ea4\u4ee5\u53ca{1}\u500b\u6a19\u7c64
+gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5eab!
+gb.repositoryNotSpecifiedFor = \u7248\u672c\u5eab\u4e26\u6c92\u6709\u6307\u5b9a\u7d66 {0}\!
+gb.canNotLoadRepository = \u7121\u6cd5\u8f09\u5165\u7248\u672c\u5eab
+gb.commitIsNull = \u63d0\u4ea4\u5167\u5bb9\u662f\u7a7a\u7684
+gb.unauthorizedAccessForRepository = \u7248\u672c\u5eab\u672a\u6388\u6b0a\u5b58\u53d6
+gb.failedToFindCommit = \u5728{1}\u4e2d\u7121\u6cd5\u627e\u5230\u8a72 {0} \u63d0\u4ea4!
+gb.couldNotFindFederationProposal = \u641c\u5c0b\u4e0d\u5230federation proposal!
+gb.invalidUsernameOrPassword = \u932f\u8aa4\u7684\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc!
+gb.OneProposalToReview = \u6709\u4e00\u500bfederation proposal \u7b49\u5f85\u5be9\u67e5
+gb.nFederationProposalsToReview = \u7e3d\u5171\u6709{0}\u500bfederation proposals\u7b49\u5f85\u5be9\u8996
+gb.couldNotFindTag = \u627e\u4e0d\u5230\u6a19\u7c64{0}
+gb.couldNotCreateFederationProposal = \u7121\u6cd5\u5efa\u7acbfederation proposals
+gb.pleaseSetGitblitUrl = \u8acb\u8f38\u5165Gitblit URL !
+gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal\!
+gb.proposalReceived = Proposal successfully received by {0}.
+gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
+gb.noProposals = \u62b1\u6b49, {0}\u6b64\u6642\u4e26\u4e0d\u662f\u53ef\u63a5\u53d7\u7684proposals.
+gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
+gb.proposalFailed = Sorry, {0} did not receive any proposal data\!
+gb.proposalError = \u62b1\u6b49, {0} \u4efd\u5831\u544a\u767c\u751f\u9810\u671f\u5916\u7684\u932f\u8aa4!
+gb.failedToSendProposal = \u63d0\u6848\u767c\u9001\u5931\u6557\!
+gb.userServiceDoesNotPermitAddUser = {0}\u4e0d\u5141\u8a31\u65b0\u589e\u4f7f\u7528\u8005\u5e33\u865f
+gb.userServiceDoesNotPermitPasswordChanges = {0}\u4e0d\u5141\u8a31\u4fee\u6539\u5bc6\u78bc
+gb.displayName = \u986f\u793a\u7684\u540d\u7a31
+gb.emailAddress = \u96fb\u5b50\u90f5\u4ef6
 gb.errorAdminLoginRequired = \u767b\u5165\u9700\u6709\u7ba1\u7406\u6b0a\u9650
 gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u8005\u80fd\u5efa\u7acb\u7248\u672c\u5eab
 gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u8005\u8207\u7248\u672c\u5eab\u64c1\u6709\u8005\u80fd\u4fee\u6539\u7248\u672c\u5eab\u5c6c\u6027
-gb.excludeFromActivity = exclude from activity page
-gb.excludeFromFederation = \u6392\u9664\u4e32\u9023
-gb.excludeFromFederationDescription = \u963b\u64cb\u5df2\u4e32\u9023\u7684Gitblit\u4f3a\u670d\u5668
-gb.excludePermission = {0} (\u6392\u9664)
-gb.exclusions = \u6392\u9664
-gb.expired = \u904e\u671f
-gb.expires = \u5230\u671f
-gb.expiring = \u5c07\u8981\u904e\u671f
-gb.export = \u532f\u51fa
-gb.extensions = \u64f4\u5145
-gb.externalPermissions = {0} access permissions are externally maintained
-gb.failedToFindAccount = \u7121\u6cd5\u641c\u5c0b\u5230\u5e33\u865f"{0}"
-gb.failedToFindCommit = Failed to find commit "{0}" in {1}\!
-gb.failedToFindGravatarProfile = \u7121\u6cd5\u627e\u5230\u5e33\u865f{0}\u7684Gravator\u8cc7\u6599
-gb.failedtoRead = \u8b80\u53d6\u5931\u6557
-gb.failedToReadMessage = Failed to read default message from {0}\!
-gb.failedToSendProposal = \u63d0\u6848\u767c\u9001\u5931\u6557\!
-gb.failedToUpdateUser = \u7121\u6cd5\u66f4\u65b0\u4f7f\u7528\u8005\u5e33\u865f
-gb.federatedRepositoryDefinitions =  \u7248\u672c\u5eab\u5b9a\u7fa9
-gb.federatedSettingDefinitions = setting definitions
-gb.federatedUserDefinitions = user definitions
-gb.federateOrigin = federate the origin
-gb.federateThis = \u8207\u672c\u6587\u4ef6\u5eab\u4e32\u9023
-gb.federation = \u4e32\u9023
-gb.federationRegistration = federation registration
-gb.federationRepositoryDescription = \u8207\u5176\u4ed6gitblit\u4f3a\u670d\u5668\u5206\u4eab\u4e00\u8d77\u4f7f\u7528\u9019\u500b\u7248\u672c\u5eab
-gb.federationResults = federation pull results
-gb.federationSets = \u4e32\u9023\u7d44\u5408
-gb.federationSetsDescription = \u6b64\u6587\u4ef6\u5eab\u5c07\u5305\u542b\u65bc\u6307\u5b9a\u7684\u4e32\u9023\u7fa4\u7d44(federation sets)
-gb.federationStrategy = \u4e32\u9023\u7b56\u7565
-gb.federationStrategyDescription = \u63a7\u5236\u5982\u4f55\u5c07\u6587\u4ef6\u5eab\u8207\u5176\u4ed6Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u4e32\u9023
-gb.feed = \u8cc7\u6599\u8a02\u95b1
-gb.filesAdded = \u65b0\u589e{0}\u500b\u6a94\u6848
-gb.filesCopied = \u8907\u88fd{0}\u500b\u6a94\u6848
-gb.filesDeleted = \u522a\u9664{0}\u500b\u6a94\u6848
-gb.filesModified = \u4fee\u6539{0}\u500b\u6a94\u6848
-gb.filesRenamed = \u4fee\u6539{0}\u500b\u6a94\u6848\u540d\u7a31
-gb.filter = \u689d\u4ef6\u904e\u6ffe
-gb.filters = \u67e5\u8a62\u689d\u4ef6
-gb.findSomeRepositories = \u641c\u5c0b\u6587\u4ef6\u5eab
-gb.folder = \u76ee\u9304
+gb.errorAdministrationDisabled = \u7ba1\u7406\u6b0a\u9650\u5df2\u53d6\u6d88
+gb.lastNDays = \u6700\u8fd1{0}\u5929
+gb.completeGravatarProfile = \u5b8c\u6210Gravator.com\u4e0a\u7684\u57fa\u672c\u8cc7\u6599\u8a2d\u5b9a
+gb.none = \u7121
+gb.line = \u884c
+gb.content = \u5167\u5bb9
+gb.empty = \u7a7a\u7684
+gb.inherited = \u7e7c\u627f
+gb.deleteRepository = \u522a\u9664\u7248\u672c\u5eab"{0}"?
+gb.repositoryDeleted = \u7248\u672c\u5eab"{0}"\u5df2\u522a\u9664
+gb.repositoryDeleteFailed = \u522a\u9664\u7248\u672c\u5eab"{0}"\u5931\u6557!
+gb.deleteUser = \u522a\u9664\u4f7f\u7528\u8005"{0}"?
+gb.userDeleted = \u4f7f\u7528\u8005"{0}"\u5df2\u522a\u9664
+gb.userDeleteFailed = \u4f7f\u7528\u8005"{0}"\u522a\u9664\u5931\u6557
+gb.time.justNow = \u525b\u525b
+gb.time.today = \u4eca\u5929
+gb.time.yesterday = \u6628\u5929
+gb.time.minsAgo = {0}\u5206\u9418\u524d
+gb.time.hoursAgo = {0}\u5c0f\u6642\u524d
+gb.time.daysAgo = {0}\u5929\u524d
+gb.time.weeksAgo = {0}\u5468\u524d
+gb.time.monthsAgo = {0}\u6708\u524d
+gb.time.oneYearAgo = 1\u5e74\u524d
+gb.time.yearsAgo = {0}\u5e74\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\u6b0a\u7ba1\u63a7
+gb.allowAuthenticatedDescription = \u6279\u51c6 RW+ \u6b0a\u9650\u7d66\u4e88\u5c08\u6848\u6210\u54e1
+gb.allowNamedDescription = grant fine-grained permissions to named users or teams
+gb.markdownFailure = \u89e3\u6790Markdown\u5931\u6557
+gb.clearCache = \u6e05\u9664\u5feb\u53d6
+gb.projects = \u7fa4\u7d44
+gb.project = \u7fa4\u7d44
+gb.allProjects = \u5168\u90e8\u7fa4\u7d44
+gb.copyToClipboard = \u8907\u88fd\u5230\u526a\u8cbc\u677f
 gb.fork = \u5efa\u7acb\u5206\u652f(fork)
-gb.forkedFrom = forked from
-gb.forkInProgress = fork in progress
-gb.forkNotAuthorized = \u5f88\u62b1\u6b49, \u4f60\u7121\u5efa\u7acb\u6587\u4ef6\u5eab{0}\u5206\u652f(fork)\u7684\u6b0a\u9650
-gb.forkRepository = \u7248\u672c\u5eab{0}\u5efa\u7acb\u5206\u652f(fork)?
 gb.forks = \u5206\u652f(forks)
+gb.forkRepository = \u7248\u672c\u5eab{0}\u5efa\u7acb\u5206\u652f(fork)?
+gb.repositoryForked = \u7248\u672c\u5eab{0}\u5df2\u7d93\u5efa\u7acb\u5206\u652f(fork)
+gb.repositoryForkFailed = \u5efa\u7acb\u5206\u652f(fork)\u5931\u6557
+gb.personalRepositories = \u500b\u4eba\u7248\u672c\u5eab
+gb.allowForks = \u5141\u8a31\u5efa\u7acb\u5206\u652f(forks)
+gb.allowForksDescription = \u5141\u8a31\u5df2\u6388\u6b0a\u7684\u4f7f\u7528\u8005\u5f9e\u7248\u672c\u5eab\u5efa\u7acb\u5206\u652f(fork)
+gb.forkedFrom = \u6e90\u81ea\u65bc
+gb.canFork = \u53ef\u5efa\u7acb\u5206\u652f(fork)
+gb.canForkDescription = \u53ef\u4ee5\u5efa\u7acb\u7248\u672c\u5eab\u5206\u652f(fork),\u4e26\u4e14\u8907\u88fd\u5230\u79c1\u4eba\u7248\u672c\u5eab\u4e2d
+gb.myFork = \u6aa2\u8996\u6211\u5efa\u7acb\u7684\u5206\u652f(fork)
 gb.forksProhibited = \u7981\u6b62\u5efa\u7acb\u5206\u652f(forks)
-gb.forksProhibitedWarning = \u672c\u6587\u4ef6\u5eab\u7981\u6b62\u5206\u652f(fork)
-gb.free = \u91cb\u653e
-gb.frequency = \u983b\u7387
-gb.from = from
-gb.garbageCollection = \u56de\u6536\u7cfb\u7d71\u8cc7\u6e90
-gb.garbageCollectionDescription = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u529f\u80fd\u5c07\u6703\u6574\u9813\u9b06\u6563\u7528\u6236\u7aef\u63a8\u9001(push)\u7684\u7269\u4ef6, \u4e5f\u6703\u79fb\u9664\u6587\u4ef6\u5eab\u4e0a\u7121\u7528\u7684\u7269\u4ef6
-gb.gc = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u5668
+gb.forksProhibitedWarning = \u672c\u7248\u672c\u5eab\u7981\u6b62\u5206\u652f(fork)
+gb.noForks = {0}\u6c92\u6709\u5206\u652f(fork)
+gb.forkNotAuthorized = \u5f88\u62b1\u6b49, \u4f60\u7121\u5efa\u7acb\u7248\u672c\u5eab{0}\u5206\u652f(fork)\u7684\u6b0a\u9650
+gb.forkInProgress = \u6b63\u5728\u8907\u88fd\u4e2d(fork)
+gb.preparingFork = \u6b63\u5728\u6e96\u5099\u8907\u88fd\u4e2d(fork)...
+gb.isFork = \u662f\u5206\u652f\u985e\u578b(fork)
+gb.canCreate = \u53ef\u5efa\u7acb
+gb.canCreateDescription = \u80fd\u5920\u5efa\u7acb\u500b\u4eba\u7248\u672c\u5eab
+gb.illegalPersonalRepositoryLocation = \u4f60\u500b\u4eba\u7248\u672c\u5eab\u5fc5\u9808\u653e\u5728"{0}"
+gb.verifyCommitter = \u63d0\u4ea4\u8005\u9700\u9a57\u8b49
+gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7b26\u5408\u63a8\u9001\u5e33\u865f
+gb.verifyCommitterNote = \u6240\u6709\u5408\u4f75\u52d5\u4f5c\u7686\u9808\u5f37\u5236\u4f7f\u7528"--no-ff"\u53c3\u6578
+gb.repositoryPermissions = \u7248\u672c\u5eab\u6b0a\u9650
+gb.userPermissions = \u4f7f\u7528\u8005\u6b0a\u9650
+gb.teamPermissions = \u5718\u968a\u6b0a\u9650
+gb.add = \u65b0\u589e
+gb.noPermission = \u522a\u9664\u9019\u500b\u6b0a\u9650
+gb.excludePermission = {0} \u6392\u9664(exclude)
+gb.viewPermission = {0} \u6aa2\u8996(view)
+gb.clonePermission = {0} \u8907\u88fd(clone)
+gb.pushPermission = {0} \u63a8\u9001(push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = \u6b0a\u9650
+gb.regexPermission = \u5df2\u7d93\u4f7f\u7528\u6b63\u898f\u8868\u793a\u5f0f(regular expression)"{0}" \u8a2d\u5b9a\u6b0a\u9650\u5b8c\u7562
+gb.accessDenied = \u62d2\u7d55\u5b58\u53d6
+gb.busyCollectingGarbage = \u62b1\u6b49,Gitblit\u6b63\u5728\u56de\u6536\u7cfb\u7d71\u8cc7\u6e90\u4e2d:{0}
 gb.gcPeriod = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u968e\u6bb5
 gb.gcPeriodDescription = \u56de\u6536\u9031\u671f
 gb.gcThreshold = GC \u57fa\u6578(threshold)
 gb.gcThresholdDescription = \u89f8\u767c\u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u7684\u6700\u5c0f\u7269\u4ef6\u5bb9\u91cf
-gb.general = \u4e00\u822c
-gb.generalDescription = \u4e00\u822c\u8a2d\u5b9a
-gb.hasNotReviewed = \u5c1a\u672a\u6aa2\u6838\u904e
-gb.head = HEAD
-gb.headRef = \u9810\u8a2d\u5206\u652f(HEAD)
-gb.headRefDescription = \u9810\u8a2d\u5206\u652f\u5c07\u6703\u8907\u88fd\u4ee5\u53ca\u986f\u793a\u5230\u532f\u7e3d\u9801\u9762
-gb.heapAllocated = \u5df2\u4f7f\u7528\u5806\u7a4d(Heap)
-gb.heapMaximum = \u6700\u5927\u5806\u7a4d(heap)
-gb.heapUsed = \u5df2\u4f7f\u7528\u7684\u5806\u7a4d(heap)
-gb.history = \u6b77\u7a0b
-gb.home = \u9996\u9801
-gb.hookScripts = hook\u7684\u8173\u672c
-gb.hookScriptsDescription = \u7576\u63a8\u9001(push)\u81f3\u6b64Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u6642, \u57f7\u884cGroovy\u8173\u672c
+gb.ownerPermission = \u7248\u672c\u5eab\u64c1\u6709\u8005
+gb.administrator = \u7ba1\u7406\u54e1
+gb.administratorPermission = Gitblit \u7ba1\u7406\u54e1
+gb.team = \u5718\u968a
+gb.teamPermission = "{0}" \u5718\u968a\u6210\u54e1\u7684\u6b0a\u9650
+gb.missing = \u5931\u8aa4!
+gb.missingPermission = \u8a72\u6b0a\u9650\u7121\u6cd5\u5c0d\u61c9\u5230\u7248\u672c\u5eab!
+gb.mutable = \u52d5\u614b\u7d66\u4e88
+gb.specified = \u6307\u5b9a\u7d66\u4e88(\u542b\u7cfb\u7d71\u9810\u8a2d)
+gb.effective = \u6240\u6709\u6b0a\u9650
+gb.organizationalUnit = \u7d44\u7e54\u55ae\u4f4d
+gb.organization = \u7d44\u7e54
+gb.locality = \u4f4d\u7f6e
+gb.stateProvince = \u5dde\u6216\u7701
+gb.countryCode = \u570b\u5bb6\u4ee3\u78bc
+gb.properties = \u5c6c\u6027
+gb.issued = \u767c\u51fa
+gb.expires = \u5230\u671f
+gb.expired = \u904e\u671f
+gb.expiring = \u5c07\u8981\u904e\u671f
+gb.revoked = \u5df2\u64a4\u92b7
+gb.serialNumber = \u5e8f\u865f
+gb.certificates = \u8b49\u66f8
+gb.newCertificate = \u5efa\u7acb\u8b49\u66f8
+gb.revokeCertificate = \u64a4\u56de\u8b49\u66f8
+gb.sendEmail = \u767cemail
+gb.passwordHint = \u5bc6\u78bc\u63d0\u793a
+gb.ok = ok
+gb.invalidExpirationDate = \u4e0d\u6b63\u78ba\u7684\u5230\u671f\u65e5
+gb.passwordHintRequired = \u5bc6\u78bc\u63d0\u793a(\u5fc5\u8981)
+gb.viewCertificate = \u6aa2\u8996\u8b49\u66f8
+gb.subject = \u6a19\u984c
+gb.issuer = \u767c\u884c\u8005
+gb.validFrom = \u6709\u6548\u671f\u5f9e
+gb.validUntil = \u6709\u6548\u671f\u81f3
+gb.publicKey = \u516c\u958b\u91d1\u9470
+gb.signatureAlgorithm = \u7c3d\u7ae0\u6f14\u7b97\u6cd5
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.md5FingerPrint = MD5 Fingerprint
+gb.reason = \u539f\u56e0
+gb.revokeCertificateReason = \u8acb\u8f38\u5165\u64a4\u56de\u8b49\u66f8\u7406\u7531
+gb.unspecified = \u672a\u6307\u5b9a
+gb.keyCompromise = \u91d1\u9470\u5bc6\u78bc\u5916\u6d29
+gb.caCompromise = CA compromise
+gb.affiliationChanged = affiliation changed
+gb.superseded = \u5df2\u88ab\u66ff\u4ee3
+gb.cessationOfOperation = cessation of operation
+gb.privilegeWithdrawn = \u53d6\u6d88\u6b0a\u9650
+gb.time.inMinutes = {0}\u5206\u9418\u5167
+gb.time.inHours = {0}\u5c0f\u6642\u5167
+gb.time.inDays = {0}\u5929\u5167
 gb.hostname = \u4e3b\u6a5f\u540d\u7a31
 gb.hostnameRequired = \u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31
-gb.ignore_whitespace =\u5ffd\u7565\u7a7a\u767d
-gb.illegalCharacterRepositoryName = \u7248\u672c\u5eab\u540d\u7a31\u6709\u4e0d\u5408\u6cd5\u7684\u5b57\u5143"{0}"
-gb.illegalLeadingSlash = \u7981\u6b62\u6839\u76ee\u9304(/)
-gb.illegalPersonalRepositoryLocation = \u4f60\u79c1\u4eba\u7248\u672c\u5eab\u5fc5\u9808\u653e\u5728"{0}"
-gb.illegalRelativeSlash = \u7981\u6b62\u76f8\u5c0d\u76ee\u9304(../)
-gb.imgdiffSubtract = Subtract (black = identical)
-gb.in = in
-gb.inclusions = inclusions
-gb.incrementalPushTagMessage = \u7576[{0}]\u5206\u652f\u63a8\u9001\u5f8c,\u81ea\u52d5\u7d66\u4e88\u6a19\u7c64\u865f.
-gb.indexedBranches = \u5206\u652f\u7d22\u5f15
-gb.indexedBranchesDescription = \u9078\u5b9a\u6b32\u57f7\u884cLucene\u7d22\u5f15\u529f\u80fd\u7684\u5206\u652f
-gb.inherited = \u7e7c\u627f
-gb.initialCommit = \u521d\u6b21\u63d0\u4ea4
-gb.initialCommitDescription = \u4ee5\u4e0b\u6b65\u9a5f\u5c07\u6703\u8b93\u4f60\u99ac\u4e0a\u57f7\u884c<code>git clone</code>.\u5982\u679c\u4f60\u672c\u6a5f\u5df2\u6709\u6b64\u6587\u4ef6\u5eab\u4e14\u57f7\u884c\u904e<code>git init</code>,\u8acb\u8df3\u904e\u6b64\u6b65\u9a5f.
-gb.initWithGitignore = \u5305\u542b .gitignore \u6a94\u6848
-gb.initWithGitignoreDescription = \u65b0\u589e\u4e00\u500b\u8a2d\u5b9a\u6a94\u7528\u4f86\u6307\u5b9a\u54ea\u4e9b\u6a94\u6848\u6216\u76ee\u9304\u9700\u8981\u5ffd\u7565
-gb.initWithReadme = \u5305\u542bREADME\u6587\u4ef6
-gb.initWithReadmeDescription = \u6587\u4ef6\u5eab\u5c07\u7522\u751f\u7c21\u55aeREADME\u6587\u4ef6
-gb.invalidExpirationDate = \u4e0d\u6b63\u78ba\u7684\u5230\u671f\u65e5
-gb.invalidUsernameOrPassword = \u932f\u8aa4\u7684\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc!
-gb.isFederated = \u5df2\u7d93\u4e32\u9023
-gb.isFork = \u662f\u5206\u652f\u985e\u578b(fork)
-gb.isFrozen = \u51cd\u7d50\u63a5\u6536
-gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001(push)
-gb.isMirror = \u8a72\u6587\u4ef6\u5eab\u70ba\u93e1\u50cf(mirror)
-gb.isNotValidFile = \u4e0d\u662f\u6b63\u5e38\u6a94\u6848
-gb.isSparkleshared = \u8a72\u6587\u4ef6\u5eab\u5df2\u70baSparkleshared (http://sparkleshare.org)
-gb.issued = \u767c\u51fa
-gb.issuer = issuer
+gb.newSSLCertificate = \u65b0\u7684\u4f3a\u670d\u5668SSL\u8b49\u66f8
+gb.newCertificateDefaults = \u65b0\u8b49\u66f8\u9810\u8a2d\u503c
+gb.duration = \u9031\u671f
+gb.certificateRevoked = \u8b49\u66f8{0,number,0} \u5df2\u7d93\u88ab\u53d6\u6d88
+gb.clientCertificateGenerated = \u6210\u529f\u7522\u751f{0}\u7684\u65b0\u8b49\u66f8
+gb.sslCertificateGenerated = \u6210\u529f\u7522\u751f\u7d66{0}\u7684\u670d\u5668SSL\u8b49\u66f8
+gb.newClientCertificateMessage = \u6ce8\u610f:\n'password'\u5bc6\u78bc\u4e26\u4e0d\u662f\u4f7f\u7528\u8005\u5bc6\u78bc, \u800c\u662f\u7528\u4f86\u4fdd\u8b77\u4f7f\u7528\u8005\u500b\u4eba\u7684keystore.\u8a72\u5bc6\u78bc\u4e26\u4e0d\u6703\u5132\u5b58,  \u56e0\u6b64\u5fc5\u9808\u8a2d\u5b9a\u63d0\u793a(hint), \u8a72\u63d0\u793a\u5c07\u6703\u5beb\u5728\u4f7f\u7528\u8005\u7684README\u6587\u4ef6\u88e1\u9762.
+gb.certificate = \u8b49\u66f8
+gb.emailCertificateBundle = \u5bc4\u767c\u7528\u6236\u7aef\u8b49\u66f8
+gb.pleaseGenerateClientCertificate = \u8acb\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684\u7528\u6236\u7aef\u8b49\u66f8
+gb.clientCertificateBundleSent = {0}\u7684\u7528\u6236\u8b49\u66f8\u5df2\u5bc4\u767c
+gb.enterKeystorePassword = \u8acb\u8f38\u5165Gitblit\u7684keystore\u5c08\u7528\u5bc6\u78bc
+gb.warning = \u8b66\u544a
 gb.jceWarning = Your Java Runtime Environment does not have the "JCE Unlimited Strength Jurisdiction Policy" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
-gb.key = \u91d1\u9470
-gb.keyCompromise = \u91d1\u9470\u5bc6\u78bc\u5916\u6d29
-gb.labels = \u6a19\u8a18
-gb.languagePreference = \u5e38\u7528\u8a9e\u8a00
-gb.languagePreferenceDescription = \u9078\u64c7\u4f60\u60f3\u8981\u7684Gitblit\u7ffb\u8b6f
-gb.lastChange = \u6700\u8fd1\u4fee\u6539
-gb.lastLogin = \u6700\u8fd1\u767b\u5165
-gb.lastNDays = \u6700\u8fd1{0}\u5929
-gb.lastPull = \u4e0a\u6b21\u4e0b\u8f09(pull)
-gb.leaveComment = \u7559\u4e0b\u8a3b\u89e3
-gb.line = \u884c
-gb.loading = \u8f09\u5165
-gb.local = \u672c\u5730\u7aef
-gb.locality = \u4f4d\u7f6e
-gb.log = \u65e5\u8a8c
-gb.login = \u767b\u5165
-gb.logout = \u767b\u51fa
-gb.looksGood = \u770b\u8d77\u4f86\u5f88\u597d
-gb.luceneDisabled = \u505c\u7528Lucene\u7d22\u5f15\u529f\u80fd
-gb.mailingLists = \u90f5\u4ef6\u540d\u55ae
-gb.maintenanceTickets = \u7dad\u8b77
-gb.manage = \u7ba1\u7406
-gb.manual = \u81ea\u884c\u8f38\u5165
-gb.markdown = markdown
-gb.markdownFailure = \u89e3\u6790Markdown\u5931\u6557
 gb.maxActivityCommits = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u7387
 gb.maxActivityCommitsDescription = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u6578\u91cf
-gb.maxHits = \u6700\u5927\u9ede\u64ca
-gb.md5FingerPrint = MD5 Fingerprint
-gb.mentions = \u63d0\u5230
-gb.mentionsMeTickets = \u63d0\u5230\u4f60
-gb.merge = \u5408\u4f75
-gb.mergeBase = \u57fa\u672c\u5408\u4f75
-gb.merged = \u5df2\u5408\u4f75
-gb.mergedPatchset = \u5c07\u88dc\u4e01\u5408\u4f75
-gb.mergedPullRequest = \u5408\u4f75\u63a8\u9001\u8981\u6c42
-gb.mergeSha = mergeSha
-gb.mergeStep1 = Check out a new branch to review the changes \u2014 run this from your project directory
-gb.mergeStep2 = Bring in the proposed changes and review
-gb.mergeStep3 = \u5c07\u63d0\u6848\u4fee\u6539\u5167\u5bb9\u5408\u4f75\u5230\u4f3a\u670d\u5668\u4e0a
-gb.mergeTo = \u5408\u4f75\u5230
-gb.mergeToDescription = \u9810\u8a2d\u5c07\u6587\u4ef6\u76f8\u95dc\u88dc\u4e01\u5305\u8207\u6307\u5b9a\u5206\u652f(branch)\u5408\u4f75
-gb.mergingViaCommandLine = \u7d93\u7531\u6307\u4ee4\u57f7\u884c\u5408\u4f75
-gb.mergingViaCommandLineNote = \u5982\u679c\u4f60\u4e0d\u60f3\u8981\u4f7f\u7528\u81ea\u52d5\u5408\u4f75\u529f\u80fd,\u6216\u662f\u6309\u4e0b\u5408\u4f75\u6309\u9215, \u4f60\u53ef\u4ee5\u4e0b\u6307\u4ee4\u624b\u52d5\u5408\u4f75
-gb.message = \u8a0a\u606f
-gb.metricAuthorExclusions = \u91cf\u5316\u7d71\u8a08\u6642\u6392\u9664\u6d3b\u8e8d\u5e33\u6236
-gb.metrics = \u91cf\u5316\u7d71\u8a08
-gb.milestone = \u91cc\u7a0b\u7891
-gb.milestoneDeleteFailed = \u522a\u9664\u91cc\u7a0b\u7891"{0}"\u5931\u6557
-gb.milestoneProgress = {0}\u958b\u555f,{1}\u7d50\u675f
-gb.milestones = \u91cc\u7a0b\u7891
-gb.mirrorOf = {0}\u7684\u93e1\u50cf
-gb.mirrorWarning = \u8a72\u6587\u4ef6\u5eab\u5c6c\u65bc\u93e1\u50cf, \u4e0d\u80fd\u5920\u63a5\u6536\u63a8\u9001(push)
-gb.miscellaneous = \u5176\u4ed6
-gb.missing = \u5931\u8aa4!
-gb.missingIntegrationBranchMore = \u76ee\u6a19\u5206\u652f\u4e0d\u5728\u6b64\u7248\u672c\u5eab
-gb.missingPermission = the repository for this permission is missing\!
-gb.missingUsername = \u7f3a\u5c11\u4f7f\u7528\u8005\u540d\u7a31
-gb.modification = \u4fee\u6539
-gb.monthlyActivity = \u6708\u6d3b\u52d5
-gb.moreChanges = \u6240\u6709\u8b8a\u66f4...
-gb.moreHistory = \u66f4\u591a\u6b77\u53f2\u7d00\u9304...
-gb.moreLogs = \u66f4\u591a\u63d0\u4ea4 ...
-gb.mutable = \u52d5\u614b\u7d66\u4e88
-gb.myDashboard = \u6211\u7684\u5100\u8868\u677f
-gb.myFork = \u6aa2\u8996\u6211\u5efa\u7acb\u7684\u5206\u652f(fork)
-gb.myProfile = \u6211\u7684\u57fa\u672c\u8cc7\u6599
-gb.myRepositories = \u6211\u7684\u7248\u672c\u5eab
-gb.myTickets = \u6211\u7684\u4efb\u52d9\u55ae
-gb.myUrlDescription = \u4f60Gitblit\u4f3a\u670d\u5668\u7684\u516c\u958bURL
-gb.name = \u540d\u5b57
-gb.nameDescription = \u4f7f\u7528"/"\u505a\u70ba\u6587\u4ef6\u5eab\u7fa4\u7d44\u5206\u985e. \u5982: library/mycoolib.git
-gb.namedPushPolicy = Restrict Push (Named)
-gb.namedPushPolicyDescription = \u4efb\u4f55\u4eba\u7686\u53ef\u6aa2\u8996\u8207\u8907\u88fd(clone)\u6587\u4ef6\u5eab. \u4f60\u53ef\u53e6\u5916\u6307\u5b9a\u8ab0\u80fd\u5920\u6709\u63a8\u9001\u529f\u80fd(push)
-gb.nAttachments = {0}\u500b\u9644\u4ef6
-gb.nClosedTickets = {0}\u9805\u7d50\u675f
-gb.nComments = {0}\u500b\u8a3b\u89e3
-gb.nCommits = {0}\u4efd\u63d0\u4ea4
-gb.needsImprovement = \u9700\u8981\u512a\u5316
-gb.new = \u5efa\u7acb
-gb.newCertificate = \u5efa\u7acb\u8b49\u66f8
-gb.newCertificateDefaults = \u65b0\u8b49\u66f8\u9810\u8a2d\u503c
-gb.newClientCertificateMessage = \u6ce8\u610f:\n'password'\u5bc6\u78bc\u4e26\u4e0d\u662f\u4f7f\u7528\u8005\u5bc6\u78bc, \u800c\u662f\u7528\u4f86\u4fdd\u8b77\u4f7f\u7528\u8005\u500b\u4eba\u7684keystore.\u8a72\u5bc6\u78bc\u4e26\u4e0d\u6703\u5132\u5b58,  \u56e0\u6b64\u5fc5\u9808\u8a2d\u5b9a\u63d0\u793a(hint), \u8a72\u63d0\u793a\u5c07\u6703\u5beb\u5728\u4f7f\u7528\u8005\u7684README\u6587\u4ef6\u88e1\u9762.
-gb.newMilestone = \u5efa\u7acb\u91cc\u7a0b\u7891
-gb.newRepository = \u5efa\u7acb\u7248\u672c\u5eab
-gb.newSSLCertificate = \u65b0\u7684\u4f3a\u670d\u5668SSL\u8b49\u66f8
-gb.newTeam = \u5efa\u7acb\u5718\u968a
-gb.newTicket = \u65b0\u589e\u4efb\u52d9\u55ae
-gb.newUser = \u5efa\u7acb\u4f7f\u7528\u8005
-gb.nextPull = next pull
-gb.nFederationProposalsToReview = \u7e3d\u5171\u6709{0}\u500b\u4e32\u9023\u8a08\u756b\u7b49\u5f85\u5be9\u8996
-gb.nMoreCommits = \u9084\u6709{0}\u4efd\u63d0\u4ea4 \u00bb
-gb.noActivity = \u904e\u53bb{0}\u5929\u4f86,\u4e26\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
-gb.noActivityToday = \u4eca\u5929\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
-gb.noComments = \u6c92\u6709\u5099\u8a3b
-gb.noDescriptionGiven = \u6c92\u6709\u7d66\u4e88\u7c21\u8ff0
-gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
-gb.noForks = {0}\u6c92\u6709\u5206\u652f(fork)
-gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
-gb.noHits = \u7121\u9ede\u64ca
-gb.noIndexedRepositoriesWarning = \u8ddf\u4f60\u76f8\u95dc\u7684\u6587\u4ef6\u5eab\u4e26\u6c92\u6709\u505aLucene\u7d22\u5f15
 gb.noMaximum = \u7121\u6700\u5927\u503c
-gb.noMilestoneSelected = \u672a\u9078\u53d6\u91cc\u7a0b\u7891
-gb.none = \u7121
-gb.nOpenTickets = {0}\u9805\u958b\u555f\u4e2d
-gb.noPermission = \u522a\u9664\u9019\u500b\u6b0a\u9650
-gb.noProposals = \u62b1\u6b49, {0}\u6b64\u6642\u4e26\u4e0d\u662f\u53ef\u63a5\u53d7\u7684\u8a08\u756b
-gb.noSelectedRepositoriesWarning = \u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u500b\u6587\u4ef6\u5eab
-gb.notifyChangedOpenTickets = \u5df2\u958b\u555f\u7684\u4efb\u52d9\u55ae\u6709\u7570\u52d5\u8acb\u767c\u9001\u901a\u77e5
-gb.notRestricted = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push
-gb.notSpecified = \u7121\u6307\u5b9a
-gb.nParticipants = {0}\u500b\u53c3\u8207
-gb.nTotalTickets = \u7e3d\u5171{0}\u9805
-gb.object = \u7269\u4ef6
-gb.of = \u7684
-gb.ok = ok
-gb.oneAttachment  = {0}\u500b\u9644\u4ef6
-gb.oneComment = {0}\u500b\u8a3b\u89e3
-gb.oneCommit = 1\u500b\u63d0\u4ea4
-gb.oneCommitTo = 1\u500b\u63d0\u4ea4\u5230
-gb.oneMoreCommit = \u9084\u6709\u4e00\u500b\u63d0\u4ea4  \u00bb
-gb.oneParticipant = {0}\u53c3\u8207
-gb.OneProposalToReview = \u6709\u4e00\u500b\u4e32\u9023\u7684\u63d0\u6848\u7b49\u5f85\u5be9\u67e5
-gb.opacityAdjust = Adjust opacity
-gb.open = \u958b\u555f
-gb.openMilestones = \u6253\u958b\u91cc\u7a0b\u7891
-gb.organization = \u7d44\u7e54
-gb.organizationalUnit = \u7d44\u7e54\u55ae\u4f4d
-gb.origin = origin
-gb.originDescription = \u6b64\u6587\u4ef6\u5eabURL\u5df2\u7d93\u88ab\u8907\u88fd(cloned)\u4e86
-gb.overdue = \u904e\u671f
-gb.overview = \u6982\u89c0
-gb.owned = \u64c1\u6709\u7684
-gb.owner = \u64c1\u6709\u8005
-gb.ownerDescription = \u64c1\u6709\u8005\u53ef\u4fee\u6539\u6587\u4ef6\u5eab\u8a2d\u5b9a\u503c
-gb.ownerPermission = \u6587\u4ef6\u5eab\u6240\u6709\u8005
-gb.owners = \u6240\u6709\u8005
-gb.ownersDescription = \u6240\u6709\u8005\u53ef\u4ee5\u7ba1\u7406\u6587\u4ef6\u5eab,\u4f46\u662f\u4e0d\u5141\u8a31\u4fee\u6539\u540d\u7a31(\u79c1\u4eba\u6587\u4ef6\u5eab\u4f8b\u5916)
-gb.pageFirst = \u7b2c\u4e00\u7b46
-gb.pageNext = \u4e0b\u4e00\u9801
-gb.pagePrevious = \u4e0a\u4e00\u9801
-gb.pages = \u6587\u4ef6
-gb.parent = \u4e0a\u500b\u7248\u672c
-gb.password = \u5bc6\u78bc
-gb.passwordChangeAborted = \u53d6\u6d88\u5bc6\u78bc\u8b8a\u66f4
-gb.passwordChanged = \u5bc6\u78bc\u8b8a\u66f4\u6210\u529f
-gb.passwordHint = \u5bc6\u78bc\u63d0\u793a
-gb.passwordHintRequired = \u5bc6\u78bc\u63d0\u793a(\u5fc5\u8981)
-gb.passwordsDoNotMatch = \u5bc6\u78bc\u4e0d\u76f8\u7b26
-gb.passwordTooShort = \u5bc6\u78bc\u904e\u77ed, \u6700\u5c11{0}\u500b\u5b57\u5143
-gb.patch = \u4fee\u88dc\u6a94
-gb.patchset = \u88dc\u4e01
-gb.patchsetAlreadyMerged = \u8a72\u88dc\u4e01\u5df2\u7d93\u5408\u4f75\u5230{0}
-gb.patchsetMergeable = \u8a72\u88dc\u4e01\u53ef\u4ee5\u81ea\u52d5\u8207{0}\u5408\u4f75
-gb.patchsetMergeableMore = \u4f7f\u7528\u547d\u4ee4\u529f\u80fd,\u8b93\u6b64\u88dc\u4e01\u53ef\u4ee5\u8207{0}\u5408\u4f75
-gb.patchsetN = \u88dc\u4e01{0}
-gb.patchsetNotApproved = \u8a72\u88dc\u4e01\u7248\u672c\u4e26\u6c92\u6709\u88ab\u6279\u51c6\u8207{0}\u5408\u4f75
-gb.patchsetNotApprovedMore = \u8a72\u88dc\u4e01\u5fc5\u9808\u7531\u5be9\u67e5\u8005\u6279\u51c6
-gb.patchsetNotMergeable = \u8a72\u88dc\u4e01\u4e0d\u80fd\u81ea\u52d5\u8207{0}\u5408\u4f75
-gb.patchsetNotMergeableMore = \u5fc5\u9808\u4ee5rebased\u6216\u662f\u624b\u52d5\u8207{0}\u5408\u4f75\u7684\u65b9\u5f0f\u624d\u80fd\u89e3\u6c7a\u8a72\u88dc\u4e01\u9020\u6210\u7684\u885d\u7a81
-gb.patchsetVetoedMore = \u5be9\u8996\u8005\u5df2\u7d93\u5c0d\u6b64\u88dc\u4e01\u6295\u7968
-gb.permission = \u6b0a\u9650
-gb.permissions = \u6b0a\u9650
-gb.permittedTeams = permitted teams
-gb.permittedUsers = permitted users
-gb.personalRepositories = \u500b\u4eba\u6587\u4ef6\u5eab
-gb.pleaseGenerateClientCertificate = \u8acb\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684\u7528\u6236\u7aef\u8b49\u66f8
-gb.pleaseSelectGitIgnore = \u8acb\u9078\u64c7\u4e00\u500b.gitignore\u6a94\u6848
-gb.pleaseSelectProject = \u8acb\u9078\u64c7\u5c08\u6848!
-gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal\!
-gb.pleaseSetGitblitUrl = \u8acb\u8f38\u5165Gitblit URL !
-gb.pleaseSetRepositoryName = \u8acb\u8a2d\u5b9a\u7248\u672c\u5eab\u540d\u7a31
-gb.pleaseSetTeamName = \u8acb\u8f38\u5165\u5718\u968a\u540d\u7a31
-gb.pleaseSetUsername = \u8acb\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31
-gb.plugins = \u63d2\u4ef6
-gb.postReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4e26\u4e14\u5728refs\u5b8c\u7562\u5f8c</em>, \u5c07\u6703\u57f7\u884cPost-receive hook..<p>This is the appropriate hook for notifications, build triggers, etc.</p>
-gb.postReceiveScripts = post-receive\u8173\u672c
-gb.preferences = \u9810\u8a2d\u5e38\u7528\u503c
-gb.preparingFork = \u6b63\u5728\u6e96\u5099\u8907\u88fd\u4e2d(fork)...
-gb.preReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4f46\u5728\u9084\u6c92\u6709\u66f4\u65b0refs\u524d</em>, \u5c07\u6703\u57f7\u884cPre-receive hook. <p>This is the appropriate hook for rejecting a push.</p>
-gb.preReceiveScripts = pre-receive \u8173\u672c
-gb.preview = \u9810\u89bd
-gb.priority = \u512a\u5148
-gb.privilegeWithdrawn = \u53d6\u6d88\u6b0a\u9650
-gb.project = \u7fa4\u7d44
-gb.projects = \u7fa4\u7d44
-gb.properties = \u5c6c\u6027
-gb.proposal = \u63d0\u6848
-gb.proposalError = \u62b1\u6b49, {0} \u4efd\u5831\u544a\u767c\u751f\u9810\u671f\u5916\u7684\u932f\u8aa4!
-gb.proposalFailed = Sorry, {0} did not receive any proposal data\!
-gb.proposalReceived = Proposal successfully received by {0}.
-gb.proposals = \u8981\u6c42\u806f\u5408\u7684\u63d0\u6848
-gb.proposalTickets = \u63d0\u6848\u4fee\u6539
-gb.proposedThisChange = proposed this change
-gb.proposeInstructions = To start, craft a patchset and upload it with Git. Gitblit will link your patchset to this ticket by the id.
-gb.proposePatchset = \u63d0\u51fa\u88dc\u4e01
-gb.proposePatchsetNote = \u6b61\u8fce\u5c0d\u6b64\u4efb\u52d9\u55ae\u63d0\u4f9b\u88dc\u4e01
-gb.proposeWith = propose a patchset with {0}
-gb.ptCheckout = Fetch & checkout the current patchset to a review branch
-gb.ptDescription = the Gitblit patchset tool
-gb.ptDescription1 = Barnum is a command-line companion for Git that simplifies the syntax for working with Gitblit Tickets and Patchsets.
-gb.ptDescription2 = Barnum requires Python 3 and native Git. It runs on Windows, Linux, and Mac OS X.
-gb.ptMerge = \u53d6\u5f97\u76ee\u524d\u88dc\u4e01,\u7136\u5f8c\u8207\u4f60\u672c\u6a5f\u7aef\u7684\u5206\u652f\u5408\u4f75
-gb.ptSimplifiedCollaboration = simplified collaboration syntax
-gb.ptSimplifiedMerge = simplified merge syntax
-gb.publicKey = \u516c\u958b\u91d1\u9470
-gb.pushedNCommitsTo = {0}\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
-gb.pushedNewBranch = \u65b0\u5206\u652f\u5df2\u63a8\u9001(pushed)
-gb.pushedNewTag = \u65b0\u6a19\u7c64\u5df2\u63a8\u9001(pushed)
-gb.pushedOneCommitTo = 1\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
-gb.pushPermission = {0}(\u63a8\u9001)
-gb.pushRestricted = authenticated push
-gb.queries = \u67e5\u8a62\u7d50\u679c
-gb.query = \u67e5\u8a62
-gb.queryHelp = \u652f\u63f4\u6a19\u6e96\u67e5\u8a62\u8a9e\u6cd5.<p/><p/>\u8a73\u60c5\u8acb\u53c3\u8003 <a target\ = "_new" href\ = "http\://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a>
-gb.queryResults = results {0} - {1} ({2} hits)
-gb.questionTickets = \u63d0\u554f
-gb.raw = \u539f\u59cb
-gb.reason = \u539f\u56e0
-gb.receive = \u63a5\u6536
-gb.received = \u5df2\u63a5\u6536
-gb.receiveSettings = \u8a2d\u5b9a\u63a5\u6536\u65b9\u5f0f
-gb.receiveSettingsDescription = \u63a7\u7ba1\u63a8\u9001\u5230\u6587\u4ef6\u5eab\u7684\u63a5\u6536\u65b9\u5f0f
-gb.recent = \u6700\u8fd1
-gb.recentActivity = \u6700\u8fd1\u6d3b\u8e8d\u72c0\u6cc1
-gb.recentActivityNone = \u904e\u53bb{0}\u5929/\u7121
-gb.recentActivityStats = \u904e\u53bb{0}\u5929,\u4e00\u5171\u6709{2}\u4eba\u57f7\u884c{1}\u4efd\u63d0\u4ea4
-gb.reflog = \u76f8\u95dc\u65e5\u8a8c
-gb.refresh = \u5237\u65b0
-gb.refs = \u5f15\u7528
-gb.regexPermission = \u5df2\u7d93\u4f7f\u7528\u6b63\u898f\u8868\u793a\u5f0f(regular expression)"{0}" \u8a2d\u5b9a\u6b0a\u9650\u5b8c\u7562
-gb.registration = \u8a3b\u518a
-gb.registrations = federation registrations
-gb.releaseDate = \u767c\u8868\u65e5
-gb.remote = \u9060\u7aef
-gb.removeVote = \u79fb\u9664\u6295\u7968
-gb.rename = \u6539\u540d\u7a31
-gb.repositories = \u6587\u4ef6\u5eab
-gb.repository = \u7248\u672c\u5eab
-gb.repositoryDeleted = \u7248\u672c\u5eab"{0}"\u5df2\u522a\u9664
-gb.repositoryDeleteFailed = \u522a\u9664\u7248\u672c\u5eab"{0}"\u5931\u6557!
-gb.repositoryDoesNotAcceptPatchsets = \u8a72\u7248\u672c\u5eab\u4e0d\u63a5\u53d7\u88dc\u4e01
-gb.repositoryForked = \u7248\u672c\u5eab{0}\u5df2\u7d93\u5efa\u7acb\u5206\u652f(fork)
-gb.repositoryForkFailed= \u5efa\u7acb\u5206\u652f(fork)\u5931\u6557
-gb.repositoryIsFrozen = \u8a72\u7248\u672c\u5eab\u5df2\u51cd\u7d50
-gb.repositoryIsMirror = \u8a72\u7248\u672c\u5eab\u70ba\u552f\u8b80\u8907\u672c
-gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5eab!
-gb.repositoryNotSpecifiedFor = \u7248\u672c\u5eab\u4e26\u6c92\u6709\u6307\u5b9a\u7d66 {0}\!
-gb.repositoryPermissions = \u7248\u672c\u5eab\u6b0a\u9650
-gb.repositoryUrl = \u7248\u672c\u5eab url
-gb.requestTickets = \u512a\u5316 & \u4efb\u52d9
-gb.requireApproval = \u9700\u6279\u51c6
-gb.requireApprovalDescription = \u5408\u4f75\u6309\u9215\u555f\u7528\u524d,\u88dc\u4e01\u5305\u5fc5\u9808\u5148\u6279\u51c6
-gb.reset = \u6e05\u9664
-gb.responsible = \u8ca0\u8cac\u4eba\u54e1
-gb.restrictedRepositories = restricted repositories
-gb.review = \u8907\u67e5(review)
-gb.reviewedPatchsetRev = reviewed patchset {0} revision {1}\: {2}
-gb.reviewers = \u5be9\u67e5\u8005
-gb.reviewPatchset = review {0} patchset {1}
-gb.reviews = reviews
-gb.revisionHistory = \u4fee\u6539\u7d00\u9304
-gb.revokeCertificate = \u64a4\u56de\u8b49\u66f8
-gb.revokeCertificateReason = \u8acb\u8f38\u5165\u64a4\u56de\u8b49\u66f8\u7406\u7531
-gb.revoked = \u5df2\u64a4\u92b7
-gb.rewind = REWIND
-gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
-gb.save = \u5132\u5b58
-gb.search = \u641c\u5c0b
-gb.searchForAuthor = Search for commits authored by
-gb.searchForCommitter = Search for commits committed by
-gb.searchTickets = \u641c\u5c0b\u4efb\u52d9\u55ae
-gb.searchTicketsTooltip = \u627e\u5230{0}\u4efd\u4efb\u52d9\u55ae
-gb.searchTooltip = \u641c\u5c0b{0}
-gb.searchTypeTooltip = \u9078\u64c7\u641c\u5c0b\u985e\u578b
-gb.selectAccessRestriction = Please select access restriction\!
-gb.selected = \u9078\u5b9a
-gb.selectFederationStrategy = Please select federation strategy\!
-gb.sendEmail = \u767cemail
-gb.sendProposal = \u63d0\u6848
-gb.serialNumber = \u5e8f\u865f
+gb.attributes = \u5c6c\u6027
 gb.serveCertificate = \u555f\u7528\u4f7f\u7528\u6b64\u8b49\u66f8\u7684https\u529f\u80fd
-gb.serverDoesNotAcceptPatchsets = \u672c\u4f3a\u670d\u5668\u4e0d\u63a5\u53d7\u88dc\u4e01
-gb.servers = \u4f3a\u670d\u5668
-gb.servletContainer = servlet\u5bb9\u5668
-gb.sessionEnded = session\u5df2\u7d93\u53d6\u6d88
-gb.setDefault = \u8a2d\u70ba\u9810\u8a2d\u503c
-gb.settings = \u8a2d\u5b9a
-gb.severity =  \u91cd\u8981
-gb.sha1FingerPrint = SHA-1 Fingerprint
-gb.show_whitespace = \u986f\u793a\u7a7a\u767d
-gb.showHideDetails = \u986f\u793a/\u96b1\u85cf \u8a73\u89e3\u5167\u5bb9
-gb.showReadme = \u986f\u793areadme\u6587\u4ef6
-gb.showReadmeDescription = \u5728\u532f\u7e3d\u9801\u9762\u4e2d\u986f\u793a"readme"(markdown\u683c\u5f0f)
-gb.showRemoteBranches = \u986f\u793a\u9060\u7aef\u5206\u652f
-gb.showRemoteBranchesDescription = \u986f\u793a\u9060\u7aef\u5206\u652f(branches)
-gb.signatureAlgorithm = \u7c3d\u7ae0\u6f14\u7b97\u6cd5
-gb.since = \u5f9e
-gb.siteName = \u7ad9\u53f0\u540d\u7a31
-gb.siteNameDescription = \u4f3a\u670d\u5668\u7c21\u7a31
-gb.size = \u5bb9\u91cf
-gb.skipSizeCalculation = \u7565\u904e\u5bb9\u91cf\u8a08\u7b97
-gb.skipSizeCalculationDescription = \u4e0d\u8a08\u7b97\u6587\u4ef6\u5eab\u5bb9\u91cf(\u52a0\u5feb\u7db2\u9801\u8f09\u5165\u901f\u5ea6)
-gb.skipSummaryMetrics = \u7565\u904e\u91cf\u5316\u532f\u7e3d
-gb.skipSummaryMetricsDescription = \u4e0d\u8981\u8a08\u7b97\u91cf\u5316\u4e26\u4e14\u986f\u793a\u5728\u532f\u7e3d\u9801\u9762\u4e0a(\u52a0\u5feb\u901f\u5ea6)
-gb.sort = \u6392\u5e8f
-gb.sortHighestPriority = \u6700\u9ad8\u512a\u5148
-gb.sortHighestSeverity = \u6700\u91cd\u8981
-gb.sortLeastComments = \u6700\u5c11\u5099\u8a3b
-gb.sortLeastPatchsetRevisions = \u6700\u5c11\u88dc\u4e01\u4fee\u6539
-gb.sortLeastRecentlyUpdated = \u6700\u8fd1\u6700\u5c11\u8b8a\u52d5
-gb.sortLeastVotes = \u6700\u5c11\u6295\u7968
-gb.sortLowestPriority = \u6700\u4f4e\u512a\u5148
-gb.sortLowestSeverity = \u6700\u4e0d\u91cd\u8981
-gb.sortMostComments = \u6700\u591a\u5099\u8a3b
-gb.sortMostPatchsetRevisions = \u6700\u591a\u88dc\u4e01\u4fee\u6b63
-gb.sortMostRecentlyUpdated = \u6700\u8fd1\u66f4\u65b0
-gb.sortMostVotes = \u6700\u591a\u6295\u7968
-gb.sortNewest = \u6700\u65b0
-gb.sortOldest = \u6700\u820a
-gb.specified = \u6307\u5b9a\u7d66\u4e88(\u542b\u7cfb\u7d71\u9810\u8a2d)
-gb.sshKeyCommentDescription = \u8acb\u8f38\u5165\u5099\u8a3b, \u82e5\u7121\u5099\u8a3b, \u5c07\u81ea\u8a02\u586b\u5165key data
-gb.sshKeyPermissionDescription = \u6307\u5b9a\u8a72SSH key\u6240\u64c1\u6709\u7684\u5b58\u53d6\u6b0a\u9650
-gb.sshKeys = SSH Keys
-gb.sshKeysDescription = SSH \u516c\u958b\u91d1\u9470\u662f\u5bc6\u78bc\u8a8d\u8b49\u5916\u66f4\u5b89\u5168\u7684\u9078\u9805
-gb.sslCertificateGenerated = \u6210\u529f\u7522\u751f\u7d66{0}\u7684\u670d\u5668SSL\u8b49\u66f8
 gb.sslCertificateGeneratedRestart = \u6210\u529f\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684SSL\u8b49\u66f8\n\u4f60\u5fc5\u9808\u91cd\u65b0\u555f\u52d5Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u624d\u80fd\u555f\u7528\u65b0\u7684\u8b49\u66f8\n\nf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
+gb.validity = validity
+gb.siteName = \u7db2\u7ad9\u540d\u7a31
+gb.siteNameDescription = \u4f3a\u670d\u5668\u7c21\u7a31
+gb.excludeFromActivity = exclude from activity page
+gb.isSparkleshared = \u8a72\u7248\u672c\u5eab\u5df2\u70baSparkleshared (http://sparkleshare.org)
+gb.owners = \u64c1\u6709\u8005
+gb.sessionEnded = session\u5df2\u7d93\u53d6\u6d88
+gb.closeBrowser = \u8acb\u95dc\u9589\u700f\u89bd\u5668\u7d50\u675f\u6b64\u767b\u5165\u968e\u6bb5
+gb.doesNotExistInTree = {0}\u4e26\u6c92\u6709\u5728\u76ee\u9304{1}\u88e1\u9762
+gb.enableIncrementalPushTags = \u555f\u7528\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u529f\u80fd
+gb.useIncrementalPushTagsDescription = \u63a8\u9001\u6642\u5c07\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u865f\u78bc
+gb.incrementalPushTagMessage = \u7576[{0}]\u5206\u652f\u63a8\u9001\u5f8c,\u81ea\u52d5\u7d66\u4e88\u6a19\u7c64\u865f.
+gb.externalPermissions = {0} access permissions are externally maintained
+gb.viewAccess = \u4f60\u6c92\u6709Gitblit\u8b80\u53d6\u6216\u662f\u4fee\u6539\u6b0a\u9650
+gb.overview = \u6982\u89c0
+gb.dashboard = \u5100\u8868\u677f
+gb.monthlyActivity = \u6708\u6d3b\u52d5
+gb.myProfile = \u6211\u7684\u57fa\u672c\u8cc7\u6599
+gb.compare = \u6bd4\u5c0d
+gb.manual = \u81ea\u884c\u8f38\u5165
+gb.from = \u5f9e
+gb.to = \u81f3
+gb.at = at
+gb.of = \u5c08\u6848\u70ba
+gb.in = in
+gb.moreChanges = \u6240\u6709\u8b8a\u66f4...
+gb.pushedNCommitsTo = {0}\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
+gb.pushedOneCommitTo = 1\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
+gb.commitsTo = {0} \u4efd\u63d0\u4ea4\u81f3\u5206\u652f
+gb.oneCommitTo = 1\u500b\u63d0\u4ea4\u81f3\u5206\u652f
+gb.byNAuthors = \u7d93\u7531{0}\u500b\u4f5c\u8005
+gb.byOneAuthor = \u7d93\u7531{0}
+gb.viewComparison = \u6bd4\u8f03\u9019{0}\u500b\u63d0\u4ea4 \u00bb
+gb.nMoreCommits = \u9084\u6709{0}\u4efd\u63d0\u4ea4 \u00bb
+gb.oneMoreCommit = \u9084\u6709\u4e00\u500b\u63d0\u4ea4  \u00bb
+gb.pushedNewTag = \u65b0\u6a19\u7c64\u5df2\u63a8\u9001(pushed)
+gb.createdNewTag = \u5efa\u7acb\u65b0\u6a19\u7c64
+gb.deletedTag = \u522a\u9664\u6a19\u7c64
+gb.pushedNewBranch = \u5df2\u63a8\u9001(pushed)\u4e4b\u5206\u652f -
+gb.createdNewBranch = \u5efa\u7acb\u65b0\u5206\u652f
+gb.deletedBranch = \u5df2\u522a\u9664\u7684\u5206\u652f
+gb.createdNewPullRequest = \u5efa\u7acb pull request
+gb.mergedPullRequest = \u5408\u4f75\u63a8\u9001\u8981\u6c42
+gb.rewind = REWIND
 gb.star = \u91cd\u8981
-gb.stargazers = stargazers
-gb.starred = \u91cd\u8981
-gb.starredAndOwned = \u91cd\u8981\u7684 & \u64c1\u6709\u7684
-gb.starredRepositories = \u91cd\u8981\u7684\u6587\u4ef6\u5eab
-gb.starting = \u555f\u52d5\u4e2d
-gb.stateProvince = \u5dde\u6216\u7701
-gb.stats = \u7d71\u8a08
-gb.status = \u72c0\u614b
-gb.stepN = \u6b65\u9a5f{0}
-gb.stopWatching = \u505c\u6b62\u8ffd\u8e64(watching)
-gb.subject = \u6a19\u984c
-gb.subscribe = \u8a02\u95b1
-gb.summary = \u532f\u7e3d
-gb.superseded = \u5df2\u88ab\u66ff\u4ee3
-gb.tag = \u6a19\u7c64
-gb.tagger = tagger
-gb.tags = \u6a19\u7c64
-gb.taskTickets = \u4efb\u52d9
-gb.team = \u5718\u968a
-gb.teamCreated = \u5718\u968a"{0}"\u65b0\u589e\u6210\u529f.
-gb.teamMembers = \u5718\u968a\u6210\u54e1
-gb.teamMemberships = \u5718\u968a\u6210\u54e1(memberships)
-gb.teamMustSpecifyRepository = \u5718\u968a\u6700\u5c11\u8981\u6307\u5b9a\u4e00\u500b\u7248\u672c\u5eab
-gb.teamName = \u5718\u968a\u540d\u7a31
-gb.teamNameUnavailable = \u5718\u968a"{0}"\u4e0d\u5b58\u5728.
-gb.teamPermission = "{0}" \u5718\u968a\u6210\u54e1\u7684\u6b0a\u9650
-gb.teamPermissions = \u5718\u968a\u6b0a\u9650
-gb.teamPermissionsDescription = \u4f60\u53ef\u4ee5\u6307\u5b9a\u5718\u968a\u6b0a\u9650.\u9019\u4e9b\u8a2d\u5b9a\u5c07\u6703\u53d6\u4ee3\u539f\u672c\u5718\u968a\u9810\u8a2d\u6b0a\u9650
-gb.teams = \u53c3\u8207\u7684\u5718\u968a
-gb.ticket = \u4efb\u52d9\u55ae
-gb.ticketAssigned = \u5df2\u6307\u5b9a
-gb.ticketComments = \u8a3b\u89e3
-gb.ticketId = \u4efb\u52d9\u55aeID
-gb.ticketIsClosed = \u8a72\u4efb\u52d9\u55ae\u5df2\u7d93\u7d50\u6848
-gb.ticketN = \u4efb\u52d9\u55ae\u865f#{0}
-gb.ticketOpenDate = \u767c\u884c\u65e5
-gb.ticketPatchset = {0}\u4efb\u52d9\u55ae,{1}\u88dc\u4e01
-gb.tickets = \u4efb\u52d9\u55ae
-gb.ticketSettings = \u4efb\u52d9\u55ae\u5167\u5bb9\u8a2d\u5b9a
-gb.ticketStatus = \u72c0\u614b
-gb.ticketsWelcome = \u4f60\u53ef\u4ee5\u5229\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71\u5efa\u69cb\u51fa\u5f85\u8fa6\u4e8b\u9805, \u81ed\u87f2\u56de\u5831\u5340\u4ee5\u53ca\u88dc\u4e01\u5305\u7684\u5354\u540c\u5408\u4f5c
-gb.time.daysAgo = {0}\u5929\u524d
-gb.time.hoursAgo = {0}\u5c0f\u6642\u524d
-gb.time.inDays = {0}\u5929\u5167
-gb.time.inHours = {0}\u5c0f\u6642\u5167
-gb.time.inMinutes = {0}\u5206\u9418\u5167
-gb.time.justNow = \u525b\u525b
-gb.time.minsAgo = {0}\u5206\u9418\u524d
-gb.time.monthsAgo = {0}\u6708\u524d
-gb.time.oneYearAgo = 1\u5e74\u524d
-gb.time.today = \u4eca\u5929
-gb.time.weeksAgo = {0}\u5468\u524d
-gb.time.yearsAgo = {0}\u5e74\u524d
-gb.time.yesterday = \u6628\u5929
-gb.title = \u6a19\u984c
-gb.to = to
-gb.toBranch = to {0}
-gb.todaysActivityNone = \u4eca\u5929/\u7121
-gb.todaysActivityStats = \u4eca\u5929/\u6709{2}\u500b\u4f5c\u8005\u5b8c\u6210{1}\u500b\u63d0\u4ea4
-gb.token = token
-gb.tokenAllDescription = \u6240\u6709\u7248\u672c\u5eab,\u4f7f\u7528\u8005\u8207\u8a2d\u5b9a
-gb.tokenJurDescription = \u6240\u6709\u7248\u672c\u5eab
-gb.tokens = federation tokens
-gb.tokenUnrDescription = \u6240\u6709\u7248\u672c\u5eab\u8207\u4f7f\u7528\u8005
-gb.topic = \u8a71\u984c
-gb.topicsAndLabels = \u8a71\u984c\u8207\u6a19\u8a18
-gb.transportPreference = \u9810\u8a2d\u901a\u8a0a\u5354\u5b9a
-gb.transportPreferenceDescription = \u8a2d\u5b9a\u4f60\u5e38\u7528\u7684\u9023\u7dda\u901a\u8a0a\u5354\u5b9a\u4ee5\u7528\u4f86\u8907\u88fd(clone)
-gb.tree = \u76ee\u9304
-gb.type = \u985e\u578b
-gb.unauthorizedAccessForRepository = \u7248\u672c\u5eab\u672a\u6388\u6b0a\u5b58\u53d6
-gb.undefinedQueryWarning = \u672a\u8a2d\u5b9a\u67e5\u8a62\u689d\u4ef6
-gb.unspecified = \u672a\u6307\u5b9a
 gb.unstar = \u53d6\u6d88
-gb.updated = \u5df2\u66f4\u65b0
-gb.updatedBy = updated by
+gb.stargazers = stargazers
+gb.starredRepositories = \u91cd\u8981\u7684\u7248\u672c\u5eab
+gb.failedToUpdateUser = \u7121\u6cd5\u66f4\u65b0\u4f7f\u7528\u8005\u5e33\u865f
+gb.myRepositories = \u6211\u7684\u7248\u672c\u5eab
+gb.noActivity = \u904e\u53bb{0}\u5929\u4f86,\u4e26\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
+gb.findSomeRepositories = \u641c\u5c0b\u7248\u672c\u5eab
+gb.metricAuthorExclusions = \u7d71\u8a08\u6642\u6392\u9664\u6d3b\u8e8d\u5e33\u6236
+gb.myDashboard = \u5100\u8868\u677f
+gb.failedToFindAccount = \u7121\u6cd5\u641c\u5c0b\u5230\u5e33\u865f"{0}"
+gb.reflog = \u76f8\u95dc\u65e5\u8a8c
+gb.active = \u6d3b\u52d5
+gb.starred = \u91cd\u8981
+gb.owned = \u64c1\u6709
+gb.starredAndOwned = \u91cd\u8981 & \u64c1\u6709
+gb.reviewPatchset = {0}\u500breview  {1}\u500bpatchset
+gb.todaysActivityStats = \u4eca\u5929/\u6709{2}\u500b\u4f5c\u8005\u5b8c\u6210{1}\u500b\u63d0\u4ea4
+gb.todaysActivityNone = \u4eca\u5929/\u7121
+gb.noActivityToday = \u4eca\u5929\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
+gb.anonymousUser = \u533f\u540d
+gb.commitMessageRenderer = \u63d0\u4ea4\u8a0a\u606f\u5448\u73fe\u65b9\u5f0f
+gb.diffStat = \u65b0\u589e{0}\u5217\u8207\u522a\u9664{1}\u5217
+gb.home = \u9996\u9801
+gb.isMirror = \u8a72\u7248\u672c\u5eab\u70ba\u93e1\u50cf(mirror)
+gb.mirrorOf = {0}\u7684\u93e1\u50cf
+gb.mirrorWarning = \u8a72\u7248\u672c\u5eab\u5c6c\u65bc\u93e1\u50cf, \u4e0d\u80fd\u5920\u63a5\u6536\u63a8\u9001(push)
+gb.docsWelcome1 = \u4f60\u53ef\u4ee5\u4f7f\u7528\u6a94\u6848\u5340\u5efa\u7acb\u7248\u672c\u5eab\u7684\u6559\u5b78\u6a94\u6848
+gb.docsWelcome2 = \u63d0\u4ea4README.md \u6216 HOME.md\u5f8c,\u518d\u958b\u59cb\u65b0\u7684\u7248\u672c\u5eab
+gb.createReadme = \u5efa\u7acbREADME\u6a94\u6848
+gb.responsible = \u8ca0\u8cac\u4eba\u54e1
+gb.createdThisTicket = \u5df2\u958b\u7acb\u7684\u4efb\u52d9
+gb.proposedThisChange = proposed this change
 gb.uploadedPatchsetN = \u88dc\u4e01{0}\u5df2\u4e0a\u50b3
 gb.uploadedPatchsetNRevisionN = \u88dc\u4e01{0}\u4fee\u6539\u7248\u672c{1}\u5df2\u4e0a\u50b3
-gb.url = URL
-gb.useDocsDescription = \u8a08\u7b97\u6587\u4ef6\u5eab\u88e1\u9762\u7684Markdown\u6a94\u6848
-gb.useIncrementalPushTagsDescription = \u63a8\u9001\u6642\u5c07\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u865f\u78bc
-gb.userCreated = \u6210\u529f\u5efa\u7acb\u65b0\u4f7f\u7528\u8005"{0}"
-gb.userDeleted = \u4f7f\u7528\u8005"{0}"\u5df2\u522a\u9664
-gb.userDeleteFailed = \u4f7f\u7528\u8005"{0}"\u522a\u9664\u5931\u6557
-gb.username = \u4f7f\u7528\u8005\u540d\u7a31
-gb.usernameUnavailable = \u4f7f\u7528\u8005\u540d\u7a31"{0}"\u4e0d\u53ef\u7528
-gb.userPermissions = \u4f7f\u7528\u8005\u6b0a\u9650
-gb.userPermissionsDescription = \u4f60\u53ef\u4ee5\u91dd\u5c0d\u5e33\u865f\u8a2d\u5b9a\u6b0a\u9650(\u9019\u4e9b\u8a2d\u5b9a\u5c07\u8986\u84cb\u5718\u968a\u6216\u5176\u4ed6\u6b0a\u9650)
-gb.users = \u4f7f\u7528\u8005
-gb.userServiceDoesNotPermitAddUser = {0}\u4e0d\u5141\u8a31\u65b0\u589e\u4f7f\u7528\u8005\u5e33\u865f
-gb.userServiceDoesNotPermitPasswordChanges = {0}\u4e0d\u5141\u8a31\u4fee\u6539\u5bc6\u78bc
-gb.useTicketsDescription = readonly, distributed Ticgit issues
-gb.validFrom = valid from
-gb.validity = validity
-gb.validUntil = valid until
-gb.verifyCommitter = \u63d0\u4ea4\u8005\u9700\u9a57\u8b49
-gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7b26\u5408\u63a8\u9001\u5e33\u865f
-gb.verifyCommitterNote = \u6240\u6709\u5408\u4f75\u52d5\u4f5c\u7686\u9808\u5f37\u5236\u4f7f\u7528"--no-ff"\u53c3\u6578
-gb.version = \u7248\u672c
-gb.veto = veto
-gb.view = \u6aa2\u8996
-gb.viewAccess = \u4f60\u6c92\u6709Gitblit\u8b80\u53d6\u6216\u662f\u4fee\u6539\u6b0a\u9650
-gb.viewCertificate = \u6aa2\u8996\u8b49\u66f8
-gb.viewComparison = \u6bd4\u8f03\u9019{0}\u500b\u63d0\u4ea4 \u00bb
-gb.viewPermission = {0} (\u6aa2\u8996)
-gb.viewPolicy  = Restrict View, Clone, & Push
-gb.viewPolicyDescription = \u9078\u64c7\u53ef\u4ee5\u5728\u6587\u4ef6\u5eab\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u7684\u4f7f\u7528\u8005, \u9664\u6b64\u4e4b\u5916\u5176\u4ed6\u4eba\u7686\u7121\u6b0a\u9650
-gb.viewRestricted = authenticated view, clone, & push
+gb.mergedPatchset = \u5c07\u88dc\u4e01\u5408\u4f75
+gb.commented = \u5df2\u8a3b\u89e3
+gb.noDescriptionGiven = \u6c92\u6709\u7d66\u4e88\u7c21\u8ff0
+gb.toBranch = \u5230\u5206\u652f {0}
+gb.createdBy = \u5efa\u7acb\u8005
+gb.oneParticipant = {0}\u53c3\u8207
+gb.nParticipants = {0}\u500b\u53c3\u8207
+gb.noComments = \u6c92\u6709\u5099\u8a3b
+gb.oneComment = {0}\u500b\u8a3b\u89e3
+gb.nComments = {0}\u500b\u8a3b\u89e3
+gb.oneAttachment = {0}\u500b\u9644\u4ef6
+gb.nAttachments = {0}\u500b\u9644\u4ef6
+gb.milestone = milestone
+gb.compareToMergeBase = \u6bd4\u5c0d\u5f8c,\u5408\u4f75\u5230\u4e3b\u8981\u5de5\u4f5c\u5340
+gb.compareToN = \u8207{0}\u9032\u884c\u6bd4\u5c0d
+gb.open = \u958b\u555f
+gb.closed = \u95dc\u9589
+gb.merged = \u5df2\u5408\u4f75
+gb.ticketPatchset = {0}\u4efb\u52d9\u55ae,{1}\u88dc\u4e01
+gb.patchsetMergeable = \u8a72\u88dc\u4e01\u53ef\u4ee5\u81ea\u52d5\u8207{0}\u5408\u4f75
+gb.patchsetMergeableMore = \u4f7f\u7528\u547d\u4ee4\u529f\u80fd,\u8b93\u6b64\u88dc\u4e01\u53ef\u4ee5\u8207{0}\u5408\u4f75
+gb.patchsetAlreadyMerged = \u8a72\u88dc\u4e01\u5df2\u7d93\u5408\u4f75\u5230{0}
+gb.patchsetNotMergeable = \u8a72\u88dc\u4e01\u4e0d\u80fd\u81ea\u52d5\u8207{0}\u5408\u4f75
+gb.patchsetNotMergeableMore = \u5fc5\u9808\u4ee5rebased\u6216\u662f\u624b\u52d5\u8207{0}\u5408\u4f75\u7684\u65b9\u5f0f\u624d\u80fd\u89e3\u6c7a\u8a72\u88dc\u4e01\u9020\u6210\u7684\u885d\u7a81
+gb.patchsetNotApproved = \u8a72\u88dc\u4e01\u7248\u672c\u4e26\u6c92\u6709\u88ab\u6279\u51c6\u8207{0}\u5408\u4f75
+gb.patchsetNotApprovedMore = \u8a72\u88dc\u4e01\u5fc5\u9808\u7531\u5be9\u67e5\u8005\u6279\u51c6
+gb.patchsetVetoedMore = \u5be9\u67e5\u8005\u5df2\u7d93\u5c0d\u6b64\u88dc\u4e01\u6295\u7968
+gb.write = \u8f38\u5165
+gb.comment = \u8a3b\u89e3
+gb.preview = \u9810\u89bd
+gb.leaveComment = \u7559\u4e0b\u8a3b\u89e3
+gb.showHideDetails = \u986f\u793a/\u96b1\u85cf \u8a73\u89e3\u5167\u5bb9
+gb.acceptNewPatchsets = \u5141\u8a31\u88dc\u4e01
+gb.acceptNewPatchsetsDescription = \u63a5\u53d7\u5230\u7248\u672c\u5eab\u9032\u884c\u4fee\u88dc\u52d5\u4f5c
+gb.acceptNewTickets = \u5141\u8a31\u5efa\u7acb\u4efb\u52d9
+gb.acceptNewTicketsDescription = \u5141\u8a31\u65b0\u589e"\u81ed\u87f2","\u512a\u5316","\u4efb\u52d9"\u5404\u985e\u578b\u4efb\u52d9
+gb.requireApproval = \u9700\u6279\u51c6
+gb.requireApprovalDescription = \u5408\u4f75\u6309\u9215\u555f\u7528\u524d,\u88dc\u4e01\u5305\u5fc5\u9808\u5148\u6279\u51c6
+gb.topic = \u8a71\u984c
+gb.proposalTickets = \u63d0\u6848\u4fee\u6539
+gb.bugTickets = \u81ed\u87f2
+gb.enhancementTickets = \u512a\u5316
+gb.taskTickets = \u4efb\u52d9
+gb.questionTickets = \u63d0\u554f
+gb.requestTickets = \u512a\u5316 & \u4efb\u52d9
+gb.yourCreatedTickets = \u4f60\u65b0\u589e\u7684
+gb.yourWatchedTickets = \u4f60\u76e3\u770b\u7684
+gb.mentionsMeTickets = \u8207\u4f60\u76f8\u95dc
+gb.updatedBy = \u66f4\u65b0\u8005
+gb.sort = \u6392\u5e8f
+gb.sortNewest = \u6700\u65b0
+gb.sortOldest = \u6700\u820a
+gb.sortMostRecentlyUpdated = \u6700\u8fd1\u66f4\u65b0
+gb.sortLeastRecentlyUpdated = \u6700\u8fd1\u6700\u5c11\u8b8a\u52d5
+gb.sortMostComments = \u6700\u591a\u5099\u8a3b
+gb.sortLeastComments = \u6700\u5c11\u5099\u8a3b
+gb.sortMostPatchsetRevisions = \u6700\u591a\u88dc\u4e01\u4fee\u6b63
+gb.sortLeastPatchsetRevisions = \u6700\u5c11\u88dc\u4e01\u4fee\u6539
+gb.sortMostVotes = \u6700\u591a\u6295\u7968
+gb.sortLeastVotes = \u6700\u5c11\u6295\u7968
+gb.topicsAndLabels = \u8a71\u984c\u8207\u6a19\u8a18
+gb.milestones = milestones
+gb.noMilestoneSelected = \u672a\u9078\u53d6milestone
+gb.notSpecified = \u7121\u6307\u5b9a
+gb.due = \u622a\u6b62
+gb.queries = \u67e5\u8a62\u7d50\u679c
+gb.searchTicketsTooltip = \u627e\u5230{0}\u4efd\u4efb\u52d9
+gb.searchTickets = \u641c\u5c0b\u4efb\u52d9
+gb.new = \u5efa\u7acb
+gb.newTicket = \u65b0\u589e\u4efb\u52d9
+gb.editTicket = \u4fee\u6539\u4efb\u52d9
+gb.ticketsWelcome = \u4f60\u53ef\u4ee5\u5229\u7528\u4efb\u52d9\u7cfb\u7d71\u5efa\u69cb\u51fa\u5f85\u8fa6\u4e8b\u9805, \u81ed\u87f2\u56de\u5831\u5340\u4ee5\u53ca\u88dc\u4e01\u5305\u7684\u5354\u540c\u5408\u4f5c
+gb.createFirstTicket = \u6309\u6b64\u5efa\u7acb\u7b2c\u4e00\u500b\u4efb\u52d9
+gb.title = \u6a19\u984c
+gb.changedStatus = changed the status
+gb.discussion = \u8a0e\u8ad6
+gb.updated = \u5df2\u66f4\u65b0
+gb.proposePatchset = \u63d0\u51fa\u88dc\u4e01
+gb.proposePatchsetNote = \u6b61\u8fce\u5c0d\u6b64\u4efb\u52d9\u63d0\u4f9b\u88dc\u4e01
+gb.proposeInstructions = \u9996\u5148, \u5efa\u7acb\u88dc\u4e01\u4e26\u4e14\u540c\u6b65\u5230\u6b64gitblit\u4f3a\u670d\u5668. Gitblit \u8b93\u88dc\u4e01\u8207\u672c\u6b21\u4efb\u52d9ID(ticket ID)\u9023\u7d50.
+gb.proposeWith = \u5982\u4f55\u5728{0} \u4e0a\u5efa\u7acb\u88dc\u4e01
+gb.revisionHistory = \u4fee\u6539\u7d00\u9304
+gb.merge = \u5408\u4f75
+gb.action = \u52d5\u4f5c
+gb.patchset = \u88dc\u4e01
+gb.all = \u5168\u90e8
+gb.mergeBase = \u57fa\u672c\u5408\u4f75
+gb.checkout = \u6aa2\u51fa(checkout)
+gb.checkoutViaCommandLine = \u4f7f\u7528\u6307\u4ee4Checkout
+gb.checkoutViaCommandLineNote = \u4f60\u53ef\u4ee5\u5f9e\u4f60\u7248\u672c\u5eab\u4e2dcheckout\u4e00\u4efd,\u7136\u5f8c\u9032\u884c\u6e2c\u8a66
+gb.checkoutStep1 = Fetch the current patchset \u2014 run this from your project directory
+gb.checkoutStep2 = \u5c07\u8a72\u88dc\u4e01\u8f49\u51fa\u5230\u65b0\u7684\u5206\u652f\u5f8c\u7528\u4f86\u6aa2\u8996
+gb.mergingViaCommandLine = \u7d93\u7531\u6307\u4ee4\u57f7\u884c\u5408\u4f75
+gb.mergingViaCommandLineNote = \u5982\u679c\u4f60\u4e0d\u60f3\u8981\u4f7f\u7528\u81ea\u52d5\u5408\u4f75\u529f\u80fd,\u6216\u662f\u6309\u4e0b\u5408\u4f75\u6309\u9215, \u4f60\u53ef\u4ee5\u4e0b\u6307\u4ee4\u624b\u52d5\u5408\u4f75
+gb.mergeStep1 = Check out a new branch to review the changes \u2014 run this from your project directory
+gb.mergeStep2 = Bring in the proposed changes and review
+gb.mergeStep3 = \u5c07\u63d0\u6848\u4fee\u6539\u5167\u5bb9\u66f4\u65b0\u5230\u4f3a\u670d\u5668\u4e0a
+gb.download = \u4e0b\u8f09
+gb.ptDescription = Gitblit \u88dc\u4e01\u5de5\u5177
+gb.ptCheckout = Fetch & checkout the current patchset to a review branch
+gb.ptMerge = \u53d6\u5f97\u76ee\u524dpatchset,\u7136\u5f8c\u8207\u4f60\u672c\u6a5f\u7aef\u7684\u5206\u652f\u5408\u4f75
+gb.ptDescription1 = Barnum is a command-line companion for Git that simplifies the syntax for working with Gitblit Tickets and Patchsets.
+gb.ptSimplifiedCollaboration = simplified collaboration syntax
+gb.ptSimplifiedMerge = simplified merge syntax
+gb.ptDescription2 = Barnum requires Python 3 and native Git. It runs on Windows, Linux, and Mac OS X.
+gb.stepN = \u6b65\u9a5f{0}
+gb.watchers = \u76e3\u7763\u8005
+gb.votes = \u6295\u7968
 gb.vote = \u5c0d{0}\u6295\u7968
-gb.voters = votes
-gb.votes = votes
-gb.warning = \u8b66\u544a
 gb.watch = \u76e3\u770b{0}
-gb.watchers = \u76e3\u770b\u8005
+gb.removeVote = \u79fb\u9664\u6295\u7968
+gb.stopWatching = \u505c\u6b62\u8ffd\u8e64(watching)
 gb.watching = \u76e3\u770b\u4e2d
-gb.workingCopy = \u5de5\u4f5c\u8907\u672c
-gb.workingCopyWarning = \u8a72\u6587\u4ef6\u5eab\u4ecd\u6709\u5de5\u4f5c\u8907\u672c,\u56e0\u6b64\u7121\u6cd5\u63a5\u53d7\u63a8\u9001(push)
-gb.write = write
-gb.youDoNotHaveClonePermission = \u4f60\u4e0d\u5141\u8a31\u8907\u88fd(clone)\u6b64\u6587\u4ef6\u5eab
+gb.comments = \u8a3b\u89e3
+gb.addComment = \u65b0\u589e\u8a3b\u89e3
+gb.export = \u532f\u51fa
+gb.oneCommit = 1\u500b\u63d0\u4ea4
+gb.nCommits = {0}\u4efd\u63d0\u4ea4
+gb.addedOneCommit = \u63d0\u4ea41\u500b\u6a94\u6848
+gb.addedNCommits = {0}\u500b\u6a94\u6848\u63d0\u4ea4\u5b8c\u7562
+gb.commitsInPatchsetN = \u88dc\u4e01 {0} \u7684\u63d0\u4ea4
+gb.patchsetN = \u88dc\u4e01{0}
+gb.reviewedPatchsetRev = reviewed patchset {0} revision {1}\: {2}
+gb.review = \u6aa2\u67e5(review)
+gb.reviews = \u6aa2\u67e5(reviews)
+gb.veto = \u5426\u6c7a
+gb.needsImprovement = \u9700\u8981\u512a\u5316
+gb.looksGood = \u770b\u8d77\u4f86\u5f88\u597d
+gb.approve = \u901a\u904e
+gb.hasNotReviewed = \u5c1a\u672a\u6aa2\u6838\u904e
+gb.about = \u95dc\u65bc
+gb.ticketN = \u4efb\u52d9\u7de8\u865f#{0}
+gb.disableUser = \u505c\u7528\u5e33\u6236
+gb.disableUserDescription = \u8a72\u5e33\u6236\u7121\u6cd5\u4f7f\u7528
+gb.any = \u4efb\u4f55
+gb.milestoneProgress = {0}\u958b\u555f,{1}\u7d50\u675f
+gb.nOpenTickets = {0}\u9805\u958b\u555f\u4e2d
+gb.nClosedTickets = {0}\u9805\u7d50\u675f
+gb.nTotalTickets = \u7e3d\u5171{0}\u9805
+gb.body = \u5167\u5bb9
+gb.mergeSha = mergeSha
+gb.mergeTo = \u5408\u4f75\u5230
+gb.labels = \u6a19\u8a18
+gb.reviewers = \u5be9\u67e5\u8005
+gb.voters = votes
+gb.mentions = \u63d0\u5230
+gb.canNotProposePatchset = \u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
+gb.repositoryIsMirror = \u8a72\u7248\u672c\u5eab\u70ba\u552f\u8b80\u8907\u672c
+gb.repositoryIsFrozen = \u8a72\u7248\u672c\u5eab\u5df2\u51cd\u7d50
+gb.repositoryDoesNotAcceptPatchsets = \u8a72\u7248\u672c\u5eab\u4e0d\u63a5\u53d7\u88dc\u4e01
+gb.serverDoesNotAcceptPatchsets = \u672c\u4f3a\u670d\u5668\u4e0d\u63a5\u53d7\u88dc\u4e01
+gb.ticketIsClosed = \u8a72\u4efb\u52d9\u5df2\u7d93\u7d50\u6848
+gb.mergeToDescription = \u9810\u8a2d\u5c07\u6587\u4ef6\u76f8\u95dc\u88dc\u4e01\u5305\u8207\u6307\u5b9a\u5206\u652f(branch)\u5408\u4f75
+gb.anonymousCanNotPropose = \u533f\u540d\u8005\u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
+gb.youDoNotHaveClonePermission = \u4f60\u4e0d\u5141\u8a31\u8907\u88fd(clone)\u6b64\u7248\u672c\u5eab
+gb.myTickets = \u6211\u7684\u4efb\u52d9
 gb.yourAssignedTickets = \u6307\u6d3e\u7d66\u4f60\u7684
-gb.yourCreatedTickets = \u7531\u4f60\u65b0\u589e\u7684
-gb.yourWatchedTickets = \u4f60\u60f3\u770b\u7684
-gb.zip = zip\u58d3\u7e2e\u6a94
-gb.ticketState =
-gb.repositoryForkFailed =
-gb.anonymousUser =
-gb.oneAttachment =
-gb.viewPolicy =
-gb.emailMeOnMyTicketChangesDescription =
+gb.newMilestone = \u5efa\u7acbmilestone
+gb.editMilestone = \u4fee\u6539milestone
+gb.deleteMilestone = \u522a\u9664milestone"{0}"?
+gb.milestoneDeleteFailed = \u522a\u9664milestone"{0}"\u5931\u6557
+gb.notifyChangedOpenTickets = \u5df2\u958b\u555f\u7684\u4efb\u52d9\u6709\u7570\u52d5\u8acb\u767c\u9001\u901a\u77e5
+gb.overdue = \u904e\u671f
+gb.openMilestones = \u5df2\u958b\u555f\u7684 milestones
+gb.closedMilestones = \u5df2\u95dc\u9589\u7684 milestones
+gb.administration = \u7ba1\u7406\u6b0a\u9650
+gb.plugins = \u5957\u4ef6
+gb.extensions = \u64f4\u5145
+gb.pleaseSelectProject = \u8acb\u9078\u64c7\u5c08\u6848!
+gb.accessPolicy = \u5b58\u53d6\u653f\u7b56
+gb.accessPolicyDescription = \u9078\u64c7\u7528\u4f86\u63a7\u5236\u7248\u672c\u5eab\u7684\u5b58\u53d6\u653f\u7b56\u4ee5\u53ca\u6b0a\u9650\u8a2d\u5b9a
+gb.anonymousPolicy = \u533f\u540d\u72c0\u614b\u53ef\u4ee5\u6aa2\u8996(view),\u8907\u88fd(clone)\u8207\u63a8\u9001(push)
+gb.anonymousPolicyDescription = \u6240\u6709\u4eba\u7686\u53ef\u6aa2\u8996(view),\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u6587\u4ef6\u5230\u7248\u672c\u5eab
+gb.authenticatedPushPolicy = \u9650\u5236\u63a8\u9001(Push)(\u6388\u6b0a)
+gb.authenticatedPushPolicyDescription = \u6240\u6709\u4eba\u7686\u53ef\u6aa2\u8996\u8207\u8907\u88fd(clone).  \u4f46\u53ea\u6709\u6210\u54e1\u6709RW+\u8207\u63a8\u9001(push)\u529f\u80fd.
+gb.namedPushPolicy = \u9650\u5236\u63a8\u9001(Push)(\u6307\u5b9a\u5e33\u865f)
+gb.namedPushPolicyDescription = \u6240\u6709\u4eba\u7686\u53ef\u6aa2\u8996\u8207\u8907\u88fd(clone)\u7248\u672c\u5eab.  \u53ef\u53e6\u5916\u6307\u5b9a\u8ab0\u80fd\u5920\u6709\u63a8\u9001\u529f\u80fd(push)
+gb.clonePolicy = \u9650\u5236\u8907\u88fd(Clone)\u8207\u63a8\u9001(Push)
+gb.clonePolicyDescription = \u6240\u6709\u4eba\u7686\u53ef\u770b\u7248\u672c\u5eab. \u53ef\u53e6\u5916\u6307\u5b9a\u8ab0\u6709\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u6b0a\u9650
+gb.viewPolicy = \u9650\u5236\u6aa2\u8996(view),\u8907\u88fd(clone)\u8207\u63a8\u9001(push)
+gb.viewPolicyDescription = \u9078\u64c7\u53ef\u4ee5\u5728\u7248\u672c\u5eab\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u7684\u4f7f\u7528\u8005,  \u9664\u6b64\u4e4b\u5916\u5176\u4ed6\u4eba\u7686\u7121\u6b0a\u9650
+gb.initialCommit = \u521d\u6b21\u63d0\u4ea4
+gb.initialCommitDescription = \u4ee5\u4e0b\u6b65\u9a5f\u5c07\u99ac\u4e0a\u57f7\u884c<code>git clone</code>.\u5982\u679c\u4f60\u672c\u6a5f\u5df2\u6709\u6b64\u7248\u672c\u5eab\u4e14\u57f7\u884c\u904e<code>git init</code>,\u8acb\u8df3\u904e\u6b64\u6b65\u9a5f.
+gb.initWithReadme = \u5305\u542bREADME\u6587\u4ef6
+gb.initWithReadmeDescription = \u7248\u672c\u5eab\u5c07\u7522\u751f\u7c21\u55aeREADME\u6587\u4ef6
+gb.initWithGitignore = \u5305\u542b .gitignore \u6a94\u6848
+gb.initWithGitignoreDescription = \u65b0\u589e\u4e00\u500b\u8a2d\u5b9a\u6a94\u7528\u4f86\u6307\u5b9a\u54ea\u4e9b\u6a94\u6848\u6216\u76ee\u9304\u9700\u8981\u5ffd\u7565
+gb.pleaseSelectGitIgnore = \u8acb\u9078\u64c7\u4e00\u500b.gitignore\u6a94\u6848
+gb.receive = \u63a5\u6536
+gb.permissions = \u6b0a\u9650
+gb.ownersDescription = \u6240\u6709\u8005\u53ef\u4ee5\u7ba1\u7406\u7248\u672c\u5eab,\u4f46\u662f\u4e0d\u5141\u8a31\u4fee\u6539\u540d\u7a31(\u500b\u4eba\u7248\u672c\u5eab\u4f8b\u5916)
+gb.userPermissionsDescription = \u4f60\u53ef\u4ee5\u91dd\u5c0d\u5e33\u865f\u8a2d\u5b9a\u6b0a\u9650(\u9019\u4e9b\u8a2d\u5b9a\u5c07\u8986\u84cb\u5718\u968a\u6216\u5176\u4ed6\u6b0a\u9650)
+gb.teamPermissionsDescription = \u4f60\u53ef\u4ee5\u6307\u5b9a\u5718\u968a\u6b0a\u9650.\u9019\u4e9b\u8a2d\u5b9a\u5c07\u6703\u53d6\u4ee3\u539f\u672c\u5718\u968a\u9810\u8a2d\u6b0a\u9650
+gb.ticketSettings = \u4efb\u52d9\u5167\u5bb9\u8a2d\u5b9a
+gb.receiveSettings = \u8a2d\u5b9a\u63a5\u6536\u65b9\u5f0f
+gb.receiveSettingsDescription = \u63a7\u7ba1\u63a8\u9001\u5230\u7248\u672c\u5eab\u7684\u63a5\u6536\u65b9\u5f0f
+gb.preReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4f46\u5728\u9084\u6c92\u6709\u66f4\u65b0refs\u524d</em>, \u5c07\u6703\u57f7\u884cPre-receive hook. <p>This is the appropriate hook for rejecting a push.</p>
+gb.postReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4e26\u4e14\u5728refs\u5b8c\u7562\u5f8c</em>, \u5c07\u6703\u57f7\u884cPost-receive hook..<p>This is the appropriate hook for notifications, build triggers, etc.</p>
+gb.federationStrategyDescription = \u63a7\u5236\u5982\u4f55\u5c07\u7248\u672c\u5eab\u8207\u5176\u4ed6Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u4e32\u9023
+gb.federationSetsDescription = \u6b64\u7248\u672c\u5eab\u5c07\u5305\u542b\u65bc\u6307\u5b9a\u7684federation sets
+gb.miscellaneous = \u5176\u4ed6
+gb.originDescription = \u6b64\u7248\u672c\u5eabURL\u5df2\u7d93\u88ab\u8907\u88fd(cloned)\u4e86
+gb.gc = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u5668
+gb.garbageCollection = \u56de\u6536\u7cfb\u7d71\u8cc7\u6e90
+gb.garbageCollectionDescription = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u529f\u80fd\u5c07\u6703\u6574\u9813\u9b06\u6563\u7528\u6236\u7aef\u63a8\u9001(push)\u7684\u7269\u4ef6, \u4e5f\u6703\u79fb\u9664\u7248\u672c\u5eab\u4e0a\u7121\u7528\u7684\u7269\u4ef6
+gb.commitMessageRendererDescription = \u63d0\u4ea4\u8a0a\u606f\u53ef\u4ee5\u4f7f\u7528\u6587\u5b57\u6216\u662f\u6a19\u8a18\u8a9e\u8a00(markup)\u5448\u73fe
+gb.preferences = \u9810\u8a2d\u5e38\u7528\u503c
+gb.accountPreferences = \u5e33\u865f\u8a2d\u5b9a
+gb.accountPreferencesDescription = \u8a2d\u5b9a\u5e33\u865f\u9810\u8a2d\u503c
+gb.languagePreference = \u5e38\u7528\u8a9e\u8a00
+gb.languagePreferenceDescription = \u9078\u64c7\u8a9e\u7cfb
+gb.emailMeOnMyTicketChanges = \u4efb\u52d9\u82e5\u6709\u8b8a\u66f4,\u8acb\u7acb\u5373(email)\u901a\u77e5\u6211
+gb.emailMeOnMyTicketChangesDescription =\u6211\u8655\u7406\u904e\u7684\u4efb\u52d9\u8acbemail\u901a\u77e5\u6211
+gb.displayNameDescription = \u5e0c\u671b\u986f\u793a\u7684\u540d\u7a31
+gb.emailAddressDescription = \u7528\u4f86\u63a5\u6536\u901a\u77e5\u7684\u4e3b\u8981\u96fb\u5b50\u90f5\u4ef6
+gb.sshKeys = SSH Keys
+gb.sshKeysDescription = SSH \u516c\u958b\u91d1\u9470\u662f\u5bc6\u78bc\u8a8d\u8b49\u5916\u66f4\u5b89\u5168\u7684\u9078\u9805
+gb.addSshKey = \u65b0\u589e SSH Key
+gb.key = \u91d1\u9470
+gb.sshKeyCommentDescription = \u8acb\u8f38\u5165\u5099\u8a3b, \u82e5\u7121\u5099\u8a3b, \u5c07\u81ea\u8a02\u586b\u5165key data
+gb.sshKeyPermissionDescription = \u6307\u5b9a\u8a72SSH key\u6240\u64c1\u6709\u7684\u5b58\u53d6\u6b0a\u9650
+gb.transportPreference = \u9810\u8a2d\u901a\u8a0a\u5354\u5b9a
+gb.transportPreferenceDescription = \u8a2d\u5b9a\u4f60\u5e38\u7528\u7684\u9023\u7dda\u7528\u4f86\u8907\u88fd(clone)
+gb.blinkComparator = Blink comparator
+gb.deleteRepositoryDescription = \u7248\u672c\u5eab\u522a\u9664\u5c07\u7121\u6cd5\u9084\u539f
+gb.deleteRepositoryHeader = \u522a\u9664\u7248\u672c\u5eab
+gb.diffCopiedFile = \u6a94\u6848\u7531 {0} \u8907\u88fd
+gb.diffDeletedFile = \u6a94\u6848\u5df2\u522a\u9664
+gb.diffDeletedFileSkipped = (\u522a\u9664)
+gb.diffFileDiffTooLarge = \u6a94\u6848\u592a\u5927
+gb.diffNewFile = \u6bd4\u5c0d\u65b0\u6a94\u6848
+gb.diffRenamedFile = \u6a94\u540d\u7531 {0} \u4fee\u6539
+gb.diffTruncated = Diff truncated after the above file
+gb.ignore_whitespace =\u5ffd\u7565\u7a7a\u767d
+gb.imgdiffSubtract = Subtract (black = identical)
+gb.maintenanceTickets = \u7dad\u8b77
+gb.missingIntegrationBranchMore = \u76ee\u6a19\u5206\u652f\u4e0d\u5728\u6b64\u7248\u672c\u5eab
+gb.opacityAdjust = Adjust opacity
+gb.priority = \u512a\u5148
+gb.severity = \u91cd\u8981
+gb.show_whitespace = \u986f\u793a\u7a7a\u767d
+gb.sortHighestPriority = \u6700\u9ad8
+gb.sortHighestSeverity = \u6700\u91cd\u8981
+gb.sortLowestPriority = \u6700\u4f4e
+gb.sortLowestSeverity = \u6700\u4e0d\u91cd\u8981
+gb.ticketStatus = \u72c0\u614b
+gb.allRepositories = \u6240\u6709\u7248\u672c\u5eab
+gb.oid = \u7de8\u865f
+gb.filestore = \u5927\u6a94\u6848\u5340
+gb.filestoreStats = \u5927\u6a94\u6848\u5340(Filestore)\u5305\u542b {0} \u6a94\u6848\u5bb9\u91cf {1}.  (\u9084\u5269\u4e0b{2}\u53ef\u7528)
+gb.statusChangedOn = \u4fee\u6539\u65e5\u671f
+gb.statusChangedBy = \u4fee\u6539\u8005
+gb.filestoreHelp = \u6309\u6b64\u4e86\u89e3\u5927\u6a94\u6848\u5340(FileStore)\u5132\u5b58\u529f\u80fd
+gb.editFile = \u7de8\u8f2f\u6a94\u6848
+gb.continueEditing = \u7e7c\u7e8c\u7de8\u8f2f
+gb.commitChanges = Commit Changes
+gb.fileNotMergeable = \u7121\u6cd5\u63d0\u4ea4 {0}.  \u6a94\u6848\u7121\u6cd5\u81ea\u52d5\u5408\u4f75.
+gb.fileCommitted = \u6210\u529f\u63d0\u4ea4
+gb.deletePatchset = \u522a\u9664 Patchset {0}
+gb.deletePatchsetSuccess = \u5df2\u522a\u9664 Patchset {0}.
+gb.deletePatchsetFailure = \u522a\u9664 Patchset {0} \u932f\u8aa4.
+gb.referencedByCommit = Referenced by commit.
+gb.referencedByTicket = Referenced by ticket.
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.html b/src/main/java/com/gitblit/wicket/pages/BasePage.html
index b998428..4dbc2e5 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.html
@@ -17,6 +17,7 @@
 		<link rel="stylesheet" href="fontawesome/css/font-awesome.min.css"/>

         <link rel="stylesheet" href="octicons/octicons.css"/>

 		<link rel="stylesheet" type="text/css" href="gitblit.css"/>

+		<link rel="stylesheet" type="text/css" href="bootstrap-fixes.css"/>

 	</wicket:head>

 

 	<body>

diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
index 7a55b9f..2c881ef 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -123,7 +123,8 @@
 			<div wicket:id="acceptNewTickets"></div>

 			<div wicket:id="requireApproval"></div>

 			<div wicket:id="mergeTo"></div>

-		

+			<div wicket:id="mergeType"></div>

+

 		</div>

 				

 		<!-- federation -->

diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
index 6bcf6f5..bf3eea8 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -56,6 +56,7 @@
 import com.gitblit.Constants.AuthorizationControl;

 import com.gitblit.Constants.CommitMessageRenderer;

 import com.gitblit.Constants.FederationStrategy;

+import com.gitblit.Constants.MergeType;

 import com.gitblit.Constants.RegistrantType;

 import com.gitblit.GitBlitException;

 import com.gitblit.Keys;

@@ -458,6 +459,11 @@
 				getString("gb.mergeToDescription"),

 				new PropertyModel<String>(repositoryModel, "mergeTo"),

 				availableBranches));

+		form.add(new ChoiceOption<MergeType>("mergeType",

+				getString("gb.mergeType"),

+				getString("gb.mergeTypeDescription"),

+				new PropertyModel<MergeType>(repositoryModel, "mergeType"),

+				Arrays.asList(MergeType.values())));

 

 		//

 		// RECEIVE

diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
index 220bee3..72dee6b 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
@@ -156,7 +156,7 @@
 						}

 

 						// change the cookie

-						userModel.cookie = StringUtils.getSHA1(userModel.username + password);

+						userModel.cookie = userModel.createCookie();

 

 						// Optionally store the password MD5 digest.

 						String type = app().settings().getString(Keys.realm.passwordStorage, "md5");

diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
index cd049f4..e213396 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -1405,14 +1405,14 @@
 

 		boolean allowMerge;

 		if (repository.requireApproval) {

-			// rpeository requires approval

+			// repository requires approval

 			allowMerge = ticket.isOpen() && ticket.isApproved(patchset);

 		} else {

-			// vetos are binding

+			// vetoes are binding

 			allowMerge = ticket.isOpen() && !ticket.isVetoed(patchset);

 		}

 

-		MergeStatus mergeStatus = JGitUtils.canMerge(getRepository(), patchset.tip, ticket.mergeTo);

+		MergeStatus mergeStatus = JGitUtils.canMerge(getRepository(), patchset.tip, ticket.mergeTo, repository.mergeType);

 		if (allowMerge) {

 			if (MergeStatus.MERGEABLE == mergeStatus) {

 				// patchset can be cleanly merged to integration branch OR has already been merged

diff --git a/src/main/java/com/gitblit/wicket/panels/PagerPanel.java b/src/main/java/com/gitblit/wicket/panels/PagerPanel.java
index 2d774c4..d1214ca 100644
--- a/src/main/java/com/gitblit/wicket/panels/PagerPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/PagerPanel.java
@@ -48,7 +48,7 @@
 			deltas = new int[] { -2, -1, 0, 1, 2 };

 		}

 

-		if (totalPages > 0) {

+		if (totalPages > 0 && currentPage > 1) {

 			pages.add(new PageObject("\u2190", currentPage - 1));

 		}

 		for (int delta : deltas) {

@@ -57,7 +57,7 @@
 				pages.add(new PageObject("" + page, page));

 			}

 		}

-		if (totalPages > 0) {

+		if (totalPages > 0 && currentPage < totalPages) {

 			pages.add(new PageObject("\u2192", currentPage + 1));

 		}

 

@@ -75,6 +75,7 @@
 				item.add(link);

 				if (pageItem.page == currentPage || pageItem.page < 1 || pageItem.page > totalPages) {

 					WicketUtils.setCssClass(item, "disabled");

+					link.setEnabled(false);

 				}

 			}

 		};

diff --git a/src/main/java/welcome_zh_TW.mkd b/src/main/java/welcome_zh_TW.mkd
index eaaee65..ec10201 100644
--- a/src/main/java/welcome_zh_TW.mkd
+++ b/src/main/java/welcome_zh_TW.mkd
@@ -1,3 +1,3 @@
 ## 歡迎來到Gitblit版本控管伺服器
 
-一個快速讓您能存放自己Git文件庫的解決方案  [Git](http://www.git-scm.com) 
+一個快速讓您擁有版本控管伺服器的解決方案 : 使用[Git](http://www.git-scm.com) 
diff --git a/src/main/resources/bootstrap-fixes.css b/src/main/resources/bootstrap-fixes.css
new file mode 100644
index 0000000..c9b6154
--- /dev/null
+++ b/src/main/resources/bootstrap-fixes.css
@@ -0,0 +1,25 @@
+/**
+ * Disabled links in a PagerPanel. Bootstrap 2.0.4 only handles <a>, but not <span>. Wicket renders disabled links as spans.
+ * The .pagination rules here are identical to the ones for <a> in bootstrap.css, but for <span>.
+ */
+.pagination span {
+  float: left;
+  padding: 0 14px;
+  line-height: 34px;
+  text-decoration: none;
+  border: 1px solid #ddd;
+  border-left-width: 0;
+}
+
+.pagination li:first-child span {
+  border-left-width: 1px;
+  -webkit-border-radius: 3px 0 0 3px;
+     -moz-border-radius: 3px 0 0 3px;
+          border-radius: 3px 0 0 3px;
+}
+
+.pagination li:last-child span {
+  -webkit-border-radius: 0 3px 3px 0;
+     -moz-border-radius: 0 3px 3px 0;
+          border-radius: 0 3px 3px 0;
+}
diff --git a/src/test/java/com/gitblit/tests/MarkdownUtilsTest.java b/src/test/java/com/gitblit/tests/MarkdownUtilsTest.java
index e40f105..bc7aad4 100644
--- a/src/test/java/com/gitblit/tests/MarkdownUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/MarkdownUtilsTest.java
@@ -15,8 +15,14 @@
  */

 package com.gitblit.tests;

 

+import java.util.HashMap;

+import java.util.Map;

+

 import org.junit.Test;

 

+import com.gitblit.IStoredSettings;

+import com.gitblit.Keys;

+import com.gitblit.tests.mock.MemorySettings;

 import com.gitblit.utils.MarkdownUtils;

 

 public class MarkdownUtilsTest extends GitblitUnitTest {

@@ -39,4 +45,70 @@
 		assertEquals("<table><tr><td>&lt;test&gt;</td></tr></table>",

 				MarkdownUtils.transformMarkdown("<table><tr><td>&lt;test&gt;</td></tr></table>"));

 	}

-}
\ No newline at end of file
+

+

+	@Test

+	public void testUserMentions() {

+		IStoredSettings settings = getSettings();

+		String repositoryName = "test3";

+		String mentionHtml = "<strong><a href=\"http://localhost/user/%1$s\">@%1$s</a></strong>";

+

+		String input = "@j.doe";

+		String output = "<p>" + String.format(mentionHtml, "j.doe") + "</p>";

+		assertEquals(output, MarkdownUtils.transformGFM(settings, input, repositoryName));

+

+		input = " @j.doe";

+		output = "<p>" + String.format(mentionHtml, "j.doe") + "</p>";

+		assertEquals(output, MarkdownUtils.transformGFM(settings, input, repositoryName));

+

+		input = "@j.doe.";

+		output = "<p>" + String.format(mentionHtml, "j.doe") + ".</p>";

+		assertEquals(output, MarkdownUtils.transformGFM(settings, input, repositoryName));

+

+		input = "To @j.doe: ask @jim.beam!";

+		output = "<p>To " + String.format(mentionHtml, "j.doe")

+				+ ": ask " + String.format(mentionHtml, "jim.beam") + "!</p>";

+		assertEquals(output, MarkdownUtils.transformGFM(settings, input, repositoryName));

+

+		input =   "@sta.rt\n"

+				+ "\n"

+				+ "User mentions in tickets are broken.\n"

+				+ "So:\n"

+				+ "@mc_guyver can fix this.\n"

+				+ "@j.doe, can you test after the fix by @m+guyver?\n"

+				+ "Please review this, @jim.beam!\n"

+				+ "Was reported by @jill and @j!doe from jane@doe yesterday.\n"

+				+ "\n"

+				+ "@jack.daniels can vote for john@wayne.name hopefully.\n"

+				+ "@en.de";

+		output =  "<p>"	+ String.format(mentionHtml, "sta.rt") + "</p>"

+				+ "<p>"	+ "User mentions in tickets are broken.<br/>"

+				+ "So:<br/>"

+				+ String.format(mentionHtml, "mc_guyver") + " can fix this.<br/>"

+				+ String.format(mentionHtml, "j.doe") + ", can you test after the fix by " + String.format(mentionHtml, "m+guyver") + "?<br/>"

+				+ "Please review this, " + String.format(mentionHtml, "jim.beam") + "!<br/>"

+				+ "Was reported by " + String.format(mentionHtml, "jill")

+				+ " and " + String.format(mentionHtml, "j!doe")

+				+ " from <a href=\"mailto:&#106;a&#110;&#x65;&#x40;&#x64;&#x6f;&#101;\">&#106;a&#110;&#x65;&#x40;&#x64;&#x6f;&#101;</a> yesterday." 

+				+ "</p>"

+				+ "<p>" + String.format(mentionHtml, "jack.daniels") + " can vote for "

+				+ "<a href=\"mailto:&#x6a;&#x6f;h&#110;&#x40;&#119;a&#121;&#110;&#101;.&#110;a&#x6d;&#101;\">&#x6a;&#x6f;h&#110;&#x40;&#119;a&#121;&#110;&#101;.&#110;a&#x6d;&#101;</a> hopefully.<br/>"

+				+ String.format(mentionHtml, "en.de")

+				+ "</p>";

+		assertEquals(output, MarkdownUtils.transformGFM(settings, input, repositoryName));

+

+	}

+

+

+

+

+	private MemorySettings getSettings() {

+		Map<String, Object> backingMap = new HashMap<String, Object>();

+

+		backingMap.put(Keys.web.canonicalUrl, "http://localhost");

+		backingMap.put(Keys.web.shortCommitIdLength, "7");

+

+		MemorySettings ms = new MemorySettings(backingMap);

+		return ms;

+	}

+}

diff --git a/src/test/java/com/gitblit/utils/SecureRandomTest.java b/src/test/java/com/gitblit/utils/SecureRandomTest.java
new file mode 100644
index 0000000..c4098c2
--- /dev/null
+++ b/src/test/java/com/gitblit/utils/SecureRandomTest.java
@@ -0,0 +1,33 @@
+package com.gitblit.utils;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class SecureRandomTest {
+
+	@Test
+	public void testRandomBytes() {
+		SecureRandom sr = new SecureRandom();
+		byte[] bytes1 = sr.randomBytes(10);
+		assertEquals(10, bytes1.length);
+		byte[] bytes2 = sr.randomBytes(10);
+		assertEquals(10, bytes2.length);
+		assertFalse(Arrays.equals(bytes1, bytes2));
+
+		assertEquals(0, sr.randomBytes(0).length);
+		assertEquals(200, sr.randomBytes(200).length);
+	}
+
+	@Test
+	public void testNextBytes() {
+		SecureRandom sr = new SecureRandom();
+		byte[] bytes1 = new byte[32];
+		sr.nextBytes(bytes1);
+		byte[] bytes2 = new byte[32];
+		sr.nextBytes(bytes2);
+		assertFalse(Arrays.equals(bytes1, bytes2));
+	}
+}