www.github.com/gitblit/gitblit.git
Advanced tools
| gbsteps: &gbsteps | ||
| steps: | ||
| - run: | ||
| name: Report on build environment | ||
| command: | | ||
| java -version | ||
| javac -version | ||
| - checkout | ||
| - run: ant | ||
| - run: ant test | ||
| - run: | ||
| name: Collect test results | ||
| command: | | ||
| mkdir -p test_reports/junit/ | ||
| cp -a build/tests/TEST-*.xml test_reports/junit/ | ||
| - store_test_results: | ||
| path: test_reports | ||
| - store_artifacts: | ||
| path: build/target/reports | ||
| version: 2 | ||
| jobs: | ||
| buildJ8: | ||
| docker: | ||
| - image: circleci/openjdk:8-jdk | ||
| <<: *gbsteps | ||
| buildJ9: | ||
| docker: | ||
| - image: circleci/openjdk:9-jdk | ||
| <<: *gbsteps | ||
| buildJ11: | ||
| docker: | ||
| - image: circleci/openjdk:11-jdk | ||
| <<: *gbsteps | ||
| workflows: | ||
| version: 2 | ||
| build: | ||
| jobs: | ||
| - "buildJ8" | ||
| - "buildJ9" | ||
| - "buildJ11" |
Sorry, the diff of this file is too big to display
| name: Continous build - build and test on every push | ||
| on: | ||
| push: | ||
| branches-ignore: | ||
| - 'release*' | ||
| - gh-pages | ||
| jobs: | ||
| build: | ||
| name: Build and test | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| matrix: | ||
| os: [ubuntu-latest, windows-latest] | ||
| java-version: [8, 11] | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v1 | ||
| with: | ||
| submodules: true | ||
| - name: Setup Java ${{ matrix.java-version }} | ||
| uses: actions/setup-java@v1 | ||
| with: | ||
| java-version: ${{ matrix.java-version }} | ||
| - name: Report Java version | ||
| run: | | ||
| java -version | ||
| javac -version | ||
| - name: Build with Ant | ||
| run: ant test | ||
| build_j7: | ||
| name: Build and test on Java 7 | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v1 | ||
| with: | ||
| submodules: true | ||
| - name: Setup Java 7 | ||
| uses: actions/setup-java@v1 | ||
| with: | ||
| java-version: 7 | ||
| - name: Report Java version | ||
| run: | | ||
| java -version | ||
| javac -version | ||
| - name: Setup Moxie | ||
| run: | | ||
| wget http://gitblit.github.io/moxie/maven/com/gitblit/moxie/moxie+ant/0.9.4/moxie+ant-0.9.4.tar.gz | ||
| tar -xzf moxie+ant-0.9.4.tar.gz | ||
| moxie-0.9.4/bin/moxie -version | ||
| - name: Build with Moxie | ||
| run: moxie-0.9.4/bin/moxie test |
| dist: trusty | ||
| language: java | ||
| jdk: | ||
| - openjdk7 | ||
| - openjdk8 | ||
| - oraclejdk11 |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| /* | ||
| * 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.ldap; | ||
| import java.net.URI; | ||
| import java.net.URISyntaxException; | ||
| import java.security.GeneralSecurityException; | ||
| import java.util.List; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import com.gitblit.IStoredSettings; | ||
| import com.gitblit.Keys; | ||
| import com.gitblit.utils.StringUtils; | ||
| import com.unboundid.ldap.sdk.BindRequest; | ||
| import com.unboundid.ldap.sdk.BindResult; | ||
| import com.unboundid.ldap.sdk.DereferencePolicy; | ||
| import com.unboundid.ldap.sdk.ExtendedResult; | ||
| import com.unboundid.ldap.sdk.LDAPConnection; | ||
| import com.unboundid.ldap.sdk.LDAPException; | ||
| import com.unboundid.ldap.sdk.LDAPSearchException; | ||
| import com.unboundid.ldap.sdk.ResultCode; | ||
| import com.unboundid.ldap.sdk.SearchRequest; | ||
| import com.unboundid.ldap.sdk.SearchResult; | ||
| import com.unboundid.ldap.sdk.SearchScope; | ||
| import com.unboundid.ldap.sdk.SimpleBindRequest; | ||
| import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; | ||
| import com.unboundid.util.ssl.SSLUtil; | ||
| import com.unboundid.util.ssl.TrustAllTrustManager; | ||
| public class LdapConnection implements AutoCloseable { | ||
| private final Logger logger = LoggerFactory.getLogger(getClass()); | ||
| private IStoredSettings settings; | ||
| private LDAPConnection conn; | ||
| private SimpleBindRequest currentBindRequest; | ||
| private SimpleBindRequest managerBindRequest; | ||
| private SimpleBindRequest userBindRequest; | ||
| // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java | ||
| public static final String escapeLDAPSearchFilter(String filter) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| for (int i = 0; i < filter.length(); i++) { | ||
| char curChar = filter.charAt(i); | ||
| switch (curChar) { | ||
| case '\\': | ||
| sb.append("\\5c"); | ||
| break; | ||
| case '*': | ||
| sb.append("\\2a"); | ||
| break; | ||
| case '(': | ||
| sb.append("\\28"); | ||
| break; | ||
| case ')': | ||
| sb.append("\\29"); | ||
| break; | ||
| case '\u0000': | ||
| sb.append("\\00"); | ||
| break; | ||
| default: | ||
| sb.append(curChar); | ||
| } | ||
| } | ||
| return sb.toString(); | ||
| } | ||
| public static String getAccountBase(IStoredSettings settings) { | ||
| return settings.getString(Keys.realm.ldap.accountBase, ""); | ||
| } | ||
| public static String getAccountPattern(IStoredSettings settings) { | ||
| return settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); | ||
| } | ||
| public LdapConnection(IStoredSettings settings) { | ||
| this.settings = settings; | ||
| String bindUserName = settings.getString(Keys.realm.ldap.username, ""); | ||
| String bindPassword = settings.getString(Keys.realm.ldap.password, ""); | ||
| if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) { | ||
| this.managerBindRequest = new SimpleBindRequest(); | ||
| } | ||
| this.managerBindRequest = new SimpleBindRequest(bindUserName, bindPassword); | ||
| } | ||
| public String getAccountBase() { | ||
| return getAccountBase(settings); | ||
| } | ||
| public String getAccountPattern() { | ||
| return getAccountPattern(settings); | ||
| } | ||
| public boolean connect() { | ||
| try { | ||
| URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server)); | ||
| String ldapHost = ldapUrl.getHost(); | ||
| int ldapPort = ldapUrl.getPort(); | ||
| if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { | ||
| // SSL | ||
| SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); | ||
| conn = new LDAPConnection(sslUtil.createSSLSocketFactory()); | ||
| if (ldapPort == -1) { | ||
| ldapPort = 636; | ||
| } | ||
| } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { | ||
| // no encryption or StartTLS | ||
| conn = new LDAPConnection(); | ||
| if (ldapPort == -1) { | ||
| ldapPort = 389; | ||
| } | ||
| } else { | ||
| logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme()); | ||
| return false; | ||
| } | ||
| conn.connect(ldapHost, ldapPort); | ||
| if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { | ||
| SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); | ||
| ExtendedResult extendedResult = conn.processExtendedOperation( | ||
| new StartTLSExtendedRequest(sslUtil.createSSLContext())); | ||
| if (extendedResult.getResultCode() != ResultCode.SUCCESS) { | ||
| throw new LDAPException(extendedResult.getResultCode()); | ||
| } | ||
| } | ||
| return true; | ||
| } catch (URISyntaxException e) { | ||
| logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e); | ||
| } catch (GeneralSecurityException e) { | ||
| logger.error("Unable to create SSL Connection", e); | ||
| } catch (LDAPException e) { | ||
| logger.error("Error Connecting to LDAP", e); | ||
| } | ||
| return false; | ||
| } | ||
| public void close() { | ||
| if (conn != null) { | ||
| conn.close(); | ||
| } | ||
| } | ||
| /** | ||
| * Bind using the manager credentials set in realm.ldap.username and ..password | ||
| * @return A bind result, or null if binding failed. | ||
| */ | ||
| public BindResult bind() { | ||
| BindResult result = null; | ||
| try { | ||
| result = conn.bind(managerBindRequest); | ||
| currentBindRequest = managerBindRequest; | ||
| } catch (LDAPException e) { | ||
| logger.error("Error authenticating to LDAP with manager account to search the directory."); | ||
| logger.error(" Please check your settings for realm.ldap.username and realm.ldap.password."); | ||
| logger.debug(" Received exception when binding to LDAP", e); | ||
| return null; | ||
| } | ||
| return result; | ||
| } | ||
| /** | ||
| * Bind using the given credentials, by filling in the username in the given {@code bindPattern} to | ||
| * create the DN. | ||
| * @return A bind result, or null if binding failed. | ||
| */ | ||
| public BindResult bind(String bindPattern, String simpleUsername, String password) { | ||
| BindResult result = null; | ||
| try { | ||
| String bindUser = StringUtils.replace(bindPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); | ||
| SimpleBindRequest request = new SimpleBindRequest(bindUser, password); | ||
| result = conn.bind(request); | ||
| userBindRequest = request; | ||
| currentBindRequest = userBindRequest; | ||
| } catch (LDAPException e) { | ||
| logger.error("Error authenticating to LDAP with user account to search the directory."); | ||
| logger.error(" Please check your settings for realm.ldap.bindpattern."); | ||
| logger.debug(" Received exception when binding to LDAP", e); | ||
| return null; | ||
| } | ||
| return result; | ||
| } | ||
| public boolean rebindAsUser() { | ||
| if (userBindRequest == null || currentBindRequest == userBindRequest) { | ||
| return false; | ||
| } | ||
| try { | ||
| conn.bind(userBindRequest); | ||
| currentBindRequest = userBindRequest; | ||
| } catch (LDAPException e) { | ||
| conn.close(); | ||
| logger.error("Error rebinding to LDAP with user account.", e); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| public boolean isAuthenticated(String userDn, String password) { | ||
| verifyCurrentBinding(); | ||
| // If the currently bound DN is already the DN of the logging in user, authentication has already happened | ||
| // during the previous bind operation. We accept this and return with the current bind left in place. | ||
| // This could also be changed to always retry binding as the logging in user, to make sure that the | ||
| // connection binding has not been tampered with in between. So far I see no way how this could happen | ||
| // and thus skip the repeated binding. | ||
| // This check also makes sure that the DN in realm.ldap.bindpattern actually matches the DN that was found | ||
| // when searching the user entry. | ||
| String boundDN = currentBindRequest.getBindDN(); | ||
| if (boundDN != null && boundDN.equals(userDn)) { | ||
| return true; | ||
| } | ||
| // Bind a the logging in user to check for authentication. | ||
| // Afterwards, bind as the original bound DN again, to restore the previous authorization. | ||
| boolean isAuthenticated = false; | ||
| try { | ||
| // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN | ||
| SimpleBindRequest ubr = new SimpleBindRequest(userDn, password); | ||
| conn.bind(ubr); | ||
| isAuthenticated = true; | ||
| userBindRequest = ubr; | ||
| } catch (LDAPException e) { | ||
| logger.error("Error authenticating user ({})", userDn, e); | ||
| } | ||
| try { | ||
| conn.bind(currentBindRequest); | ||
| } catch (LDAPException e) { | ||
| logger.error("Error reinstating original LDAP authorization (code {}). Team information may be inaccurate for this log in.", | ||
| e.getResultCode(), e); | ||
| } | ||
| return isAuthenticated; | ||
| } | ||
| public SearchResult search(SearchRequest request) { | ||
| try { | ||
| return conn.search(request); | ||
| } catch (LDAPSearchException e) { | ||
| logger.error("Problem Searching LDAP [{}]", e.getResultCode()); | ||
| return e.getSearchResult(); | ||
| } | ||
| } | ||
| public SearchResult search(String base, boolean dereferenceAliases, String filter, List<String> attributes) { | ||
| try { | ||
| SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter); | ||
| if (dereferenceAliases) { | ||
| searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING); | ||
| } | ||
| if (attributes != null) { | ||
| searchRequest.setAttributes(attributes); | ||
| } | ||
| SearchResult result = search(searchRequest); | ||
| return result; | ||
| } catch (LDAPException e) { | ||
| logger.error("Problem creating LDAP search", e); | ||
| return null; | ||
| } | ||
| } | ||
| public SearchResult searchUser(String username, List<String> attributes) { | ||
| String accountPattern = getAccountPattern(); | ||
| accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(username)); | ||
| return search(getAccountBase(), false, accountPattern, attributes); | ||
| } | ||
| public SearchResult searchUser(String username) { | ||
| return searchUser(username, null); | ||
| } | ||
| private boolean verifyCurrentBinding() { | ||
| BindRequest lastBind = conn.getLastBindRequest(); | ||
| if (lastBind == currentBindRequest) { | ||
| return true; | ||
| } | ||
| logger.debug("Unexpected binding in LdapConnection. {} != {}", lastBind, currentBindRequest); | ||
| String lastBoundDN = ((SimpleBindRequest)lastBind).getBindDN(); | ||
| String boundDN = currentBindRequest.getBindDN(); | ||
| logger.debug("Currently bound as '{}', check authentication for '{}'", lastBoundDN, boundDN); | ||
| if (boundDN != null && ! boundDN.equals(lastBoundDN)) { | ||
| logger.warn("Unexpected binding DN in LdapConnection. '{}' != '{}'.", lastBoundDN, boundDN); | ||
| logger.warn("Updated binding information in LDAP connection."); | ||
| currentBindRequest = (SimpleBindRequest)lastBind; | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| } |
| package com.gitblit.models; | ||
| import java.io.Serializable; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import com.gitblit.utils.StringUtils; | ||
| public class TreeNodeModel implements Serializable, Comparable<TreeNodeModel> { | ||
| private static final long serialVersionUID = 1L; | ||
| final TreeNodeModel parent; | ||
| final String name; | ||
| final List<TreeNodeModel> subFolders = new ArrayList<>(); | ||
| final List<RepositoryModel> repositories = new ArrayList<>(); | ||
| /** | ||
| * Create a new tree root | ||
| */ | ||
| public TreeNodeModel() { | ||
| this.name = "/"; | ||
| this.parent = null; | ||
| } | ||
| protected TreeNodeModel(String name, TreeNodeModel parent) { | ||
| this.name = name; | ||
| this.parent = parent; | ||
| } | ||
| public int getDepth() { | ||
| if(parent == null) { | ||
| return 0; | ||
| }else { | ||
| return parent.getDepth() +1; | ||
| } | ||
| } | ||
| /** | ||
| * Add a new sub folder to the current folder | ||
| * | ||
| * @param subFolder the subFolder to create | ||
| * @return the newly created folder to allow chaining | ||
| */ | ||
| public TreeNodeModel add(String subFolder) { | ||
| TreeNodeModel n = new TreeNodeModel(subFolder, this); | ||
| subFolders.add(n); | ||
| Collections.sort(subFolders); | ||
| return n; | ||
| } | ||
| /** | ||
| * Add the given repo to the current folder | ||
| * | ||
| * @param repo | ||
| */ | ||
| public void add(RepositoryModel repo) { | ||
| repositories.add(repo); | ||
| Collections.sort(repositories); | ||
| } | ||
| /** | ||
| * Adds the given repository model within the given path. Creates parent folders if they do not exist | ||
| * | ||
| * @param path | ||
| * @param model | ||
| */ | ||
| public void add(String path, RepositoryModel model) { | ||
| TreeNodeModel folder = getSubTreeNode(this, path, true); | ||
| folder.add(model); | ||
| } | ||
| @Override | ||
| public String toString() { | ||
| String string = name + "\n"; | ||
| for(TreeNodeModel n : subFolders) { | ||
| string += "f"; | ||
| for(int i = 0; i < n.getDepth(); i++) { | ||
| string += "-"; | ||
| } | ||
| string += n.toString(); | ||
| } | ||
| for(RepositoryModel n : repositories) { | ||
| string += "r"; | ||
| for(int i = 0; i < getDepth()+1; i++) { | ||
| string += "-"; | ||
| } | ||
| string += n.toString() + "\n"; | ||
| } | ||
| return string; | ||
| } | ||
| public boolean containsSubFolder(String path) { | ||
| return containsSubFolder(this, path); | ||
| } | ||
| public TreeNodeModel getSubFolder(String path) { | ||
| return getSubTreeNode(this, path, false); | ||
| } | ||
| public List<Serializable> getTreeAsListForFrontend(){ | ||
| List<Serializable> l = new ArrayList<>(); | ||
| getTreeAsListForFrontend(l, this); | ||
| return l; | ||
| } | ||
| private static void getTreeAsListForFrontend(List<Serializable> list, TreeNodeModel node) { | ||
| list.add(node); | ||
| for(TreeNodeModel t : node.subFolders) { | ||
| getTreeAsListForFrontend(list, t); | ||
| } | ||
| for(RepositoryModel r : node.repositories) { | ||
| list.add(r); | ||
| } | ||
| } | ||
| private static TreeNodeModel getSubTreeNode(TreeNodeModel node, String path, boolean create) { | ||
| if(!StringUtils.isEmpty(path)) { | ||
| boolean isPathInCurrentHierarchyLevel = path.lastIndexOf('/') < 0; | ||
| if(isPathInCurrentHierarchyLevel) { | ||
| for(TreeNodeModel t : node.subFolders) { | ||
| if(t.name.equals(path) ) { | ||
| return t; | ||
| } | ||
| } | ||
| if(create) { | ||
| node.add(path); | ||
| return getSubTreeNode(node, path, true); | ||
| } | ||
| }else { | ||
| //traverse into subFolder | ||
| String folderInCurrentHierarchyLevel = StringUtils.getFirstPathElement(path); | ||
| for(TreeNodeModel t : node.subFolders) { | ||
| if(t.name.equals(folderInCurrentHierarchyLevel) ) { | ||
| String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length()); | ||
| return getSubTreeNode(t, folderInNextHierarchyLevel, create); | ||
| } | ||
| } | ||
| if(create) { | ||
| String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length()); | ||
| return getSubTreeNode(node.add(folderInCurrentHierarchyLevel), folderInNextHierarchyLevel, true); | ||
| } | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| private static boolean containsSubFolder(TreeNodeModel node, String path) { | ||
| return getSubTreeNode(node, path, false) != null; | ||
| } | ||
| @Override | ||
| public int compareTo(TreeNodeModel t) { | ||
| return StringUtils.compareRepositoryNames(name, t.name); | ||
| } | ||
| public TreeNodeModel getParent() { | ||
| return parent; | ||
| } | ||
| public String getName() { | ||
| return name; | ||
| } | ||
| public List<TreeNodeModel> getSubFolders() { | ||
| return subFolders; | ||
| } | ||
| public List<RepositoryModel> getRepositories() { | ||
| return repositories; | ||
| } | ||
| } |
| /* | ||
| * Copyright 2017 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.service; | ||
| import java.io.File; | ||
| import com.gitblit.utils.LuceneIndexStore; | ||
| /** | ||
| * @author Florian Zschocke | ||
| * | ||
| * @since 1.9.0 | ||
| */ | ||
| class LuceneRepoIndexStore extends LuceneIndexStore | ||
| { | ||
| private static final String LUCENE_DIR = "lucene"; | ||
| private static final String CONF_FILE = "gb_lucene.conf"; | ||
| /** | ||
| * @param repositoryFolder | ||
| * The directory of the repository for this index | ||
| * @param indexVersion | ||
| * Version of the index definition | ||
| */ | ||
| public LuceneRepoIndexStore(File repositoryFolder, int indexVersion) { | ||
| super(new File(repositoryFolder, LUCENE_DIR), indexVersion); | ||
| } | ||
| /** | ||
| * Get the index config File. | ||
| * | ||
| * @return The index config File | ||
| */ | ||
| public File getConfigFile() { | ||
| return new File(this.indexFolder, CONF_FILE); | ||
| } | ||
| } |
| /* | ||
| * 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.transport.ssh; | ||
| import java.io.IOException; | ||
| import java.security.GeneralSecurityException; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.TreeMap; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
| import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; | ||
| import org.apache.sshd.common.config.keys.KeyUtils; | ||
| import org.apache.sshd.common.util.GenericUtils; | ||
| import com.gitblit.IStoredSettings; | ||
| import com.gitblit.Keys; | ||
| import com.gitblit.Constants.AccessPermission; | ||
| import com.gitblit.ldap.LdapConnection; | ||
| import com.gitblit.models.UserModel; | ||
| import com.gitblit.utils.StringUtils; | ||
| import com.google.common.base.Joiner; | ||
| import com.google.inject.Inject; | ||
| import com.unboundid.ldap.sdk.BindResult; | ||
| import com.unboundid.ldap.sdk.ResultCode; | ||
| import com.unboundid.ldap.sdk.SearchResult; | ||
| import com.unboundid.ldap.sdk.SearchResultEntry; | ||
| /** | ||
| * LDAP-only public key manager | ||
| * | ||
| * Retrieves public keys from user's LDAP entries. Using this key manager, | ||
| * no SSH keys can be edited, i.e. added, removed, permissions changed, etc. | ||
| * | ||
| * This key manager supports SSH key entries in LDAP of the following form: | ||
| * [<prefix>:] [<options>] <type> <key> [<comment>] | ||
| * This follows the required form of entries in the authenticated_keys file, | ||
| * with an additional optional prefix. Key entries must have a key type | ||
| * (like "ssh-rsa") and a key, and may have a comment at the end. | ||
| * | ||
| * An entry may specify login options as specified for the authorized_keys file. | ||
| * The 'environment' option may be used to set the permissions for the key | ||
| * by setting a 'gbPerm' environment variable. The key manager will interpret | ||
| * such a environment variable option and use the set permission string to set | ||
| * the permission on the key in Gitblit. Example: | ||
| * environment="gbPerm=V",pty ssh-rsa AAAxjka.....dv= Clone only key | ||
| * Above entry would create a RSA key with the comment "Clone only key" and | ||
| * set the key permission to CLONE. All other options are ignored. | ||
| * | ||
| * In Active Directory SSH public keys are sometimes stored in the attribute | ||
| * 'altSecurityIdentity'. The attribute value is usually prefixed by a type | ||
| * identifier. LDAP entries could have the following attribute values: | ||
| * altSecurityIdentity: X.509: ADKEJBAKDBZUPABBD... | ||
| * altSecurityIdentity: SshKey: ssh-dsa AAAAknenazuzucbhda... | ||
| * This key manager supports this by allowing an optional prefix to identify | ||
| * SSH keys. The prefix to be used should be set in the 'realm.ldap.sshPublicKey' | ||
| * setting by separating it from the attribute name with a colon, e.g.: | ||
| * realm.ldap.sshPublicKey = altSecurityIdentity:SshKey | ||
| * | ||
| * @author Florian Zschocke | ||
| * | ||
| */ | ||
| public class LdapKeyManager extends IPublicKeyManager { | ||
| /** | ||
| * Pattern to find prefixes like 'SSHKey:' in key entries. | ||
| * These prefixes describe the type of an altSecurityIdentity. | ||
| * The pattern accepts anything but quote and colon up to the | ||
| * first colon at the start of a string. | ||
| */ | ||
| private static final Pattern PREFIX_PATTERN = Pattern.compile("^([^\":]+):"); | ||
| /** | ||
| * Pattern to find the string describing Gitblit permissions for a SSH key. | ||
| * The pattern matches on a string starting with 'gbPerm', matched case-insensitive, | ||
| * followed by '=' with optional whitespace around it, followed by a string of | ||
| * upper and lower case letters and '+' and '-' for the permission, which can optionally | ||
| * be enclosed in '"' or '\"' (only the leading quote is matched in the pattern). | ||
| * Only the group describing the permission is a capturing group. | ||
| */ | ||
| private static final Pattern GB_PERM_PATTERN = Pattern.compile("(?i:gbPerm)\\s*=\\s*(?:\\\\\"|\")?\\s*([A-Za-z+-]+)"); | ||
| private final IStoredSettings settings; | ||
| @Inject | ||
| public LdapKeyManager(IStoredSettings settings) { | ||
| this.settings = settings; | ||
| } | ||
| @Override | ||
| public String toString() { | ||
| return getClass().getSimpleName(); | ||
| } | ||
| @Override | ||
| public LdapKeyManager start() { | ||
| log.info(toString()); | ||
| return this; | ||
| } | ||
| @Override | ||
| public boolean isReady() { | ||
| return true; | ||
| } | ||
| @Override | ||
| public LdapKeyManager stop() { | ||
| return this; | ||
| } | ||
| @Override | ||
| protected boolean isStale(String username) { | ||
| // always return true so we gets keys from LDAP every time | ||
| return true; | ||
| } | ||
| @Override | ||
| protected List<SshKey> getKeysImpl(String username) { | ||
| try (LdapConnection conn = new LdapConnection(settings)) { | ||
| if (conn.connect()) { | ||
| log.info("loading ssh key for {} from LDAP directory", username); | ||
| BindResult bindResult = conn.bind(); | ||
| if (bindResult == null) { | ||
| conn.close(); | ||
| return null; | ||
| } | ||
| // Search the user entity | ||
| // Support prefixing the key data, e.g. when using altSecurityIdentities in AD. | ||
| String pubKeyAttribute = settings.getString(Keys.realm.ldap.sshPublicKey, "sshPublicKey"); | ||
| String pkaPrefix = null; | ||
| int idx = pubKeyAttribute.indexOf(':'); | ||
| if (idx > 0) { | ||
| pkaPrefix = pubKeyAttribute.substring(idx +1); | ||
| pubKeyAttribute = pubKeyAttribute.substring(0, idx); | ||
| } | ||
| SearchResult result = conn.searchUser(getSimpleUsername(username), Arrays.asList(pubKeyAttribute)); | ||
| conn.close(); | ||
| if (result != null && result.getResultCode() == ResultCode.SUCCESS) { | ||
| if ( result.getEntryCount() > 1) { | ||
| log.info("Found more than one entry for user {} in LDAP. Cannot retrieve SSH key.", username); | ||
| return null; | ||
| } else if ( result.getEntryCount() < 1) { | ||
| log.info("Found no entry for user {} in LDAP. Cannot retrieve SSH key.", username); | ||
| return null; | ||
| } | ||
| // Retrieve the SSH key attributes | ||
| SearchResultEntry foundUser = result.getSearchEntries().get(0); | ||
| String[] attrs = foundUser.getAttributeValues(pubKeyAttribute); | ||
| if (attrs == null ||attrs.length == 0) { | ||
| log.info("found no keys for user {} under attribute {} in directory", username, pubKeyAttribute); | ||
| return null; | ||
| } | ||
| // Filter resulting list to match with required special prefix in entry | ||
| List<GbAuthorizedKeyEntry> authorizedKeys = new ArrayList<>(attrs.length); | ||
| Matcher m = PREFIX_PATTERN.matcher(""); | ||
| for (int i = 0; i < attrs.length; ++i) { | ||
| // strip out line breaks | ||
| String keyEntry = Joiner.on("").join(attrs[i].replace("\r\n", "\n").split("\n")); | ||
| m.reset(keyEntry); | ||
| try { | ||
| if (m.lookingAt()) { // Key is prefixed in LDAP | ||
| if (pkaPrefix == null) { | ||
| continue; | ||
| } | ||
| String prefix = m.group(1).trim(); | ||
| if (! pkaPrefix.equalsIgnoreCase(prefix)) { | ||
| continue; | ||
| } | ||
| String s = keyEntry.substring(m.end()); // Strip prefix off | ||
| authorizedKeys.add(GbAuthorizedKeyEntry.parseAuthorizedKeyEntry(s)); | ||
| } else { // Key is not prefixed in LDAP | ||
| if (pkaPrefix != null) { | ||
| continue; | ||
| } | ||
| String s = keyEntry; // Strip prefix off | ||
| authorizedKeys.add(GbAuthorizedKeyEntry.parseAuthorizedKeyEntry(s)); | ||
| } | ||
| } catch (IllegalArgumentException e) { | ||
| log.info("Failed to parse key entry={}:", keyEntry, e.getMessage()); | ||
| } | ||
| } | ||
| List<SshKey> keyList = new ArrayList<>(authorizedKeys.size()); | ||
| for (GbAuthorizedKeyEntry keyEntry : authorizedKeys) { | ||
| try { | ||
| SshKey key = new SshKey(keyEntry.resolvePublicKey(null)); | ||
| key.setComment(keyEntry.getComment()); | ||
| setKeyPermissions(key, keyEntry); | ||
| keyList.add(key); | ||
| } catch (GeneralSecurityException | IOException e) { | ||
| log.warn("Error resolving key entry for user {}. Entry={}", username, keyEntry, e); | ||
| } | ||
| } | ||
| return keyList; | ||
| } | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| @Override | ||
| public boolean addKey(String username, SshKey key) { | ||
| return false; | ||
| } | ||
| @Override | ||
| public boolean removeKey(String username, SshKey key) { | ||
| return false; | ||
| } | ||
| @Override | ||
| public boolean removeAllKeys(String username) { | ||
| return false; | ||
| } | ||
| public boolean supportsWritingKeys(UserModel user) { | ||
| return false; | ||
| } | ||
| public boolean supportsCommentChanges(UserModel user) { | ||
| return false; | ||
| } | ||
| public boolean supportsPermissionChanges(UserModel user) { | ||
| return false; | ||
| } | ||
| private void setKeyPermissions(SshKey key, GbAuthorizedKeyEntry keyEntry) { | ||
| List<String> env = keyEntry.getLoginOptionValues("environment"); | ||
| if (env != null && !env.isEmpty()) { | ||
| // Walk over all entries and find one that sets 'gbPerm'. The last one wins. | ||
| for (String envi : env) { | ||
| Matcher m = GB_PERM_PATTERN.matcher(envi); | ||
| if (m.find()) { | ||
| String perm = m.group(1).trim(); | ||
| AccessPermission ap = AccessPermission.fromCode(perm); | ||
| if (ap == AccessPermission.NONE) { | ||
| ap = AccessPermission.valueOf(perm.toUpperCase()); | ||
| } | ||
| if (ap != null && ap != AccessPermission.NONE) { | ||
| try { | ||
| key.setPermission(ap); | ||
| } catch (IllegalArgumentException e) { | ||
| log.warn("Incorrect permissions ({}) set for SSH key entry {}.", ap, envi, e); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Returns a simple username without any domain prefixes. | ||
| * | ||
| * @param username | ||
| * @return a simple username | ||
| */ | ||
| private String getSimpleUsername(String username) { | ||
| int lastSlash = username.lastIndexOf('\\'); | ||
| if (lastSlash > -1) { | ||
| username = username.substring(lastSlash + 1); | ||
| } | ||
| return username; | ||
| } | ||
| /** | ||
| * Extension of the AuthorizedKeyEntry from Mina SSHD with better option parsing. | ||
| * | ||
| * The class makes use of code from the two methods copied from the original | ||
| * Mina SSHD AuthorizedKeyEntry class. The code is rewritten to improve user login | ||
| * option support. Options are correctly parsed even if they have whitespace within | ||
| * double quotes. Options can occur multiple times, which is needed for example for | ||
| * the "environment" option. Thus for an option a list of strings is kept, holding | ||
| * multiple option values. | ||
| */ | ||
| private static class GbAuthorizedKeyEntry extends AuthorizedKeyEntry { | ||
| private static final long serialVersionUID = 1L; | ||
| /** | ||
| * Pattern to extract the first part of the key entry without whitespace or only with quoted whitespace. | ||
| * The pattern essentially splits the line in two parts with two capturing groups. All other groups | ||
| * in the pattern are non-capturing. The first part is a continuous string that only includes double quoted | ||
| * whitespace and ends in whitespace. The second part is the rest of the line. | ||
| * The first part is at the beginning of the line, the lead-in. For a SSH key entry this can either be | ||
| * login options (see authorized keys file description) or the key type. Since options, other than the | ||
| * key type, can include whitespace and escaped double quotes within double quotes, the pattern takes | ||
| * care of that by searching for either "characters that are not whitespace and not double quotes" | ||
| * or "a double quote, followed by 'characters that are not a double quote or backslash, or a backslash | ||
| * and then a double quote, or a backslash', followed by a double quote". | ||
| */ | ||
| private static final Pattern LEADIN_PATTERN = Pattern.compile("^((?:[^\\s\"]*|(?:\"(?:[^\"\\\\]|\\\\\"|\\\\)*\"))*\\s+)(.+)"); | ||
| /** | ||
| * Pattern to split a comma separated list of options. | ||
| * Since an option could contain commas (as well as escaped double quotes) within double quotes | ||
| * in the option value, a simple split on comma is not enough. So the pattern searches for multiple | ||
| * occurrences of: | ||
| * characters that are not double quotes or a comma, or | ||
| * a double quote followed by: characters that are not a double quote or backslash, or | ||
| * a backslash and then a double quote, or | ||
| * a backslash, | ||
| * followed by a double quote. | ||
| */ | ||
| private static final Pattern OPTION_PATTERN = Pattern.compile("([^\",]+|(?:\"(?:[^\"\\\\]|\\\\\"|\\\\)*\"))+"); | ||
| // for options that have no value, "true" is used | ||
| private Map<String, List<String>> loginOptionsMulti = Collections.emptyMap(); | ||
| List<String> getLoginOptionValues(String option) { | ||
| return loginOptionsMulti.get(option); | ||
| } | ||
| /** | ||
| * @param line Original line from an <code>authorized_keys</code> file | ||
| * @return {@link GbAuthorizedKeyEntry} or {@code null} if the line is | ||
| * {@code null}/empty or a comment line | ||
| * @throws IllegalArgumentException If failed to parse/decode the line | ||
| * @see #COMMENT_CHAR | ||
| */ | ||
| public static GbAuthorizedKeyEntry parseAuthorizedKeyEntry(String line) throws IllegalArgumentException { | ||
| line = GenericUtils.trimToEmpty(line); | ||
| if (StringUtils.isEmpty(line) || (line.charAt(0) == COMMENT_CHAR) /* comment ? */) { | ||
| return null; | ||
| } | ||
| Matcher m = LEADIN_PATTERN.matcher(line); | ||
| if (! m.lookingAt()) { | ||
| throw new IllegalArgumentException("Bad format (no key data delimiter): " + line); | ||
| } | ||
| String keyType = m.group(1).trim(); | ||
| final GbAuthorizedKeyEntry entry; | ||
| if (KeyUtils.getPublicKeyEntryDecoder(keyType) == null) { // assume this is due to the fact that it starts with login options | ||
| entry = parseAuthorizedKeyEntry(m.group(2)); | ||
| if (entry == null) { | ||
| throw new IllegalArgumentException("Bad format (no key data after login options): " + line); | ||
| } | ||
| entry.parseAndSetLoginOptions(keyType); | ||
| } else { | ||
| int startPos = line.indexOf(' '); | ||
| if (startPos <= 0) { | ||
| throw new IllegalArgumentException("Bad format (no key data delimiter): " + line); | ||
| } | ||
| int endPos = line.indexOf(' ', startPos + 1); | ||
| if (endPos <= startPos) { | ||
| endPos = line.length(); | ||
| } | ||
| String encData = (endPos < (line.length() - 1)) ? line.substring(0, endPos).trim() : line; | ||
| String comment = (endPos < (line.length() - 1)) ? line.substring(endPos + 1).trim() : null; | ||
| entry = parsePublicKeyEntry(new GbAuthorizedKeyEntry(), encData); | ||
| entry.setComment(comment); | ||
| } | ||
| return entry; | ||
| } | ||
| private void parseAndSetLoginOptions(String options) { | ||
| Matcher m = OPTION_PATTERN.matcher(options); | ||
| if (! m.find()) { | ||
| loginOptionsMulti = Collections.emptyMap(); | ||
| } | ||
| Map<String, List<String>> optsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); | ||
| do { | ||
| String p = m.group(); | ||
| p = GenericUtils.trimToEmpty(p); | ||
| if (StringUtils.isEmpty(p)) { | ||
| continue; | ||
| } | ||
| int pos = p.indexOf('='); | ||
| String name = (pos < 0) ? p : GenericUtils.trimToEmpty(p.substring(0, pos)); | ||
| CharSequence value = (pos < 0) ? null : GenericUtils.trimToEmpty(p.substring(pos + 1)); | ||
| value = GenericUtils.stripQuotes(value); | ||
| // For options without value the value is set to TRUE. | ||
| if (value == null) { | ||
| value = Boolean.TRUE.toString(); | ||
| } | ||
| List<String> opts = optsMap.get(name); | ||
| if (opts == null) { | ||
| opts = new ArrayList<String>(); | ||
| optsMap.put(name, opts); | ||
| } | ||
| opts.add(value.toString()); | ||
| } while(m.find()); | ||
| loginOptionsMulti = optsMap; | ||
| } | ||
| } | ||
| } |
| /* | ||
| * Copyright 2017 gitblit.com. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.gitblit.utils; | ||
| import java.io.File; | ||
| import java.nio.file.Path; | ||
| /** | ||
| * @author Florian Zschocke | ||
| * | ||
| * @since 1.9.0 | ||
| */ | ||
| public class LuceneIndexStore | ||
| { | ||
| public static final int LUCENE_CODEC_VERSION = 54; | ||
| protected File indexFolder; | ||
| /** | ||
| * Constructor for a base folder that contains the version specific index folders | ||
| * and an index version. | ||
| * | ||
| * @param luceneFolder | ||
| * Path to the base folder for the Lucene indices, i.e. the common "lucene" directory. | ||
| * @param indexVersion | ||
| * Version of the index definition | ||
| */ | ||
| public LuceneIndexStore(File luceneFolder, int indexVersion) | ||
| { | ||
| this.indexFolder = new File(luceneFolder, indexVersion + "_" + LUCENE_CODEC_VERSION); | ||
| } | ||
| /** | ||
| * Create the Lucene index directory for this index version and Lucene codec version | ||
| */ | ||
| public void create() | ||
| { | ||
| if (! indexFolder.exists()) { | ||
| indexFolder.mkdirs(); | ||
| } | ||
| } | ||
| /** | ||
| * Delete the Lucene index directory for this index version and Lucene codec version | ||
| * | ||
| * @return True if the directory could successfully be deleted. | ||
| */ | ||
| public boolean delete() | ||
| { | ||
| if (indexFolder.exists()) { | ||
| return FileUtils.delete(indexFolder); | ||
| } | ||
| return true; | ||
| } | ||
| /** | ||
| * @return The Path to the index folder | ||
| */ | ||
| public Path getPath() | ||
| { | ||
| return indexFolder.toPath(); | ||
| } | ||
| /** | ||
| * Check if an index of the respective version, or compatible, already exists. | ||
| * | ||
| * @return True if an index exists, False otherwise | ||
| */ | ||
| public boolean hasIndex() | ||
| { | ||
| return indexFolder.exists() && | ||
| indexFolder.isDirectory() && | ||
| (indexFolder.list().length > 1); // Must have more than 'write.lock' | ||
| } | ||
| } |
| /* | ||
| * Copyright 2017 gitblit.com. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.gitblit.utils; | ||
| import java.util.Arrays; | ||
| /** | ||
| * This is the superclass for classes responsible for handling password hashing. | ||
| * | ||
| * It provides a factory-like interface to create an instance of a class that | ||
| * is responsible for the mechanics of a specific password hashing method. | ||
| * It also provides the common interface, leaving implementation specifics | ||
| * to subclasses of itself, which are the factory products. | ||
| * | ||
| * @author Florian Zschocke | ||
| * @since 1.9.0 | ||
| */ | ||
| public abstract class PasswordHash { | ||
| /** | ||
| * The types of implemented password hashing schemes. | ||
| */ | ||
| public enum Type { | ||
| MD5, | ||
| CMD5, | ||
| PBKDF2; | ||
| static Type fromName(String name) { | ||
| if (name == null) return null; | ||
| for (Type type : Type.values()) { | ||
| if (type.name().equalsIgnoreCase(name)) return type; | ||
| } | ||
| // Compatibility with type id "PBKDF2WITHHMACSHA256", which is also handled by PBKDF2 type. | ||
| if (name.equalsIgnoreCase("PBKDF2WITHHMACSHA256")) return Type.PBKDF2; | ||
| // Recognise the name used for CMD5 in the settings file. | ||
| if (name.equalsIgnoreCase("combined-md5")) return Type.CMD5; | ||
| return null; | ||
| } | ||
| } | ||
| /** | ||
| * The hashing scheme type handled by an instance of a subclass | ||
| */ | ||
| final Type type; | ||
| /** | ||
| * Constructor for subclasses to initialize the final type field. | ||
| * @param type | ||
| * Type of hashing scheme implemented by this instance. | ||
| */ | ||
| PasswordHash(Type type) { | ||
| this.type = type; | ||
| } | ||
| /** | ||
| * When no hash type is specified, this determines the default that should be used. | ||
| */ | ||
| public static Type getDefaultType() { | ||
| return Type.PBKDF2; | ||
| } | ||
| /** | ||
| * Create an instance of a password hashing class for the given hash type. | ||
| * | ||
| * @param type | ||
| * Type of hash to be used. | ||
| * @return A class that can calculate the given hash type and verify a user password, | ||
| * or null if the given hash type is not a valid one. | ||
| */ | ||
| public static PasswordHash instanceOf(String type) { | ||
| Type hashType = Type.fromName(type); | ||
| if (hashType == null) return null; | ||
| switch (hashType) { | ||
| case MD5: | ||
| return new PasswordHashMD5(); | ||
| case CMD5: | ||
| return new PasswordHashCombinedMD5(); | ||
| case PBKDF2: | ||
| return new PasswordHashPbkdf2(); | ||
| default: | ||
| return null; | ||
| } | ||
| } | ||
| /** | ||
| * Create an instance of a password hashing class of the correct type for a given | ||
| * hashed password from the user password table. The stored hashed password needs | ||
| * to be prefixed with the hash type identifier. | ||
| * | ||
| * @param hashedEntry | ||
| * Hashed password string from the user table. | ||
| * @return | ||
| * A class that can calculate the given hash type and verify a user password, | ||
| * or null if no instance can be created for the hashed user password. | ||
| */ | ||
| public static PasswordHash instanceFor(String hashedEntry) { | ||
| Type type = getEntryType(hashedEntry); | ||
| if (type != null) return instanceOf(type.name()); | ||
| return null; | ||
| } | ||
| /** | ||
| * Test if a given string is a hashed password entry. This method simply checks if the | ||
| * given string is prefixed by a known hash type identifier. | ||
| * | ||
| * @param storedPassword | ||
| * A stored user password. | ||
| * @return True if the given string is detected to be hashed with a known hash type, | ||
| * false otherwise. | ||
| */ | ||
| public static boolean isHashedEntry(String storedPassword) { | ||
| return null != getEntryType(storedPassword); | ||
| } | ||
| /** | ||
| * Some hash methods are considered more secure than others. This method determines for a certain type | ||
| * of password hash if it is inferior than a given other type and stored passwords should be | ||
| * upgraded to the given hashing method. | ||
| * | ||
| * @param algorithm | ||
| * Password hashing type to be checked if it is superior than the one of this instance. | ||
| * @return True, if the given type in parameter {@code algorithm} is better and stored passwords should be upgraded to it, | ||
| * false, otehrwise. | ||
| */ | ||
| public boolean needsUpgradeTo(String algorithm) { | ||
| Type hashType = Type.fromName(algorithm); | ||
| if (hashType == null) return false; | ||
| if (this.type == hashType) return false; | ||
| // Right now we keep it really simple. With the existing types, only PBKDF2 is considered secure, | ||
| // everything else is inferior. This will need to be updated once more secure hashing algorithms | ||
| // are implemented, or the workload/parameters of the PBKDF2 are changed. | ||
| return hashType == Type.PBKDF2; | ||
| } | ||
| /** | ||
| * Convert the given password to a hashed password entry to be stored in the user table. | ||
| * The resulting string is prefixed by the hashing scheme type followed by a colon: | ||
| * TYPE:theactualhashinhex | ||
| * | ||
| * @param password | ||
| * Password to be hashed. | ||
| * @param username | ||
| * User name, only used for the Combined-MD5 (user+MD5) hashing type. | ||
| * @return | ||
| * Hashed password entry to be stored in the user table. | ||
| */ | ||
| abstract public String toHashedEntry(char[] password, String username); | ||
| /** | ||
| * Convert the given password to a hashed password entry to be stored in the user table. | ||
| * The resulting string is prefixed by the hashing scheme type followed by a colon: | ||
| * TYPE:theactualhashinhex | ||
| * | ||
| * @param password | ||
| * Password to be hashed. | ||
| * @param username | ||
| * User name, only used for the Combined-MD5 (user+MD5) hashing type. | ||
| * @return | ||
| * Hashed password entry to be stored in the user table. | ||
| */ | ||
| public String toHashedEntry(String password, String username) { | ||
| if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | ||
| return toHashedEntry(password.toCharArray(), username); | ||
| } | ||
| /** | ||
| * Test if a given password (and user name) match a hashed password. | ||
| * The instance of the password hash class has to be created with | ||
| * {code instanceFor}, so that it matches the type of the hashed password | ||
| * entry to test against. | ||
| * | ||
| * | ||
| * @param hashedEntry | ||
| * The hashed password entry from the user password table. | ||
| * @param password | ||
| * Clear text password to test against the hashed one. | ||
| * @param username | ||
| * User name, needed for the MD5+USER hash type. | ||
| * @return True, if the password (and username) match the hashed password, | ||
| * false, otherwise. | ||
| */ | ||
| public boolean matches(String hashedEntry, char[] password, String username) { | ||
| if (hashedEntry == null || type != PasswordHash.getEntryType(hashedEntry)) return false; | ||
| if (password == null) return false; | ||
| String hashed = toHashedEntry(password, username); | ||
| Arrays.fill(password, Character.MIN_VALUE); | ||
| return hashed.equalsIgnoreCase(hashedEntry); | ||
| } | ||
| static Type getEntryType(String hashedEntry) { | ||
| if (hashedEntry == null) return null; | ||
| int indexOfSeparator = hashedEntry.indexOf(':'); | ||
| if (indexOfSeparator <= 0) return null; | ||
| String typeId = hashedEntry.substring(0, indexOfSeparator); | ||
| return Type.fromName(typeId); | ||
| } | ||
| static String getEntryValue(String hashedEntry) { | ||
| if (hashedEntry == null) return null; | ||
| int indexOfSeparator = hashedEntry.indexOf(':'); | ||
| return hashedEntry.substring(indexOfSeparator +1); | ||
| } | ||
| /************************************** Implementations *************************************************/ | ||
| private static class PasswordHashMD5 extends PasswordHash | ||
| { | ||
| PasswordHashMD5() { | ||
| super(Type.MD5); | ||
| } | ||
| // To keep the handling identical to how it was before, and therefore not risk invalidating stored passwords, | ||
| // for MD5 the (String,String) variant of the method is the one implementing the hashing. | ||
| @Override | ||
| public String toHashedEntry(char[] password, String username) { | ||
| if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | ||
| return toHashedEntry(new String(password), username); | ||
| } | ||
| @Override | ||
| public String toHashedEntry(String password, String username) { | ||
| if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | ||
| return type.name() + ":" | ||
| + StringUtils.getMD5(password); | ||
| } | ||
| } | ||
| private static class PasswordHashCombinedMD5 extends PasswordHash | ||
| { | ||
| PasswordHashCombinedMD5() { | ||
| super(Type.CMD5); | ||
| } | ||
| // To keep the handling identical to how it was before, and therefore not risk invalidating stored passwords, | ||
| // for Combined-MD5 the (String,String) variant of the method is the one implementing the hashing. | ||
| @Override | ||
| public String toHashedEntry(char[] password, String username) { | ||
| if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | ||
| return toHashedEntry(new String(password), username); | ||
| } | ||
| @Override | ||
| public String toHashedEntry(String password, String username) { | ||
| if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | ||
| if (username == null) throw new IllegalArgumentException("The username argument may not be null when hashing a password with Combined-MD5."); | ||
| if (StringUtils.isEmpty(username)) throw new IllegalArgumentException("The username argument may not be empty when hashing a password with Combined-MD5."); | ||
| return type.name() + ":" | ||
| + StringUtils.getMD5(username.toLowerCase() + password); | ||
| } | ||
| @Override | ||
| public boolean matches(String hashedEntry, char[] password, String username) { | ||
| if (username == null || StringUtils.isEmpty(username)) return false; | ||
| return super.matches(hashedEntry, password, username); | ||
| } | ||
| } | ||
| } |
| package com.gitblit.utils; | ||
| import org.apache.commons.codec.DecoderException; | ||
| import org.apache.commons.codec.binary.Hex; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import javax.crypto.SecretKeyFactory; | ||
| import javax.crypto.spec.PBEKeySpec; | ||
| import java.security.NoSuchAlgorithmException; | ||
| import java.security.spec.InvalidKeySpecException; | ||
| import java.util.Arrays; | ||
| /** | ||
| * The class PasswordHashPbkdf2 implements password hashing and validation | ||
| * with PBKDF2 | ||
| * | ||
| * It uses the concept proposed by OWASP - Hashing Java: | ||
| * https://www.owasp.org/index.php/Hashing_Java | ||
| */ | ||
| class PasswordHashPbkdf2 extends PasswordHash | ||
| { | ||
| private static final Logger LOGGER = LoggerFactory.getLogger(PasswordHashPbkdf2.class); | ||
| /** | ||
| * The PBKDF has some parameters that define security and workload. | ||
| * The Configuration class keeps these parameters. | ||
| */ | ||
| private static class Configuration | ||
| { | ||
| private final String algorithm; | ||
| private final int iterations; | ||
| private final int keyLen; | ||
| private final int saltLen; | ||
| private Configuration(String algorithm, int iterations, int keyLen, int saltLen) { | ||
| this.algorithm = algorithm; | ||
| this.iterations = iterations; | ||
| this.keyLen = keyLen; | ||
| this.saltLen = saltLen; | ||
| } | ||
| } | ||
| private static final SecureRandom RANDOM = new SecureRandom(); | ||
| /** | ||
| * A list of Configurations is created to list the configurations supported by | ||
| * this implementation. The configuration id is stored in the hashed entry, | ||
| * identifying the Configuration in this array. | ||
| * When adding a new variant with different values for these parameters, add | ||
| * it to this array. | ||
| * The code uses the last configuration in the array as the most secure, to be used | ||
| * when creating new hashes when no configuration is specified. | ||
| */ | ||
| private static final Configuration[] configurations = { | ||
| // Configuration 0, also default when none is specified in the stored hashed entry. | ||
| new Configuration("PBKDF2WithHmacSHA256", 10000, 256, 32) | ||
| }; | ||
| PasswordHashPbkdf2() { | ||
| super(Type.PBKDF2); | ||
| } | ||
| /* | ||
| * We return a hashed entry, where the hash part (salt+hash) itself is prefixed | ||
| * again by the configuration id of the configuration that was used for the PBKDF, | ||
| * enclosed in '$': | ||
| * PBKDF2:$0$thesaltThehash | ||
| */ | ||
| @Override | ||
| public String toHashedEntry(char[] password, String username) { | ||
| if (password == null) { | ||
| LOGGER.warn("The password argument may not be null when hashing a password."); | ||
| throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | ||
| } | ||
| int configId = getLatestConfigurationId(); | ||
| Configuration config = configurations[configId]; | ||
| byte[] salt = new byte[config.saltLen]; | ||
| RANDOM.nextBytes(salt); | ||
| byte[] hash = hash(password, salt, config); | ||
| return type.name() + ":" | ||
| + "$" + configId + "$" | ||
| + StringUtils.toHex(salt) | ||
| + StringUtils.toHex(hash); | ||
| } | ||
| @Override | ||
| public boolean matches(String hashedEntry, char[] password, String username) { | ||
| if (hashedEntry == null || type != PasswordHash.getEntryType(hashedEntry)) return false; | ||
| if (password == null) return false; | ||
| String hashedPart = getEntryValue(hashedEntry); | ||
| int configId = getConfigIdFromStoredPassword(hashedPart); | ||
| return isPasswordCorrect(password, hashedPart, configurations[configId]); | ||
| } | ||
| /** | ||
| * Return the id of the most updated configuration of parameters for the PBKDF. | ||
| * New password hashes should be generated with this one. | ||
| * | ||
| * @return An index into the configurations array for the latest configuration. | ||
| */ | ||
| private int getLatestConfigurationId() { | ||
| return configurations.length-1; | ||
| } | ||
| /** | ||
| * Get the configuration id from the stored hashed password, that was used when the | ||
| * hash was created. The configuration id is the index into the configuration array, | ||
| * and is stored in the format $Id$ after the type identifier: TYPE:$Id$.... | ||
| * If there is no identifier in the stored entry, id 0 is used, to keep backward | ||
| * compatibility. | ||
| * If an id is found that is not in the range of the declared configurations, | ||
| * 0 is returned. This may fail password validation. As of now there is only one | ||
| * configuration and even if there were more, chances are slim that anything else | ||
| * was used. So we try at least the first one instead of failing with an exception | ||
| * as the probability of success is high enough to save the user from a bad experience | ||
| * and to risk some hassle for the admin finding out in the logs why a login failed, | ||
| * when it does. | ||
| * | ||
| * @param hashPart | ||
| * The hash part of the stored entry, i.e. the part after the TYPE: | ||
| * @return The configuration id, or | ||
| * 0 if none was found. | ||
| */ | ||
| private static int getConfigIdFromStoredPassword(String hashPart) { | ||
| String[] parts = hashPart.split("\\$", 3); | ||
| // If there are not two parts, there is no '$'-enclosed part and we have no configuration information stored. | ||
| // Return default 0. | ||
| if (parts.length <= 2) return 0; | ||
| // The first string wil be empty. Even if it isn't we ignore it because it doesn't contain our information. | ||
| try { | ||
| int configId = Integer.parseInt(parts[1]); | ||
| if (configId < 0 || configId >= configurations.length) { | ||
| LOGGER.warn("A user table password entry contains a configuration id that is not valid: {}." + | ||
| "Assuming PBKDF configuration 0. This may fail to validate the password.", configId); | ||
| return 0; | ||
| } | ||
| return configId; | ||
| } | ||
| catch (NumberFormatException e) { | ||
| LOGGER.warn("A user table password entry contains a configuration id that is not a parsable number ({}${}$...)." + | ||
| "Assuming PBKDF configuration 0. This may fail to validate the password.", parts[0], parts[1], e); | ||
| return 0; | ||
| } | ||
| } | ||
| /** | ||
| * Hash. | ||
| * | ||
| * @param password | ||
| * the password | ||
| * @param salt | ||
| * the salt | ||
| * @param config | ||
| * Parameter configuration to use for the PBKDF | ||
| * @return Hashed result | ||
| */ | ||
| private static byte[] hash(char[] password, byte[] salt, Configuration config) { | ||
| PBEKeySpec spec = new PBEKeySpec(password, salt, config.iterations, config.keyLen); | ||
| Arrays.fill(password, Character.MIN_VALUE); | ||
| try { | ||
| SecretKeyFactory skf = SecretKeyFactory.getInstance(config.algorithm); | ||
| return skf.generateSecret(spec).getEncoded(); | ||
| } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { | ||
| LOGGER.warn("Error while hashing password.", e); | ||
| throw new IllegalStateException("Error while hashing password", e); | ||
| } finally { | ||
| spec.clearPassword(); | ||
| } | ||
| } | ||
| /** | ||
| * Checks if is password correct. | ||
| * | ||
| * @param passwordToCheck | ||
| * the password to check | ||
| * @param salt | ||
| * the salt | ||
| * @param expectedHash | ||
| * the expected hash | ||
| * @return true, if is password correct | ||
| */ | ||
| private static boolean isPasswordCorrect(char[] passwordToCheck, byte[] salt, byte[] expectedHash, Configuration config) { | ||
| byte[] hashToCheck = hash(passwordToCheck, salt, config); | ||
| Arrays.fill(passwordToCheck, Character.MIN_VALUE); | ||
| if (hashToCheck.length != expectedHash.length) { | ||
| return false; | ||
| } | ||
| for (int i = 0; i < hashToCheck.length; i++) { | ||
| if (hashToCheck[i] != expectedHash[i]) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| /** | ||
| * Gets the salt from stored password. | ||
| * | ||
| * @param storedPassword | ||
| * the stored password | ||
| * @return the salt from stored password | ||
| */ | ||
| private static byte[] getSaltFromStoredPassword(String storedPassword, Configuration config) { | ||
| byte[] pw = getStoredHashWithStrippedPrefix(storedPassword); | ||
| return Arrays.copyOfRange(pw, 0, config.saltLen); | ||
| } | ||
| /** | ||
| * Gets the hash from stored password. | ||
| * | ||
| * @param storedPassword | ||
| * the stored password | ||
| * @return the hash from stored password | ||
| */ | ||
| private static byte[] getHashFromStoredPassword(String storedPassword, Configuration config) { | ||
| byte[] pw = getStoredHashWithStrippedPrefix(storedPassword); | ||
| return Arrays.copyOfRange(pw, config.saltLen, pw.length); | ||
| } | ||
| /** | ||
| * Strips the configuration id prefix ($Id$) from a stored | ||
| * password and returns the decoded hash | ||
| * | ||
| * @param storedPassword | ||
| * the stored password | ||
| * @return the stored hash with stripped prefix | ||
| */ | ||
| private static byte[] getStoredHashWithStrippedPrefix(String storedPassword) { | ||
| String[] strings = storedPassword.split("\\$", 3); | ||
| String saltAndHash = strings[strings.length -1]; | ||
| try { | ||
| return Hex.decodeHex(saltAndHash.toCharArray()); | ||
| } catch (DecoderException e) { | ||
| LOGGER.warn("Failed to decode stored password entry from hex to string.", e); | ||
| throw new IllegalStateException("Error while reading stored credentials", e); | ||
| } | ||
| } | ||
| /** | ||
| * Checks if password is correct. | ||
| * | ||
| * @param password | ||
| * the password to validate | ||
| * @param storedPassword | ||
| * the stored password, i.e. the password entry value, without the leading TYPE: | ||
| * @return true, if password is correct, false otherwise | ||
| */ | ||
| private static boolean isPasswordCorrect(char[] password, String storedPassword, Configuration config) { | ||
| byte[] storedSalt = getSaltFromStoredPassword(storedPassword, config); | ||
| byte[] storedHash = getHashFromStoredPassword(storedPassword, config); | ||
| return isPasswordCorrect(password, storedSalt, storedHash, config); | ||
| } | ||
| } |
| /* | ||
| * 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(); | ||
| } | ||
| } |
| gb.zip=zip | ||
| gb.repository=repozit\u00e1\u0159 | ||
| gb.owner=vlastn\u00edk | ||
| gb.description=popis | ||
| gb.lastChange=posledn\u00ed zm\u011bna | ||
| gb.author=autor | ||
| gb.age=st\u00e1\u0159\u00ed | ||
| gb.tree=strom | ||
| gb.parent=rodi\u010d | ||
| gb.url=URL | ||
| gb.history=historie | ||
| gb.object=objekt | ||
| gb.ticketAssigned=p\u0159i\u0159azen | ||
| gb.ticketStatus=status | ||
| gb.ticketComments=koment\u00e1\u0159e | ||
| gb.local=lok\u00e1ln\u00ed | ||
| gb.branches=v\u011btve | ||
| gb.patch=patch | ||
| gb.diff=diff | ||
| gb.allTags=v\u0161echny tagy... | ||
| gb.allBranches=v\u0161echny v\u011btve... | ||
| gb.moreLogs=v\u0161echny commity... | ||
| gb.summary=souhrn | ||
| gb.newRepository=nov\u00fd repozit\u00e1\u0159 | ||
| gb.newUser=nov\u00fd u\u017eivatel | ||
| gb.pageFirst=prvn\u00ed | ||
| gb.pagePrevious=p\u0159edchoz\u00ed | ||
| gb.pageNext=dal\u0161\u00ed | ||
| gb.head=HEAD | ||
| gb.login=p\u0159ihl\u00e1sit | ||
| gb.logout=odhl\u00e1sit | ||
| gb.username=u\u017eivatelsk\u00e9 jm\u00e9no | ||
| gb.password=heslo | ||
| gb.search=hledat | ||
| gb.modification=zmena | ||
| gb.rename=p\u0159ejmenov\u00e1n\u00ed | ||
| gb.stats=statistiky | ||
| gb.tagger=taguj\u00edc\u00ed | ||
| gb.moreHistory=v\u00edce historie... | ||
| gb.changedFiles=zm\u011bn\u011bn\u00e9 soubory | ||
| gb.filesAdded={0} soubr\u016f p\u0159id\u00e1no | ||
| gb.filesModified={0} soubr\u016f zm\u011bn\u011bno | ||
| gb.filesDeleted={0} soubr\u016f smaz\u00e1no | ||
| gb.filesCopied={0} soubr\u016f zkopirov\u00e1no | ||
| gb.filesRenamed={0} soubr\u016f p\u0159ejmenov\u00e1no | ||
| gb.edit=upravit | ||
| gb.searchTooltip=Hledej {0} | ||
| gb.delete=smazat | ||
| gb.docs=dokumentace | ||
| gb.accessRestriction=Omezen\u00ed p\u0159\u00edstupu | ||
| gb.name=n\u00e1zev | ||
| gb.enableDocs=Povolit dokumentaci | ||
| gb.save=ulo\u017eit | ||
| gb.isFrozen=je zmra\u017een\u00fd | ||
| gb.cancel=zru\u0161it | ||
| gb.changePassword=zm\u011bnit heslo | ||
| gb.repositories=repozit\u00e1\u0159e | ||
| gb.frequency=frekvence | ||
| gb.folder=slo\u017eka | ||
| gb.lastPull=posledn\u00ed pull | ||
| gb.nextPull=n\u00e1sleduj\u00edc\u00ed pull | ||
| gb.registration=registrace | ||
| gb.origin=origin | ||
| gb.headRef=v\u00fdchoz\u00ed v\u011btev (HEAD) | ||
| gb.oid=id objektu | ||
| gb.editFile=upravit soubor | ||
| gb.continueEditing=Pokra\u010dovat v uprav\u00e1ch | ||
| gb.allRepositories=V\u0161echny repozit\u00e1\u0159e | ||
| gb.ignore_whitespace=ignorovat b\u00edle znaky | ||
| gb.show_whitespace=uk\u00e1zat b\u00edl\u00e9 znaky | ||
| gb.diffDeletedFileSkipped=(smazan\u00fd) | ||
| gb.comment=Koment\u00e1\u0159 | ||
| gb.tag=tag | ||
| gb.tags=tagy | ||
| gb.commit=commit | ||
| gb.committer=commituj\u00edc\u00ed | ||
| gb.log=z\u00e1znam | ||
| gb.ticket=\u00fakol | ||
| gb.commitdiff=commitdiff | ||
| gb.tickets=\u00fakoly | ||
| gb.difftocurrent=porovnej se sou\u010dasn\u00fdm | ||
| gb.metrics=metriky | ||
| gb.markdown=markdown | ||
| gb.missingUsername=Chyb\u00fd u\u017eivatelsk\u00e9 jm\u00e9no | ||
| gb.searchTypeTooltip=Vyberte typ hled\u00e1n\u00ed | ||
| gb.enableTickets=povolit \u00fakoly | ||
| gb.showRemoteBranches=uk\u00e1zat vzd\u00e1len\u00e9 v\u011btve | ||
| gb.editUsers=upravit u\u017eivatele | ||
| gb.confirmPassword=potvrdit heslo | ||
| gb.restrictedRepositories=omezen\u00fd repozit\u00e1\u0159 | ||
| gb.canAdmin=m\u016f\u017ee administrovat | ||
| gb.notRestricted=anonymn\u00ed prohl\u00ed\u017een\u00ed, clone a push | ||
| gb.view=prohl\u00ed\u017een\u00ed | ||
| gb.remote=vzd\u00e1len\u00fd | ||
| gb.deletion=vym\u00e1z\u00e1n\u00ed | ||
| gb.pushRestricted=ov\u011b\u0159en\u00fd push | ||
| gb.cloneRestricted=ov\u011b\u0159en\u00fd clone a push | ||
| gb.viewRestricted=ov\u011b\u0159en\u00e9 prohl\u00ed\u017een\u00ed, clone a push | ||
| gb.useDocsDescription=vyp\u00ed\u0161e Markdown dokumentaci v repozit\u00e1\u0159i | ||
| gb.federatedRepositoryDefinitions=definice repozit\u00e1\u0159\u016f | ||
| gb.federatedUserDefinitions=definice u\u017eivatel\u016f | ||
| gb.federatedSettingDefinitions=definice nastaven\u00ed | ||
| gb.type=typ | ||
| gb.inclusions=zahrnut\u00e9 | ||
| gb.exclusions=vylou\u010den\u00e9 | ||
| gb.sendProposal=navrhnout | ||
| gb.message=zpr\u00e1va | ||
| gb.destinationUrl=odeslat do | ||
| gb.destinationUrlDescription=URL Gitblit instance kam poslat n\u00e1vrh | ||
| gb.users=u\u017eivatel\u00e9 | ||
| gb.federation=federace | ||
| gb.error=chyba | ||
| gb.refresh=obnovit | ||
| gb.browse=proch\u00e1zet | ||
| gb.clone=klonovat | ||
| gb.filter=filtrovat | ||
| gb.create=vytvo\u0159it | ||
| gb.recent=ned\u00e1vn\u00e9 | ||
| gb.available=dostupn\u00e9 | ||
| gb.selected=vybran\u00e9 | ||
| gb.size=velikost | ||
| gb.downloading=stahov\u00e1n\u00ed | ||
| gb.loading=nahr\u00e1v\u00e1n\u00ed | ||
| gb.starting=spou\u0161t\u011bn\u00ed | ||
| gb.general=obecn\u00e9 | ||
| gb.settings=nastaven\u00ed | ||
| gb.manage=spravovat | ||
| gb.lastLogin=posledn\u00ed p\u0159ihl\u00e1\u0161en\u00ed | ||
| gb.skipSizeCalculation=p\u0159esko\u010dit v\u00fdpo\u010det velikosti | ||
| gb.default=v\u00fdchoz\u00ed | ||
| gb.setDefault=nastavit v\u00fdchoz\u00ed | ||
| gb.since=od | ||
| gb.status=status | ||
| gb.servletContainer=kontejner servletu | ||
| gb.version=verze | ||
| gb.releaseDate=datum vyd\u00e1n\u00ed | ||
| gb.date=datum | ||
| gb.activity=aktivita | ||
| gb.subscribe=p\u0159ihl\u00e1sit | ||
| gb.branch=v\u011btev | ||
| gb.recentActivity=posledn\u00ed aktivita | ||
| gb.commits=zm\u011bny | ||
| gb.teams=t\u00fdmy | ||
| gb.teamName=jm\u00e9no t\u00fdmu | ||
| gb.teamMembers=\u010dlenov\u00e9 t\u00fdmu | ||
| gb.teamMemberships=\u010dlenstv\u00ed v t\u00fdmu | ||
| gb.newTeam=nov\u00fd t\u00fdm | ||
| gb.permittedTeams=povolen\u00e9 t\u00fdmy | ||
| gb.emptyRepository=pr\u00e1zdn\u00fd repozit\u00e1\u0159 | ||
| gb.repositoryUrl=URL repozit\u00e1\u0159e | ||
| gb.filters=filtry | ||
| gb.generalDescription=obecn\u00e1 nastaven\u00ed | ||
| gb.pages=str\u00e1nky | ||
| gb.workingCopy=pracovn\u00ed kopie | ||
| gb.empty=pr\u00e1zdn\u00fd | ||
| gb.deleteRepository=Smazat repozit\u00e1\u0159 "{0}"? | ||
| gb.repositoryDeleted=Repozit\u00e1\u0159 ''{0}'' smaz\u00e1no. | ||
| gb.repositoryDeleteFailed=Smaz\u00e1n\u00ed repozit\u00e1\u0159e ''{0}'' selhalo! | ||
| gb.deleteUser=Smazat u\u017eivatele "{0}"? | ||
| gb.userDeleted=U\u017eivatel ''{0}'' smaz\u00e1n. | ||
| gb.userDeleteFailed=Chyba pri maz\u00e1n\u00ed u\u017eivatele ''{0}''! | ||
| gb.time.justNow=nyn\u00ed | ||
| gb.time.today=dnes | ||
| gb.time.yesterday=v\u010dera | ||
| gb.time.hoursAgo=p\u0159ed {0} hodinami | ||
| gb.time.minsAgo=p\u0159ed {0} minutami | ||
| gb.time.daysAgo=p\u0159ed {0} dny | ||
| gb.time.weeksAgo=p\u0159ed {0} t\u00fddny | ||
| gb.time.monthsAgo=p\u0159ed {0} m\u011bs\u00edci | ||
| gb.time.oneYearAgo=p\u0159ed jedn\u00edm rokem | ||
| gb.time.yearsAgo=p\u0159ed {0} roky | ||
| gb.duration.oneDay=1 den | ||
| gb.duration.days={0} dn\u00ed | ||
| gb.duration.oneMonth=1 m\u011bs\u00edc | ||
| gb.duration.months={0} m\u011bs\u00edc\u016f | ||
| gb.duration.oneYear=1 rok | ||
| gb.duration.years={0} let | ||
| gb.projects=projekty | ||
| gb.project=projekt | ||
| gb.allProjects=v\u0161echny projetky | ||
| gb.copyToClipboard=zkop\u00edrovat do schr\u00e1nky | ||
| gb.add=p\u0159idat | ||
| gb.noPermission=SMAZAT TOTO OPR\u00c1VN\u011aN\u00cd | ||
| gb.viewPermission={0} (zobrazit) | ||
| gb.clonePermission={0} (klonovat) | ||
| gb.pushPermission={0} (push) | ||
| gb.createPermission={0} (push, vytv\u00e1\u0159en\u00ed ref) | ||
| gb.deletePermission={0} (push, vytv\u00e1\u0159en\u00ed+maz\u00e1n\u00ed ref) | ||
| gb.rewindPermission={0} (push, vytv\u00e1\u0159en\u00ed+maz\u00e1n\u00ed+rewind ref) | ||
| gb.regexPermission=toto opr\u00e1vn\u011bn\u00ed je nastaveno z regul\u00e1rn\u00edho v\u00fdrazu "{0}" | ||
| gb.accessDenied=p\u0159\u00edstup odep\u0159en | ||
| gb.administrator=admin | ||
| gb.administratorPermission=administr\u00e1tor Gitblitu | ||
| gb.team=t\u00fdm | ||
| gb.missing=chyb\u00ed! | ||
| gb.effective=efektivn\u00ed | ||
| gb.properties=vlastnosti | ||
| gb.serialNumber=s\u00e9riov\u00e9 \u010d\u00edslo | ||
| gb.certificates=certifik\u00e1ty | ||
| gb.newCertificate=nov\u00fd certifik\u00e1t | ||
| gb.revokeCertificate=zru\u0161it certifik\u00e1t | ||
| gb.sendEmail=odeslat e-mail | ||
| gb.passwordHint=n\u00e1pov\u011bda hesla | ||
| gb.ok=ok | ||
| gb.subject=p\u0159edm\u011bt | ||
| gb.issuer=vydavatel | ||
| gb.validFrom=platn\u00fd od | ||
| gb.validUntil=platn\u00fd do | ||
| gb.publicKey=ve\u0159ejn\u00fd kl\u00ed\u010d | ||
| gb.unspecified=nespecifikov\u00e1no | ||
| gb.oneCommitTo=1 commit do | ||
| gb.noMilestoneSelected=mezn\u00edk nevybr\u00e1n | ||
| gb.veto=vetovat | ||
| gb.illegalCharacterRepositoryName=Neplatn\u00fd znak ''{0}'' ve jm\u00e9n\u011b repozit\u00e1\u0159e. | ||
| gb.errorAdminLoginRequired=Administrace vy\u017eaduje p\u0159ihl\u00e1\u0161en\u00ed | ||
| gb.compare=porovnat | ||
| gb.nClosedTickets={0} zav\u0159eno | ||
| gb.canCreate=m\u016f\u017ee vytv\u00e1\u0159et | ||
| gb.leaveComment=zanechat koment\u00e1\u0159... | ||
| gb.sort=se\u0159adit | ||
| gb.deletedBranch=smazat v\u011btev | ||
| gb.export=exportovat | ||
| gb.myRepositories=moje repozit\u00e1\u0159e | ||
| gb.md5FingerPrint=MD5 otisk | ||
| gb.watching=sledovat | ||
| gb.sha1FingerPrint=SHA1 otisk | ||
| gb.clearCache=vymazat cache | ||
| gb.warning=varov\u00e1n\u00ed | ||
| gb.discussion=diskuze | ||
| gb.oneCommit=jeden commit | ||
| gb.duration=doba trv\u00e1n\u00ed | ||
| gb.watchers=sleduj\u00edc\u00ed | ||
| gb.addedNCommits=p\u0159id\u00e1no {0} commit\u016f | ||
| gb.expires=vypr\u0161\u00ed | ||
| gb.diffNewFile=Nov\u00fd soubor | ||
| gb.revisionHistory=historie reviz\u00ed | ||
| gb.initWithReadme=P\u0159idat README | ||
| gb.editMilestone=upravit mezn\u00edk | ||
| gb.signatureAlgorithm=algoritmus podpisu | ||
| gb.overview=p\u0159ehled | ||
| gb.expired=vypr\u0161el | ||
| gb.dailyActivity=denn\u00ed aktivita | ||
| gb.validity=platnost | ||
| gb.query=dotaz | ||
| gb.ticketState=Stav \u00fakolu | ||
| gb.to=do | ||
| gb.newTicket=nov\u00fd \u00fakol | ||
| gb.download=st\u00e1hnout | ||
| gb.issued=vydan\u00fd | ||
| gb.owners=vlastn\u00edci | ||
| gb.closed=uzav\u0159en\u00fd | ||
| gb.bugTickets=chyba | ||
| gb.write=ps\u00e1t | ||
| gb.receive=p\u0159ijmout | ||
| gb.updated=aktualizov\u00e1no | ||
| gb.voters=hlasuj\u00edc\u00ed | ||
| gb.at=v | ||
| gb.noSelectedRepositoriesWarning=pros\u00edm vyberte jeden nebo v\u00edce repozit\u00e1\u0159\u016f! | ||
| gb.ticketN=\u00fakol #{0 | ||
| gb.time.inHours=b\u011bhem {0} hodin | ||
| gb.sortNewest=nejnov\u011bj\u0161\u00ed | ||
| gb.deleteMilestone=Smazat mezn\u00edk "{0}"? | ||
| gb.emailAddressDescription=Prim\u00e1rn\u00ed e-mailov\u00e1 adresa pro p\u0159\u00edjem upozorn\u011bn\u00ed | ||
| gb.line=linka | ||
| gb.lastNDays=posledn\u00edch {0} dn\u00ed | ||
| gb.sortMostRecentlyUpdated=ned\u00e1vno aktualizov\u00e1no | ||
| gb.displayName=zobrazovan\u00fd n\u00e1zev | ||
| gb.administration=administrace | ||
| gb.priority=priorita | ||
| gb.overdue=zpo\u017ed\u011bn\u00fd | ||
| gb.any=libovoln\u00fd | ||
| gb.indexedBranches=Indexovan\u00e9 v\u011btve | ||
| gb.diffDeletedFile=Soubor byl smaz\u00e1n | ||
| gb.userCreated=Nov\u00fd u\u017eivatel ''{0}'' \u00fasp\u011b\u0161n\u011b vytvo\u0159en. | ||
| gb.teamCreated=Nov\u00fd t\u00fdm ''{0}'' \u00fasp\u011b\u0161n\u011b vytvo\u0159en. | ||
| gb.isSparkleshared=repozit\u00e1\u0159 je Sparkleshare | ||
| gb.nTotalTickets={0} celkem | ||
| gb.all=v\u0161echny | ||
| gb.new=nov\u00fd | ||
| gb.excludeFromActivity=odebrat z aktivn\u00ed str\u00e1nky | ||
| gb.referencedByTicket=Odkazovan\u00fd \u00fakolem. | ||
| gb.ticketId=id \u00fakolu | ||
| gb.forkNotAuthorized=Promint\u011b, nejste opr\u00e1vn\u011bni k vytvo\u0159en\u00ed forku {0} | ||
| gb.servers=servry | ||
| gb.nParticipants={0} \u00fa\u010dastn\u00edk\u016f | ||
| gb.oneParticipant={0} \u00fa\u010dastn\u00edk | ||
| gb.reset=reset | ||
| gb.oneComment={0} koment\u00e1\u0159 | ||
| gb.oneAttachment={0} p\u0159\u00edloha | ||
| gb.from=od | ||
| gb.blob=blob | ||
| gb.severity=z\u00e1va\u017enost | ||
| gb.accessLevel=\u00farove\u0148 p\u0159\u00edstupu | ||
| gb.yourCreatedTickets=vytvo\u0159eno v\u00e1mi | ||
| gb.editTicket=upravit \u00fakol | ||
| gb.milestones=mezn\u00edky | ||
| gb.sortLowestSeverity=nejni\u017e\u0161\u00ed z\u00e1va\u017enost | ||
| gb.permittedUsers=opr\u00e1vn\u011bn\u00fd u\u017eivatel | ||
| gb.sortMostVotes=nejv\u00edce hlas\u016f | ||
| gb.plugins=pluginy | ||
| gb.specified=specifikovan\u00e9 | ||
| gb.sortHighestPriority=nejvy\u0161\u0161\u00ed priorita | ||
| gb.content=obsah | ||
| gb.organization=organizace | ||
| gb.passwordTooShort=Heslo je p\u0159\u00edli\u0161 kr\u00e1tke. minimum je {0} znak\u016f. | ||
| gb.notSpecified=nen\u00ed specifikov\u00e1no | ||
| gb.superseded=nahrazeno | ||
| gb.keyCompromise=kl\u00ed\u010d kompromitov\u00e1n | ||
| gb.locality=lokalita | ||
| gb.addSshKey=P\u0159idat SSH kl\u00ed\u010d | ||
| gb.myFork=zobrazit m\u016fj fork | ||
| gb.mergeType=typ slou\u010den\u00ed | ||
| gb.commitsTo={0} commit\u016f do | ||
| gb.indexedBranchesDescription=Vyberte v\u011btev kter\u00e1 bude indexov\u00e1na pomoc\u00ed Lucene | ||
| gb.couldNotCreateFederationProposal=Nelze vytvo\u0159it n\u00e1vrh federace! | ||
| gb.completeGravatarProfile=Dokon\u010dete profil na Gravatar.com | ||
| gb.errorOnlyAdminOrOwnerMayEditRepository=Pouze administr\u00e1tor nebo vlastn\u00edk m\u016f\u017ee upravovat repozit\u00e1\u0159 | ||
| gb.diffCopiedFile=Soubor byl zkop\u00edrov\u00e1n z {0} | ||
| gb.failedToReadMessage=Nelze p\u0159e\u010d\u00edst v\u00fdchoz\u00ed zpr\u00e1vu z {0}! | ||
| gb.organizationalUnit=organiza\u010dn\u00ed jednotka | ||
| gb.newSSLCertificate=nov\u00fd serverov\u00fd SSL certifik\u00e1t | ||
| gb.anonymousPolicyDescription=Kdokoli m\u016f\u017ee zobrazit repozit\u00e1\u0159 a prov\u00e1d\u011bt clone a push. | ||
| gb.deleteRepositoryDescription=Smazan\u00e9 repozit\u00e1\u0159e budou neobnoviteln\u00e1. | ||
| gb.allowForks=povolit forky | ||
| gb.newMilestone=nov\u00fd mezn\u00edk | ||
| gb.nFederationProposalsToReview={0} n\u00e1vrh\u016f federac\u00ed \u010dek\u00e1 na posouzen\u00ed. | ||
| gb.anonymousCanNotPropose=Anonymn\u00ed u\u017eivatel nem\u016f\u017ee navrhovat patchsety. | ||
| gb.patchsetN=sada zm\u011bn {0} | ||
| gb.failedToUpdateUser=Aktualizace u\u017eivatelsk\u00e9ho \u00fa\u010dtu selhala! | ||
| gb.commitIsNull=Commit je null | ||
| gb.certificate=certifik\u00e1t | ||
| gb.isMirror=tento repozit\u00e1\u0159 je zrcadlo | ||
| gb.accessPolicy=Opr\u00e1vn\u011bn\u00ed p\u0159\u00edstupu | ||
| gb.todaysActivityNone=dnes / nic | ||
| gb.transportPreference=Preference transportu | ||
| gb.reviews=posouzen\u00ed | ||
| gb.open=otev\u0159\u00edt | ||
| gb.oneMoreCommit=1 dal\u0161\u00ed commit \u00bb | ||
| gb.patchsetVetoedMore=Posuzovatel volil tento patchset. | ||
| gb.about=o | ||
| gb.action=akce | ||
| gb.acceptNewPatchsets=p\u0159ijmout sady zm\u011bn | ||
| gb.accessPermissions=p\u0159\u00edstupov\u00e1 opr\u00e1vn\u011bn\u00ed | ||
| gb.accountPreferences=Preference \u00fa\u010dtu | ||
| gb.active=aktivn\u00ed | ||
| gb.activeAuthors=aktivn\u00ed u\u017eivatel\u00e9 | ||
| gb.activeRepositories=aktivn\u00ed repozit\u00e1\u0159e | ||
| gb.addComment=p\u0159idat koment\u00e1\u0159 | ||
| gb.addedOneCommit=p\u0159id\u00e1n 1 commit | ||
| gb.opacityAdjust=Nastavit nepr\u016fhlednost | ||
| gb.errorAdministrationDisabled=Administrace je zak\u00e1z\u00e1na | ||
| gb.affiliationChanged=p\u0159\u00edslu\u0161nost se zm\u011bnila | ||
| gb.moreChanges=v\u0161echny zm\u011bny... | ||
| gb.verifyCommitterNote=v\u0161echna slou\u010den\u00ed vy\u017eaduj\u00ed "--no-ff" aby se vynutila identita v\u00fdvoj\u00e1\u0159e | ||
| gb.tokenJurDescription=v\u0161echny repozit\u00e1\u0159e | ||
| gb.tokenUnrDescription=v\u0161echny repozit\u00e1\u0159e a u\u017eivatel\u00e9 | ||
| gb.tokenAllDescription=v\u0161echny repozit\u00e1\u0159e, u\u017eivatel\u00e9 a nastaven\u00ed | ||
| gb.heapAllocated=alokovan\u00e1 halda | ||
| gb.allowForksDescription=povolit opr\u00e1vn\u011bn\u00fdm u\u017eivatel\u016fm vytv\u00e1\u0159et fork tohoto repozit\u00e1\u0159e | ||
| gb.acceptNewTicketsDescription=povolit vytv\u00e1\u0159en\u00ed hl\u00e1\u0161en\u00ed o chyb\u00e1ch, vylep\u0161en\u00ed, ulohy a dal\u0161\u00edch \u00fakol\u016f | ||
| gb.acceptNewTickets=povolit nov\u00e9 \u00fakoly | ||
| gb.anonymousUser=anonym | ||
| gb.anonymousPolicy=Anonymn\u00ed prohl\u00ed\u017een\u00ed, klonov\u00e1n\u00ed a push | ||
| gb.approve=schv\u00e1lit | ||
| gb.yourAssignedTickets=p\u0159i\u0159azeno v\u00e1m | ||
| gb.attributes=atributy | ||
| gb.body=t\u011blo | ||
| gb.caCompromise=CA kompromitov\u00e1na | ||
| gb.canAdminDescription=m\u016f\u017ee spravovat Gitblit server | ||
| gb.canCreateDescription=m\u016f\u017ee vytv\u00e1\u0159et osobn\u00ed repozit\u00e1\u0159 | ||
| gb.canFork=m\u016f\u017ee prov\u00e1d\u011bt fork | ||
| gb.canForkDescription=m\u016f\u017ee prov\u00e1d\u011bt fork opr\u00e1vn\u011bn\u00fdch repozit\u00e1\u0159\u016f do osobn\u00edch repozit\u00e1\u0159\u016f | ||
| gb.canNotLoadRepository=Nelze na\u010d\u00edst repozit\u00e1\u0159 | ||
| gb.canNotProposePatchset=nelze navrhnout sadu zm\u011bn | ||
| gb.acceptNewPatchsetsDescription=p\u0159imout sady zm\u011bn p\u0159edan\u00fdch pomoc\u00ed push do tohoto repozit\u00e1\u0159e | ||
| gb.checkoutStep2=P\u0159ekontrolovat sadu zm\u011bn | ||
| gb.checkoutViaCommandLine=P\u0159ekontrolovat pomoc\u00ed p\u0159\u00edkazov\u00e9 \u0159\u00e1dky | ||
| gb.checkout=checkout | ||
| gb.clientCertificateBundleSent=Bal\u00edk klientsk\u00e9ho certifik\u00e1tu {0} odesl\u00e1n | ||
| gb.closedMilestones=uzav\u0159en\u00e9 mezn\u00edky | ||
| gb.comments=koment\u00e1\u0159e | ||
| gb.commented=komentov\u00e1no | ||
| gb.compareToMergeBase=porovnat k z\u00e1kladu slu\u010dov\u00e1n\u00ed | ||
| gb.requireApprovalDescription=sady zm\u011bn mus\u00ed b\u00fdt odsouhlaseny p\u0159ed t\u00edm, ne\u017e je povoleno tla\u010d\u00edtko slou\u010den\u00ed | ||
| gb.nComments={0} koment\u00e1\u0159\u016f | ||
| gb.milestoneProgress={0} otev\u0159eno, {1} uzav\u0159eno | ||
| gb.nOpenTickets={0} otev\u0159eno | ||
| gb.nMoreCommits={0} v\u00edce commit\u016f \u00bb | ||
| gb.diffStat={0} p\u0159id\u00e1n\u00ed a {1} odebr\u00e1n\u00ed | ||
| gb.noForks={0} nem\u00e1 \u017e\u00e1dn\u00e9 forky | ||
| gb.repositoryForked={0} byl forknut | ||
| gb.userServiceDoesNotPermitPasswordChanges={0} nepovoluje zm\u011bnu hesla! | ||
| gb.userServiceDoesNotPermitAddUser={0} nedovoluje p\u0159id\u00e1n\u00ed u\u017eivatelsk\u00e9ho \u00fa\u010dtu! | ||
| gb.doesNotExistInTree={0} nen\u00ed obsa\u017een ve stromu {1} | ||
| gb.branchStats={0} commit\u016f a {1} tag\u016f b\u011bhem {2} | ||
| gb.nCommits={0} commit\u016f | ||
| gb.nAttachments={0} p\u0159\u00edloh | ||
| gb.externalPermissions={0} p\u0159\u00edstupov\u00e1 pr\u00e1va jsou spravov\u00e1na vzd\u00e1len\u011b | ||
| gb.excludePermission={0} (vyjmout) | ||
| gb.yourWatchedTickets=sledov\u00e1no v\u00e1mi | ||
| gb.viewCertificate=zobrazit certifik\u00e1t | ||
| gb.viewComparison=zobrazit porovn\u00e1n\u00ed t\u011bchto {0} commit\u016f \u00bb | ||
| gb.verifyCommitter=ov\u011b\u0159it v\u00fdvoj\u00e1\u0159e | ||
| gb.usernameUnavailable=U\u017eivatelsk\u00e9 jm\u00e9no ''{0}'' nen\u00ed dostupn\u00e9. | ||
| gb.userPermissions=opr\u00e1vn\u011bn\u00ed u\u017eivatele | ||
| gb.nameDescription=pou\u017e\u00edvat '/' k seskupen\u00ed repozit\u00e1\u0159\u016f, nap\u0159. libraries/mycoollib.git | ||
| gb.heapUsed=pou\u017eit\u00e1 halda | ||
| gb.uploadedPatchsetNRevisionN=nahr\u00e1na sada zm\u011bn {0} revize {1} | ||
| gb.uploadedPatchsetN=nahr\u00e1na sada zm\u011bn {0} | ||
| gb.unstar=odzna\u010d | ||
| gb.title=titulek | ||
| gb.ticketSettings=Nastaven\u00ed \u00fakolu | ||
| gb.repositoryIsFrozen=Repozit\u00e1\u0159 je zmra\u017een\u00e9. | ||
| gb.repositoryIsMirror=Repozit\u00e1\u0159 je zrcadlo pouze pro \u010dten\u00ed. | ||
| gb.serverDoesNotAcceptPatchsets=Tento server neakceptuje sady zm\u011bn. | ||
| gb.ticketIsClosed=Tento \u00fakol je uzav\u0159en\u00fd. | ||
| gb.initWithReadmeDescription=Toto vytvo\u0159\u00ed jednoduch\u00fd README dokument ve va\u0161em repozit\u00e1\u0159i. | ||
| gb.ticketPatchset=\u00fakol {0}, psada zm\u011bn {1} | ||
| gb.vote=hlasovat pro toto {0} | ||
| gb.votes=hlasy | ||
| gb.watch=sledovat toto {0} | ||
| gb.updatedBy=aktualizoval | ||
| gb.unauthorizedAccessForRepository=Neautorizovan\u00fd p\u0159\u00edstup do repozit\u00e1\u0159e. | ||
| gb.fileNotMergeable=Nelze prov\u00e9st commit {0}. Tento soubor nelze automaticky slou\u010dit. | ||
| gb.patchsetNotApprovedMore=Posuzovatel mus\u00ed schv\u00e1lit tuto sadu zm\u011bn. | ||
| gb.teamPermission=opr\u00e1vn\u011bn\u00ed nastavil "{0}" \u010dlenstv\u00ed v t\u00fdmu | ||
| gb.ticketsWelcome=M\u016f\u017eete pou\u017e\u00edt \u00fakoly k organizaci va\u0161eho seznamu \u00fakol\u016f, diskutovat chyby a spolupracovat na sad\u00e1ch zm\u011bn. | ||
| gb.sshKeyCommentDescription=Vlo\u017ete voliteln\u00fd koment\u00e1\u0159. Pokud bude pr\u00e1zdn\u00fd, bude automaticky vyta\u017een z dat kl\u00ed\u010de. | ||
| gb.feed=kan\u00e1l | ||
| gb.recentActivityNone=posledn\u00edch {0} dn\u00ed / nic | ||
| gb.selectFederationStrategy=Pros\u00edm zvolte strategii federace! | ||
| gb.ptDescription=n\u00e1stroj sad zm\u011bn Gitblitu | ||
| gb.milestone=mezn\u00edk | ||
| gb.federationSets=sady federac\u00ed | ||
| gb.diffRenamedFile=Soubor byl p\u0159ejmenov\u00e1n z {0} | ||
| gb.noActivityToday=dnes tu nebyla \u017e\u00e1dn\u00e1 aktivita | ||
| gb.noActivity=nebyla tu \u017e\u00e1dn\u00e1 aktivita b\u011bhem posledn\u00edch {0} dn\u00ed | ||
| gb.dashboard=hlavn\u00ed panel | ||
| gb.myDashboard=m\u016fj hlavn\u00ed panel | ||
| gb.myProfile=m\u016fj profil | ||
| gb.myTickets=moje \u00fakoly | ||
| gb.merged=slou\u010deno | ||
| gb.mergeTo=slou\u010dit do | ||
| gb.mergedPatchset=slou\u010dit sadu zm\u011bn | ||
| gb.mergedPullRequest=slou\u010dit \u017e\u00e1dost o p\u0159eta\u017een\u00ed | ||
| gb.mergingViaCommandLine=Slou\u010dit pomoc\u00ed p\u0159\u00edkazov\u00e9 \u0159\u00e1dky | ||
| gb.merge=slou\u010dit | ||
| gb.heapMaximum=maxim\u00e1ln\u00ed halda | ||
| gb.sortLowestPriority=nejni\u017e\u010d\u00ed priorita | ||
| gb.luceneDisabled=indexov\u00e1n\u00ed pomoc\u00ed Lucene je zak\u00e1zan\u00e9 | ||
| gb.maintenanceTickets=\u00fadr\u017eba | ||
| gb.isFork=je fork | ||
| gb.isNotValidFile=nen\u00ed platn\u00fd soubor | ||
| gb.key=Kl\u00ed\u010d | ||
| gb.initialCommit=Prvn\u00ed commit | ||
| gb.invalidExpirationDate=deplatn\u00e9 datum vypr\u0161en\u00ed! | ||
| gb.invalidUsernameOrPassword=Neplatn\u00e9 u\u017eivatelsk\u00e9 jm\u00e9no nebo heslo! | ||
| gb.filestoreHelp=Jak pou\u017e\u00edvat \u00dalo\u017ei\u010dt\u011b velk\u00fdch soubor\u016f? | ||
| gb.filestore=velk\u00e9 soubory | ||
| gb.filestoreStats=\u00dalo\u017ei\u0161t\u011b velk\u00fdch soubor\u016f obsahuje {0} soubor\u016f o celkov\u00e9 velikosti {1}. ({2} zb\u00fdv\u00e1) | ||
| gb.findSomeRepositories=naj\u00edt n\u011bjak\u00e9 repozit\u00e1\u0159e | ||
| gb.repositoryForkFailed=fork selhal | ||
| gb.forkInProgress=fork prob\u00edh\u00e1 | ||
| gb.forkRepository=prov\u00e9st fork {0}? | ||
| gb.forkedFrom=fork z | ||
| gb.forks=forky | ||
| gb.forksProhibited=vytvo\u0159en\u00ed forku zak\u00e1z\u00e1no | ||
| gb.free=uvolnit | ||
| gb.expiring=exportov\u00e1n\u00ed | ||
| gb.excludeFromFederation=vyjmout z federace | ||
| gb.deletePatchsetFailure=Chyba p\u0159i odstranov\u00e1n\u00ed sady zm\u011bn {0}. | ||
| gb.enhancementTickets=vylep\u0161en\u00ed | ||
| gb.requestTickets=vylep\u0161en\u00ed a \u00falohy | ||
| gb.emailAddress=e-mailov\u00e1 adresa | ||
| gb.gcPeriodDescription=doba mezi \u00faklidy | ||
| gb.garbageCollection=\u00daklid | ||
| gb.gcPeriod=GC perioda | ||
| gb.gcThreshold=GC pr\u00e1h | ||
| gb.gc=GC | ||
| gb.sortLeastVotes=posledn\u00ed hlasy | ||
| gb.sortLeastComments=posledn\u00ed koment\u00e1\u0159e | ||
| gb.looksGood=vypad\u00e1 dob\u0159e | ||
| gb.mergeBase=z\u00e1klad slou\u010den\u00ed | ||
| gb.mergeStep3=Slou\u010dit navrhovan\u00e9 zm\u011bny a aktualizuj server | ||
| gb.mentionsMeTickets=zmi\u0148uj\u00edc\u00ed v\u00e1s | ||
| gb.mirrorOf=zrcadlo pro {0} | ||
| gb.miscellaneous=r\u016fzn\u00e9 | ||
| gb.sortMostComments=nejv\u00edce koment\u00e1\u0159\u016f | ||
| gb.mutable=prom\u011bnliv\u00fd | ||
| gb.needsImprovement=pot\u0159ebuje zlep\u0161en\u00ed | ||
| gb.newCertificateDefaults=v\u00fdchoz\u00ed hodnoty nov\u00e9ho certifik\u00e1tu | ||
| gb.noComments=\u017e\u00e1dn\u00e9 koment\u00e1\u0159e | ||
| gb.none=\u017e\u00e1dn\u00e9 | ||
| gb.noIndexedRepositoriesWarning=\u017e\u00e1dn\u00fd z va\u0161ich repozit\u00e1\u0159\u016f nen\u00ed nakonfigurov\u00e1n pro indexov\u00e1n\u00ed pomoc\u00ed Lucene | ||
| gb.sortOldest=nejstar\u0161\u00ed | ||
| gb.owned=vlastn\u00ed | ||
| gb.starred=ozna\u010den\u00e9 | ||
| gb.starredAndOwned=ozna\u010den\u00e9 a vlastn\u00ed | ||
| gb.starredRepositories=ozna\u010den\u00e9 repozit\u00e1\u0159e | ||
| gb.star=ozna\u010dit | ||
| gb.initWithGitignore=P\u0159idat soubor .gitignore | ||
| gb.time.inMinutes=b\u011bhem {0} minut | ||
| gb.time.inDays=b\u011bhem {0} dn\u00ed | ||
| gb.languagePreference=Nastaven\u00ed jazyka | ||
| gb.recentActivityStats=posledn\u00edch {0} dn\u00ed / {1} commit\u016f od {2} autor\u016f | ||
| gb.incrementalPushTagMessage=Automaticky otagovan\u00e1 [{0}] v\u011btev p\u0159i push | ||
| gb.undefinedQueryWarning=dotaz je nedefinovan\u00fd! | ||
| gb.sessionEnded=Sezen\u00ed bylo zav\u0159eno | ||
| gb.myUrlDescription=ve\u0159ejn\u011b dostupn\u00e1 URL va\u0161\u00ed Gitblit instance | ||
| gb.addition=p\u0159\u00eddavek | ||
| gb.accessPermissionsForUserDescription=nastavte \u010dlenstv\u00ed v t\u00fdmech nebo povolte p\u0159\u00edstup do zvolen\u00fdch omezen\u00fdch repozit\u00e1\u0159\u016f | ||
| gb.accessPermissionsForTeamDescription=nastavte \u010dleny t\u00fdmu a povolte p\u0159\u00edstup do zvolen\u00fdch omezen\u00fdch repozit\u00e1\u0159\u016f | ||
| gb.headRefDescription=V\u00fdchoz\u00ed v\u011btev kter\u00e1 bude klonov\u00e1na a bude zobrazena na souhrnn\u00e9 str\u00e1nce. | ||
| gb.passwordChangeAborted=Zm\u011bna hesla p\u0159eru\u0161ena. | ||
| gb.monthlyActivity=m\u011bs\u00ed\u010dn\u00ed aktivita | ||
| gb.excludeFromFederationDescription=blokuj prov\u00e1den\u00ed pull tohoto \u00fa\u010dtu instanc\u00edm Gitblitu ve federaci | ||
| gb.siteNameDescription=kr\u00e1tk\u00e9 popisn\u00e9 jm\u00e9no va\u0161eho serveru | ||
| gb.federationRegistration=registrace federace | ||
| gb.registrations=registrace federace | ||
| gb.customFields=vlastn\u00ed pole | ||
| gb.customFieldsDescription=vlastn\u00ed pole dostupn\u00e1 pro h\u00e1\u010dky Groovy | ||
| gb.certificateRevoked=Certifik\u00e1t {0,\u010d\u00edslo,0} byl zam\u00edtnut | ||
| gb.cessationOfOperation=zastaven\u00ed provozu | ||
| gb.committed=commitov\u00e1no | ||
| gb.commitActivityDOW=aktivita commit\u016f podle dne v t\u00fddnu | ||
| gb.federationRepositoryDescription=sd\u00edlet tento repozit\u00e1\u0159 s ostatn\u00edmi Gitblit servery | ||
| gb.allowAuthenticatedDescription=ud\u011blit pr\u00e1vo RW+ v\u0161em p\u0159ihl\u00e1\u0161en\u00fdm u\u017eivatel\u016fm | ||
| gb.allowNamedDescription=ud\u011blit p\u0159esn\u00e1 opr\u00e1vn\u011bn\u00ed pojmenovan\u00fdm u\u017eivatel\u016fm a t\u00fdm\u016fm | ||
| gb.authorizationControl=kontrola autorizace | ||
| gb.countryCode=k\u00f3d zem\u011b | ||
| gb.ticketOpenDate=datum otev\u0159en\u00ed | ||
| gb.bootDate=datum startu | ||
| gb.of=z | ||
| gb.busyCollectingGarbage=Promint\u011b, Gitblit je zanepr\u00e1zdn\u011bn \u00faklidem v {0} | ||
| gb.noProposals=Promint\u011b, {0} nyn\u00ed neakceptuje n\u00e1vrhy. | ||
| gb.noFederation=Promint\u011b, {0} nen\u00ed nakonfigurov\u00e1n k federaci s jin\u00fdmi Gitblit instancemi. | ||
| gb.noGitblitFound=Promint\u011b, {0} nelze nal\u00e9zt Gitblit instanci {1} | ||
| gb.proposalFailed=Promint\u011b, {0} nep\u0159ijala \u017e\u00e1dn\u00e1 data n\u00e1vrhu! | ||
| gb.proposalError=Promint\u011b, {0} hl\u00e1s\u00ed, \u017ee do\u0161lo k neo\u010dek\u00e1van\u00e9 chyb\u011b. | ||
| gb.passwordHintRequired=n\u00e1pov\u011bda hesla je po\u017eadov\u00e1na! | ||
| gb.in=v | ||
| gb.stateProvince=st\u00e1t nebo provincie | ||
| gb.forksProhibitedWarning=repozit\u00e1\u0159 zakazuje forky | ||
| gb.workingCopyWarning=tento repozit\u00e1\u0159 m\u00e1 pracovn\u00ed kopii a nem\u016f\u017ee p\u0159ij\u00edmat push | ||
| gb.federationStrategy=strategie federace | ||
| gb.isFederated=je ve federaci | ||
| gb.metricAuthorExclusions=vylou\u010dit autora z metrik | ||
| gb.failedToFindAccount=chyba p\u0159i hled\u00e1n\u00ed u\u017eivatelsk\u00e9ho \u00fa\u010dtu ''{0}'' | ||
| gb.failedToFindGravatarProfile=Chyba p\u0159i hled\u00e1n\u00ed profilu Gravatar pro {0} | ||
| gb.federateThis=tento repozit\u00e1\u0159 ve federaci | ||
| gb.federateOrigin=origin ve federaci | ||
| gb.fork=fork | ||
| gb.combinedMd5Rename=Gitblit je nakonfigurov\u00e1n pro kombinovan\u00e9 MD5 he\u0161ov\u00e1n\u00ed hesla. Mus\u00edte zadat nov\u00e9 heslo p\u0159i p\u0159ejmenov\u00e1n\u00ed \u00fa\u010dtu. | ||
| gb.inherited=zd\u011bd\u011bn\u00fd | ||
| gb.todaysActivityStats=dnes / {1} commit\u016f od {2} autor\u016f | ||
| gb.hostname=hostname | ||
| gb.noMaximum=\u017e\u00e1dn\u00e9 maximum | ||
| gb.failedtoRead=Chyba \u010dten\u00ed | ||
| gb.showRemoteBranchesDescription=uk\u00e1zat vzd\u00e1len\u00e9 v\u011btve | ||
| gb.showReadme=uk\u00e1zat readme | ||
| gb.showReadmeDescription=uk\u00e1zat "readme" Markdown soubor na souhrnn\u00e9 str\u00e1nce | ||
| gb.siteName=jm\u00e9no str\u00e1nky | ||
| gb.clientCertificateGenerated=\u00dasp\u011b\u0161n\u011b vygenerov\u00e1n klientsk\u00fd certifik\u00e1t pro {0} | ||
| gb.sslCertificateGeneratedRestart=\u00dasp\u011b\u0161n\u011b vygenerov\u00e1n serverov\u00fd SSL certifik\u00e1t pro {0}.\nMus\u00edte restartovat Gitblit pro pou\u017eit\u00ed nov\u00e9ho certifik\u00e1tu.\n\nPokud spou\u0161t\u00edte s parametrem '--alias', budete jej muset nastavit na ''--alias {0}''. | ||
| gb.sslCertificateGenerated=\u00dasp\u011b\u0161n\u011b vygenerov\u00e1n serverov\u00fd SSL certifik\u00e1t pro {0} | ||
| gb.viewAccess=V Gitblitu nem\u00e1te pr\u00e1vo pro p\u0159\u00edstup ke \u010dten\u00ed nebo z\u00e1pisu | ||
| gb.createdNewTag=vytvo\u0159it nov\u00fd tag | ||
| gb.enableIncrementalPushTags=povolit inkrement\u00e1ln\u00ed push tags | ||
| gb.OneProposalToReview=Je tu jeden n\u00e1vrh na federaci \u010dekaj\u00edc\u00ed na vy\u0159\u00edzen\u00ed. | ||
| gb.pushedOneCommitTo=proveden push 1 commitu do | ||
| gb.pushedNewBranch=proveden push do nov\u00e9 v\u011btve | ||
| gb.pushedNewTag=proveden push nov\u00e9ho tagu | ||
| gb.pushedNCommitsTo=proveden push {0} commit\u016f do | ||
| gb.authored=napsan\u00fd | ||
| gb.skipSummaryMetrics=p\u0159esko\u010dit souhrnn\u00e9 metriky | ||
| gb.maxActivityCommits=maxim\u00e1ln\u00ed aktivita commit\u016f | ||
| gb.skipSummaryMetricsDescription=nepo\u010d\u00edtat metriky na souhrnn\u00e9 str\u00e1nce (redukuje \u010das na\u010d\u00edt\u00e1n\u00ed str\u00e1nky) | ||
| gb.skipSizeCalculationDescription=nepo\u010d\u00edtat velikost repozit\u00e1\u0159e (redukuje \u010das na\u010d\u00edt\u00e1n\u00ed str\u00e1nky) | ||
| gb.failedToFindCommit=Nelze nal\u00e9zt commit "{0}" v {1}! | ||
| gb.markdownFailure=Chyba p\u0159i zpracov\u00e1n\u00ed Markdown obsahu! | ||
| gb.failedToSendProposal=Selhalo odesl\u00e1n\u00ed n\u00e1vrhu! | ||
| gb.couldNotFindTag=Nelze nal\u00e9zt tag {0} | ||
| gb.couldNotFindFederationRegistration=Nelze nal\u00e9zt registraci federace! | ||
| gb.couldNotFindFederationProposal=Nelze nal\u00e9zt n\u00e1vrh k federaci! | ||
| gb.teamNameUnavailable=T\u00fdm se jm\u00e9nem ''{0}'' nen\u00ed dostupn\u00fd. | ||
| gb.ownerDescription=vlastn\u00edk m\u016f\u017ee upravovat nastaven\u00ed repozit\u00e1\u0159e | ||
| gb.newClientCertificateMessage=POZN\u00c1MKA:\n'Heslo' nen\u00ed u\u017eivatelsk\u00e9 heslo. Je to heslo k ochran\u011b \u00falo\u017ei\u0161t\u011b kl\u00ed\u010d\u016f. Toto heslo nebude nikam ulo\u017eeno tak\u017ee mus\u00edte zadat tak\u00e9 n\u00e1pov\u011bdu, kter\u00e1 bude p\u0159ilo\u017eena k instrukc\u00edm u\u017eivatelova README. | ||
| gb.emailCertificateBundle=bal\u00edk klientsk\u00e9ho certifik\u00e1tu e-mailu | ||
| gb.passwordChanged=Heslo \u00fasp\u011b\u0161n\u011b zm\u011bn\u011bno. | ||
| gb.passwordsDoNotMatch=Hesla se neshoduj\u00ed! | ||
| gb.permission=Opr\u00e1vn\u011bn\u00ed | ||
| gb.teamPermissions=opr\u00e1vn\u011bn\u00ed t\u00fdmu | ||
| gb.repositoryPermissions=opr\u00e1vn\u011bn\u00ed repozit\u00e1\u0159e | ||
| gb.createdNewBranch=vytvo\u0159it novou v\u011bt\u011bv | ||
| gb.teamMustSpecifyRepository=T\u00fdm mus\u00ed nastavit nejm\u00e9n\u011b jeden repozit\u00e1\u0159. | ||
| gb.emailMeOnMyTicketChanges=Poslat e-mail p\u0159i zm\u011bn\u011b m\u00e9ho \u00fakolu | ||
| gb.patchsetMergeableMore=Tato sada zm\u011bn m\u016f\u017ee b\u00fdt slou\u010dena do {0} tak\u00e9 pomoc\u00ed p\u0159\u00edkazov\u00e9 \u0159\u00e1dky. | ||
| gb.createdThisTicket=\u00fakol vytvo\u0159en | ||
| gb.showHideDetails=zobrazit/skr\u00fdt detaily | ||
| gb.labels=popisky | ||
| gb.topicsAndLabels=t\u00e9mata a popisky | ||
| gb.topic=t\u00e9ma | ||
| gb.preview=n\u00e1hled | ||
| gb.commitsInPatchsetN=commity v sad\u011b zm\u011bn {0} | ||
| gb.ptSimplifiedMerge=zjednodu\u0161en\u00e1 syntaxe slu\u010dov\u00e1n\u00ed | ||
| gb.compareToN=porovnat s {0} | ||
| gb.authenticatedPushPolicy=Omezit Push (P\u0159ihl\u00e1\u0161en\u00fd) | ||
| gb.authenticatedPushPolicyDescription=Kdokoli m\u016f\u017ee prohl\u00ed\u017eet a klonovat tento repozit\u00e1\u0159. V\u0161ichni p\u0159ihl\u00e1\u0161en\u00ed u\u017eivatel\u00e9 budou m\u00edt pr\u00e1va RW+. | ||
| gb.clonePolicyDescription=Kdokoli m\u016f\u017ee vid\u011bt tento repozit\u00e1\u0159. M\u016f\u017eete vybrat, kdo m\u016f\u017ee klonovat a prov\u00e1d\u011bt push. | ||
| gb.clonePolicy=Omezit klonov\u00e1n\u00ed a Push | ||
| gb.closeBrowser=Pros\u00edm zav\u0159ete prohl\u00ed\u017ee\u010d ke spr\u00e1vn\u00e9mu ukon\u010den\u00ed sezen\u00ed. | ||
| gb.commitMessageRendererDescription=Zpr\u00e1va commitu m\u016f\u017ee b\u00fdt zobrazena jako prost\u00fd text nebo vykreslena jako markup. | ||
| gb.commitMessageRenderer=vykreslen\u00ed zpr\u00e1vy commitu | ||
| gb.commitChanges=Zmeny commitu | ||
| gb.commitActivityTrend=Trend aktivity commit\u016f | ||
| gb.commitActivityAuthors=prim\u00e1rn\u00ed auto\u0159i podle aktivity commit\u016f | ||
| gb.createdNewPullRequest=vytvo\u0159ena \u017e\u00e1dost o p\u0159eta\u017een\u00ed | ||
| gb.createFirstTicket=vytvo\u0159te v\u00e1\u0161 prvn\u00ed \u00fakol | ||
| gb.createReadme=vytvo\u0159it README | ||
| gb.deletedTag=smazat tag | ||
| gb.deletePatchset=Smazat sadu zm\u011bn {0} | ||
| gb.deletePatchsetSuccess=Smazat sadu zm\u011bn {0}. | ||
| gb.deleteRepositoryHeader=Smazat repozit\u00e1\u0159 | ||
| gb.disableUser=zak\u00e1zat u\u017eivatele | ||
| gb.disableUserDescription=zamezit tomuto \u00fa\u010dtu v p\u0159ihl\u00e1\u0161en\u00ed | ||
| gb.displayNameDescription=Preferovan\u00e9 jm\u00e9no prozobrazen\u00ed | ||
| gb.docsWelcome1=M\u016f\u017eete pou\u017e\u00edt dokumentaci k dokumentov\u00e1n\u00ed va\u0161eho repozit\u00e1\u0159e. | ||
| gb.docsWelcome2=Za\u010dn\u011bte commitem souboru README.md nebo HOME.md. | ||
| gb.emailClientCertificateSubject=V\u00e1\u0161 Gitblit klientsk\u00fd certifik\u00e1t pro {0} | ||
| gb.manual=ru\u010dn\u011b | ||
| gb.refs=refs | ||
| gb.removeVote=vzd\u00e1len\u00e9 hlasov\u00e1n\u00ed | ||
| gb.review=posoudit | ||
| gb.revoked=zru\u0161it | ||
| gb.rewind=REWIND | ||
| gb.permissions=opr\u00e1vn\u011bn\u00ed | ||
| gb.personalRepositories=osobn\u00ed repozit\u00e1\u0159e | ||
| gb.pleaseSetDestinationUrl=Pros\u00edm zadejte cilovou URL pro v\u00e1\u0161 n\u00e1vrh! | ||
| gb.patchset=sada zm\u011bn | ||
| gb.proposal=n\u00e1vrh | ||
| gb.proposalReceived=N\u00e1vrh \u00fasp\u011b\u0161n\u011b p\u0159ijat od {0}. | ||
| gb.proposePatchset=navrhnout sadu zm\u011bn | ||
| gb.proposeWith=navrhnout sadu zm\u011bn s {0} | ||
| gb.proposedThisChange=navrhnout tuto zm\u011bnu | ||
| gb.proposalTickets=navrhnout zm\u011bny | ||
| gb.queries=dotazy | ||
| gb.questionTickets=ot\u00e1zky | ||
| gb.reason=d\u016fvod | ||
| gb.received=p\u0159ijat | ||
| gb.receiveSettings=Nastaven\u00ed p\u0159\u00edjmu | ||
| gb.referencedByCommit=Odkazovan\u00fd commitem. | ||
| gb.reflog=reflog | ||
| gb.illegalRelativeSlash=RElativn\u00ed reference na adres\u00e1\u0159 (../) jsou zak\u00e1z\u00e1ny. | ||
| gb.useTicketsDescription=pouze pro \u010dten\u00ed, idstribuovan\u00e9 Ticgit \u00fakoly | ||
| gb.privilegeWithdrawn=sta\u017een\u00ed privilegi\u00ed | ||
| gb.repositoryNotSpecifiedFor=Repozit\u00e1\u0159 nen\u00ed nastaven pro {0}! | ||
| gb.ownerPermission=vlastn\u00edk repozit\u00e1\u0159e | ||
| gb.requireApproval=vy\u017eaduje schv\u00e1len\u00ed | ||
| gb.verifyCommitterDescription=vy\u017eaduje aby se identita v\u00fdvoj\u00e1\u0159e shodovala s Gitblit \u00fa\u010dtem pou\u017eit\u00fdm pro push | ||
| gb.responsible=zodpov\u011bdn\u00fd | ||
| gb.accessPermissionsDescription=omezit p\u0159\u00edstup na u\u017eivatel\u00e9 a t\u00fdmy\n | ||
| gb.namedPushPolicy=Omezit Push (Pojmenovan\u00fd) | ||
| gb.viewPolicy=Omezit prohl\u00ed\u017een\u00ed, klonov\u00e1n\u00ed a Push | ||
| gb.raw=raw | ||
| gb.searchForAuthor=Vyhledat commity podle autora | ||
| gb.searchForCommitter=Vyhledat commity podle v\u00fdvoj\u00e1\u0159e | ||
| gb.isFrozenDescription=zak\u00e1zat operace push | ||
| gb.tokens=token federace | ||
| gb.proposals=n\u00e1vrh federace | ||
| gb.token=token | ||
| gb.federationResults=v\u00fdsledek p\u0159eta\u017een\u00ed federace | ||
| gb.maxHits=maximum v\u00fdsledk\u016f | ||
| gb.preReceiveScripts=skript p\u0159ed p\u0159\u00edjmem | ||
| gb.postReceiveScripts=skript po p\u0159\u00edjmu | ||
| gb.hookScripts=k\u00e1\u010dkovac\u00ed skripty | ||
| gb.hookScriptsDescription=spustit Groovy skript po push na tento Gitblit server | ||
| gb.noHits=\u017e\u00e1dn\u00e9 v\u00fdsledky | ||
| gb.queryResults=v\u00fdsledek {0} - {1} ({2} v\u00fdsledk\u016f) | ||
| gb.pleaseSetRepositoryName=Pros\u00edm, nastavte jm\u00e9no repozit\u00e1\u0159e! | ||
| gb.illegalLeadingSlash=Po\u010d\u00e1te\u010dn\u00ed reference na ko\u0159enov\u00fd adres\u00e1\u0159 (/) jsou zak\u00e1z\u00e1ny. | ||
| gb.selectAccessRestriction=Pros\u00edm, vyberte omezen\u00ed p\u0159\u00edstupu! | ||
| gb.pleaseSetTeamName=Pros\u00edm, zadejte jm\u00e9no t\u00fdmu! | ||
| gb.pleaseSetUsername=Pros\u00edm, zadejte u\u017eivatelsk\u00e9 jm\u00e9no! | ||
| gb.repositoryNotSpecified=Repozit\u00e1\u0159 nen\u00ed vybr\u00e1n! | ||
| gb.pleaseSetGitblitUrl=Pros\u00edm, zadejte URL va\u0161eho Gitblitu! | ||
| gb.errorOnlyAdminMayCreateRepository=Pouze administr\u00e1tor m\u016f\u017ee vytv\u00e1\u0159et repozit\u00e1\u0159 | ||
| gb.preparingFork=vytv\u00e1\u0159en\u00ed va\u0161eho forku... | ||
| gb.illegalPersonalRepositoryLocation=v\u00e1\u0161 soukrom\u00fd repozit\u00e1\u0159 mus\u00ed b\u00fdt um\u00edst\u011bn v "{0}" | ||
| gb.gcThresholdDescription=minim\u00e1ln\u00ed celkov\u00e1 velikost uvoln\u011bn\u00fdch objekt\u016f pro vyvol\u00e1n\u00ed \u00faklidu | ||
| gb.missingPermission=repozit\u00e1\u0159 pro toto opr\u00e1vn\u011bn\u00ed chyb\u00ed! | ||
| gb.revokeCertificateReason=Pros\u00edm, vyberte d\u016fvod zam\u00edtnut\u00ed certifik\u00e1tu | ||
| gb.hostnameRequired=Pros\u00edm, zadejte hostname | ||
| gb.pleaseGenerateClientCertificate=Pros\u00edm, vytvo\u0159te klientsk\u00fd certifik\u00e1t pro {0} | ||
| gb.enterKeystorePassword=Pros\u00edm, zadejte heslo k \u00falo\u017ei\u0161ti kl\u00ed\u010d\u016f Gitblitu | ||
| gb.maxActivityCommitsDescription=maxim\u00e1ln\u00ed po\u010det commit\u016f pro p\u0159isp\u011bn\u00ed na str\u00e1nku aktivit | ||
| gb.serveCertificate=provozovat https s t\u00edmto certifik\u00e1tem | ||
| gb.useIncrementalPushTagsDescription=p\u0159i akci push, automaticky tagovat ka\u017edou v\u011btev s inkrement\u00e1ln\u00edm \u010d\u00edslem revize | ||
| gb.stargazers=pozorovatel\u00e9 | ||
| gb.reviewPatchset=posoudit {0} sadu zm\u011bn {1} | ||
| gb.home=dom\u016f | ||
| gb.mirrorWarning=tento repozit\u00e1\u0159 je zrcadlo a nem\u016f\u017ee p\u0159ij\u00edmat push | ||
| gb.noDescriptionGiven=popis nen\u00ed zad\u00e1n | ||
| gb.toBranch=do {0} | ||
| gb.createdBy=vytvo\u0159il | ||
| gb.patchsetMergeable=Tato sada zm\u011bn m\u016f\u017ee b\u00fdt automaticky slou\u010dena do {0}. | ||
| gb.patchsetAlreadyMerged=Tato sada zm\u011bn byla slou\u010dena do {0}. | ||
| gb.patchsetNotMergeable=Tato sada zm\u011bn nem\u016f\u017ee b\u00fdt automaticky slou\u010dena do {0}. | ||
| gb.patchsetNotMergeableMore=Tato sada zm\u011bn mus\u00ed b\u00fdt p\u0159evedena na nov\u00fd z\u00e1klad nebo slou\u010dena manu\u00e1ln\u011b do {0}, aby se vy\u0159e\u0161ily konflikty. | ||
| gb.patchsetNotApproved=Tato sada zm\u011bn nebyla schv\u00e1lena pro slou\u010den\u00ed do {0}. | ||
| gb.taskTickets=\u00falohy | ||
| gb.sortLeastRecentlyUpdated=naposledy aktualizovan\u00e9 | ||
| gb.searchTickets=prohledat \u00fakol | ||
| gb.searchTicketsTooltip=prohledat {0} \u00fakol\u016f | ||
| gb.changedStatus=zm\u011bnit status | ||
| gb.proposePatchsetNote=Jste v\u00fdt\u00e1n\u00ed k navr\u017een\u00ed sady zm\u011bn pro tento \u00fakol. | ||
| gb.proposeInstructions=Pro za\u010d\u00e1tek, vytvo\u0159te sadu zm\u011bn a nahrajte ji pomoc\u00ed Gitu. Gitblit propoj\u00ed va\u0161i sadu zm\u011bn s t\u00edmto \u00fakolem pomoc\u00ed id. | ||
| gb.jceWarning=Va\u0161e Java Runtime Environment nem\u00e1 soubory "JCE Unlimited Strength Jurisdiction Policy".\nToto zmen\u0161\u00ed d\u00e9lku hesla, kterou m\u016f\u017eete za\u0161ifrovat \u00falo\u017ei\u0161t\u011b kl\u00ed\u010d\u016f na 7 znak\u016f.\nSoubory politik jsou dodate\u010dn\u00e1 mo\u017enost ke sta\u017een\u00ed od Oracle.\n\nP\u0159ejete si pokra\u010dovat a vygenerovat i p\u0159es to certifika\u010dn\u00ed infrastrukturu?\n\nOdpov\u011b\u010f Ne v\u00e1s p\u0159esm\u011bruje na str\u00e1nky Oracle, kde sy m\u016f\u017eete tyto soubory politik st\u00e1hnout. | ||
| gb.byNAuthors=od {0} autor\u016f | ||
| gb.byOneAuthor=od {0} | ||
| gb.sortMostPatchsetRevisions=nejv\u00edce reviz\u00ed sad zm\u011bn | ||
| gb.sortLeastPatchsetRevisions=nejm\u00e9n\u011b reviz\u00ed sad zm\u011bn | ||
| gb.stepN=Krok {0} | ||
| gb.stopWatching=p\u0159estat sledovat | ||
| gb.mergeSha=slou\u010dit SHA | ||
| gb.reviewers=recenzenti | ||
| gb.mentions=zminuje | ||
| gb.repositoryDoesNotAcceptPatchsets=Tento repozit\u00e1\u0159 nep\u0159ij\u00edm\u00e1 sady zm\u011bn. | ||
| gb.openMilestones=otev\u0159en\u00e9 mezn\u00edky | ||
| gb.extensions=roz\u0161\u00ed\u0159en\u00ed | ||
| gb.pleaseSelectProject=Pros\u00edm, vyberte projekt! | ||
| gb.checkoutViaCommandLineNote=M\u016f\u017eete p\u0159ekontrolovat a otestovat tyto zm\u011bny lok\u00e1ln\u011b v klonu tohoto repozit\u00e1\u0159e. | ||
| gb.checkoutStep1=Z\u00edskat aktu\u00e1ln\u00ed sadu zm\u011bn \u2014 spus\u0165te toto z va\u0161eho projektov\u00e9ho adres\u00e1\u0159e | ||
| gb.mergingViaCommandLineNote=Pokud si nep\u0159ejete pou\u017e\u00edt tla\u010d\u00edtko pro slou\u010den\u00ed, nebo nelze prov\u00e9st automatick\u00e9 slou\u010den\u00ed, m\u016f\u017eete prov\u00e9st manu\u00e1ln\u00ed slou\u010den\u00ed pomoc\u00ed p\u0159\u00edkazov\u00e9 \u0159\u00e1dky. | ||
| gb.mergeStep1=Vytvo\u0159it novou v\u011btev pro prohl\u00e9dnut\u00ed zm\u011bn \u2014 spus\u0165te tot z va\u0161eho projektov\u00e9ho adres\u00e1\u0159e | ||
| gb.mergeStep2=P\u0159evz\u00edt navrhovan\u00e9 zm\u011bny a prozkoumat | ||
| gb.ptCheckout=St\u00e1hnout a p\u0159ekontrolovat aktu\u00e1ln\u00ed sadu zm\u011bn do posuzovac\u00ed v\u011btve | ||
| gb.ptMerge=St\u00e1hnout a slou\u010dit aktu\u00e1ln\u00ed sadu zm\u011bn do va\u0161\u00ed lok\u00e1ln\u00ed v\u011btve | ||
| gb.ptDescription1=Barnum je pomocn\u00edk v p\u0159\u00edkazov\u00e9 \u0159\u00e1dce pro Git, kter\u00fd zjednodu\u0161uje syntaxi pro pr\u00e1ci s \u00fakly a sadami zm\u011bn v Gitblitu. | ||
| gb.ptSimplifiedCollaboration=zjednodu\u0161en\u00e1 syntaxe pro spolupr\u00e1ci | ||
| gb.ptDescription2=Barnum vy\u017eaduje Python 3 a nativn\u00ed Git. B\u011b\u017e\u00ed na Windows, Linux a Mac OS X. | ||
| gb.reviewedPatchsetRev=posouzen\u00e9 sady zm\u011bn {0} revize {1}: {2} | ||
| gb.hasNotReviewed=neposoudil | ||
| gb.mergeToDescription=v\u00fdchoz\u00ed integra\u010dn\u00ed v\u011btev pro slu\u010dov\u00e1n\u00ed sad zm\u011bn z \u00fakol\u016f | ||
| gb.mergeTypeDescription=slou\u010dit \u00fakol s p\u0159esunem vp\u0159ed, pokud je pot\u0159eba, nebo v\u017edy s commitem o slou\u010den\u00ed do integra\u010dn\u00ed v\u011btve | ||
| gb.youDoNotHaveClonePermission=Nem\u00e1te opr\u00e1vn\u011bn\u00ed pro klonov\u00e1n\u00ed tohoto repozit\u00e1\u0159e. | ||
| gb.milestoneDeleteFailed=Selhalo odstran\u011bn\u00ed mezn\u00edku ''{0}''! | ||
| gb.notifyChangedOpenTickets=pos\u00edlat upozorn\u011bn\u00ed na zm\u011bn\u011bn\u00e9 otev\u0159en\u00e9 \u00fakoly | ||
| gb.accessPolicyDescription=Vyberte politiky p\u0159\u00edstupu pro ovl\u00e1d\u00e1n\u00ed viditelnosti a pr\u00e1vech gitu. | ||
| gb.namedPushPolicyDescription=Kdokoli m\u016f\u017ee vid\u011bt a klonovat tento repozit\u00e1\u0159. M\u016f\u017eete vybrat, kdo m\u016f\u017ee prov\u00e1d\u011bt push. | ||
| gb.viewPolicyDescription=M\u016f\u017eete vybrat, kdo m\u016f\u017ee vid\u011bt, klonovat a prov\u00e1d\u011bt push do tohoto repozit\u00e1\u0159e. | ||
| gb.initialCommitDescription=Umo\u017en\u00ed v\u00e1m okam\u017eit\u00fd <code>git clone</code> tohoto repozit\u00e1\u0159e. P\u0159esko\u010dte tento krok, pokud jste ji\u017e spustili lok\u00e1ln\u011b <code>git init</code>. | ||
| gb.initWithGitignoreDescription=Vlo\u017e\u00ed konfigura\u010dn\u00ed soubor, kter\u00fd bude \u0159\u00edkat klient\u016fm Git, jak\u00e9 soubory, adres\u00e1\u0159e nebo vzory ignorovat. | ||
| gb.pleaseSelectGitIgnore=Pros\u00edm, vyberte soubor .gitignore | ||
| gb.ownersDescription=Vlastn\u00edci mohou spravovat v\u0161echna nastaven\u00ed repozit\u00e1\u0159e, ale nemohou p\u0159ejmenovat repozit\u00e1\u0159, pokud to nen\u00ed jejich osobn\u00ed repozit\u00e1\u0159. | ||
| gb.userPermissionsDescription=M\u016f\u017eete nastavit individu\u00e1ln\u00ed u\u017eivatelsk\u00e1 opr\u00e1vn\u011bn\u00ed. Toto nastaven\u00ed potla\u010d\u00ed pr\u00e1va t\u00fdmu nebo regex. | ||
| gb.teamPermissionsDescription=M\u016f\u017eete nastavit individu\u00e1ln\u00ed opr\u00e1vn\u011bn\u00ed t\u00fdmu. Toto nastaven\u00ed potla\u010d\u00ed opr\u00e1vn\u011bn\u00ed regex. | ||
| gb.receiveSettingsDescription=Nastaven\u00ed p\u0159\u00edjmu ovl\u00e1d\u00e1 prov\u00e1d\u011bn\u00ed operace push s repozit\u00e1\u0159em. | ||
| gb.preReceiveDescription=H\u00e1\u010dky p\u0159ed p\u0159ijet\u00edm budou vykon\u00e1ny po p\u0159ijet\u00ed commitu, ale <em>P\u0158EDT\u00cdM</em>, ne\u017e budou aktualizov\u00e1ny refs.<p>Toto je vhodn\u00fd h\u00e1\u010dek pro zam\u00edtnut\u00ed operace push.</p> | ||
| gb.postReceiveDescription=H\u00e1\u010dky po p\u0159ijet\u00ed budou vykon\u00e1ny po p\u0159ijet\u00ed commitu, ale <em>POTOM</em>, co budou aktualizov\u00e1ny refs.<p>Toto je vhodn\u00fd h\u00e1\u010dek pro upozorn\u011bn\u00ed, spu\u0161t\u011bn\u00ed sestaven\u00ed, atd.</p> | ||
| gb.federationStrategyDescription=Ovl\u00e1dat jestli a jak federovat tento repozit\u00e1\u0159 s jim\u00fdn Gitblit. | ||
| gb.federationSetsDescription=Tento repozit\u00e1\u0159 bude obsa\u017een ve vybran\u00fdch federac\u00edch. | ||
| gb.originDescription=URL, ze kter\u00e9ho byl tento repozit\u00e1\u0159 klonov\u00e1no. | ||
| gb.garbageCollectionDescription=\u00daklid zabal\u00ed voln\u00e9 objekty, kter\u00e9 byly nahr\u00e1ny klienty pomoc\u00ed push a odstran\u00ed obj\u011bkty, na kter\u00e9 v repozit\u00e1\u0159i nevede reference. | ||
| gb.preferences=preference | ||
| gb.accountPreferencesDescription=Stanovte preference va\u0161eho \u00fa\u010dtu | ||
| gb.languagePreferenceDescription=Vyberte v\u00e1mi preferovan\u00fd p\u0159eklad pro Gitblit | ||
| gb.emailMeOnMyTicketChangesDescription=Pos\u00edlat mi e-malov\u00e9 notifikace o zm\u011bn\u00e1ch, kter\u00e9 jsem provedl na \u00fakolech | ||
| gb.sshKeys=SSH kl\u00ed\u010de | ||
| gb.sshKeysDescription=SSH ve\u0159ejn\u00fd kl\u00ed\u010d je bezpe\u010dn\u00e1 alternativa k autentifikaci pomoc\u00ed hesla | ||
| gb.sshKeyPermissionDescription=Ur\u010dete p\u0159\u00edstupov\u00e1 pr\u00e1va pro SSH kl\u00ed\u010d | ||
| gb.transportPreferenceDescription=Vyberte transport, kter\u00fd preferujete pro klonov\u00e1n\u00ed | ||
| gb.sortHighestSeverity=nejvy\u0161\u0161\u00ed z\u00e1va\u017enost | ||
| gb.diffFileDiffTooLarge=Rozd\u00edl je p\u0159\u00edli\u0161 velk\u00fd | ||
| gb.missingIntegrationBranchMore=C\u00edlov\u00e1 integra\u010dn\u00ed v\u011btev v repozit\u00e1\u0159i neexistuje! | ||
| gb.queryHelp=Standardn\u00ed syntaxe dotaz\u016f je podporov\u00e1na.<p/><p/>Pod\u00edvejte se pros\u00edm na ${querySyntax} pro detaily. | ||
| gb.querySyntax=Lucene Query Parser Syntax | ||
| gb.diffTruncated=Diff o\u0159iznut po p\u0159edchoz\u00edm souboru | ||
| gb.blame=blame | ||
| gb.imgdiffSubtract=Od\u010d\u00edtat (\u010dern\u00e1 = stejn\u00e9l) | ||
| gb.statusChangedOn=status zm\u011bn\u011bn na | ||
| gb.statusChangedBy=status zm\u011bnil | ||
| gb.fileCommitted=\u00dasp\u011b\u0161n\u011b commitov\u00e1no {0}. | ||
| gb.due=z d\u016fvodu | ||
| gb.mailingLists=e-mailov\u00e1 konference | ||
| gb.blinkComparator=Blink kompar\u00e1tor | ||
| palette.available=Dostupn\u00ed | ||
| palette.selected=Vybran\u00ed |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| <html xmlns="http://www.w3.org/1999/xhtml" | ||
| xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | ||
| xml:lang="en" | ||
| lang="en"> | ||
| <body> | ||
| <wicket:extend> | ||
| <div class="container"> | ||
| <div class="markdown"> | ||
| <div class="row"> | ||
| <div class="span10 offset1"> | ||
| <h3><center>Prázdné úložiště</center></h3> | ||
| <div class="alert alert-info"> | ||
| <span wicket:id="repository" style="font-weight: bold;">[repository]</span> je prázdné úložiště a nemůže být prohlíženo pomocí Gitblitu. | ||
| <p></p> | ||
| Prosím použijte push pro nahrání nějakách commitů do <span wicket:id="pushurl"></span> | ||
| <hr/> | ||
| Poté, co použijete push pro nahrání commitů, můžete <b>aktualizovat</b> tuto stránku pro prohlížení vašeho úložiště. | ||
| </div> | ||
| <h3><center>Vytvoření nového úložiště z příkazové řádky</center></h3> | ||
| <pre wicket:id="createSyntax"></pre> | ||
| <h3><center>Nahrání existujícího úložiště s použitím push</center></h3> | ||
| <pre wicket:id="existingSyntax"></pre> | ||
| <div class="span8 offset1"> | ||
| <h2><center>Naučit se Git</center></h2> | ||
| <p>Pokud si nejste jisti jak použít tuto informaci, zvažte prohlédnutí <a href="http://book.git-scm.com">Git komunitní knihy</a>, abyste lépe porozuměli, jak použít Git.</p> | ||
| <h4>Open Source Git klienti</h4> | ||
| <table> | ||
| <tbody> | ||
| <tr><td><a href="http://git-scm.com">Git</a></td><td>oficiální, z příkazové řádky</td></tr> | ||
| <tr><td><a href="http://tortoisegit.googlecode.com">TortoiseGit</a></td><td>Integrace do Průzkumníka Windows (vyžaduje oficiální řádkový Git)</td></tr> | ||
| <tr><td><a href="http://eclipse.org/egit">Eclipse/EGit</a></td><td>Git pro Eclipse IDE (založený na JGit, jako Gitblit)</td></tr> | ||
| <tr><td><a href="https://code.google.com/p/gitextensions/">Git Extensions</a></td><td>C# frontend pro Git, který obsahuje integraci do Průzkumníka Windows a do Visual Studia</td></tr> | ||
| <tr><td><a href="http://rowanj.github.io/gitx/">GitX-dev</a></td><td>Mac OS X Git klient</td></tr> | ||
| </tbody> | ||
| </table> | ||
| <h4>Komerční/Closed-Source Git klienti</h4> | ||
| <table> | ||
| <tbody> | ||
| <tr><td><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a></td><td>Na Javě založený klient pro Git and Mercurial pro Windows, Mac a Linux</td></tr> | ||
| <tr><td><a href="http://www.sourcetreeapp.com/">SourceTree</a></td><td>Volný Git a Mercurial klient pro Windows a Mac</td></tr> | ||
| <tr><td><a href="http://www.git-tower.com/">Tower</a></td><td>Mac OS X Git klient</td></tr> | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </wicket:extend> | ||
| </body> | ||
| </html> |
| package com.gitblit.wicket.pages; | ||
| import java.io.Serializable; | ||
| public class Language implements Serializable { | ||
| private static final long serialVersionUID = 1L; | ||
| final String name; | ||
| final String code; | ||
| public Language(String name, String code) { | ||
| this.name = name; | ||
| this.code = code; | ||
| } | ||
| @Override | ||
| public String toString() { | ||
| return name + " (" + code + ")"; | ||
| } | ||
| } |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| <html xmlns="http://www.w3.org/1999/xhtml" | ||
| xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | ||
| xml:lang="en" lang="en"> | ||
| <body> | ||
| <wicket:panel> | ||
| <tr style="background-color: #bbb" wicket:id="nodeHeader" data-row-type="folder"></tr> | ||
| <wicket:container wicket:id="repositories"> | ||
| <tr wicket:id="rowContent" data-row-type="repo"> | ||
| <td wicket:id="firstColumn" class="left" | ||
| style="padding-left: 3px;"> | ||
| <div style="margin-left: 7px; width: 8px;display: inline-block;float: left;" | ||
| wicket:id="depth"> </div> | ||
| <span wicket:id="repoIcon"></span><span | ||
| style="padding-left: 3px;" wicket:id="repositoryName">[repository | ||
| name]</span> | ||
| </td> | ||
| <td class="hidden-phone"><span class="list" | ||
| wicket:id="repositoryDescription">[repository description]</span></td> | ||
| <td class="hidden-tablet hidden-phone author"><span | ||
| wicket:id="repositoryOwner">[repository owner]</span></td> | ||
| <td class="hidden-phone" | ||
| style="text-align: right; padding-right: 10px;"><img | ||
| class="inlineIcon" wicket:id="sparkleshareIcon" /><img | ||
| class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" | ||
| wicket:id="federatedIcon" /><img class="inlineIcon" | ||
| wicket:id="accessRestrictionIcon" /></td> | ||
| <td><span wicket:id="repositoryLastChange">[last change]</span></td> | ||
| <td class="rightAlign hidden-phone" | ||
| style="text-align: right; padding-right: 15px;"><span | ||
| style="font-size: 0.8em;" wicket:id="repositorySize">[repository | ||
| size]</span></td> | ||
| </tr> | ||
| </wicket:container> | ||
| <tr wicket:id="subFolders"> | ||
| <span wicket:id="rowContent"></span> | ||
| </tr> | ||
| <wicket:fragment wicket:id="emptyFragment"> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="repoIconFragment"> | ||
| <span class="octicon octicon-centered octicon-repo"></span> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="mirrorIconFragment"> | ||
| <span class="octicon octicon-centered octicon-mirror"></span> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="forkIconFragment"> | ||
| <span class="octicon octicon-centered octicon-repo-forked"></span> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="cloneIconFragment"> | ||
| <span class="octicon octicon-centered octicon-repo-push" | ||
| wicket:message="title:gb.workingCopyWarning"></span> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="tableGroupMinusCollapsible"> | ||
| <i title="Click to expand/collapse" class="fa fa-minus-square-o table-group-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="tableGroupPlusCollapsible"> | ||
| <i title="Click to expand/collapse" class="fa fa-plus-square-o table-group-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="tableAllCollapsible"> | ||
| <i title="Click to expand all" | ||
| class="fa fa-plus-square-o table-openall-collapsible" | ||
| aria-hidden="true" style="padding-right: 3px; cursor: pointer;"></i> | ||
| <i title="Click to collapse all" | ||
| class="fa fa-minus-square-o table-closeall-collapsible" | ||
| aria-hidden="true" style="padding-right: 3px; cursor: pointer;"></i> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="groupRepositoryHeader"> | ||
| <tr> | ||
| <th class="left"><span wicket:id="allCollapsible"></span> <img | ||
| style="vertical-align: middle;" src="git-black-16x16.png" /> <wicket:message | ||
| key="gb.repository">Repository</wicket:message></th> | ||
| <th class="hidden-phone"><span><wicket:message | ||
| key="gb.description">Description</wicket:message></span></th> | ||
| <th class="hidden-tablet hidden-phone"><span><wicket:message | ||
| key="gb.owner">Owner</wicket:message></span></th> | ||
| <th class="hidden-phone"></th> | ||
| <th><wicket:message key="gb.lastChange">Last Change</wicket:message></th> | ||
| <th class="right hidden-phone"></th> | ||
| </tr> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="groupRepositoryRow"> | ||
| <td wicket:id="firstColumn" style="" colspan="1"> | ||
| <div style="margin-left:6px; width: 10px; display: inline-block;float: left;" | ||
| wicket:id="depth"> </div> | ||
| <span | ||
| wicket:id="groupCollapsible"></span><span wicket:id="groupName">[group | ||
| name]</span></td> | ||
| <td colspan="6" style="padding: 2px;"><span class="hidden-phone" | ||
| style="font-weight: normal; color: #666;" | ||
| wicket:id="groupDescription">[description]</span></td> | ||
| </wicket:fragment> | ||
| </wicket:panel> | ||
| </body> | ||
| </html> |
| package com.gitblit.wicket.panels; | ||
| import java.util.Map; | ||
| import org.apache.wicket.Component; | ||
| import org.apache.wicket.PageParameters; | ||
| import org.apache.wicket.behavior.AttributeAppender; | ||
| import org.apache.wicket.markup.html.WebMarkupContainer; | ||
| import org.apache.wicket.markup.html.basic.Label; | ||
| import org.apache.wicket.markup.html.list.ListItem; | ||
| import org.apache.wicket.markup.html.list.ListView; | ||
| import org.apache.wicket.markup.html.panel.Fragment; | ||
| import org.apache.wicket.markup.repeater.RepeatingView; | ||
| import org.apache.wicket.model.IModel; | ||
| import org.apache.wicket.model.Model; | ||
| import com.gitblit.Constants.AccessRestrictionType; | ||
| import com.gitblit.Keys; | ||
| import com.gitblit.models.RepositoryModel; | ||
| import com.gitblit.models.TreeNodeModel; | ||
| import com.gitblit.models.UserModel; | ||
| import com.gitblit.utils.ArrayUtils; | ||
| import com.gitblit.utils.ModelUtils; | ||
| import com.gitblit.utils.StringUtils; | ||
| import com.gitblit.wicket.WicketUtils; | ||
| import com.gitblit.wicket.pages.BasePage; | ||
| import com.gitblit.wicket.pages.ProjectPage; | ||
| import com.gitblit.wicket.pages.SummaryPage; | ||
| import com.gitblit.wicket.pages.UserPage; | ||
| public class NestedRepositoryTreePanel extends BasePanel { | ||
| private static final long serialVersionUID = 1L; | ||
| public NestedRepositoryTreePanel(final String wicketId, final IModel<TreeNodeModel> model, final Map<AccessRestrictionType, String> accessRestrictionTranslations, final boolean linksActive) { | ||
| super(wicketId); | ||
| final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true); | ||
| final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true); | ||
| final TreeNodeModel node = model.getObject(); | ||
| Fragment nodeHeader = new Fragment("nodeHeader", "groupRepositoryRow", this); | ||
| add(nodeHeader); | ||
| WebMarkupContainer firstColumn = new WebMarkupContainer("firstColumn"); | ||
| nodeHeader.add(firstColumn); | ||
| RepeatingView depth = new RepeatingView("depth"); | ||
| for(int i=0; i<node.getDepth();i++) { | ||
| depth.add(new WebMarkupContainer(depth.newChildId())); | ||
| } | ||
| firstColumn.add(depth); | ||
| firstColumn.add(new Fragment("groupCollapsible", "tableGroupMinusCollapsible", this)); | ||
| if(node.getParent()!=null) { | ||
| addChildOfNodeIdCssClassesToRow(nodeHeader, node.getParent()); | ||
| } | ||
| nodeHeader.add(new AttributeAppender("data-node-id", Model.of(node.hashCode()), " ")); | ||
| String name = node.getName(); | ||
| if (name.startsWith(ModelUtils.getUserRepoPrefix())) { | ||
| // user page | ||
| String username = ModelUtils.getUserNameFromRepoPath(name); | ||
| UserModel user = app().users().getUserModel(username); | ||
| firstColumn.add(new LinkPanel("groupName", null, (user == null ? username : user.getDisplayName()), UserPage.class, WicketUtils.newUsernameParameter(username))); | ||
| nodeHeader.add(new Label("groupDescription", getString("gb.personalRepositories"))); | ||
| } else { | ||
| // project page | ||
| firstColumn.add(new LinkPanel("groupName", null, name, ProjectPage.class, WicketUtils.newProjectParameter(name))); | ||
| nodeHeader.add(new Label("groupDescription", "")); | ||
| } | ||
| WicketUtils.addCssClass(nodeHeader, "group collapsible tree"); | ||
| add(new ListView<RepositoryModel>("repositories", node.getRepositories()) { | ||
| private static final long serialVersionUID = 1L; | ||
| int counter = 0; | ||
| @Override | ||
| public boolean isVisible() { | ||
| return super.isVisible() && !node.getRepositories().isEmpty(); | ||
| } | ||
| @Override | ||
| protected void populateItem(ListItem<RepositoryModel> item) { | ||
| RepositoryModel entry = item.getModelObject(); | ||
| WebMarkupContainer rowContent = new WebMarkupContainer("rowContent"); | ||
| item.add(rowContent); | ||
| addChildOfNodeIdCssClassesToRow(rowContent, node); | ||
| WebMarkupContainer firstColumn = new WebMarkupContainer("firstColumn"); | ||
| rowContent.add(firstColumn); | ||
| RepeatingView depth = new RepeatingView("depth"); | ||
| for(int i=0; i<node.getDepth();i++) { | ||
| depth.add(new WebMarkupContainer(depth.newChildId())); | ||
| } | ||
| firstColumn.add(depth); | ||
| // show colored repository type icon | ||
| Fragment iconFragment; | ||
| if (entry.isMirror) { | ||
| iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this); | ||
| } else if (entry.isFork()) { | ||
| iconFragment = new Fragment("repoIcon", "forkIconFragment", this); | ||
| } else if (entry.isBare) { | ||
| iconFragment = new Fragment("repoIcon", "repoIconFragment", this); | ||
| } else { | ||
| iconFragment = new Fragment("repoIcon", "cloneIconFragment", this); | ||
| } | ||
| if (showSwatch) { | ||
| WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString())); | ||
| } | ||
| firstColumn.add(iconFragment); | ||
| // try to strip group name for less cluttered list | ||
| String repoName = StringUtils.getLastPathElement(entry.toString()); | ||
| if (linksActive) { | ||
| Class<? extends BasePage> linkPage = SummaryPage.class; | ||
| PageParameters pp = WicketUtils.newRepositoryParameter(entry.name); | ||
| firstColumn.add(new LinkPanel("repositoryName", "list", repoName, linkPage, pp)); | ||
| rowContent.add(new LinkPanel("repositoryDescription", "list", entry.description, linkPage, pp)); | ||
| } else { | ||
| // no links like on a federation page | ||
| firstColumn.add(new Label("repositoryName", repoName)); | ||
| rowContent.add(new Label("repositoryDescription", entry.description)); | ||
| } | ||
| if (entry.hasCommits) { | ||
| // Existing repository | ||
| rowContent.add(new Label("repositorySize", entry.size).setVisible(showSize)); | ||
| } else { | ||
| // New repository | ||
| rowContent.add(new Label("repositorySize", "<span class='empty'>(" + getString("gb.empty") + ")</span>").setEscapeModelStrings(false)); | ||
| } | ||
| if (entry.isSparkleshared()) { | ||
| rowContent.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png", getString("gb.isSparkleshared"))); | ||
| } else { | ||
| rowContent.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false)); | ||
| } | ||
| if (!entry.isMirror && entry.isFrozen) { | ||
| rowContent.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", getString("gb.isFrozen"))); | ||
| } else { | ||
| rowContent.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false)); | ||
| } | ||
| if (entry.isFederated) { | ||
| rowContent.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png", getString("gb.isFederated"))); | ||
| } else { | ||
| rowContent.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false)); | ||
| } | ||
| if (entry.isMirror) { | ||
| rowContent.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png", getString("gb.isMirror"))); | ||
| } else { | ||
| switch (entry.accessRestriction) { | ||
| case NONE: | ||
| rowContent.add(WicketUtils.newBlankImage("accessRestrictionIcon")); | ||
| break; | ||
| case PUSH: | ||
| rowContent.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction))); | ||
| break; | ||
| case CLONE: | ||
| rowContent.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction))); | ||
| break; | ||
| case VIEW: | ||
| rowContent.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction))); | ||
| break; | ||
| default: | ||
| rowContent.add(WicketUtils.newBlankImage("accessRestrictionIcon")); | ||
| } | ||
| } | ||
| String owner = ""; | ||
| if (!ArrayUtils.isEmpty(entry.owners)) { | ||
| // display first owner | ||
| for (String username : entry.owners) { | ||
| UserModel ownerModel = app().users().getUserModel(username); | ||
| if (ownerModel != null) { | ||
| owner = ownerModel.getDisplayName(); | ||
| break; | ||
| } | ||
| } | ||
| if (entry.owners.size() > 1) { | ||
| owner += ", ..."; | ||
| } | ||
| } | ||
| Label ownerLabel = new Label("repositoryOwner", owner); | ||
| WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners)); | ||
| rowContent.add(ownerLabel); | ||
| String lastChange; | ||
| if (entry.lastChange.getTime() == 0) { | ||
| lastChange = "--"; | ||
| } else { | ||
| lastChange = getTimeUtils().timeAgo(entry.lastChange); | ||
| } | ||
| Label lastChangeLabel = new Label("repositoryLastChange", lastChange); | ||
| rowContent.add(lastChangeLabel); | ||
| WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange)); | ||
| if (!StringUtils.isEmpty(entry.lastChangeAuthor)) { | ||
| WicketUtils.setHtmlTooltip(lastChangeLabel, getString("gb.author") + ": " + entry.lastChangeAuthor); | ||
| } | ||
| String clazz = counter % 2 == 0 ? "light" : "dark"; | ||
| WicketUtils.addCssClass(rowContent, clazz); | ||
| counter++; | ||
| } | ||
| }); | ||
| add(new ListView<TreeNodeModel>("subFolders", node.getSubFolders()) { | ||
| private static final long serialVersionUID = 1L; | ||
| @Override | ||
| protected void populateItem(ListItem<TreeNodeModel> item) { | ||
| item.add(new NestedRepositoryTreePanel("rowContent", item.getModel(), accessRestrictionTranslations, linksActive)); | ||
| } | ||
| @Override | ||
| public boolean isVisible() { | ||
| return super.isVisible() && !node.getSubFolders().isEmpty(); | ||
| } | ||
| }); | ||
| } | ||
| private void addChildOfNodeIdCssClassesToRow(Component row, TreeNodeModel parentNode) { | ||
| row.add(new AttributeAppender("class", Model.of("child-of-"+ parentNode.hashCode()), " ")); | ||
| if(parentNode.getParent() != null) { | ||
| addChildOfNodeIdCssClassesToRow(row, parentNode.getParent()); | ||
| } | ||
| } | ||
| } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| /** | ||
| * 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; | ||
| } |
| $(function() { | ||
| $('i.table-group-collapsible') | ||
| .click(function(){ | ||
| var nodeId = $(this).closest('tr.group.collapsible.tree').data('nodeId'); | ||
| if(nodeId!==undefined){ | ||
| //we are in tree view | ||
| if($(this).hasClass('fa-minus-square-o')){ | ||
| $(this).closest('tr.group.collapsible.tree').nextAll('tr.child-of-'+nodeId).hide(); | ||
| $(this).closest('tr.group.collapsible.tree').nextAll('tr.child-of-'+nodeId).addClass('hidden-by-'+nodeId); | ||
| }else{ | ||
| $(this).closest('tr.group.collapsible.tree').nextAll('tr.child-of-'+nodeId).removeClass('hidden-by-'+nodeId); | ||
| $(this).closest('tr.group.collapsible.tree').nextAll('tr.child-of-'+nodeId+':not([class*="hidden-by-"])').show(); | ||
| } | ||
| }else{ | ||
| $(this).closest('tr.group.collapsible').nextUntil('tr.group.collapsible').toggle(); | ||
| } | ||
| $(this).toggleClass('fa-minus-square-o'); | ||
| $(this).toggleClass('fa-plus-square-o'); | ||
| }); | ||
| $('i.table-openall-collapsible') | ||
| .click(function(){ | ||
| $('tr.group.collapsible').first().find('i').addClass('fa-minus-square-o'); | ||
| $('tr.group.collapsible').first().find('i').removeClass('fa-plus-square-o'); | ||
| $('tr.group.collapsible').first().nextAll('tr:not(tr.group.collapsible),tr.group.collapsible.tree').show(); | ||
| $('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').addClass('fa-minus-square-o'); | ||
| $('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').removeClass('fa-plus-square-o'); | ||
| var nodeId = $('tr.group.collapsible.tree').data('nodeId'); | ||
| if(nodeId!==undefined){ | ||
| //we are in tree view | ||
| $('tr[class*="child-of-"]').removeClass(function(index, className){ | ||
| return (className.match(/\hidden-by-\S+/g)||[]).join(' '); | ||
| }); | ||
| $('tr.group.collapsible > i').addClass('fa-minus-square-o'); | ||
| $('tr.group.collapsible > i').removeClass('fa-plus-square-o'); | ||
| } | ||
| }); | ||
| $('i.table-closeall-collapsible') | ||
| .click(function(){ | ||
| $('tr.group.collapsible').first().find('i').addClass('fa-plus-square-o'); | ||
| $('tr.group.collapsible').first().find('i').removeClass('fa-minus-square-o'); | ||
| $('tr.group.collapsible').first().nextAll('tr:not(tr.group.collapsible),tr.group.collapsible.tree').hide(); | ||
| $('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').addClass('fa-plus-square-o'); | ||
| $('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').removeClass('fa-minus-square-o'); | ||
| var nodeId = $('tr.group.collapsible.tree').first().data('nodeId'); | ||
| if(nodeId!==undefined){ | ||
| //we are in tree view, hide all sub trees | ||
| $('tr[class*="child-of-"]').each(function(){ | ||
| var row = $(this); | ||
| var classList = row.attr('class').split('/\s+/'); | ||
| $.each(classList, function(index, c){ | ||
| if(c.match(/^child-of-*/)){ | ||
| row.addClass(c.replace(/^child-of-(\d)/, 'hidden-by-$1')); | ||
| } | ||
| }); | ||
| }); | ||
| $('tr.group.collapsible i').addClass('fa-plus-square-o'); | ||
| $('tr.group.collapsible i').removeClass('fa-minus-square-o'); | ||
| } | ||
| }); | ||
| $( document ).ready(function() { | ||
| if($('tr.group.collapsible').first().find('i').hasClass('fa-plus-square-o')) { | ||
| $('tr.group.collapsible').first().nextAll('tr:not(tr.group.collapsible),tr.group.collapsible.tree').hide(); | ||
| } | ||
| }); | ||
| }); |
Sorry, the diff of this file is not supported yet
| /*.git/ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| commit.first=192cdede1cc81da7b393aeb7aba9f88998b04713 | ||
| commit.second=8caad51 | ||
| commit.fifth=55f6796044dc51f0bb9301f07920f0fb64c3d12c | ||
| commit.fifteen=5ebfaca | ||
| commit.added=192cded | ||
| commit.changed=b2c50ce | ||
| commit.deleted=8613bee10bde27a0fbaca66447cdc3f0f9483365 | ||
| commits.since_20190605=10 | ||
| users.byEmail=11 | ||
| users.byName=10 | ||
| files.top=14 | ||
| files.C.top=2 | ||
| files.C.KnR=1 | ||
| files.Cpp=1 |
Sorry, the diff of this file is too big to display
| /* | ||
| * Copyright 2017 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.service; | ||
| import static org.junit.Assert.*; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.nio.file.Path; | ||
| import org.junit.Rule; | ||
| import org.junit.Test; | ||
| import org.junit.rules.TemporaryFolder; | ||
| import com.gitblit.utils.LuceneIndexStore; | ||
| /** | ||
| * @author Florian Zschocke | ||
| * | ||
| */ | ||
| public class LuceneRepoIndexStoreTest | ||
| { | ||
| private static final int LUCENE_VERSION = LuceneIndexStore.LUCENE_CODEC_VERSION; | ||
| private static final String LUCENE_DIR = "lucene"; | ||
| @Rule | ||
| public TemporaryFolder baseFolder = new TemporaryFolder(); | ||
| private String getIndexDir(int version) | ||
| { | ||
| return version + "_" + LUCENE_VERSION; | ||
| } | ||
| private String getLuceneIndexDir(int version) | ||
| { | ||
| return LUCENE_DIR + "/" + version + "_" + LUCENE_VERSION; | ||
| } | ||
| @Test | ||
| public void testGetConfigFile() throws IOException | ||
| { | ||
| int version = 1; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| LuceneRepoIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| File confFile= li.getConfigFile(); | ||
| File luceneDir = new File(repositoryFolder, getLuceneIndexDir(version) + "/gb_lucene.conf"); | ||
| assertEquals(luceneDir, confFile); | ||
| } | ||
| @Test | ||
| public void testCreate() | ||
| { | ||
| int version = 0; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| File luceneDir = new File(repositoryFolder, getLuceneIndexDir(version)); | ||
| assertFalse("Precondition failure: directory exists already", new File(repositoryFolder, LUCENE_DIR).exists()); | ||
| assertFalse("Precondition failure: directory exists already", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| li.create(); | ||
| assertTrue(luceneDir.exists()); | ||
| assertTrue(luceneDir.isDirectory()); | ||
| } | ||
| @Test | ||
| public void testCreateIndexDir() | ||
| { | ||
| int version = 7777; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| try { | ||
| baseFolder.newFolder(LUCENE_DIR); | ||
| } catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| File luceneDir = new File(repositoryFolder, getLuceneIndexDir(version)); | ||
| assertTrue("Precondition failure: directory does not exist", new File(repositoryFolder, LUCENE_DIR).exists()); | ||
| assertFalse("Precondition failure: directory exists already", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| li.create(); | ||
| assertTrue(luceneDir.exists()); | ||
| assertTrue(luceneDir.isDirectory()); | ||
| // Make sure nothing else was created. | ||
| assertEquals(0, luceneDir.list().length); | ||
| assertEquals(1, luceneDir.getParentFile().list().length); | ||
| } | ||
| @Test | ||
| public void testCreateIfNecessary() | ||
| { | ||
| int version = 7777888; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| File luceneDir = null; | ||
| try { | ||
| luceneDir = baseFolder.newFolder(LUCENE_DIR, getIndexDir(version)); | ||
| } catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| assertTrue("Precondition failure: directory does not exist", new File(repositoryFolder, LUCENE_DIR).exists()); | ||
| assertTrue("Precondition failure: directory does not exist", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| li.create(); | ||
| assertTrue(luceneDir.exists()); | ||
| assertTrue(luceneDir.isDirectory()); | ||
| // Make sure nothing else was created. | ||
| assertEquals(0, luceneDir.list().length); | ||
| assertEquals(1, luceneDir.getParentFile().list().length); | ||
| } | ||
| @Test | ||
| public void testDelete() | ||
| { | ||
| int version = 111222333; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| File luceneDir = null; | ||
| try { | ||
| luceneDir = baseFolder.newFolder(LUCENE_DIR, getIndexDir(version)); | ||
| } catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| assertTrue("Precondition failure: directory does not exist", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| assertTrue(li.delete()); | ||
| assertFalse(luceneDir.exists()); | ||
| assertTrue(new File(repositoryFolder, LUCENE_DIR).exists()); | ||
| } | ||
| @Test | ||
| public void testDeleteNotExist() | ||
| { | ||
| int version = 0; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| try { | ||
| baseFolder.newFolder(LUCENE_DIR); | ||
| } catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| File luceneDir = new File(repositoryFolder, getLuceneIndexDir(version)); | ||
| assertTrue("Precondition failure: directory does not exist", new File(repositoryFolder, LUCENE_DIR).exists()); | ||
| assertFalse("Precondition failure: directory does exist", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| assertTrue(li.delete()); | ||
| assertFalse(luceneDir.exists()); | ||
| assertTrue(new File(repositoryFolder, LUCENE_DIR).exists()); | ||
| } | ||
| @Test | ||
| public void testDeleteWithFiles() | ||
| { | ||
| int version = 5; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| File luceneFolder = new File(baseFolder.getRoot(), LUCENE_DIR); | ||
| File luceneDir = null; | ||
| File otherDir = new File(luceneFolder, version + "_10"); | ||
| File dbFile = null; | ||
| try { | ||
| luceneDir = baseFolder.newFolder(LUCENE_DIR, getIndexDir(version)); | ||
| File file = new File(luceneDir, "_file1"); | ||
| file.createNewFile(); | ||
| file = new File(luceneDir, "_file2.db"); | ||
| file.createNewFile(); | ||
| file = new File(luceneDir, "conf.conf"); | ||
| file.createNewFile(); | ||
| otherDir.mkdirs(); | ||
| dbFile = new File(otherDir, "_file2.db"); | ||
| dbFile.createNewFile(); | ||
| file = new File(otherDir, "conf.conf"); | ||
| file.createNewFile(); | ||
| } | ||
| catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| assertTrue("Precondition failure: index directory does not exist", luceneDir.exists()); | ||
| assertTrue("Precondition failure: other index directory does not exist", otherDir.exists()); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| li.delete(); | ||
| assertFalse(luceneDir.exists()); | ||
| assertTrue(luceneFolder.exists()); | ||
| assertTrue(otherDir.exists()); | ||
| assertTrue(dbFile.exists()); | ||
| } | ||
| @Test | ||
| public void testGetPath() throws IOException | ||
| { | ||
| int version = 7; | ||
| File repositoryFolder = baseFolder.getRoot(); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(repositoryFolder, version); | ||
| Path dir = li.getPath(); | ||
| File luceneDir = new File(repositoryFolder, getLuceneIndexDir(version)); | ||
| assertEquals(luceneDir.toPath(), dir); | ||
| } | ||
| @Test | ||
| public void testHasIndex() throws IOException | ||
| { | ||
| int version = 0; | ||
| File luceneFolder = new File(baseFolder.getRoot(), "lucene"); | ||
| LuceneIndexStore li = new LuceneRepoIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| baseFolder.newFolder("lucene"); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| File luceneDir = baseFolder.newFolder("lucene", getIndexDir(version)); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| new File(luceneDir, "write.lock").createNewFile(); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| new File(luceneDir, "segments_1").createNewFile(); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| System.out.println("Check " + luceneDir); | ||
| assertTrue(li.hasIndex()); | ||
| } | ||
| } |
| /HelloworldKeys.java |
| package com.gitblit.tests; | ||
| import java.io.File; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.EnumSet; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import org.apache.commons.io.FileUtils; | ||
| import org.junit.AfterClass; | ||
| import org.junit.Before; | ||
| import org.junit.BeforeClass; | ||
| import org.junit.Rule; | ||
| import org.junit.rules.TemporaryFolder; | ||
| import org.junit.runners.Parameterized.Parameter; | ||
| import org.junit.runners.Parameterized.Parameters; | ||
| import com.gitblit.Keys; | ||
| import com.gitblit.tests.mock.MemorySettings; | ||
| import com.unboundid.ldap.listener.InMemoryDirectoryServer; | ||
| import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; | ||
| import com.unboundid.ldap.listener.InMemoryDirectoryServerSnapshot; | ||
| import com.unboundid.ldap.listener.InMemoryListenerConfig; | ||
| import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedRequest; | ||
| import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedResult; | ||
| import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchEntry; | ||
| import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchRequest; | ||
| import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; | ||
| import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSimpleBindResult; | ||
| import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; | ||
| import com.unboundid.ldap.sdk.BindRequest; | ||
| import com.unboundid.ldap.sdk.BindResult; | ||
| import com.unboundid.ldap.sdk.LDAPException; | ||
| import com.unboundid.ldap.sdk.LDAPResult; | ||
| import com.unboundid.ldap.sdk.OperationType; | ||
| import com.unboundid.ldap.sdk.ResultCode; | ||
| import com.unboundid.ldap.sdk.SimpleBindRequest; | ||
| /** | ||
| * Base class for Unit (/Integration) tests that test going against an | ||
| * in-memory UnboundID LDAP server. | ||
| * | ||
| * This base class creates separate in-memory LDAP servers for different scenarios: | ||
| * - ANONYMOUS: anonymous bind to LDAP. | ||
| * - DS_MANAGER: The DIRECTORY_MANAGER is set as DN to bind as an admin. | ||
| * Normal users are prohibited to search the DS, they can only bind. | ||
| * - USR_MANAGER: The USER_MANAGER is set as DN to bind as an admin. | ||
| * This account can only search users but not groups. Normal users can search groups. | ||
| * | ||
| * @author Florian Zschocke | ||
| * | ||
| */ | ||
| public abstract class LdapBasedUnitTest extends GitblitUnitTest { | ||
| protected static final String RESOURCE_DIR = "src/test/resources/ldap/"; | ||
| private static final String DIRECTORY_MANAGER = "cn=Directory Manager"; | ||
| private static final String USER_MANAGER = "cn=UserManager"; | ||
| protected static final String ACCOUNT_BASE = "OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"; | ||
| private static final String GROUP_BASE = "OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain"; | ||
| protected static final String DN_USER_ONE = "CN=UserOne,OU=US," + ACCOUNT_BASE; | ||
| protected static final String DN_USER_TWO = "CN=UserTwo,OU=US," + ACCOUNT_BASE; | ||
| protected static final String DN_USER_THREE = "CN=UserThree,OU=Canada," + ACCOUNT_BASE; | ||
| /** | ||
| * Enumeration of different test modes, representing different use scenarios. | ||
| * With ANONYMOUS anonymous binds are used to search LDAP. | ||
| * DS_MANAGER will use a DIRECTORY_MANAGER to search LDAP. Normal users are prohibited to search the DS. | ||
| * With USR_MANAGER, a USER_MANAGER account is used to search in LDAP. This account can only search users | ||
| * but not groups. Normal users can search groups, though. | ||
| * | ||
| */ | ||
| protected enum AuthMode { | ||
| ANONYMOUS, | ||
| DS_MANAGER, | ||
| USR_MANAGER; | ||
| private int ldapPort; | ||
| private InMemoryDirectoryServer ds; | ||
| private InMemoryDirectoryServerSnapshot dsSnapshot; | ||
| private BindTracker bindTracker; | ||
| void setLdapPort(int port) { | ||
| this.ldapPort = port; | ||
| } | ||
| int ldapPort() { | ||
| return this.ldapPort; | ||
| } | ||
| void setDS(InMemoryDirectoryServer ds) { | ||
| if (this.ds == null) { | ||
| this.ds = ds; | ||
| this.dsSnapshot = ds.createSnapshot(); | ||
| }; | ||
| } | ||
| InMemoryDirectoryServer getDS() { | ||
| return ds; | ||
| } | ||
| void setBindTracker(BindTracker bindTracker) { | ||
| this.bindTracker = bindTracker; | ||
| } | ||
| BindTracker getBindTracker() { | ||
| return bindTracker; | ||
| } | ||
| void restoreSnapshot() { | ||
| ds.restoreSnapshot(dsSnapshot); | ||
| } | ||
| } | ||
| @Parameter | ||
| public AuthMode authMode = AuthMode.ANONYMOUS; | ||
| @Rule | ||
| public TemporaryFolder folder = new TemporaryFolder(); | ||
| protected File usersConf; | ||
| protected MemorySettings settings; | ||
| /** | ||
| * Run the tests with each authentication scenario once. | ||
| */ | ||
| @Parameters(name = "{0}") | ||
| public static Collection<Object[]> data() { | ||
| return Arrays.asList(new Object[][] { {AuthMode.ANONYMOUS}, {AuthMode.DS_MANAGER}, {AuthMode.USR_MANAGER} }); | ||
| } | ||
| /** | ||
| * Create three different in memory DS. | ||
| * | ||
| * Each DS has a different configuration: | ||
| * The first allows anonymous binds. | ||
| * The second requires authentication for all operations. It will only allow the DIRECTORY_MANAGER account | ||
| * to search for users and groups. | ||
| * The third one is like the second, but it allows users to search for users and groups, and restricts the | ||
| * USER_MANAGER from searching for groups. | ||
| */ | ||
| @BeforeClass | ||
| public static void ldapInit() throws Exception { | ||
| InMemoryDirectoryServer ds; | ||
| InMemoryDirectoryServerConfig config = createInMemoryLdapServerConfig(AuthMode.ANONYMOUS); | ||
| config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("anonymous")); | ||
| ds = createInMemoryLdapServer(config); | ||
| AuthMode.ANONYMOUS.setDS(ds); | ||
| AuthMode.ANONYMOUS.setLdapPort(ds.getListenPort("anonymous")); | ||
| config = createInMemoryLdapServerConfig(AuthMode.DS_MANAGER); | ||
| config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("ds_manager")); | ||
| config.setAuthenticationRequiredOperationTypes(EnumSet.allOf(OperationType.class)); | ||
| ds = createInMemoryLdapServer(config); | ||
| AuthMode.DS_MANAGER.setDS(ds); | ||
| AuthMode.DS_MANAGER.setLdapPort(ds.getListenPort("ds_manager")); | ||
| config = createInMemoryLdapServerConfig(AuthMode.USR_MANAGER); | ||
| config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("usr_manager")); | ||
| config.setAuthenticationRequiredOperationTypes(EnumSet.allOf(OperationType.class)); | ||
| ds = createInMemoryLdapServer(config); | ||
| AuthMode.USR_MANAGER.setDS(ds); | ||
| AuthMode.USR_MANAGER.setLdapPort(ds.getListenPort("usr_manager")); | ||
| } | ||
| @AfterClass | ||
| public static void destroy() throws Exception { | ||
| for (AuthMode am : AuthMode.values()) { | ||
| am.getDS().shutDown(true); | ||
| } | ||
| } | ||
| public static InMemoryDirectoryServer createInMemoryLdapServer(InMemoryDirectoryServerConfig config) throws Exception { | ||
| InMemoryDirectoryServer imds = new InMemoryDirectoryServer(config); | ||
| imds.importFromLDIF(true, RESOURCE_DIR + "sampledata.ldif"); | ||
| imds.startListening(); | ||
| return imds; | ||
| } | ||
| public static InMemoryDirectoryServerConfig createInMemoryLdapServerConfig(AuthMode authMode) throws Exception { | ||
| InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=MyDomain"); | ||
| config.addAdditionalBindCredentials(DIRECTORY_MANAGER, "password"); | ||
| config.addAdditionalBindCredentials(USER_MANAGER, "passwd"); | ||
| config.setSchema(null); | ||
| authMode.setBindTracker(new BindTracker()); | ||
| config.addInMemoryOperationInterceptor(authMode.getBindTracker()); | ||
| config.addInMemoryOperationInterceptor(new AccessInterceptor(authMode)); | ||
| return config; | ||
| } | ||
| @Before | ||
| public void setupBase() throws Exception { | ||
| authMode.restoreSnapshot(); | ||
| authMode.getBindTracker().reset(); | ||
| usersConf = folder.newFile("users.conf"); | ||
| FileUtils.copyFile(new File(RESOURCE_DIR + "users.conf"), usersConf); | ||
| settings = getSettings(); | ||
| } | ||
| protected InMemoryDirectoryServer getDS() { | ||
| return authMode.getDS(); | ||
| } | ||
| protected MemorySettings getSettings() { | ||
| Map<String, Object> backingMap = new HashMap<String, Object>(); | ||
| backingMap.put(Keys.realm.userService, usersConf.getAbsolutePath()); | ||
| backingMap.put(Keys.realm.ldap.server, "ldap://localhost:" + authMode.ldapPort()); | ||
| switch(authMode) { | ||
| case ANONYMOUS: | ||
| backingMap.put(Keys.realm.ldap.username, ""); | ||
| backingMap.put(Keys.realm.ldap.password, ""); | ||
| break; | ||
| case DS_MANAGER: | ||
| backingMap.put(Keys.realm.ldap.username, DIRECTORY_MANAGER); | ||
| backingMap.put(Keys.realm.ldap.password, "password"); | ||
| break; | ||
| case USR_MANAGER: | ||
| backingMap.put(Keys.realm.ldap.username, USER_MANAGER); | ||
| backingMap.put(Keys.realm.ldap.password, "passwd"); | ||
| break; | ||
| default: | ||
| throw new RuntimeException("Unimplemented AuthMode case!"); | ||
| } | ||
| backingMap.put(Keys.realm.ldap.maintainTeams, "true"); | ||
| backingMap.put(Keys.realm.ldap.accountBase, ACCOUNT_BASE); | ||
| backingMap.put(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); | ||
| backingMap.put(Keys.realm.ldap.groupBase, GROUP_BASE); | ||
| backingMap.put(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))"); | ||
| backingMap.put(Keys.realm.ldap.admins, "UserThree @Git_Admins \"@Git Admins\""); | ||
| backingMap.put(Keys.realm.ldap.displayName, "displayName"); | ||
| backingMap.put(Keys.realm.ldap.email, "email"); | ||
| backingMap.put(Keys.realm.ldap.uid, "sAMAccountName"); | ||
| MemorySettings ms = new MemorySettings(backingMap); | ||
| return ms; | ||
| } | ||
| /** | ||
| * Operation interceptor for the in memory DS. This interceptor | ||
| * tracks bind requests. | ||
| * | ||
| */ | ||
| protected static class BindTracker extends InMemoryOperationInterceptor { | ||
| private Map<Integer,String> lastSuccessfulBindDNs = new HashMap<>(); | ||
| private String lastSuccessfulBindDN; | ||
| @Override | ||
| public void processSimpleBindResult(InMemoryInterceptedSimpleBindResult bind) { | ||
| BindResult result = bind.getResult(); | ||
| if (result.getResultCode() == ResultCode.SUCCESS) { | ||
| BindRequest bindRequest = bind.getRequest(); | ||
| lastSuccessfulBindDNs.put(bind.getMessageID(), ((SimpleBindRequest)bindRequest).getBindDN()); | ||
| lastSuccessfulBindDN = ((SimpleBindRequest)bindRequest).getBindDN(); | ||
| } | ||
| } | ||
| String getLastSuccessfulBindDN() { | ||
| return lastSuccessfulBindDN; | ||
| } | ||
| String getLastSuccessfulBindDN(int messageID) { | ||
| return lastSuccessfulBindDNs.get(messageID); | ||
| } | ||
| void reset() { | ||
| lastSuccessfulBindDNs = new HashMap<>(); | ||
| lastSuccessfulBindDN = null; | ||
| } | ||
| } | ||
| /** | ||
| * Operation interceptor for the in memory DS. This interceptor | ||
| * implements access restrictions for certain user/DN combinations. | ||
| * | ||
| * The USER_MANAGER is only allowed to search for users, but not for groups. | ||
| * This is to test the original behaviour where the teams were searched under | ||
| * the user binding. | ||
| * When running in a DIRECTORY_MANAGER scenario, only the manager account | ||
| * is allowed to search for users and groups, while a normal user may not do so. | ||
| * This tests the scenario where a normal user cannot read teams and thus the | ||
| * manager account needs to be used for all searches. | ||
| * | ||
| */ | ||
| protected static class AccessInterceptor extends InMemoryOperationInterceptor { | ||
| AuthMode authMode; | ||
| Map<Long,String> lastSuccessfulBindDN = new HashMap<>(); | ||
| Map<Long,Boolean> resultProhibited = new HashMap<>(); | ||
| public AccessInterceptor(AuthMode authMode) { | ||
| this.authMode = authMode; | ||
| } | ||
| @Override | ||
| public void processSimpleBindResult(InMemoryInterceptedSimpleBindResult bind) { | ||
| BindResult result = bind.getResult(); | ||
| if (result.getResultCode() == ResultCode.SUCCESS) { | ||
| BindRequest bindRequest = bind.getRequest(); | ||
| lastSuccessfulBindDN.put(bind.getConnectionID(), ((SimpleBindRequest)bindRequest).getBindDN()); | ||
| resultProhibited.remove(bind.getConnectionID()); | ||
| } | ||
| } | ||
| @Override | ||
| public void processSearchRequest(InMemoryInterceptedSearchRequest request) throws LDAPException { | ||
| String bindDN = getLastBindDN(request); | ||
| if (USER_MANAGER.equals(bindDN)) { | ||
| if (request.getRequest().getBaseDN().endsWith(GROUP_BASE)) { | ||
| throw new LDAPException(ResultCode.NO_SUCH_OBJECT); | ||
| } | ||
| } | ||
| else if(authMode == AuthMode.DS_MANAGER && !DIRECTORY_MANAGER.equals(bindDN)) { | ||
| throw new LDAPException(ResultCode.NO_SUCH_OBJECT); | ||
| } | ||
| } | ||
| @Override | ||
| public void processSearchEntry(InMemoryInterceptedSearchEntry entry) { | ||
| String bindDN = getLastBindDN(entry); | ||
| boolean prohibited = false; | ||
| if (USER_MANAGER.equals(bindDN)) { | ||
| if (entry.getSearchEntry().getDN().endsWith(GROUP_BASE)) { | ||
| prohibited = true; | ||
| } | ||
| } | ||
| else if(authMode == AuthMode.DS_MANAGER && !DIRECTORY_MANAGER.equals(bindDN)) { | ||
| prohibited = true; | ||
| } | ||
| if (prohibited) { | ||
| // Found entry prohibited for bound user. Setting entry to null. | ||
| entry.setSearchEntry(null); | ||
| resultProhibited.put(entry.getConnectionID(), Boolean.TRUE); | ||
| } | ||
| } | ||
| @Override | ||
| public void processSearchResult(InMemoryInterceptedSearchResult result) { | ||
| String bindDN = getLastBindDN(result); | ||
| boolean prohibited = false; | ||
| Boolean rspb = resultProhibited.get(result.getConnectionID()); | ||
| if (USER_MANAGER.equals(bindDN)) { | ||
| if (rspb != null && rspb) { | ||
| prohibited = true; | ||
| } | ||
| } | ||
| else if(authMode == AuthMode.DS_MANAGER && !DIRECTORY_MANAGER.equals(bindDN)) { | ||
| if (rspb != null && rspb) { | ||
| prohibited = true; | ||
| } | ||
| } | ||
| if (prohibited) { | ||
| // Result prohibited for bound user. Returning error | ||
| result.setResult(new LDAPResult(result.getMessageID(), ResultCode.INSUFFICIENT_ACCESS_RIGHTS)); | ||
| resultProhibited.remove(result.getConnectionID()); | ||
| } | ||
| } | ||
| private String getLastBindDN(InMemoryInterceptedResult result) { | ||
| String bindDN = lastSuccessfulBindDN.get(result.getConnectionID()); | ||
| if (bindDN == null) { | ||
| return "UNKNOWN"; | ||
| } | ||
| return bindDN; | ||
| } | ||
| private String getLastBindDN(InMemoryInterceptedRequest request) { | ||
| String bindDN = lastSuccessfulBindDN.get(request.getConnectionID()); | ||
| if (bindDN == null) { | ||
| return "UNKNOWN"; | ||
| } | ||
| return bindDN; | ||
| } | ||
| } | ||
| } |
| package com.gitblit.tests; | ||
| import static org.junit.Assume.assumeTrue; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import org.junit.Test; | ||
| import org.junit.runner.RunWith; | ||
| import org.junit.runners.Parameterized; | ||
| import com.gitblit.Keys; | ||
| import com.gitblit.ldap.LdapConnection; | ||
| import com.unboundid.ldap.sdk.BindResult; | ||
| import com.unboundid.ldap.sdk.LDAPException; | ||
| import com.unboundid.ldap.sdk.ResultCode; | ||
| import com.unboundid.ldap.sdk.SearchRequest; | ||
| import com.unboundid.ldap.sdk.SearchResult; | ||
| import com.unboundid.ldap.sdk.SearchResultEntry; | ||
| import com.unboundid.ldap.sdk.SearchScope; | ||
| /* | ||
| * Test for the LdapConnection | ||
| * | ||
| * @author Florian Zschocke | ||
| * | ||
| */ | ||
| @RunWith(Parameterized.class) | ||
| public class LdapConnectionTest extends LdapBasedUnitTest { | ||
| @Test | ||
| public void testEscapeLDAPFilterString() { | ||
| // This test is independent from authentication mode, so run only once. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS); | ||
| // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java | ||
| assertEquals("No special characters to escape", "Hi This is a test #çà", LdapConnection.escapeLDAPSearchFilter("Hi This is a test #çà")); | ||
| assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is \\2a a \\5c test # ç à ô", LdapConnection.escapeLDAPSearchFilter("Hi (This) = is * a \\ test # ç à ô")); | ||
| assertEquals("Injection", "\\2a\\29\\28userPassword=secret", LdapConnection.escapeLDAPSearchFilter("*)(userPassword=secret")); | ||
| } | ||
| @Test | ||
| public void testConnect() { | ||
| // This test is independent from authentication mode, so run only once. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS); | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| @Test | ||
| public void testBindAnonymous() { | ||
| // This test tests for anonymous bind, so run only in authentication mode ANONYMOUS. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS); | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| BindResult br = conn.bind(); | ||
| assertNotNull(br); | ||
| assertEquals(ResultCode.SUCCESS, br.getResultCode()); | ||
| assertEquals("", authMode.getBindTracker().getLastSuccessfulBindDN(br.getMessageID())); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| @Test | ||
| public void testBindAsAdmin() { | ||
| // This test tests for anonymous bind, so run only in authentication mode DS_MANAGER. | ||
| assumeTrue(authMode == AuthMode.DS_MANAGER); | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| BindResult br = conn.bind(); | ||
| assertNotNull(br); | ||
| assertEquals(ResultCode.SUCCESS, br.getResultCode()); | ||
| assertEquals(settings.getString(Keys.realm.ldap.username, "UNSET"), authMode.getBindTracker().getLastSuccessfulBindDN(br.getMessageID())); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| @Test | ||
| public void testBindToBindpattern() { | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| String bindPattern = "CN=${username},OU=Canada," + ACCOUNT_BASE; | ||
| BindResult br = conn.bind(bindPattern, "UserThree", "userThreePassword"); | ||
| assertNotNull(br); | ||
| assertEquals(ResultCode.SUCCESS, br.getResultCode()); | ||
| assertEquals("CN=UserThree,OU=Canada," + ACCOUNT_BASE, authMode.getBindTracker().getLastSuccessfulBindDN(br.getMessageID())); | ||
| br = conn.bind(bindPattern, "UserFour", "userThreePassword"); | ||
| assertNull(br); | ||
| br = conn.bind(bindPattern, "UserTwo", "userTwoPassword"); | ||
| assertNull(br); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| @Test | ||
| public void testRebindAsUser() { | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| assertFalse(conn.rebindAsUser()); | ||
| BindResult br = conn.bind(); | ||
| assertNotNull(br); | ||
| assertFalse(conn.rebindAsUser()); | ||
| String bindPattern = "CN=${username},OU=Canada," + ACCOUNT_BASE; | ||
| br = conn.bind(bindPattern, "UserThree", "userThreePassword"); | ||
| assertNotNull(br); | ||
| assertFalse(conn.rebindAsUser()); | ||
| br = conn.bind(); | ||
| assertNotNull(br); | ||
| assertTrue(conn.rebindAsUser()); | ||
| assertEquals(ResultCode.SUCCESS, br.getResultCode()); | ||
| assertEquals("CN=UserThree,OU=Canada," + ACCOUNT_BASE, authMode.getBindTracker().getLastSuccessfulBindDN()); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| @Test | ||
| public void testSearchRequest() throws LDAPException { | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| BindResult br = conn.bind(); | ||
| assertNotNull(br); | ||
| SearchRequest req; | ||
| SearchResult result; | ||
| SearchResultEntry entry; | ||
| req = new SearchRequest(ACCOUNT_BASE, SearchScope.BASE, "(CN=UserOne)"); | ||
| result = conn.search(req); | ||
| assertNotNull(result); | ||
| assertEquals(0, result.getEntryCount()); | ||
| req = new SearchRequest(ACCOUNT_BASE, SearchScope.ONE, "(CN=UserTwo)"); | ||
| result = conn.search(req); | ||
| assertNotNull(result); | ||
| assertEquals(0, result.getEntryCount()); | ||
| req = new SearchRequest(ACCOUNT_BASE, SearchScope.SUB, "(CN=UserThree)"); | ||
| result = conn.search(req); | ||
| assertNotNull(result); | ||
| assertEquals(1, result.getEntryCount()); | ||
| entry = result.getSearchEntries().get(0); | ||
| assertEquals("CN=UserThree,OU=Canada," + ACCOUNT_BASE, entry.getDN()); | ||
| req = new SearchRequest(ACCOUNT_BASE, SearchScope.SUBORDINATE_SUBTREE, "(CN=UserFour)"); | ||
| result = conn.search(req); | ||
| assertNotNull(result); | ||
| assertEquals(1, result.getEntryCount()); | ||
| entry = result.getSearchEntries().get(0); | ||
| assertEquals("CN=UserFour,OU=Canada," + ACCOUNT_BASE, entry.getDN()); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| @Test | ||
| public void testSearch() throws LDAPException { | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| BindResult br = conn.bind(); | ||
| assertNotNull(br); | ||
| SearchResult result; | ||
| SearchResultEntry entry; | ||
| result = conn.search(ACCOUNT_BASE, false, "(CN=UserOne)", null); | ||
| assertNotNull(result); | ||
| assertEquals(1, result.getEntryCount()); | ||
| entry = result.getSearchEntries().get(0); | ||
| assertEquals("CN=UserOne,OU=US," + ACCOUNT_BASE, entry.getDN()); | ||
| result = conn.search(ACCOUNT_BASE, true, "(&(CN=UserOne)(surname=One))", null); | ||
| assertNotNull(result); | ||
| assertEquals(1, result.getEntryCount()); | ||
| entry = result.getSearchEntries().get(0); | ||
| assertEquals("CN=UserOne,OU=US," + ACCOUNT_BASE, entry.getDN()); | ||
| result = conn.search(ACCOUNT_BASE, true, "(&(CN=UserOne)(surname=Two))", null); | ||
| assertNotNull(result); | ||
| assertEquals(0, result.getEntryCount()); | ||
| result = conn.search(ACCOUNT_BASE, true, "(surname=Two)", Arrays.asList("givenName", "surname")); | ||
| assertNotNull(result); | ||
| assertEquals(1, result.getEntryCount()); | ||
| entry = result.getSearchEntries().get(0); | ||
| assertEquals("CN=UserTwo,OU=US," + ACCOUNT_BASE, entry.getDN()); | ||
| assertEquals(2, entry.getAttributes().size()); | ||
| assertEquals("User", entry.getAttributeValue("givenName")); | ||
| assertEquals("Two", entry.getAttributeValue("surname")); | ||
| result = conn.search(ACCOUNT_BASE, true, "(personalTitle=Mr*)", null); | ||
| assertNotNull(result); | ||
| assertEquals(3, result.getEntryCount()); | ||
| ArrayList<String> names = new ArrayList<>(3); | ||
| names.add(result.getSearchEntries().get(0).getAttributeValue("surname")); | ||
| names.add(result.getSearchEntries().get(1).getAttributeValue("surname")); | ||
| names.add(result.getSearchEntries().get(2).getAttributeValue("surname")); | ||
| assertTrue(names.contains("One")); | ||
| assertTrue(names.contains("Two")); | ||
| assertTrue(names.contains("Three")); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| @Test | ||
| public void testSearchUser() throws LDAPException { | ||
| LdapConnection conn = new LdapConnection(settings); | ||
| try { | ||
| assertTrue(conn.connect()); | ||
| BindResult br = conn.bind(); | ||
| assertNotNull(br); | ||
| SearchResult result; | ||
| SearchResultEntry entry; | ||
| result = conn.searchUser("UserOne"); | ||
| assertNotNull(result); | ||
| assertEquals(1, result.getEntryCount()); | ||
| entry = result.getSearchEntries().get(0); | ||
| assertEquals("CN=UserOne,OU=US," + ACCOUNT_BASE, entry.getDN()); | ||
| result = conn.searchUser("UserFour", Arrays.asList("givenName", "surname")); | ||
| assertNotNull(result); | ||
| assertEquals(1, result.getEntryCount()); | ||
| entry = result.getSearchEntries().get(0); | ||
| assertEquals("CN=UserFour,OU=Canada," + ACCOUNT_BASE, entry.getDN()); | ||
| assertEquals(2, entry.getAttributes().size()); | ||
| assertEquals("User", entry.getAttributeValue("givenName")); | ||
| assertEquals("Four", entry.getAttributeValue("surname")); | ||
| } finally { | ||
| conn.close(); | ||
| } | ||
| } | ||
| } |
| /* | ||
| * Copyright 2016 Florian Zschocke | ||
| * 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.tests; | ||
| import static org.junit.Assume.assumeTrue; | ||
| import java.security.GeneralSecurityException; | ||
| import java.security.InvalidAlgorithmParameterException; | ||
| import java.security.KeyPair; | ||
| import java.security.KeyPairGenerator; | ||
| import java.security.Signature; | ||
| import java.security.spec.ECGenParameterSpec; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import org.apache.sshd.common.util.SecurityUtils; | ||
| import org.junit.BeforeClass; | ||
| import org.junit.Test; | ||
| import org.junit.runner.RunWith; | ||
| import org.junit.runners.Parameterized; | ||
| import com.gitblit.Keys; | ||
| import com.gitblit.Constants.AccessPermission; | ||
| import com.gitblit.transport.ssh.LdapKeyManager; | ||
| import com.gitblit.transport.ssh.SshKey; | ||
| import com.unboundid.ldap.sdk.LDAPException; | ||
| import com.unboundid.ldap.sdk.Modification; | ||
| import com.unboundid.ldap.sdk.ModificationType; | ||
| /** | ||
| * Test LdapPublicKeyManager going against an in-memory UnboundID | ||
| * LDAP server. | ||
| * | ||
| * @author Florian Zschocke | ||
| * | ||
| */ | ||
| @RunWith(Parameterized.class) | ||
| public class LdapPublicKeyManagerTest extends LdapBasedUnitTest { | ||
| private static Map<String,KeyPair> keyPairs = new HashMap<>(10); | ||
| private static KeyPairGenerator rsaGenerator; | ||
| private static KeyPairGenerator dsaGenerator; | ||
| private static KeyPairGenerator ecGenerator; | ||
| @BeforeClass | ||
| public static void init() throws GeneralSecurityException { | ||
| rsaGenerator = SecurityUtils.getKeyPairGenerator("RSA"); | ||
| dsaGenerator = SecurityUtils.getKeyPairGenerator("DSA"); | ||
| ecGenerator = SecurityUtils.getKeyPairGenerator("ECDSA"); | ||
| } | ||
| @Test | ||
| public void testGetKeys() throws LDAPException { | ||
| String keyRsaOne = getRsaPubKey("UserOne@example.com"); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne)); | ||
| String keyRsaTwo = getRsaPubKey("UserTwo@example.com"); | ||
| String keyDsaTwo = getDsaPubKey("UserTwo@example.com"); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaTwo, keyDsaTwo)); | ||
| String keyRsaThree = getRsaPubKey("UserThree@example.com"); | ||
| String keyDsaThree = getDsaPubKey("UserThree@example.com"); | ||
| String keyEcThree = getEcPubKey("UserThree@example.com"); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", keyEcThree, keyRsaThree, keyDsaThree)); | ||
| LdapKeyManager kmgr = new LdapKeyManager(settings); | ||
| List<SshKey> keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertTrue(keys.size() == 1); | ||
| assertEquals(keyRsaOne, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertTrue(keys.size() == 2); | ||
| if (keyRsaTwo.equals(keys.get(0).getRawData())) { | ||
| assertEquals(keyDsaTwo, keys.get(1).getRawData()); | ||
| } else if (keyDsaTwo.equals(keys.get(0).getRawData())) { | ||
| assertEquals(keyRsaTwo, keys.get(1).getRawData()); | ||
| } else { | ||
| fail("Mismatch in UserTwo keys."); | ||
| } | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertTrue(keys.size() == 3); | ||
| assertEquals(keyEcThree, keys.get(0).getRawData()); | ||
| assertEquals(keyRsaThree, keys.get(1).getRawData()); | ||
| assertEquals(keyDsaThree, keys.get(2).getRawData()); | ||
| keys = kmgr.getKeys("UserFour"); | ||
| assertNotNull(keys); | ||
| assertTrue(keys.size() == 0); | ||
| } | ||
| @Test | ||
| public void testGetKeysAttributeName() throws LDAPException { | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "sshPublicKey"); | ||
| String keyRsaOne = getRsaPubKey("UserOne@example.com"); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne)); | ||
| String keyDsaTwo = getDsaPubKey("UserTwo@example.com"); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "publicsshkey", keyDsaTwo)); | ||
| String keyRsaThree = getRsaPubKey("UserThree@example.com"); | ||
| String keyDsaThree = getDsaPubKey("UserThree@example.com"); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "publicsshkey", keyDsaThree)); | ||
| LdapKeyManager kmgr = new LdapKeyManager(settings); | ||
| List<SshKey> keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| assertEquals(keyRsaOne, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| assertEquals(keyRsaThree, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserFour"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "publicsshkey"); | ||
| keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| assertEquals(keyDsaTwo, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| assertEquals(keyDsaThree, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserFour"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| } | ||
| @Test | ||
| public void testGetKeysPrefixed() throws LDAPException { | ||
| // This test is independent from authentication mode, so run only once. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS); | ||
| String keyRsaOne = getRsaPubKey("UserOne@example.com"); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne)); | ||
| String keyRsaTwo = getRsaPubKey("UserTwo@example.com"); | ||
| String keyDsaTwo = getDsaPubKey("UserTwo@example.com"); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", keyRsaTwo)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHKey: " + keyDsaTwo)); | ||
| String keyRsaThree = getRsaPubKey("UserThree@example.com"); | ||
| String keyDsaThree = getDsaPubKey("UserThree@example.com"); | ||
| String keyEcThree = getEcPubKey("UserThree@example.com"); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " SshKey :\r\n" + keyRsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " sshkey: " + keyDsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "ECDSAKey :\n " + keyEcThree)); | ||
| LdapKeyManager kmgr = new LdapKeyManager(settings); | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities"); | ||
| List<SshKey> keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| assertEquals(keyRsaTwo, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| keys = kmgr.getKeys("UserFour"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:SSHKey"); | ||
| keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| assertEquals(keyDsaTwo, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(2, keys.size()); | ||
| assertEquals(keyRsaThree, keys.get(0).getRawData()); | ||
| assertEquals(keyDsaThree, keys.get(1).getRawData()); | ||
| keys = kmgr.getKeys("UserFour"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:ECDSAKey"); | ||
| keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| assertEquals(keyEcThree, keys.get(0).getRawData()); | ||
| keys = kmgr.getKeys("UserFour"); | ||
| assertNotNull(keys); | ||
| assertEquals(0, keys.size()); | ||
| } | ||
| @Test | ||
| public void testGetKeysPermissions() throws LDAPException { | ||
| // This test is independent from authentication mode, so run only once. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS); | ||
| String keyRsaOne = getRsaPubKey("UserOne@example.com"); | ||
| String keyRsaTwo = getRsaPubKey(""); | ||
| String keyDsaTwo = getDsaPubKey("UserTwo at example.com"); | ||
| String keyRsaThree = getRsaPubKey("UserThree@example.com"); | ||
| String keyDsaThree = getDsaPubKey("READ key for user 'Three' @example.com"); | ||
| String keyEcThree = getEcPubKey("UserThree@example.com"); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", " " + keyRsaTwo)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", "no-agent-forwarding " + keyDsaTwo)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", " command=\"sh /etc/netstart tun0 \" " + keyRsaThree)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", " command=\"netstat -nult\",environment=\"gb=\\\"What now\\\"\" " + keyDsaThree)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerms=VIEW\" " + keyEcThree)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"gbPerm=R\" " + keyRsaOne)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", " restrict,environment=\"gbperm=V\" " + keyRsaTwo)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", "restrict,environment=\"GBPerm=RW\",pty " + keyDsaTwo)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", " environment=\"gbPerm=CLONE\",environment=\"X=\\\" Y \\\"\" " + keyRsaThree)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", " environment=\"A = B \",from=\"*.example.com,!pc.example.com\",environment=\"gbPerm=VIEW\" " + keyDsaThree)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"SSH=git\",environment=\"gbPerm=PUSH\",environment=\"XYZ='Ali Baba'\" " + keyEcThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"gbPerm=R\",environment=\"josh=\\\"mean\\\"\",tunnel=\"0\" " + keyRsaOne)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", " environment=\" gbPerm = V \" " + keyRsaTwo)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "command=\"sh echo \\\"Nope, not you!\\\" \",user-rc,environment=\"gbPerm=RW\" " + keyDsaTwo)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"gbPerm=VIEW\",command=\"sh /etc/netstart tun0 \",environment=\"gbPerm=CLONE\",no-pty " + keyRsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", " command=\"netstat -nult\",environment=\"gbPerm=VIEW\" " + keyDsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerm=PUSH\" " + keyEcThree)); | ||
| LdapKeyManager kmgr = new LdapKeyManager(settings); | ||
| List<SshKey> keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(6, keys.size()); | ||
| for (SshKey key : keys) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| } | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(6, keys.size()); | ||
| int seen = 0; | ||
| for (SshKey key : keys) { | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(63, seen); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(6, keys.size()); | ||
| seen = 0; | ||
| for (SshKey key : keys) { | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(63, seen); | ||
| } | ||
| @Test | ||
| public void testGetKeysPrefixedPermissions() throws LDAPException { | ||
| // This test is independent from authentication mode, so run only once. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS); | ||
| String keyRsaOne = getRsaPubKey("UserOne@example.com"); | ||
| String keyRsaTwo = getRsaPubKey("UserTwo at example.com"); | ||
| String keyDsaTwo = getDsaPubKey("UserTwo@example.com"); | ||
| String keyRsaThree = getRsaPubKey("example.com: user Three"); | ||
| String keyDsaThree = getDsaPubKey(""); | ||
| String keyEcThree = getEcPubKey(" "); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "permitopen=\"host:220\"" + keyRsaOne)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "sshkey:" + " " + keyRsaTwo)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHKEY :" + "no-agent-forwarding " + keyDsaTwo)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + " command=\"sh /etc/netstart tun0 \" " + keyRsaThree)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + " command=\"netstat -nult\",environment=\"gb=\\\"What now\\\"\" " + keyDsaThree)); | ||
| getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerms=VIEW\" " + keyEcThree)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "environment=\"gbPerm=R\" " + keyRsaOne)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHKey : " + " restrict,environment=\"gbPerm=V\",permitopen=\"sshkey: 220\" " + keyRsaTwo)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "permitopen=\"sshkey: 443\",restrict,environment=\"gbPerm=RW\",pty " + keyDsaTwo)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"gbPerm=CLONE\",permitopen=\"pubkey: 29184\",environment=\"X=\\\" Y \\\"\" " + keyRsaThree)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + " environment=\"A = B \",from=\"*.example.com,!pc.example.com\",environment=\"gbPerm=VIEW\" " + keyDsaThree)); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"SSH=git\",environment=\"gbPerm=PUSH\",environemnt=\"XYZ='Ali Baba'\" " + keyEcThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "environment=\"gbPerm=R\",environment=\"josh=\\\"mean\\\"\",tunnel=\"0\" " + keyRsaOne)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey : " + " environment=\" gbPerm = V \" " + keyRsaTwo)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "command=\"sh echo \\\"Nope, not you! \\b (bell)\\\" \",user-rc,environment=\"gbPerm=RW\" " + keyDsaTwo)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"gbPerm=VIEW\",command=\"sh /etc/netstart tun0 \",environment=\"gbPerm=CLONE\",no-pty " + keyRsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + " command=\"netstat -nult\",environment=\"gbPerm=VIEW\" " + keyDsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerm=PUSH\" " + keyEcThree)); | ||
| // Weird stuff, not to specification but shouldn't make it stumble. | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "opttest: " + "permitopen=host:443,command=,environment=\"gbPerm=CLONE\",no-pty= " + keyRsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " opttest: " + " cmd=git,environment=\"gbPerm=\\\"VIEW\\\"\" " + keyDsaThree)); | ||
| getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " opttest:" + "environment=,command=netstat,environment=gbperm=push " + keyEcThree)); | ||
| LdapKeyManager kmgr = new LdapKeyManager(settings); | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:SSHkey"); | ||
| List<SshKey> keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(2, keys.size()); | ||
| int seen = 0; | ||
| for (SshKey key : keys) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(6, seen); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(3, keys.size()); | ||
| seen = 0; | ||
| for (SshKey key : keys) { | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(7, seen); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(3, keys.size()); | ||
| seen = 0; | ||
| for (SshKey key : keys) { | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(7, seen); | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:pubKey"); | ||
| keys = kmgr.getKeys("UserOne"); | ||
| assertNotNull(keys); | ||
| assertEquals(3, keys.size()); | ||
| seen = 0; | ||
| for (SshKey key : keys) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(56, seen); | ||
| keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(3, keys.size()); | ||
| seen = 0; | ||
| for (SshKey key : keys) { | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(56, seen); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(3, keys.size()); | ||
| seen = 0; | ||
| for (SshKey key : keys) { | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(56, seen); | ||
| settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:opttest"); | ||
| keys = kmgr.getKeys("UserThree"); | ||
| assertNotNull(keys); | ||
| assertEquals(3, keys.size()); | ||
| seen = 0; | ||
| for (SshKey key : keys) { | ||
| if (keyRsaOne.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 0; | ||
| } | ||
| else if (keyRsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 1; | ||
| } | ||
| else if (keyDsaTwo.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 2; | ||
| } | ||
| else if (keyRsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.CLONE, key.getPermission()); | ||
| seen += 1 << 3; | ||
| } | ||
| else if (keyDsaThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.VIEW, key.getPermission()); | ||
| seen += 1 << 4; | ||
| } | ||
| else if (keyEcThree.equals(key.getRawData())) { | ||
| assertEquals(AccessPermission.PUSH, key.getPermission()); | ||
| seen += 1 << 5; | ||
| } | ||
| } | ||
| assertEquals(56, seen); | ||
| } | ||
| @Test | ||
| public void testKeyValidity() throws LDAPException, GeneralSecurityException { | ||
| LdapKeyManager kmgr = new LdapKeyManager(settings); | ||
| String comment = "UserTwo@example.com"; | ||
| String keyDsaTwo = getDsaPubKey(comment); | ||
| getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", keyDsaTwo)); | ||
| List<SshKey> keys = kmgr.getKeys("UserTwo"); | ||
| assertNotNull(keys); | ||
| assertEquals(1, keys.size()); | ||
| SshKey sshKey = keys.get(0); | ||
| assertEquals(keyDsaTwo, sshKey.getRawData()); | ||
| Signature signature = SecurityUtils.getSignature("DSA"); | ||
| signature.initSign(getDsaKeyPair(comment).getPrivate()); | ||
| byte[] message = comment.getBytes(); | ||
| signature.update(message); | ||
| byte[] sigBytes = signature.sign(); | ||
| signature.initVerify(sshKey.getPublicKey()); | ||
| signature.update(message); | ||
| assertTrue("Verify failed with retrieved SSH key.", signature.verify(sigBytes)); | ||
| } | ||
| private KeyPair getDsaKeyPair(String comment) { | ||
| return getKeyPair("DSA", comment, dsaGenerator); | ||
| } | ||
| private KeyPair getKeyPair(String type, String comment, KeyPairGenerator generator) { | ||
| String kpkey = type + ":" + comment; | ||
| KeyPair kp = keyPairs.get(kpkey); | ||
| if (kp == null) { | ||
| if ("EC".equals(type)) { | ||
| ECGenParameterSpec ecSpec = new ECGenParameterSpec("P-384"); | ||
| try { | ||
| ecGenerator.initialize(ecSpec); | ||
| } catch (InvalidAlgorithmParameterException e) { | ||
| kp = generator.generateKeyPair(); | ||
| e.printStackTrace(); | ||
| } | ||
| kp = ecGenerator.generateKeyPair(); | ||
| } else { | ||
| kp = generator.generateKeyPair(); | ||
| } | ||
| keyPairs.put(kpkey, kp); | ||
| } | ||
| return kp; | ||
| } | ||
| private String getRsaPubKey(String comment) { | ||
| return getPubKey("RSA", comment, rsaGenerator); | ||
| } | ||
| private String getDsaPubKey(String comment) { | ||
| return getPubKey("DSA", comment, dsaGenerator); | ||
| } | ||
| private String getEcPubKey(String comment) { | ||
| return getPubKey("EC", comment, ecGenerator); | ||
| } | ||
| private String getPubKey(String type, String comment, KeyPairGenerator generator) { | ||
| KeyPair kp = getKeyPair(type, comment, generator); | ||
| if (kp == null) { | ||
| return null; | ||
| } | ||
| SshKey sk = new SshKey(kp.getPublic()); | ||
| sk.setComment(comment); | ||
| return sk.getRawData(); | ||
| } | ||
| } |
| /* | ||
| * Copyright 2017 gitblit.com. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.gitblit.utils; | ||
| import static org.junit.Assert.*; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.nio.file.Path; | ||
| import org.junit.Rule; | ||
| import org.junit.Test; | ||
| import org.junit.rules.TemporaryFolder; | ||
| /** | ||
| * @author Florian Zschocke | ||
| * | ||
| */ | ||
| public class LuceneIndexStoreTest | ||
| { | ||
| private final int LUCENE_VERSION = LuceneIndexStore.LUCENE_CODEC_VERSION; | ||
| @Rule | ||
| public TemporaryFolder baseFolder = new TemporaryFolder(); | ||
| private String getIndexDir(int version) | ||
| { | ||
| return version + "_" + LUCENE_VERSION; | ||
| } | ||
| @Test | ||
| public void testCreate() | ||
| { | ||
| int version = 0; | ||
| File luceneFolder = new File(baseFolder.getRoot(), "tickets/lucene"); | ||
| assertFalse("Precondition failure: directory exists already", luceneFolder.exists()); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| li.create(); | ||
| File luceneDir = new File(luceneFolder, getIndexDir(version)); | ||
| assertTrue(luceneDir.exists()); | ||
| } | ||
| @Test | ||
| public void testCreateIndexDir() | ||
| { | ||
| int version = 111222; | ||
| File luceneFolder = null; | ||
| try { | ||
| luceneFolder = baseFolder.newFolder("tickets", "lucene"); | ||
| } | ||
| catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| assertTrue("Precondition failure: directory does not exist", luceneFolder.exists()); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| li.create(); | ||
| File luceneDir = new File(luceneFolder, getIndexDir(version)); | ||
| assertTrue(luceneDir.exists()); | ||
| assertTrue(luceneDir.isDirectory()); | ||
| // Make sure nothing else was created. | ||
| assertEquals(0, luceneDir.list().length); | ||
| assertEquals(1, luceneDir.getParentFile().list().length); | ||
| assertEquals(1, luceneDir.getParentFile().getParentFile().list().length); | ||
| } | ||
| @Test | ||
| public void testCreateIfNecessary() | ||
| { | ||
| int version = 1; | ||
| File luceneFolder = new File(baseFolder.getRoot(), "tickets/lucene"); | ||
| File luceneDir = null; | ||
| try { | ||
| luceneDir = baseFolder.newFolder("tickets", "lucene", getIndexDir(version)); | ||
| } | ||
| catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| assertTrue("Precondition failure: directory does not exist", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| li.create(); | ||
| assertTrue(luceneDir.exists()); | ||
| assertTrue(luceneDir.isDirectory()); | ||
| // Make sure nothing else was created. | ||
| assertEquals(0, luceneDir.list().length); | ||
| assertEquals(1, luceneDir.getParentFile().list().length); | ||
| assertEquals(1, luceneDir.getParentFile().getParentFile().list().length); | ||
| } | ||
| @Test | ||
| public void testDelete() | ||
| { | ||
| int version = 111222333; | ||
| File luceneFolder = new File(baseFolder.getRoot(), "repo1/lucene"); | ||
| File luceneDir = null; | ||
| try { | ||
| luceneDir = baseFolder.newFolder("repo1", "lucene", getIndexDir(version)); | ||
| } | ||
| catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| assertTrue("Precondition failure: index directory does not exist", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| assertTrue(li.delete()); | ||
| assertFalse(luceneDir.exists()); | ||
| assertTrue(luceneFolder.exists()); | ||
| } | ||
| @Test | ||
| public void testDeleteNotExist() | ||
| { | ||
| int version = 0; | ||
| File luceneFolder = null; | ||
| try { | ||
| luceneFolder = baseFolder.newFolder("repo1", "lucene"); | ||
| } | ||
| catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| File luceneDir = new File(luceneFolder, getIndexDir(version)); | ||
| assertFalse("Precondition failure: index directory exists already", luceneDir.exists()); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| assertTrue(li.delete()); | ||
| assertFalse(luceneDir.exists()); | ||
| assertTrue(luceneFolder.exists()); | ||
| } | ||
| @Test | ||
| public void testDeleteWithFiles() | ||
| { | ||
| int version = 111222333; | ||
| File luceneFolder = new File(baseFolder.getRoot(), "tickets/lucene"); | ||
| File luceneDir = null; | ||
| File otherDir = new File(baseFolder.getRoot(), "tickets/lucene/" + version + "_10"); | ||
| File dbFile = null; | ||
| try { | ||
| luceneDir = baseFolder.newFolder("tickets", "lucene", getIndexDir(version)); | ||
| File file = new File(luceneDir, "_file1"); | ||
| file.createNewFile(); | ||
| file = new File(luceneDir, "_file2.db"); | ||
| file.createNewFile(); | ||
| file = new File(luceneDir, "conf.conf"); | ||
| file.createNewFile(); | ||
| otherDir.mkdirs(); | ||
| dbFile = new File(otherDir, "_file2.db"); | ||
| dbFile.createNewFile(); | ||
| file = new File(otherDir, "conf.conf"); | ||
| file.createNewFile(); | ||
| } | ||
| catch (IOException e) { | ||
| fail("Failed in setup of folder: " + e); | ||
| } | ||
| assertTrue("Precondition failure: index directory does not exist", luceneDir.exists()); | ||
| assertTrue("Precondition failure: other index directory does not exist", otherDir.exists()); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| li.delete(); | ||
| assertFalse(luceneDir.exists()); | ||
| assertTrue(luceneFolder.exists()); | ||
| assertTrue(otherDir.exists()); | ||
| assertTrue(dbFile.exists()); | ||
| } | ||
| @Test | ||
| public void testGetPath() throws IOException | ||
| { | ||
| int version = 2; | ||
| File luceneFolder = baseFolder.newFolder("tickets", "lucene"); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| Path dir = li.getPath(); | ||
| File luceneDir = new File(luceneFolder, getIndexDir(version)); | ||
| assertEquals(luceneDir.toPath(), dir); | ||
| } | ||
| @Test | ||
| public void testHasIndex() throws IOException | ||
| { | ||
| int version = 0; | ||
| File luceneFolder = new File(baseFolder.getRoot(), "ticktock/lucene"); | ||
| LuceneIndexStore li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| baseFolder.newFolder("ticktock"); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| baseFolder.newFolder("ticktock", "lucene"); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| File luceneDir = baseFolder.newFolder("ticktock", "lucene", getIndexDir(version)); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| new File(luceneDir, "write.lock").createNewFile(); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertFalse(li.hasIndex()); | ||
| new File(luceneDir, "segments_1").createNewFile(); | ||
| li = new LuceneIndexStore(luceneFolder, version); | ||
| assertTrue(li.hasIndex()); | ||
| } | ||
| } |
| /* | ||
| * Copyright 2017 gitblit.com. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.gitblit.utils; | ||
| import static org.junit.Assert.*; | ||
| import org.junit.Test; | ||
| /** | ||
| * @author Florian Zschocke | ||
| * | ||
| */ | ||
| public class PasswordHashTest { | ||
| static final String MD5_PASSWORD_0 = "password"; | ||
| static final String MD5_HASHED_ENTRY_0 = "MD5:5F4DCC3B5AA765D61D8327DEB882CF99"; | ||
| static final String MD5_PASSWORD_1 = "This is a test password"; | ||
| static final String MD5_HASHED_ENTRY_1 = "md5:8e1901831af502c0f842d4efb9083bcf"; | ||
| static final String MD5_PASSWORD_2 = "版本库管理方案"; | ||
| static final String MD5_HASHED_ENTRY_2 = "MD5:980017891ff67cf8a20f23aa810e7b5a"; | ||
| static final String MD5_PASSWORD_3 = "PÿrâṃiĐ"; | ||
| static final String MD5_HASHED_ENTRY_3 = "MD5:60359b7e22941164708ae2040040521f"; | ||
| static final String CMD5_USERNAME_0 = "Jane Doe"; | ||
| static final String CMD5_PASSWORD_0 = "password"; | ||
| static final String CMD5_HASHED_ENTRY_0 = "CMD5:DB9639A6E5F21457F9DFD7735FAFA68B"; | ||
| static final String CMD5_USERNAME_1 = "Joe Black"; | ||
| static final String CMD5_PASSWORD_1 = "ThisIsAWeirdScheme.Weird"; | ||
| static final String CMD5_HASHED_ENTRY_1 = "cmd5:5c154768287e32fa605656b98894da89"; | ||
| static final String CMD5_USERNAME_2 = "快速便"; | ||
| static final String CMD5_PASSWORD_2 = "版本库管理方案"; | ||
| static final String CMD5_HASHED_ENTRY_2 = "CMD5:f38575ee8af23ba6d923c0d98ee767fc"; | ||
| static final String CMD5_USERNAME_3 = "İńa"; | ||
| static final String CMD5_PASSWORD_3 = "PÿrâṃiĐ"; | ||
| static final String CMD5_HASHED_ENTRY_3 = "CMD5:f1cdc2348c907677529e0e1b011f6793"; | ||
| static final String PBKDF2_PASSWORD_0 = "password"; | ||
| static final String PBKDF2_HASHED_ENTRY_0 = "PBKDF2:70617373776f726450415353574f524470617373776f726450415353574f52440f17d16621b32ae1bb2b1041fcb19e294b35d514d361c08eed385766e38f6f3a"; | ||
| static final String PBKDF2_PASSWORD_1 = "A REALLY better scheme than MD5"; | ||
| static final String PBKDF2_HASHED_ENTRY_1 = "PBKDF2:$0$46726573682066726f6d207468652053414c54206d696e65206f6620446f6f6de8e50b035679b25ce8b6ab41440938b7b1f97fc0c797fcf59302c2916f6c8fef"; | ||
| static final String PBKDF2_PASSWORD_2 = "passwordPASSWORDpassword"; | ||
| static final String PBKDF2_HASHED_ENTRY_2 = "pbkdf2:$0$73616c7453414c5473616c7453414c5473616c7453414c5473616c7453414c54560d0f02b565e37695da15141044506d54cb633a5a70b41c574069ea50a1247a"; | ||
| static final String PBKDF2_PASSWORD_3 = "foo"; | ||
| static final String PBKDF2_HASHED_ENTRY_3 = "PBKDF2WITHHMACSHA256:2d7d3ccaa277787f288e9f929247361bfc83607c6a8447bf496267512e360ba0a97b3114937213b23230072517d65a2e00695a1cbc47a732510840817f22c1bc"; | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for MD5. | ||
| */ | ||
| @Test | ||
| public void testInstanceOfMD5() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("md5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.MD5, pwdh.type); | ||
| assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceOf("MD5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.MD5, pwdh.type); | ||
| assertTrue("Failed to match " +MD5_HASHED_ENTRY_0, pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceOf("mD5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.MD5, pwdh.type); | ||
| assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceOf("CMD5"); | ||
| assertNotNull(pwdh); | ||
| assertNotEquals(PasswordHash.Type.MD5, pwdh.type); | ||
| assertFalse("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | ||
| } | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for combined MD5. | ||
| */ | ||
| @Test | ||
| public void testInstanceOfCombinedMD5() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("cmd5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | ||
| pwdh = PasswordHash.instanceOf("cMD5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | ||
| pwdh = PasswordHash.instanceOf("CMD5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| assertTrue("Failed to match " +CMD5_HASHED_ENTRY_0, pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| pwdh = PasswordHash.instanceOf("combined-md5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| pwdh = PasswordHash.instanceOf("COMBINED-MD5"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| pwdh = PasswordHash.instanceOf("MD5"); | ||
| assertNotNull(pwdh); | ||
| assertNotEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| assertFalse("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | ||
| } | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for PBKDF2. | ||
| */ | ||
| @Test | ||
| public void testInstanceOfPBKDF2() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | ||
| assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_0, pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceOf("pbkdf2"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | ||
| assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceOf("pbKDF2"); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | ||
| assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceOf("md5"); | ||
| assertNotNull(pwdh); | ||
| assertNotEquals(PasswordHash.Type.PBKDF2, pwdh.type); | ||
| assertFalse("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | ||
| } | ||
| /** | ||
| * Test that no instance is returned for plaintext or unknown or not | ||
| * yet implemented hashing schemes. | ||
| */ | ||
| @Test | ||
| public void testNoInstanceOf() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("plain"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceOf("PLAIN"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceOf("Plain"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceOf("scrypt"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceOf("bCrypt"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceOf("BCRYPT"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceOf("nixe"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceOf(null); | ||
| assertNull(pwdh); | ||
| } | ||
| /** | ||
| * Test that for all known hash types an instance is created for a hashed entry | ||
| * that can verify the known password. | ||
| * | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#instanceFor(java.lang.String)}. | ||
| */ | ||
| @Test | ||
| public void testInstanceFor() { | ||
| PasswordHash pwdh = PasswordHash.instanceFor(MD5_HASHED_ENTRY_0); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.MD5, pwdh.type); | ||
| assertTrue("Failed to match " +MD5_HASHED_ENTRY_0, pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceFor(MD5_HASHED_ENTRY_1); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.MD5, pwdh.type); | ||
| assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceFor(CMD5_HASHED_ENTRY_0); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| assertTrue("Failed to match " +CMD5_HASHED_ENTRY_0, pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| pwdh = PasswordHash.instanceFor(CMD5_HASHED_ENTRY_1); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.CMD5, pwdh.type); | ||
| assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | ||
| pwdh = PasswordHash.instanceFor(PBKDF2_HASHED_ENTRY_0); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | ||
| assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_0, pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceFor(PBKDF2_HASHED_ENTRY_1); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | ||
| assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | ||
| pwdh = PasswordHash.instanceFor(PBKDF2_HASHED_ENTRY_3); | ||
| assertNotNull(pwdh); | ||
| assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | ||
| assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_3, pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), null)); | ||
| } | ||
| /** | ||
| * Test that for no instance is returned for plaintext or unknown or | ||
| * not yet implemented hashing schemes. | ||
| * | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#instanceFor(java.lang.String)}. | ||
| */ | ||
| @Test | ||
| public void testInstanceForNaught() { | ||
| PasswordHash pwdh = PasswordHash.instanceFor("password"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceFor("top secret"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceFor("pass:word"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceFor("PLAIN:password"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceFor("SCRYPT:1232rwv12w"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceFor("BCRYPT:urbvahiaufbvhabaiuevuzggubsbliue"); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceFor(""); | ||
| assertNull(pwdh); | ||
| pwdh = PasswordHash.instanceFor(null); | ||
| assertNull(pwdh); | ||
| } | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. | ||
| */ | ||
| @Test | ||
| public void testIsHashedEntry() { | ||
| assertTrue(MD5_HASHED_ENTRY_0, PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_0)); | ||
| assertTrue(MD5_HASHED_ENTRY_1, PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_1)); | ||
| assertTrue(CMD5_HASHED_ENTRY_0, PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_0)); | ||
| assertTrue(CMD5_HASHED_ENTRY_1, PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_1)); | ||
| assertTrue(PBKDF2_HASHED_ENTRY_0, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_0)); | ||
| assertTrue(PBKDF2_HASHED_ENTRY_1, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_1)); | ||
| assertTrue(PBKDF2_HASHED_ENTRY_2, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_2)); | ||
| assertTrue(PBKDF2_HASHED_ENTRY_3, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_3)); | ||
| assertFalse(MD5_PASSWORD_1, PasswordHash.isHashedEntry(MD5_PASSWORD_1)); | ||
| assertFalse("topsecret", PasswordHash.isHashedEntry("topsecret")); | ||
| assertFalse("top:secret", PasswordHash.isHashedEntry("top:secret")); | ||
| assertFalse("secret Password", PasswordHash.isHashedEntry("secret Password")); | ||
| assertFalse("Empty string", PasswordHash.isHashedEntry("")); | ||
| assertFalse("Null", PasswordHash.isHashedEntry(null)); | ||
| } | ||
| /** | ||
| * Test that hashed entry detection is not case sensitive on the hash type identifier. | ||
| * | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. | ||
| */ | ||
| @Test | ||
| public void testIsHashedEntryCaseInsenitive() { | ||
| assertTrue(MD5_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_1.toLowerCase())); | ||
| assertTrue(CMD5_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_1.toLowerCase())); | ||
| assertTrue(PBKDF2_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_1.toLowerCase())); | ||
| assertTrue(PBKDF2_HASHED_ENTRY_3.toLowerCase(), PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_3.toLowerCase())); | ||
| } | ||
| /** | ||
| * Test that unknown or not yet implemented hashing schemes are not detected as hashed entries. | ||
| * | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. | ||
| */ | ||
| @Test | ||
| public void testIsHashedEntryUnknown() { | ||
| assertFalse("BCRYPT:thisismypassword", PasswordHash.isHashedEntry("BCRYPT:thisismypassword")); | ||
| assertFalse("TSTHSH:asdchabufzuzfbhbakrzburzbcuzkuzcbajhbcasjdhbckajsbc", PasswordHash.isHashedEntry("TSTHSH:asdchabufzuzfbhbakrzburzbcuzkuzcbajhbcasjdhbckajsbc")); | ||
| } | ||
| /** | ||
| * Test creating a hashed entry for scheme MD5. In this scheme there is no salt, so a direct | ||
| * comparison to a constant value is possible. | ||
| * | ||
| * Test method for {@link PasswordHash#toHashedEntry(String, String)} for MD5. | ||
| */ | ||
| @Test | ||
| public void testToHashedEntryMD5() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("MD5"); | ||
| String hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_1, null); | ||
| assertTrue(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_2, null); | ||
| assertTrue(MD5_HASHED_ENTRY_2.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_1, "charlie"); | ||
| assertTrue(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_3, CMD5_USERNAME_3); | ||
| assertTrue(MD5_HASHED_ENTRY_3.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry("badpassword", "charlie"); | ||
| assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry("badpassword", null); | ||
| assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | ||
| } | ||
| @Test(expected = IllegalArgumentException.class) | ||
| public void testToHashedEntryMD5NullPassword() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("MD5"); | ||
| pwdh.toHashedEntry((String)null, null); | ||
| } | ||
| /** | ||
| * Test creating a hashed entry for scheme Combined-MD5. In this scheme there is no salt, so a direct | ||
| * comparison to a constant value is possible. | ||
| * | ||
| * Test method for {@link PasswordHash#toHashedEntry(String, String)} for CMD5. | ||
| */ | ||
| @Test | ||
| public void testToHashedEntryCMD5() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | ||
| String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, CMD5_USERNAME_1); | ||
| assertTrue(CMD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_2, CMD5_USERNAME_2); | ||
| assertTrue(CMD5_HASHED_ENTRY_2.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_3, CMD5_USERNAME_3); | ||
| assertTrue(CMD5_HASHED_ENTRY_3.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, "charlie"); | ||
| assertFalse(CMD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | ||
| hashedEntry = pwdh.toHashedEntry("badpassword", "charlie"); | ||
| assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | ||
| } | ||
| @Test(expected = IllegalArgumentException.class) | ||
| public void testToHashedEntryCMD5NullPassword() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | ||
| pwdh.toHashedEntry((String)null, CMD5_USERNAME_1); | ||
| } | ||
| /** | ||
| * Test creating a hashed entry for scheme Combined-MD5, when no user is given. | ||
| * This should never happen in the application, so we expect an exception to be thrown. | ||
| * | ||
| * Test method for {@link PasswordHash#toHashedEntry(String, String)} for broken CMD5. | ||
| */ | ||
| @Test | ||
| public void testToHashedEntryCMD5NoUsername() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | ||
| try { | ||
| String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, ""); | ||
| fail("CMD5 cannot work with an empty '' username. Got: " + hashedEntry); | ||
| } | ||
| catch (IllegalArgumentException ignored) { /*success*/ } | ||
| try { | ||
| String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, " "); | ||
| fail("CMD5 cannot work with an empty ' ' username. Got: " + hashedEntry); | ||
| } | ||
| catch (IllegalArgumentException ignored) { /*success*/ } | ||
| try { | ||
| String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, " "); | ||
| fail("CMD5 cannot work with an empty ' ' username. Got: " + hashedEntry); | ||
| } | ||
| catch (IllegalArgumentException ignored) { /*success*/ } | ||
| try { | ||
| String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, null); | ||
| fail("CMD5 cannot work with a null username. Got: " + hashedEntry); | ||
| } | ||
| catch (IllegalArgumentException ignored) { /*success*/ } | ||
| } | ||
| /** | ||
| * Test creating a hashed entry for scheme PBKDF2. | ||
| * Since this scheme uses a salt, we test by running a match. This is a bit backwards, | ||
| * but recreating the PBKDF2 here seems a little overkill. | ||
| * | ||
| * Test method for {@link PasswordHash#toHashedEntry(String, String)} for PBKDF2. | ||
| */ | ||
| @Test | ||
| public void testToHashedEntryPBKDF2() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | ||
| String hashedEntry = pwdh.toHashedEntry(PBKDF2_PASSWORD_1, null); | ||
| assertTrue("Type identifier is incorrect.", hashedEntry.startsWith(PasswordHash.Type.PBKDF2.name())); | ||
| PasswordHash pwdhverify = PasswordHash.instanceFor(hashedEntry); | ||
| assertNotNull(pwdhverify); | ||
| assertTrue(PBKDF2_PASSWORD_1, pwdhverify.matches(hashedEntry, PBKDF2_PASSWORD_1.toCharArray(), null)); | ||
| hashedEntry = pwdh.toHashedEntry(PBKDF2_PASSWORD_2, ""); | ||
| assertTrue("Type identifier is incorrect.", hashedEntry.startsWith(PasswordHash.Type.PBKDF2.name())); | ||
| pwdhverify = PasswordHash.instanceFor(hashedEntry); | ||
| assertNotNull(pwdhverify); | ||
| assertTrue(PBKDF2_PASSWORD_2, pwdhverify.matches(hashedEntry, PBKDF2_PASSWORD_2.toCharArray(), null)); | ||
| hashedEntry = pwdh.toHashedEntry(PBKDF2_PASSWORD_0, "alpha"); | ||
| assertTrue("Type identifier is incorrect.", hashedEntry.startsWith(PasswordHash.Type.PBKDF2.name())); | ||
| pwdhverify = PasswordHash.instanceFor(hashedEntry); | ||
| assertNotNull(pwdhverify); | ||
| assertTrue(PBKDF2_PASSWORD_0, pwdhverify.matches(hashedEntry, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| } | ||
| @Test(expected = IllegalArgumentException.class) | ||
| public void testToHashedEntryPBKDF2NullPassword() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | ||
| pwdh.toHashedEntry((String)null, null); | ||
| } | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for MD5. | ||
| */ | ||
| @Test | ||
| public void testMatchesMD5() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("MD5"); | ||
| assertTrue("PWD0, Null user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | ||
| assertTrue("PWD0, Empty user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "")); | ||
| assertTrue("PWD0, With user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "maxine")); | ||
| assertTrue("PWD1, Null user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | ||
| assertTrue("PWD1, Empty user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), "")); | ||
| assertTrue("PWD1, With user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), "maxine")); | ||
| assertTrue("PWD2", pwdh.matches(MD5_HASHED_ENTRY_2, MD5_PASSWORD_2.toCharArray(), null)); | ||
| assertTrue("PWD3", pwdh.matches(MD5_HASHED_ENTRY_3, MD5_PASSWORD_3.toCharArray(), null)); | ||
| assertFalse("Matched wrong password", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), null)); | ||
| assertFalse("Matched wrong password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), " ")); | ||
| assertFalse("Matched wrong password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), "someuser")); | ||
| assertFalse("Matched empty password", pwdh.matches(MD5_HASHED_ENTRY_1, "".toCharArray(), null)); | ||
| assertFalse("Matched empty password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, " ".toCharArray(), " ")); | ||
| assertFalse("Matched empty password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, " ".toCharArray(), "someuser")); | ||
| assertFalse("Matched null password", pwdh.matches(MD5_HASHED_ENTRY_1, null, null)); | ||
| assertFalse("Matched null password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, null, " ")); | ||
| assertFalse("Matched null password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, null, "someuser")); | ||
| assertFalse("Matched wrong hashed entry", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched wrong hashed entry, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched wrong hashed entry, with user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), "someuser")); | ||
| assertFalse("Matched empty hashed entry", pwdh.matches("", MD5_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", MD5_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched empty hashed entry, with user", pwdh.matches(" ", MD5_PASSWORD_0.toCharArray(), "someuser")); | ||
| assertFalse("Matched null entry", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched null entry, with empty user", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched null entry, with user", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), "someuser")); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(PBKDF2_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| } | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for Combined-MD5. | ||
| */ | ||
| @Test | ||
| public void testMatchesCombinedMD5() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | ||
| assertTrue("PWD0", pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| assertTrue("PWD1", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | ||
| assertTrue("PWD2", pwdh.matches(CMD5_HASHED_ENTRY_2, CMD5_PASSWORD_2.toCharArray(), CMD5_USERNAME_2)); | ||
| assertTrue("PWD3", pwdh.matches(CMD5_HASHED_ENTRY_3, CMD5_PASSWORD_3.toCharArray(), CMD5_USERNAME_3)); | ||
| assertFalse("Matched wrong password", pwdh.matches(CMD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), CMD5_USERNAME_1)); | ||
| assertFalse("Matched wrong password", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_1)); | ||
| assertFalse("Matched wrong user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_0)); | ||
| assertFalse("Matched wrong user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), "Samantha Jones")); | ||
| assertFalse("Matched empty user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), "")); | ||
| assertFalse("Matched empty user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), " ")); | ||
| assertFalse("Matched null user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), null)); | ||
| assertFalse("Matched empty hashed entry", pwdh.matches("", CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", CMD5_PASSWORD_1.toCharArray(), "")); | ||
| assertFalse("Matched empty hashed entry, with null user", pwdh.matches(" ", CMD5_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched null entry, with user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | ||
| assertFalse("Matched null entry, with empty user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), "")); | ||
| assertFalse("Matched null entry, with null user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), null)); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(PBKDF2_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| } | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for PBKDF2. | ||
| */ | ||
| @Test | ||
| public void testMatchesPBKDF2() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | ||
| assertTrue("PWD0, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| assertTrue("PWD0, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), "")); | ||
| assertTrue("PWD0, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), "maxine")); | ||
| assertTrue("PWD1, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | ||
| assertTrue("PWD1, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), "")); | ||
| assertTrue("PWD1, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), "Maxim Gorki")); | ||
| assertTrue("PWD2, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, PBKDF2_PASSWORD_2.toCharArray(), null)); | ||
| assertTrue("PWD2, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, PBKDF2_PASSWORD_2.toCharArray(), "")); | ||
| assertTrue("PWD2, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, PBKDF2_PASSWORD_2.toCharArray(), "Epson")); | ||
| assertFalse("Matched wrong password", pwdh.matches(PBKDF2_HASHED_ENTRY_1, "wrongpassword".toCharArray(), null)); | ||
| assertFalse("Matched wrong password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, "wrongpassword".toCharArray(), " ")); | ||
| assertFalse("Matched wrong password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, "wrongpassword".toCharArray(), "someuser")); | ||
| assertFalse("Matched empty password", pwdh.matches(PBKDF2_HASHED_ENTRY_2, "".toCharArray(), null)); | ||
| assertFalse("Matched empty password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, " ".toCharArray(), " ")); | ||
| assertFalse("Matched empty password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, " ".toCharArray(), "someuser")); | ||
| assertFalse("Matched null password", pwdh.matches(PBKDF2_HASHED_ENTRY_0, null, null)); | ||
| assertFalse("Matched null password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, null, " ")); | ||
| assertFalse("Matched null password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, null, "someuser")); | ||
| assertFalse("Matched wrong hashed entry", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched wrong hashed entry, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched wrong hashed entry, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | ||
| assertFalse("Matched empty hashed entry", pwdh.matches("", PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", PBKDF2_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched empty hashed entry, with user", pwdh.matches(" ", PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | ||
| assertFalse("Matched null entry", pwdh.matches(null, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched null entry, with empty user", pwdh.matches(null, PBKDF2_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched null entry, with user", pwdh.matches(null, PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | ||
| } | ||
| /** | ||
| * Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} | ||
| * for old existing entries with the "PBKDF2WITHHMACSHA256" type identifier. | ||
| */ | ||
| @Test | ||
| public void testMatchesPBKDF2Compat() { | ||
| PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | ||
| assertTrue("PWD3, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), null)); | ||
| assertTrue("PWD3, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), "")); | ||
| assertTrue("PWD3, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), "maxine")); | ||
| assertFalse("Matched wrong password", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "bar".toCharArray(), null)); | ||
| assertFalse("Matched wrong password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "bar".toCharArray(), " ")); | ||
| assertFalse("Matched wrong password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "bar".toCharArray(), "someuser")); | ||
| assertFalse("Matched empty password", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "".toCharArray(), null)); | ||
| assertFalse("Matched empty password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, " ".toCharArray(), " ")); | ||
| assertFalse("Matched empty password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, " ".toCharArray(), "someuser")); | ||
| assertFalse("Matched null password", pwdh.matches(PBKDF2_HASHED_ENTRY_3, null, null)); | ||
| assertFalse("Matched null password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, null, " ")); | ||
| assertFalse("Matched null password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, null, "someuser")); | ||
| assertFalse("Matched wrong hashed entry", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_0.toCharArray(), null)); | ||
| assertFalse("Matched wrong hashed entry, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_0.toCharArray(), "")); | ||
| assertFalse("Matched wrong hashed entry, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | ||
| } | ||
| @Test | ||
| public void getEntryType() { | ||
| assertEquals(PasswordHash.Type.MD5, PasswordHash.getEntryType("MD5:blah")); | ||
| assertEquals(PasswordHash.Type.MD5, PasswordHash.getEntryType("md5:blah")); | ||
| assertEquals(PasswordHash.Type.MD5, PasswordHash.getEntryType("mD5:blah")); | ||
| assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("CMD5:blah")); | ||
| assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("cmd5:blah")); | ||
| assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("Cmd5:blah")); | ||
| assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("combined-md5:blah")); | ||
| assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("COMBINED-MD5:blah")); | ||
| assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("combined-MD5:blah")); | ||
| assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("PBKDF2:blah")); | ||
| assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("pbkdf2:blah")); | ||
| assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("Pbkdf2:blah")); | ||
| assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("pbKDF2:blah")); | ||
| assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("PBKDF2WithHmacSHA256:blah")); | ||
| assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("PBKDF2WITHHMACSHA256:blah")); | ||
| } | ||
| @Test | ||
| public void getEntryValue() { | ||
| assertEquals("value", PasswordHash.getEntryValue("MD5:value")); | ||
| assertEquals("plain text", PasswordHash.getEntryValue("plain text")); | ||
| assertEquals("what this", PasswordHash.getEntryValue(":what this")); | ||
| assertEquals("", PasswordHash.getEntryValue(":")); | ||
| } | ||
| } |
| 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)); | ||
| } | ||
| } |
| package com.gitblit.wicket.panels; | ||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertFalse; | ||
| import static org.junit.Assert.assertTrue; | ||
| import org.junit.Test; | ||
| import com.gitblit.models.RepositoryModel; | ||
| import com.gitblit.models.TreeNodeModel; | ||
| public class TreeNodeModelTest { | ||
| @Test | ||
| public void testContainsSubFolder() { | ||
| TreeNodeModel tree = new TreeNodeModel(); | ||
| tree.add("foo").add("bar").add("baz"); | ||
| assertTrue(tree.containsSubFolder("foo/bar/baz")); | ||
| assertTrue(tree.containsSubFolder("foo/bar")); | ||
| assertFalse(tree.containsSubFolder("foo/bar/blub")); | ||
| } | ||
| @Test | ||
| public void testAddInHierarchy() { | ||
| TreeNodeModel tree = new TreeNodeModel(); | ||
| tree.add("foo").add("bar"); | ||
| RepositoryModel model = new RepositoryModel("test","","",null); | ||
| // add model to non-existing folder. should be created automatically | ||
| tree.add("foo/bar/baz", model); | ||
| tree.add("another/non/existing/folder", model); | ||
| assertTrue(tree.containsSubFolder("foo/bar/baz")); | ||
| assertTrue(tree.containsSubFolder("another/non/existing/folder")); | ||
| } | ||
| @Test | ||
| public void testGetDepth() { | ||
| TreeNodeModel tree = new TreeNodeModel(); | ||
| TreeNodeModel bar = tree.add("foo").add("bar").add("baz"); | ||
| assertEquals(0, tree.getDepth()); | ||
| assertEquals(3, bar.getDepth()); | ||
| } | ||
| } |
+233
-32
@@ -30,3 +30,10 @@ <?xml version="1.0" encoding="UTF-8"?> | ||
| <property name="project.distrib.dir" value="${basedir}/src/main/distrib" /> | ||
| <!-- Tools --> | ||
| <property name="octokit" location="${basedir}/.github/ok.sh" /> | ||
| <property name="relnoawk" location="${basedir}/src/site/templates/ghreleasenotes.awk" /> | ||
| <!-- GitHub user/organization name --> | ||
| <property name="gh.org" value="gitblit" /> | ||
| <!-- | ||
@@ -45,2 +52,4 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| <!-- Set Ant project properties --> | ||
| <property name="release.tag" value="v${project.version}" /> | ||
| <property name="currentRelease.tag" value="v${project.releaseVersion}" /> | ||
| <property name="release.name" value="gitblit-${project.version}"/> | ||
@@ -55,5 +64,14 @@ <property name="distribution.zipfile" value="${release.name}.zip" /> | ||
| <property name="maven.directory" value="${basedir}/../gitblit-maven" /> | ||
| <property name="releaselog" value="${basedir}/releases.moxie" /> | ||
| <!-- Download links --> | ||
| <property name="gc.url" value="http://dl.bintray.com/gitblit/releases/" /> | ||
| <property name="gc.url" value="https://github.com/${gh.org}/gitblit/releases/download/" /> | ||
| <!-- Report Java version --> | ||
| <echo>JDK version: ${ant.java.version}</echo> | ||
| <exec executable="javac"> | ||
| <arg value="-version" /> | ||
| </exec> | ||
| <echo>Java/JVM version: ${java.version}</echo> | ||
| </target> | ||
@@ -138,2 +156,7 @@ | ||
| <!-- Generate the HelloworldKeys class from the hello-world.properties file --> | ||
| <mx:keys propertiesfile="${basedir}/src/test/data/hello-world.properties" | ||
| outputclass="com.gitblit.tests.HelloworldKeys" | ||
| todir="${basedir}/src/test/java" /> | ||
| <!-- Compile unit tests --> | ||
@@ -200,3 +223,2 @@ <mx:javac scope="test" /> | ||
| <mainclass name="com.gitblit.GitBlitServer" /> | ||
| <launcher paths="ext" /> | ||
| </mx:jar> | ||
@@ -305,3 +327,2 @@ | ||
| <class name="com.gitblit.Keys" /> | ||
| <launcher paths="ext" /> | ||
| <resource file="${project.compileOutputDirectory}/log4j.properties" /> | ||
@@ -377,3 +398,3 @@ </mx:genjar> | ||
| <mainclass name="com.gitblit.client.GitblitManagerLauncher" /> | ||
| <mainclass name="com.gitblit.client.GitblitManager" /> | ||
| <class name="com.gitblit.Keys" /> | ||
@@ -480,5 +501,5 @@ <class name="com.gitblit.client.GitblitClient" /> | ||
| </target> | ||
| <!-- | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
@@ -492,7 +513,5 @@ Build the Gitblit Website | ||
| <property name="releaselog" value="${basedir}/releases.moxie" /> | ||
| <!-- Build Site --> | ||
| <mx:doc googleplusid="114464678392593421684" googleanalyticsid="UA-24377072-1" | ||
| googlePlusOne="true" minify="true" customless="custom.less"> | ||
| <mx:doc googleanalyticsid="UA-24377072-1" | ||
| minify="true" customless="custom.less"> | ||
| <structure> | ||
@@ -569,5 +588,5 @@ <menu name="about"> | ||
| <menu name="downloads"> | ||
| <link name="Gitblit GO (Windows)" src="${gc.url}gitblit-${project.releaseVersion}.zip" /> | ||
| <link name="Gitblit GO (Linux/OSX)" src="${gc.url}gitblit-${project.releaseVersion}.tar.gz" /> | ||
| <link name="Gitblit WAR" src="${gc.url}gitblit-${project.releaseVersion}.war" /> | ||
| <link name="Gitblit GO (Windows)" src="${gc.url}${currentRelease.tag}/gitblit-${project.releaseVersion}.zip" /> | ||
| <link name="Gitblit GO (Linux/OSX)" src="${gc.url}${currentRelease.tag}/gitblit-${project.releaseVersion}.tar.gz" /> | ||
| <link name="Gitblit WAR" src="${gc.url}${currentRelease.tag}/gitblit-${project.releaseVersion}.war" /> | ||
| <divider /> | ||
@@ -578,8 +597,9 @@ <link name="Gitblit GO (Docker)" src="https://registry.hub.docker.com/u/jmoger/gitblit/" /> | ||
| <divider /> | ||
| <link name="Gitblit Manager" src="${gc.url}manager-${project.releaseVersion}.zip" /> | ||
| <link name="Federation Client" src="${gc.url}fedclient-${project.releaseVersion}.zip" /> | ||
| <link name="Gitblit Manager" src="${gc.url}${currentRelease.tag}/manager-${project.releaseVersion}.zip" /> | ||
| <link name="Federation Client" src="${gc.url}${currentRelease.tag}/fedclient-${project.releaseVersion}.zip" /> | ||
| <divider /> | ||
| <link name="API Library" src="${gc.url}gbapi-${project.releaseVersion}.zip" /> | ||
| <link name="API Library" src="${gc.url}${currentRelease.tag}/gbapi-${project.releaseVersion}.zip" /> | ||
| <divider /> | ||
| <link name="Bintray (1.4.0+)" src="https://bintray.com/gitblit/releases/gitblit" /> | ||
| <link name="GitHub (1.9.0+)" src="https://github.com/${gh.org}/gitblit/releases" /> | ||
| <link name="Bintray (1.4.0-1.8.0)" src="https://bintray.com/gitblit/releases/gitblit" /> | ||
| <link name="GoogleCode (pre-1.4.0)" src="https://code.google.com/p/gitblit/downloads/list?can=1" /> | ||
@@ -610,7 +630,8 @@ <divider /> | ||
| <replace token="%GCURL%" value="${gc.url}" /> | ||
| <replace token="%GCURL%" value="${gc.url}${currentRelease.tag}/" /> | ||
| <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" /> | ||
| <regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>" /> | ||
| <regex searchPattern="\b(commit)(\s*[#]?|-){0,1}([0-9a-fA-F]{5,})\b" replacePattern="<a href='https://github.com/gitblit/gitblit/commit/$3'>commit $3</a>" /> | ||
| <regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/issues/$3'>issue $3</a>" /> | ||
| <regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/pull/$3'>pull request #$3</a>" /> | ||
@@ -692,4 +713,4 @@ <regex searchPattern="\b(ticket)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://dev.gitblit.com/tickets/gitblit.git/$3'>ticket $3</a>" /> | ||
| </target> | ||
| <!-- | ||
@@ -736,4 +757,81 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Publish binaries to GitHub release | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| --> | ||
| <target name="releaseBinaries" depends="prepare" description="Publish the Gitblit binaries to a GitHub release"> | ||
| <ghReleaseDraft | ||
| releaselog="${releaselog}" | ||
| releasetag="${release.tag}"/> | ||
| <echo>Uploading Gitblit ${project.version} binaries</echo> | ||
| <!-- Upload Gitblit GO Windows ZIP file --> | ||
| <githubUpload | ||
| source="${project.targetDirectory}/${distribution.zipfile}" | ||
| target="gitblit-${project.version}.zip" /> | ||
| <!-- Upload Gitblit GO Linux/Unix tar.gz file --> | ||
| <githubUpload | ||
| source="${project.targetDirectory}/${distribution.tgzfile}" | ||
| target="gitblit-${project.version}.tar.gz" /> | ||
| <!-- Upload Gitblit WAR file --> | ||
| <githubUpload | ||
| source="${project.targetDirectory}/${distribution.warfile}" | ||
| target="gitblit-${project.version}.war" /> | ||
| <!-- Upload Gitblit FedClient --> | ||
| <githubUpload | ||
| source="${project.targetDirectory}/${fedclient.zipfile}" | ||
| target="fedclient-${project.version}.zip" /> | ||
| <!-- Upload Gitblit Manager --> | ||
| <githubUpload | ||
| source="${project.targetDirectory}/${manager.zipfile}" | ||
| target="manager-${project.version}.zip" /> | ||
| <!-- Upload Gitblit API Library --> | ||
| <githubUpload | ||
| source="${project.targetDirectory}/${gbapi.zipfile}" | ||
| target="gbapi-${project.version}.zip" /> | ||
| </target> | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Publish GH release draft | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| --> | ||
| <target name="publishRelease" depends="prepare" description="Publish the GitHub release draft" > | ||
| <echo>Publishing Gitblit ${project.version} release draft on GitHub for tag ${release.tag}</echo> | ||
| <ghGetReleaseId | ||
| releaseVersion="${project.version}"/> | ||
| <exec executable="bash" logError="true" > | ||
| <arg value="-c" /> | ||
| <arg value="${octokit} -q edit_release ${gh.org} gitblit ${ghrelease.id} tag_name='${release.tag}'"></arg> | ||
| </exec> | ||
| <ghPublishReleaseDraft | ||
| releaseid="${ghrelease.id}"/> | ||
| </target> | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Build site and update GH pages for publishing | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| --> | ||
| <target name="updateSite" depends="buildSite,updateGhPages" description="Update the Gitblit pages site" > | ||
| </target> | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
@@ -766,7 +864,7 @@ Publish site to site hosting service | ||
| <property name="dryrun" value="false" /> | ||
| <mx:version stage="release" dryrun="${dryrun}" /> | ||
| <mx:version stage="release" dryrun="${dryrun}" /> | ||
| <property name="project.tag" value="v${project.version}" /> | ||
| <!-- commit build.moxie & releases.moxie (automatic) --> | ||
| <mx:commit showtitle="no"> | ||
| <message>Prepare ${project.version} release</message> | ||
| <message>Prepare ${project.version} release</message> | ||
| <tag name="${project.tag}"> | ||
@@ -777,2 +875,16 @@ <message>${project.name} ${project.version} release</message> | ||
| <!-- output version information for other scripts/programs to pick up --> | ||
| <mx:if> | ||
| <and> | ||
| <isset property="versionInfo" /> | ||
| <not><equals arg1="${versionInfo}" arg2="" trim="true"/></not> | ||
| </and> | ||
| <then> | ||
| <echo file="${basedir}/${versionInfo}"> | ||
| GB_RELEASE_VERSION=${project.version} | ||
| GB_RELEASE_TAG=${project.tag} | ||
| </echo> | ||
| </then> | ||
| </mx:if> | ||
| <!-- create the release process script --> | ||
@@ -870,2 +982,4 @@ <mx:if> | ||
| <page name="scaling" src="setup_scaling.mkd" /> | ||
| <page name="fail2ban" src="setup_fail2ban.mkd" /> | ||
| <page name="filestore (Git LFS)" src="setup_filestore.mkd" /> | ||
| <divider /> | ||
@@ -902,6 +1016,12 @@ <page name="Gitblit as a viewer" src="setup_viewer.mkd" /> | ||
| </menu> | ||
| <menu name="changelog"> | ||
| <page name="current release" src="releasecurrent.mkd" /> | ||
| <page name="older releases" src="releasehistory.mkd" /> | ||
| <page name="current release" out="releasenotes.html"> | ||
| <template src="releasecurrent.ftl" data="${releaselog}" /> | ||
| </page> | ||
| <page name="all releases" out="releases.html"> | ||
| <template src="releasehistory.ftl" data="${releaselog}" /> | ||
| </page> | ||
| </menu> | ||
| <menu name="links"> | ||
@@ -915,9 +1035,13 @@ <link name="dev.gitblit.com (self-hosted)" src="https://dev.gitblit.com" /> | ||
| <link name="Discussion" src="${project.forumUrl}" /> | ||
| <link name="Twitter" src="https://twitter.com/gitblit" /> | ||
| <link name="Ohloh" src="http://www.ohloh.net/p/gitblit" /> | ||
| </menu> | ||
| </structure> | ||
| <replace token="%GCURL%" value="${gc.url}${currentRelease.tag}/" /> | ||
| <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" /> | ||
| <regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>" /> | ||
| <regex searchPattern="\b(commit)(\s*[#]?|-){0,1}([0-9a-fA-F]{5,})\b" replacePattern="<a href='https://github.com/gitblit/gitblit/commit/$3'>commit $3</a>" /> | ||
| <regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/issues/$3'>issue $3</a>" /> | ||
| <regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/pull/$3'>pull request #$3</a>" /> | ||
@@ -994,3 +1118,3 @@ <regex searchPattern="\b(ticket)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://dev.gitblit.com/tickets/gitblit.git/$3'>ticket $3</a>" /> | ||
| </macrodef> | ||
| <!-- | ||
@@ -1014,2 +1138,79 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Macro to create release draft on GitHub | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| --> | ||
| <macrodef name="ghReleaseDraft"> | ||
| <attribute name="releaselog" /> | ||
| <attribute name="releasetag" /> | ||
| <sequential> | ||
| <echo>creating release ${release.tag} draft on GitHub</echo> | ||
| <exec executable="bash" logError="true" failonerror="true" outputproperty="ghrelease.id"> | ||
| <arg value="-c" /> | ||
| <arg value="${octokit} create_release ${gh.org} gitblit @{releasetag} name=${project.version} draft=true | cut -f2"></arg> | ||
| </exec> | ||
| <exec executable="bash" logError="true" failonerror="true" outputproperty="ghrelease.upldUrl"> | ||
| <arg value="-c" /> | ||
| <arg value="${octokit} release ${gh.org} gitblit ${ghrelease.id} _filter=.upload_url | sed 's/{.*$/?name=/'"></arg> | ||
| </exec> | ||
| <exec executable="bash" logError="true" failonerror="true" outputproperty="ghrelease.notes"> | ||
| <arg value="-c" /> | ||
| <arg value="cat @{releaselog} | awk -f ${relnoawk} protect=true"></arg> | ||
| </exec> | ||
| <exec executable="bash" logError="true" > | ||
| <arg value="-c" /> | ||
| <arg value="${octokit} -q edit_release ${gh.org} gitblit ${ghrelease.id} body='${ghrelease.notes}'"></arg> | ||
| </exec> | ||
| </sequential> | ||
| </macrodef> | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Macro to upload binaries to GitHub | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| --> | ||
| <macrodef name="githubUpload"> | ||
| <attribute name="source"/> | ||
| <attribute name="target"/> | ||
| <sequential> | ||
| <echo>uploading @{source} to GitHub release ${ghrelease.id}</echo> | ||
| <exec executable="bash" logError="true" failonerror="true" > | ||
| <arg value="-c" /> | ||
| <arg value="${octokit} upload_asset ${ghrelease.upldUrl}@{target} @{source}"></arg> | ||
| </exec> | ||
| </sequential> | ||
| </macrodef> | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Macro to publish release draft on GitHub | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| --> | ||
| <macrodef name="ghPublishReleaseDraft"> | ||
| <attribute name="releaseid"/> | ||
| <sequential> | ||
| <echo>publishing GitHub release draft @{releaseid}</echo> | ||
| <exec executable="bash" logError="true" > | ||
| <arg value="-c" /> | ||
| <arg value="${octokit} -q edit_release ${gh.org} gitblit @{releaseid} draft=false"></arg> | ||
| </exec> | ||
| </sequential> | ||
| </macrodef> | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Macro to publish release draft on GitHub | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| --> | ||
| <macrodef name="ghGetReleaseId"> | ||
| <attribute name="releaseVersion"/> | ||
| <sequential> | ||
| <exec executable="bash" logError="true" failonerror="true" outputproperty="ghrelease.id"> | ||
| <arg value="-c" /> | ||
| <arg value="${octokit} list_releases ${gh.org} gitblit _filter='.[] | "\(.name)\t\(.tag_name)\t\(.id)"' | grep @{releaseVersion} | cut -f3"></arg> | ||
| </exec> | ||
| </sequential> | ||
| </macrodef> | ||
| <!-- | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Install Gitblit JAR for usage as Maven module | ||
@@ -1016,0 +1217,0 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
@@ -339,3 +339,3 @@ /* | ||
| mkp.yield header.oldPath | ||
| mkp.yieldUnescaped "<b> -&rt; </b>" | ||
| mkp.yieldUnescaped "<b> -> </b>" | ||
| a(href:blobDiffUrl(id, header.newPath), header.newPath) | ||
@@ -342,0 +342,0 @@ } |
| #!/bin/bash | ||
| java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data | ||
| java -cp gitblit.jar:ext/* com.gitblit.authority.GitblitAuthority --baseFolder data |
| #!/bin/bash | ||
| java -jar gitblit.jar --baseFolder data --stop | ||
| java -cp "gitblit.jar:ext/*" com.gitblit.GitBlitServer --baseFolder data --stop |
| #!/bin/bash | ||
| java -jar gitblit.jar --baseFolder data | ||
| java -cp "gitblit.jar:ext/*" com.gitblit.GitBlitServer --baseFolder data |
@@ -1,1 +0,1 @@ | ||
| @java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data %* | ||
| @java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.authority.GitblitAuthority --baseFolder data %* |
@@ -1,1 +0,1 @@ | ||
| @java -jar gitblit.jar --stop --baseFolder data %* | ||
| @java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.GitBlitServer --stop --baseFolder data %* |
@@ -1,1 +0,1 @@ | ||
| @java -jar gitblit.jar --baseFolder data %* | ||
| @java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.GitBlitServer --baseFolder data %* |
@@ -26,3 +26,3 @@ @REM Install Gitblit as a Windows service. | ||
| --StartPath="%CD%" ^ | ||
| --StartClass=org.moxie.MxLauncher ^ | ||
| --StartClass=com.gitblit.GitBlitServer ^ | ||
| --StartMethod=main ^ | ||
@@ -32,9 +32,9 @@ --StartParams="--storePassword;gitblit;--baseFolder;%CD%\data" ^ | ||
| --StopPath="%CD%" ^ | ||
| --StopClass=org.moxie.MxLauncher ^ | ||
| --StopClass=com.gitblit.GitBlitServer ^ | ||
| --StopMethod=main ^ | ||
| --StopParams="--stop;--baseFolder;%CD%\data" ^ | ||
| --StopMode=jvm ^ | ||
| --Classpath="%CD%\gitblit.jar" ^ | ||
| --Classpath="%CD%\gitblit.jar;%CD%\ext\*" ^ | ||
| --Jvm=auto ^ | ||
| --JvmMx=1024 | ||
@@ -81,6 +81,6 @@ /* | ||
| 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(); | ||
| } | ||
@@ -87,0 +87,0 @@ } |
@@ -199,3 +199,3 @@ /* | ||
| // create a user cookie | ||
| setCookie(user, password); | ||
| setCookie(user); | ||
@@ -202,0 +202,0 @@ // Set user attributes, hide password from backing user service. |
@@ -19,5 +19,2 @@ /* | ||
| import java.net.URI; | ||
| import java.net.URISyntaxException; | ||
| import java.security.GeneralSecurityException; | ||
| import java.text.MessageFormat; | ||
@@ -37,2 +34,3 @@ import java.util.Arrays; | ||
| import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; | ||
| import com.gitblit.ldap.LdapConnection; | ||
| import com.gitblit.models.TeamModel; | ||
@@ -44,7 +42,4 @@ import com.gitblit.models.UserModel; | ||
| import com.unboundid.ldap.sdk.Attribute; | ||
| import com.unboundid.ldap.sdk.DereferencePolicy; | ||
| import com.unboundid.ldap.sdk.ExtendedResult; | ||
| import com.unboundid.ldap.sdk.LDAPConnection; | ||
| import com.unboundid.ldap.sdk.BindResult; | ||
| import com.unboundid.ldap.sdk.LDAPException; | ||
| import com.unboundid.ldap.sdk.LDAPSearchException; | ||
| import com.unboundid.ldap.sdk.ResultCode; | ||
@@ -55,6 +50,2 @@ import com.unboundid.ldap.sdk.SearchRequest; | ||
| import com.unboundid.ldap.sdk.SearchScope; | ||
| import com.unboundid.ldap.sdk.SimpleBindRequest; | ||
| import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; | ||
| import com.unboundid.util.ssl.SSLUtil; | ||
| import com.unboundid.util.ssl.TrustAllTrustManager; | ||
@@ -114,8 +105,14 @@ /** | ||
| final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.removeDeletedUsers, true); | ||
| LDAPConnection ldapConnection = getLdapConnection(); | ||
| if (ldapConnection != null) { | ||
| LdapConnection ldapConnection = new LdapConnection(settings); | ||
| if (ldapConnection.connect()) { | ||
| if (ldapConnection.bind() == null) { | ||
| ldapConnection.close(); | ||
| logger.error("Cannot synchronize with LDAP."); | ||
| return; | ||
| } | ||
| try { | ||
| String accountBase = settings.getString(Keys.realm.ldap.accountBase, ""); | ||
| String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid"); | ||
| String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); | ||
| String accountBase = ldapConnection.getAccountBase(); | ||
| String accountPattern = ldapConnection.getAccountPattern(); | ||
| accountPattern = StringUtils.replace(accountPattern, "${username}", "*"); | ||
@@ -171,2 +168,4 @@ | ||
| for (TeamModel userTeam : user.teams) { | ||
| // Is this an administrative team? | ||
| setAdminAttribute(userTeam); | ||
| userTeams.put(userTeam.name, userTeam); | ||
@@ -188,62 +187,2 @@ } | ||
| private LDAPConnection getLdapConnection() { | ||
| try { | ||
| URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server)); | ||
| String ldapHost = ldapUrl.getHost(); | ||
| int ldapPort = ldapUrl.getPort(); | ||
| String bindUserName = settings.getString(Keys.realm.ldap.username, ""); | ||
| String bindPassword = settings.getString(Keys.realm.ldap.password, ""); | ||
| LDAPConnection conn; | ||
| if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { | ||
| // SSL | ||
| SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); | ||
| conn = new LDAPConnection(sslUtil.createSSLSocketFactory()); | ||
| if (ldapPort == -1) { | ||
| ldapPort = 636; | ||
| } | ||
| } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { | ||
| // no encryption or StartTLS | ||
| conn = new LDAPConnection(); | ||
| if (ldapPort == -1) { | ||
| ldapPort = 389; | ||
| } | ||
| } else { | ||
| logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme()); | ||
| return null; | ||
| } | ||
| conn.connect(ldapHost, ldapPort); | ||
| if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { | ||
| SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); | ||
| ExtendedResult extendedResult = conn.processExtendedOperation( | ||
| new StartTLSExtendedRequest(sslUtil.createSSLContext())); | ||
| if (extendedResult.getResultCode() != ResultCode.SUCCESS) { | ||
| throw new LDAPException(extendedResult.getResultCode()); | ||
| } | ||
| } | ||
| if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) { | ||
| // anonymous bind | ||
| conn.bind(new SimpleBindRequest()); | ||
| } else { | ||
| // authenticated bind | ||
| conn.bind(new SimpleBindRequest(bindUserName, bindPassword)); | ||
| } | ||
| return conn; | ||
| } catch (URISyntaxException e) { | ||
| logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e); | ||
| } catch (GeneralSecurityException e) { | ||
| logger.error("Unable to create SSL Connection", e); | ||
| } catch (LDAPException e) { | ||
| logger.error("Error Connecting to LDAP", e); | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
@@ -300,6 +239,3 @@ * Credentials are defined in the LDAP server and can not be manipulated | ||
| if (!supportsTeamMembershipChanges()) { | ||
| List<String> admins = settings.getStrings(Keys.realm.ldap.admins); | ||
| if (admins.contains(user.username)) { | ||
| return false; | ||
| } | ||
| return false; | ||
| } | ||
@@ -314,6 +250,3 @@ } | ||
| if (!supportsTeamMembershipChanges()) { | ||
| List<String> admins = settings.getStrings(Keys.realm.ldap.admins); | ||
| if (admins.contains("@" + team.name)) { | ||
| return false; | ||
| } | ||
| return false; | ||
| } | ||
@@ -333,25 +266,24 @@ } | ||
| LDAPConnection ldapConnection = getLdapConnection(); | ||
| if (ldapConnection != null) { | ||
| try { | ||
| boolean alreadyAuthenticated = false; | ||
| LdapConnection ldapConnection = new LdapConnection(settings); | ||
| if (ldapConnection.connect()) { | ||
| String bindPattern = settings.getString(Keys.realm.ldap.bindpattern, ""); | ||
| if (!StringUtils.isEmpty(bindPattern)) { | ||
| try { | ||
| String bindUser = StringUtils.replace(bindPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); | ||
| ldapConnection.bind(bindUser, new String(password)); | ||
| // Try to bind either to the "manager" account, | ||
| // or directly to the DN of the user logging in, if realm.ldap.bindpattern is configured. | ||
| String passwd = new String(password); | ||
| BindResult bindResult = null; | ||
| String bindPattern = settings.getString(Keys.realm.ldap.bindpattern, ""); | ||
| if (! StringUtils.isEmpty(bindPattern)) { | ||
| bindResult = ldapConnection.bind(bindPattern, simpleUsername, passwd); | ||
| } else { | ||
| bindResult = ldapConnection.bind(); | ||
| } | ||
| if (bindResult == null) { | ||
| ldapConnection.close(); | ||
| return null; | ||
| } | ||
| alreadyAuthenticated = true; | ||
| } catch (LDAPException e) { | ||
| return null; | ||
| } | ||
| } | ||
| try { | ||
| // Find the logging in user's DN | ||
| String accountBase = settings.getString(Keys.realm.ldap.accountBase, ""); | ||
| String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); | ||
| accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); | ||
| SearchResult result = doSearch(ldapConnection, accountBase, accountPattern); | ||
| SearchResult result = ldapConnection.searchUser(simpleUsername); | ||
| if (result != null && result.getEntryCount() == 1) { | ||
@@ -361,3 +293,3 @@ SearchResultEntry loggingInUser = result.getSearchEntries().get(0); | ||
| if (alreadyAuthenticated || isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) { | ||
| if (ldapConnection.isAuthenticated(loggingInUserDN, passwd)) { | ||
| logger.debug("LDAP authenticated: " + username); | ||
@@ -374,3 +306,3 @@ | ||
| // create a user cookie | ||
| setCookie(user, password); | ||
| setCookie(user); | ||
@@ -389,2 +321,4 @@ if (!supportsTeamMembershipChanges()) { | ||
| for (TeamModel userTeam : user.teams) { | ||
| // Is this an administrative team? | ||
| setAdminAttribute(userTeam); | ||
| updateTeam(userTeam); | ||
@@ -420,6 +354,3 @@ } | ||
| for (String admin : admins) { | ||
| if (admin.startsWith("@") && user.isTeamMember(admin.substring(1))) { | ||
| // admin team | ||
| user.canAdmin = true; | ||
| } else if (user.getName().equalsIgnoreCase(admin)) { | ||
| if (user.getName().equalsIgnoreCase(admin)) { | ||
| // admin user | ||
@@ -433,2 +364,26 @@ user.canAdmin = true; | ||
| /** | ||
| * Set the canAdmin attribute for team retrieved from LDAP. | ||
| * If we are not storing teams in LDAP and/or we have not defined any | ||
| * administrator teams, then do not change the admin flag. | ||
| * | ||
| * @param team | ||
| */ | ||
| private void setAdminAttribute(TeamModel team) { | ||
| if (!supportsTeamMembershipChanges()) { | ||
| List<String> admins = settings.getStrings(Keys.realm.ldap.admins); | ||
| // if we have defined administrative teams, then set admin flag | ||
| // otherwise leave admin flag unchanged | ||
| if (!ArrayUtils.isEmpty(admins)) { | ||
| team.canAdmin = false; | ||
| for (String admin : admins) { | ||
| if (admin.startsWith("@") && team.name.equalsIgnoreCase(admin.substring(1))) { | ||
| // admin team | ||
| team.canAdmin = true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| private void setUserAttributes(UserModel user, SearchResultEntry userEntry) { | ||
@@ -480,3 +435,3 @@ // Is this user an admin? | ||
| private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) { | ||
| private void getTeamsFromLdap(LdapConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) { | ||
| String loggingInUserDN = loggingInUser.getDN(); | ||
@@ -490,11 +445,11 @@ | ||
| groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN)); | ||
| groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); | ||
| groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", LdapConnection.escapeLDAPSearchFilter(loggingInUserDN)); | ||
| groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", LdapConnection.escapeLDAPSearchFilter(simpleUsername)); | ||
| // Fill in attributes into groupMemberPattern | ||
| for (Attribute userAttribute : loggingInUser.getAttributes()) { | ||
| groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue())); | ||
| groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", LdapConnection.escapeLDAPSearchFilter(userAttribute.getValue())); | ||
| } | ||
| SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn")); | ||
| SearchResult teamMembershipResult = searchTeamsInLdap(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn")); | ||
| if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) { | ||
@@ -516,3 +471,3 @@ for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) { | ||
| private void getEmptyTeamsFromLdap(LDAPConnection ldapConnection) { | ||
| private void getEmptyTeamsFromLdap(LdapConnection ldapConnection) { | ||
| logger.info("Start fetching empty teams from ldap."); | ||
@@ -522,3 +477,3 @@ String groupBase = settings.getString(Keys.realm.ldap.groupBase, ""); | ||
| SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, null); | ||
| SearchResult teamMembershipResult = searchTeamsInLdap(ldapConnection, groupBase, true, groupMemberPattern, null); | ||
| if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) { | ||
@@ -533,2 +488,3 @@ for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) { | ||
| teamModel = createTeamFromLdap(teamEntry); | ||
| setAdminAttribute(teamModel); | ||
| userManager.updateTeamModel(teamModel); | ||
@@ -542,2 +498,26 @@ } | ||
| private SearchResult searchTeamsInLdap(LdapConnection ldapConnection, String base, boolean dereferenceAliases, String filter, List<String> attributes) { | ||
| SearchResult result = ldapConnection.search(base, dereferenceAliases, filter, attributes); | ||
| if (result == null) { | ||
| return null; | ||
| } | ||
| if (result.getResultCode() != ResultCode.SUCCESS) { | ||
| // Retry the search with user authorization in case we searched as a manager account that could not search for teams. | ||
| logger.debug("Rebinding as user to search for teams in LDAP"); | ||
| result = null; | ||
| if (ldapConnection.rebindAsUser()) { | ||
| result = ldapConnection.search(base, dereferenceAliases, filter, attributes); | ||
| if (result.getResultCode() != ResultCode.SUCCESS) { | ||
| return null; | ||
| } | ||
| logger.info("Successful search after rebinding as user."); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) { | ||
@@ -551,27 +531,10 @@ TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn")); | ||
| private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) { | ||
| private SearchResult doSearch(LdapConnection ldapConnection, String base, String filter) { | ||
| try { | ||
| return ldapConnection.search(base, SearchScope.SUB, filter); | ||
| } catch (LDAPSearchException e) { | ||
| logger.error("Problem Searching LDAP", e); | ||
| return null; | ||
| } | ||
| } | ||
| private SearchResult doSearch(LDAPConnection ldapConnection, String base, boolean dereferenceAliases, String filter, List<String> attributes) { | ||
| try { | ||
| SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter); | ||
| if (dereferenceAliases) { | ||
| searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING); | ||
| SearchResult result = ldapConnection.search(searchRequest); | ||
| if (result.getResultCode() != ResultCode.SUCCESS) { | ||
| return null; | ||
| } | ||
| if (attributes != null) { | ||
| searchRequest.setAttributes(attributes); | ||
| } | ||
| return ldapConnection.search(searchRequest); | ||
| } catch (LDAPSearchException e) { | ||
| logger.error("Problem Searching LDAP", e); | ||
| return null; | ||
| return result; | ||
| } catch (LDAPException e) { | ||
@@ -583,13 +546,5 @@ logger.error("Problem creating LDAP search", e); | ||
| private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) { | ||
| try { | ||
| // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN | ||
| ldapConnection.bind(userDn, password); | ||
| return true; | ||
| } catch (LDAPException e) { | ||
| logger.error("Error authenticating user", e); | ||
| return false; | ||
| } | ||
| } | ||
| /** | ||
@@ -610,30 +565,2 @@ * Returns a simple username without any domain prefixes. | ||
| // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java | ||
| public static final String escapeLDAPSearchFilter(String filter) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| for (int i = 0; i < filter.length(); i++) { | ||
| char curChar = filter.charAt(i); | ||
| switch (curChar) { | ||
| case '\\': | ||
| sb.append("\\5c"); | ||
| break; | ||
| case '*': | ||
| sb.append("\\2a"); | ||
| break; | ||
| case '(': | ||
| sb.append("\\28"); | ||
| break; | ||
| case ')': | ||
| sb.append("\\29"); | ||
| break; | ||
| case '\u0000': | ||
| sb.append("\\00"); | ||
| break; | ||
| default: | ||
| sb.append(curChar); | ||
| } | ||
| } | ||
| return sb.toString(); | ||
| } | ||
| private void configureSyncService() { | ||
@@ -651,3 +578,2 @@ LdapSyncService ldapSyncService = new LdapSyncService(settings, this); | ||
| } | ||
| } |
@@ -125,3 +125,3 @@ /* | ||
| // create a user cookie | ||
| setCookie(user, password); | ||
| setCookie(user); | ||
@@ -128,0 +128,0 @@ // update user attributes from UnixUser |
@@ -142,3 +142,3 @@ /* | ||
| // create a user cookie | ||
| setCookie(user, password); | ||
| setCookie(user); | ||
@@ -145,0 +145,0 @@ // update user attributes from Redmine |
@@ -69,3 +69,3 @@ package com.gitblit.auth; | ||
| setCookie(user, password); | ||
| setCookie(user); | ||
| setUserAttributes(user, info); | ||
@@ -72,0 +72,0 @@ |
@@ -156,3 +156,3 @@ /* | ||
| // create a user cookie | ||
| setCookie(user, password); | ||
| setCookie(user); | ||
@@ -159,0 +159,0 @@ // update user attributes from Windows identity |
@@ -51,3 +51,5 @@ /* | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.Map; | ||
| import java.util.ResourceBundle; | ||
@@ -102,2 +104,3 @@ import javax.mail.Message; | ||
| import com.gitblit.utils.X509Utils.X509Metadata; | ||
| import com.gitblit.wicket.GitBlitWebSession; | ||
@@ -452,3 +455,3 @@ /** | ||
| File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE); | ||
| File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this); | ||
| File zip = X509Utils.newClientBundle(user,metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this); | ||
@@ -856,5 +859,15 @@ // save latest expiration date | ||
| Mailing mailing = Mailing.newPlain(); | ||
| mailing.subject = "Your Gitblit client certificate for " + metadata.serverHostname; | ||
| if( user.getPreferences().getLocale()!=null ) | ||
| mailing.subject = MessageFormat.format(ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp",user.getPreferences().getLocale()).getString("gb.emailClientCertificateSubject"), metadata.serverHostname); | ||
| else | ||
| mailing.subject = MessageFormat.format(ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", Locale.ENGLISH).getString("gb.emailClientCertificateSubject") , metadata.serverHostname); | ||
| mailing.setRecipients(user.emailAddress); | ||
| String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata); | ||
| File fileMailTmp = null; | ||
| String body = null; | ||
| if( (fileMailTmp = new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"+"_"+user.getPreferences().getLocale())).exists()) | ||
| body = X509Utils.processTemplate(fileMailTmp, metadata); | ||
| else{ | ||
| fileMailTmp = new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"); | ||
| body = X509Utils.processTemplate(fileMailTmp, metadata); | ||
| } | ||
| if (StringUtils.isEmpty(body)) { | ||
@@ -861,0 +874,0 @@ body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName()); |
@@ -61,4 +61,6 @@ /* | ||
| import com.gitblit.models.UserModel; | ||
| import com.gitblit.utils.PasswordHash; | ||
| import com.gitblit.utils.StringUtils; | ||
| public class EditUserDialog extends JDialog { | ||
@@ -321,4 +323,6 @@ | ||
| } | ||
| if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE) | ||
| && !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | ||
| // What we actually test for here, is if the password has been changed. But this also catches if the password | ||
| // was not changed, but is stored in plain-text. Which is good because then editing the user will hash the | ||
| // password if by now the storage has been changed to a hashed variant. | ||
| if (!PasswordHash.isHashedEntry(password)) { | ||
| String cpw = new String(confirmPasswordField.getPassword()); | ||
@@ -335,17 +339,15 @@ if (cpw == null || cpw.length() != password.length()) { | ||
| // 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")) { | ||
| // store MD5 digest of password | ||
| user.password = StringUtils.MD5_TYPE + StringUtils.getMD5(password); | ||
| } else if (type.equalsIgnoreCase("combined-md5")) { | ||
| // store MD5 digest of username+password | ||
| user.password = StringUtils.COMBINED_MD5_TYPE | ||
| + StringUtils.getMD5(user.username + password); | ||
| String type = settings.get(Keys.realm.passwordStorage).getString(PasswordHash.getDefaultType().name()); | ||
| PasswordHash pwdHash = PasswordHash.instanceOf(type); | ||
| if (pwdHash != null) { | ||
| user.password = pwdHash.toHashedEntry(password, user.username); | ||
| } else { | ||
| // plain-text password | ||
| // plain-text password. | ||
| // TODO: This is also used when the "realm.passwordStorage" configuration is not a valid type. | ||
| // This is a rather bad default, and should probably caught and changed to a secure default. | ||
| user.password = password; | ||
| } | ||
| } else if (rename && password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | ||
| } else if (rename && password.toUpperCase().startsWith(PasswordHash.Type.CMD5.name())) { | ||
| error("Gitblit is configured for combined-md5 password hashing. You must enter a new password on account rename."); | ||
@@ -352,0 +354,0 @@ return false; |
@@ -901,3 +901,3 @@ /* | ||
| if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) { | ||
| user.cookie = StringUtils.getSHA1(user.username + user.password); | ||
| user.cookie = user.createCookie(); | ||
| } | ||
@@ -904,0 +904,0 @@ |
@@ -65,2 +65,8 @@ /* | ||
| 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"; | ||
@@ -643,2 +649,33 @@ public static final String ZIP_PATH = "/zip/"; | ||
| /** | ||
| * 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 | ||
@@ -645,0 +682,0 @@ @Retention(RetentionPolicy.RUNTIME) |
@@ -602,3 +602,3 @@ /* | ||
| // 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) { | ||
@@ -1283,2 +1283,3 @@ case ALREADY_MERGED: | ||
| ticket.mergeTo, | ||
| getRepositoryModel().mergeType, | ||
| committer, | ||
@@ -1285,0 +1286,0 @@ message); |
@@ -146,3 +146,3 @@ /* | ||
| System.out | ||
| .println("\nExample:\n java -server -Xmx1024M -jar gitblit.jar --repositoriesFolder c:\\git --httpPort 80 --httpsPort 443"); | ||
| .println("\nExample:\n java -server -Xmx1024M -cp gitblit.jar:ext/* com.gitblit.GitBlitServer --repositoriesFolder /srv/git --httpPort 80 --httpsPort 443"); | ||
| } | ||
@@ -229,2 +229,6 @@ System.exit(0); | ||
| String javaversion = System.getProperty("java.version"); | ||
| String javavendor = System.getProperty("java.vendor"); | ||
| logger.info("JVM version " + javaversion + " (" + javavendor + ")"); | ||
| QueuedThreadPool threadPool = new QueuedThreadPool(); | ||
@@ -298,3 +302,3 @@ int maxThreads = settings.getInteger(Keys.server.threadPoolSize, 50); | ||
| connector.setSoLingerTime(-1); | ||
| connector.setIdleTimeout(30000); | ||
| connector.setIdleTimeout(settings.getLong(Keys.server.httpIdleTimeout, 30000L)); | ||
| connector.setPort(params.securePort); | ||
@@ -336,3 +340,3 @@ String bindInterface = settings.getString(Keys.server.httpsBindInterface, null); | ||
| connector.setSoLingerTime(-1); | ||
| connector.setIdleTimeout(30000); | ||
| connector.setIdleTimeout(settings.getLong(Keys.server.httpIdleTimeout, 30000L)); | ||
| connector.setPort(params.port); | ||
@@ -382,3 +386,4 @@ String bindInterface = settings.getString(Keys.server.httpBindInterface, null); | ||
| // 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); | ||
@@ -615,2 +620,2 @@ | ||
| } | ||
| } | ||
| } |
@@ -29,2 +29,5 @@ /* | ||
| * | ||
| * Plugins implementing this interface (which are instantiated during {@link com.gitblit.manager.UserManager#start()}) can provide | ||
| * a default constructor or might also use {@link IRuntimeManager} as a constructor argument which will be passed automatically then. | ||
| * | ||
| * @author James Moger | ||
@@ -31,0 +34,0 @@ * |
@@ -55,2 +55,3 @@ /* | ||
| import com.gitblit.utils.HttpUtils; | ||
| import com.gitblit.utils.PasswordHash; | ||
| import com.gitblit.utils.StringUtils; | ||
@@ -522,22 +523,47 @@ import com.gitblit.utils.X509Utils.X509Metadata; | ||
| UserModel returnedUser = null; | ||
| if (user.password.startsWith(StringUtils.MD5_TYPE)) { | ||
| // password digest | ||
| String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); | ||
| if (user.password.equalsIgnoreCase(md5)) { | ||
| PasswordHash pwdHash = PasswordHash.instanceFor(user.password); | ||
| if (pwdHash != null) { | ||
| if (pwdHash.matches(user.password, password, user.username)) { | ||
| returnedUser = user; | ||
| } | ||
| } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { | ||
| // username+password digest | ||
| String md5 = StringUtils.COMBINED_MD5_TYPE | ||
| + StringUtils.getMD5(user.username.toLowerCase() + new String(password)); | ||
| if (user.password.equalsIgnoreCase(md5)) { | ||
| returnedUser = user; | ||
| } | ||
| } else if (user.password.equals(new String(password))) { | ||
| // plain-text password | ||
| returnedUser = user; | ||
| } | ||
| // validate user | ||
| returnedUser = validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); | ||
| // try to upgrade the stored password hash to a stronger hash, if necessary | ||
| upgradeStoredPassword(returnedUser, password, pwdHash); | ||
| return returnedUser; | ||
| } | ||
| /** | ||
| * Upgrade stored password to a strong hash if configured. | ||
| * | ||
| * @param user the user to be updated | ||
| * @param password the password | ||
| * @param pwdHash | ||
| * Instance of PasswordHash for the stored password entry. If null, no current hashing is assumed. | ||
| */ | ||
| private void upgradeStoredPassword(UserModel user, char[] password, PasswordHash pwdHash) { | ||
| // check if user has successfully authenticated i.e. is not null | ||
| if (user == null) return; | ||
| // check if strong hash algorithm is configured | ||
| String algorithm = settings.getString(Keys.realm.passwordStorage, PasswordHash.getDefaultType().name()); | ||
| if (pwdHash == null || pwdHash.needsUpgradeTo(algorithm)) { | ||
| // rehash the provided correct password and update the user model | ||
| pwdHash = PasswordHash.instanceOf(algorithm); | ||
| if (pwdHash != null) { // necessary since the algorithm name could be something not supported. | ||
| user.password = pwdHash.toHashedEntry(password, user.username); | ||
| userManager.updateUserModel(user); | ||
| } | ||
| } | ||
| return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); | ||
| } | ||
| /** | ||
@@ -613,2 +639,7 @@ * Returns the Gitlbit cookie in the request. | ||
| 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()); | ||
| } | ||
@@ -628,2 +659,11 @@ } | ||
| 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) ); | ||
| } | ||
| /** | ||
@@ -630,0 +670,0 @@ * Logout a user. |
@@ -20,2 +20,4 @@ /* | ||
| import java.io.IOException; | ||
| import java.lang.reflect.Constructor; | ||
| import java.lang.reflect.InvocationTargetException; | ||
| import java.text.MessageFormat; | ||
@@ -123,4 +125,6 @@ import java.util.ArrayList; | ||
| service = createUserService(realmFile); | ||
| } catch (InstantiationException | IllegalAccessException e) { | ||
| logger.error("failed to instantiate user service {}: {}", realm, e.getMessage()); | ||
| } catch (InstantiationException | IllegalAccessException e) { | ||
| logger.error("failed to instantiate user service {}: {}. Trying once again with IRuntimeManager constructor", realm, e.getMessage()); | ||
| //try once again with IRuntimeManager constructor. This adds support for subclasses of ConfigUserService and other custom IUserServices | ||
| service = createIRuntimeManagerAwareUserService(realm); | ||
| } | ||
@@ -133,2 +137,18 @@ } | ||
| /** | ||
| * Tries to create an {@link IUserService} with {@link #runtimeManager} as a constructor parameter | ||
| * | ||
| * @param realm the class name of the {@link IUserService} to be instantiated | ||
| * @return the {@link IUserService} or {@code null} if instantiation fails | ||
| */ | ||
| private IUserService createIRuntimeManagerAwareUserService(String realm) { | ||
| try { | ||
| Constructor<?> constructor = Class.forName(realm).getConstructor(IRuntimeManager.class); | ||
| return (IUserService) constructor.newInstance(runtimeManager); | ||
| } catch (NoSuchMethodException | SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { | ||
| logger.error("failed to instantiate user service {}: {}", realm, e.getMessage()); | ||
| return null; | ||
| } | ||
| } | ||
| protected IUserService createUserService(File realmFile) { | ||
@@ -135,0 +155,0 @@ IUserService service = null; |
@@ -119,3 +119,3 @@ /* | ||
| System.out | ||
| .println("\nExample:\n java -gitblit.jar com.gitblit.MigrateTickets com.gitblit.tickets.RedisTicketService --baseFolder c:\\gitblit-data"); | ||
| .println("\nExample:\n java -cp gitblit.jar;\"%CD%/ext/*\" com.gitblit.MigrateTickets com.gitblit.tickets.RedisTicketService --baseFolder c:\\gitblit-data"); | ||
| } | ||
@@ -122,0 +122,0 @@ System.exit(0); |
@@ -31,2 +31,3 @@ /* | ||
| import com.gitblit.Constants.FederationStrategy; | ||
| import com.gitblit.Constants.MergeType; | ||
| import com.gitblit.utils.ArrayUtils; | ||
@@ -93,2 +94,3 @@ import com.gitblit.utils.ModelUtils; | ||
| public String mergeTo; | ||
| public MergeType mergeType; | ||
@@ -116,2 +118,3 @@ public transient boolean isCollectingGarbage; | ||
| this.acceptNewPatchsets = true; | ||
| this.mergeType = MergeType.DEFAULT_MERGE_TYPE; | ||
@@ -118,0 +121,0 @@ addOwner(owner); |
@@ -46,2 +46,4 @@ /* | ||
| import com.gitblit.Constants; | ||
| /** | ||
@@ -777,6 +779,6 @@ * The Gitblit Ticket model, its component classes, and enums. | ||
| 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); | ||
@@ -783,0 +785,0 @@ } |
@@ -39,2 +39,3 @@ /* | ||
| import com.gitblit.utils.ModelUtils; | ||
| import com.gitblit.utils.SecureRandom; | ||
| import com.gitblit.utils.StringUtils; | ||
@@ -56,2 +57,4 @@ | ||
| private static final SecureRandom RANDOM = new SecureRandom(); | ||
| // field names are reflectively mapped in EditUser page | ||
@@ -665,2 +668,6 @@ public String username; | ||
| } | ||
| public String createCookie() { | ||
| return StringUtils.getSHA1(RANDOM.randomBytes(32)); | ||
| } | ||
| } |
@@ -114,3 +114,3 @@ /* | ||
| System.out | ||
| .println("\nExample:\n java -gitblit.jar com.gitblit.ReindexTickets --baseFolder c:\\gitblit-data"); | ||
| .println("\nExample:\n java -cp gitblit.jar;\"%CD%/ext/*\" com.gitblit.ReindexTickets --baseFolder c:\\gitblit-data"); | ||
| } | ||
@@ -117,0 +117,0 @@ System.exit(0); |
@@ -68,3 +68,2 @@ /* | ||
| import org.apache.lucene.store.FSDirectory; | ||
| import org.apache.lucene.util.Version; | ||
| import org.eclipse.jgit.diff.DiffEntry.ChangeType; | ||
@@ -122,11 +121,5 @@ import org.eclipse.jgit.lib.Constants; | ||
| private static final String CONF_FILE = "lucene.conf"; | ||
| private static final String LUCENE_DIR = "lucene"; | ||
| private static final String CONF_INDEX = "index"; | ||
| private static final String CONF_VERSION = "version"; | ||
| private static final String CONF_ALIAS = "aliases"; | ||
| private static final String CONF_BRANCH = "branches"; | ||
| private static final Version LUCENE_VERSION = Version.LUCENE_4_10_0; | ||
| private final Logger logger = LoggerFactory.getLogger(LuceneService.class); | ||
@@ -272,3 +265,3 @@ | ||
| try { | ||
| writers.get(writer).close(true); | ||
| writers.get(writer).close(); | ||
| } catch (Throwable t) { | ||
@@ -299,22 +292,9 @@ logger.error("Failed to close Lucene writer for " + writer, t); | ||
| public boolean deleteIndex(String repositoryName) { | ||
| try { | ||
| // close any open writer/searcher | ||
| close(repositoryName); | ||
| // close any open writer/searcher | ||
| close(repositoryName); | ||
| // delete the index folder | ||
| File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED); | ||
| File luceneIndex = new File(repositoryFolder, LUCENE_DIR); | ||
| if (luceneIndex.exists()) { | ||
| org.eclipse.jgit.util.FileUtils.delete(luceneIndex, | ||
| org.eclipse.jgit.util.FileUtils.RECURSIVE); | ||
| } | ||
| // delete the config file | ||
| File luceneConfig = new File(repositoryFolder, CONF_FILE); | ||
| if (luceneConfig.exists()) { | ||
| luceneConfig.delete(); | ||
| } | ||
| return true; | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| // delete the index folder | ||
| File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED); | ||
| LuceneRepoIndexStore luceneIndex = new LuceneRepoIndexStore(repositoryFolder, INDEX_VERSION); | ||
| return luceneIndex.delete(); | ||
| } | ||
@@ -393,4 +373,4 @@ | ||
| private FileBasedConfig getConfig(Repository repository) { | ||
| File file = new File(repository.getDirectory(), CONF_FILE); | ||
| FileBasedConfig config = new FileBasedConfig(file, FS.detect()); | ||
| LuceneRepoIndexStore luceneIndex = new LuceneRepoIndexStore(repository.getDirectory(), INDEX_VERSION); | ||
| FileBasedConfig config = new FileBasedConfig(luceneIndex.getConfigFile(), FS.detect()); | ||
| return config; | ||
@@ -400,19 +380,10 @@ } | ||
| /** | ||
| * Reads the Lucene config file for the repository to check the index | ||
| * version. If the index version is different, then rebuild the repository | ||
| * index. | ||
| * Checks if an index exists for the repository, that is compatible with | ||
| * INDEX_VERSION and the Lucene version. | ||
| * | ||
| * @param repository | ||
| * @return true of the on-disk index format is different than INDEX_VERSION | ||
| * @return true if no index is found for the repository, false otherwise. | ||
| */ | ||
| private boolean shouldReindex(Repository repository) { | ||
| try { | ||
| FileBasedConfig config = getConfig(repository); | ||
| config.load(); | ||
| int indexVersion = config.getInt(CONF_INDEX, CONF_VERSION, 0); | ||
| // reindex if versions do not match | ||
| return indexVersion != INDEX_VERSION; | ||
| } catch (Throwable t) { | ||
| } | ||
| return true; | ||
| return ! (new LuceneRepoIndexStore(repository.getDirectory(), INDEX_VERSION).hasIndex()); | ||
| } | ||
@@ -627,3 +598,2 @@ | ||
| // commit all changes and reset the searcher | ||
| config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION); | ||
| config.save(); | ||
@@ -731,6 +701,5 @@ writer.commit(); | ||
| BooleanQuery query = new BooleanQuery(); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); | ||
| QueryParser qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer); | ||
| query.add(qp.parse(q), Occur.MUST); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| QueryParser qp = new QueryParser(FIELD_SUMMARY, analyzer); | ||
| BooleanQuery query = new BooleanQuery.Builder().add(qp.parse(q), Occur.MUST).build(); | ||
@@ -859,3 +828,2 @@ IndexWriter writer = getIndexWriter(repositoryName); | ||
| // update the config | ||
| config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION); | ||
| config.setString(CONF_ALIAS, null, keyName, branchName); | ||
@@ -978,12 +946,9 @@ config.setString(CONF_BRANCH, null, keyName, branch.getObjectId().getName()); | ||
| IndexWriter indexWriter = writers.get(repository); | ||
| File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repository), FS.DETECTED); | ||
| File indexFolder = new File(repositoryFolder, LUCENE_DIR); | ||
| Directory directory = FSDirectory.open(indexFolder); | ||
| if (indexWriter == null) { | ||
| if (!indexFolder.exists()) { | ||
| indexFolder.mkdirs(); | ||
| } | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); | ||
| IndexWriterConfig config = new IndexWriterConfig(LUCENE_VERSION, analyzer); | ||
| File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repository), FS.DETECTED); | ||
| LuceneRepoIndexStore indexStore = new LuceneRepoIndexStore(repositoryFolder, INDEX_VERSION); | ||
| indexStore.create(); | ||
| Directory directory = FSDirectory.open(indexStore.getPath()); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| IndexWriterConfig config = new IndexWriterConfig(analyzer); | ||
| config.setOpenMode(OpenMode.CREATE_OR_APPEND); | ||
@@ -1041,14 +1006,14 @@ indexWriter = new IndexWriter(directory, config); | ||
| Set<SearchResult> results = new LinkedHashSet<SearchResult>(); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| try { | ||
| // default search checks summary and content | ||
| BooleanQuery query = new BooleanQuery(); | ||
| BooleanQuery.Builder bldr = new BooleanQuery.Builder(); | ||
| QueryParser qp; | ||
| qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer); | ||
| qp = new QueryParser(FIELD_SUMMARY, analyzer); | ||
| qp.setAllowLeadingWildcard(true); | ||
| query.add(qp.parse(text), Occur.SHOULD); | ||
| bldr.add(qp.parse(text), Occur.SHOULD); | ||
| qp = new QueryParser(LUCENE_VERSION, FIELD_CONTENT, analyzer); | ||
| qp = new QueryParser(FIELD_CONTENT, analyzer); | ||
| qp.setAllowLeadingWildcard(true); | ||
| query.add(qp.parse(text), Occur.SHOULD); | ||
| bldr.add(qp.parse(text), Occur.SHOULD); | ||
@@ -1071,6 +1036,7 @@ IndexSearcher searcher; | ||
| BooleanQuery query = bldr.build(); | ||
| Query rewrittenQuery = searcher.rewrite(query); | ||
| logger.debug(rewrittenQuery.toString()); | ||
| TopScoreDocCollector collector = TopScoreDocCollector.create(5000, true); | ||
| TopScoreDocCollector collector = TopScoreDocCollector.create(5000); | ||
| searcher.search(rewrittenQuery, collector); | ||
@@ -1240,3 +1206,3 @@ int offset = Math.max(0, (page - 1) * pageSize); | ||
| MultiSourceReader(IndexReader [] readers) { | ||
| MultiSourceReader(IndexReader [] readers) throws IOException { | ||
| super(readers, false); | ||
@@ -1243,0 +1209,0 @@ } |
@@ -20,2 +20,3 @@ /* | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Date; | ||
@@ -35,2 +36,3 @@ import java.util.List; | ||
| import javax.mail.PasswordAuthentication; | ||
| import javax.mail.SendFailedException; | ||
| import javax.mail.Session; | ||
@@ -148,3 +150,3 @@ import javax.mail.Transport; | ||
| } | ||
| InternetAddress from = new InternetAddress(fromAddress, mailing.from == null ? "Gitblit" : mailing.from); | ||
| InternetAddress from = new InternetAddress(fromAddress, mailing.from == null ? "Gitblit" : mailing.from,"utf-8"); | ||
| message.setFrom(from); | ||
@@ -278,5 +280,18 @@ | ||
| 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) { | ||
@@ -283,0 +298,0 @@ logger.error("Failed to send message", e); |
@@ -170,2 +170,3 @@ /* | ||
| if (StringUtils.isEmpty(repository)) { | ||
| logger.info("ARF: Rejecting request, empty repository name in URL {}", fullUrl); | ||
| httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); | ||
@@ -185,2 +186,17 @@ return; | ||
| if (StringUtils.isEmpty(urlRequestType)) { | ||
| logger.info("ARF: Rejecting request for {}, no supported action found in URL {}", repository, fullSuffix); | ||
| httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); | ||
| return; | ||
| } | ||
| // TODO: Maybe checking for clone bundle should be done somewhere else? Like other stuff? | ||
| // In any way, the access to the constant from here is messed up an needs some cleaning up. | ||
| if (GitFilter.CLONE_BUNDLE.equalsIgnoreCase(urlRequestType)) { | ||
| logger.info(MessageFormat.format("ARF: Rejecting request for {0}, clone bundle is not implemented.", repository)); | ||
| httpResponse.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "The 'clone.bundle' command is currently not implemented. " + | ||
| "Please use a normal clone command."); | ||
| return; | ||
| } | ||
| UserModel user = getUser(httpRequest); | ||
@@ -187,0 +203,0 @@ |
@@ -296,7 +296,9 @@ /* | ||
| IPluginManager pluginManager = getManager(IPluginManager.class); | ||
| for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) { | ||
| try { | ||
| listener.onShutdown(); | ||
| } catch (Throwable t) { | ||
| logger.error(null, t); | ||
| if (pluginManager != null) { | ||
| for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) { | ||
| try { | ||
| listener.onShutdown(); | ||
| } catch (Throwable t) { | ||
| logger.error(null, t); | ||
| } | ||
| } | ||
@@ -303,0 +305,0 @@ } |
@@ -49,10 +49,12 @@ /* | ||
| protected static final String gitReceivePack = "/git-receive-pack"; | ||
| static final String GIT_RECEIVE_PACK = "/git-receive-pack"; | ||
| protected static final String gitUploadPack = "/git-upload-pack"; | ||
| static final String GIT_UPLOAD_PACK = "/git-upload-pack"; | ||
| static final String CLONE_BUNDLE = "/clone.bundle"; | ||
| protected static final String gitLfs = "/info/lfs"; | ||
| static final String GIT_LFS = "/info/lfs"; | ||
| protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD", | ||
| "/objects", gitLfs }; | ||
| static final String[] SUFFIXES = {GIT_RECEIVE_PACK, GIT_UPLOAD_PACK, "/info/refs", "/HEAD", | ||
| "/objects", GIT_LFS, CLONE_BUNDLE}; | ||
@@ -80,9 +82,15 @@ private IStoredSettings settings; | ||
| * | ||
| * @param cloneUrl | ||
| * @param url | ||
| * Request URL with repository name in it | ||
| * @return repository name | ||
| */ | ||
| public static String getRepositoryName(String value) { | ||
| String repository = value; | ||
| public static String getRepositoryName(String url) { | ||
| String repository = url; | ||
| // strip off parameters from the URL | ||
| if (repository.contains("?")) { | ||
| repository = repository.substring(0, repository.indexOf("?")); | ||
| } | ||
| // get the repository name from the url by finding a known url suffix | ||
| for (String urlSuffix : suffixes) { | ||
| for (String urlSuffix : SUFFIXES) { | ||
| if (repository.indexOf(urlSuffix) > -1) { | ||
@@ -116,14 +124,16 @@ repository = repository.substring(0, repository.indexOf(urlSuffix)); | ||
| if (!StringUtils.isEmpty(suffix)) { | ||
| if (suffix.startsWith(gitReceivePack)) { | ||
| return gitReceivePack; | ||
| } else if (suffix.startsWith(gitUploadPack)) { | ||
| return gitUploadPack; | ||
| if (suffix.startsWith(GIT_RECEIVE_PACK)) { | ||
| return GIT_RECEIVE_PACK; | ||
| } else if (suffix.startsWith(GIT_UPLOAD_PACK)) { | ||
| return GIT_UPLOAD_PACK; | ||
| } else if (suffix.contains("?service=git-receive-pack")) { | ||
| return gitReceivePack; | ||
| return GIT_RECEIVE_PACK; | ||
| } else if (suffix.contains("?service=git-upload-pack")) { | ||
| return gitUploadPack; | ||
| } else if (suffix.startsWith(gitLfs)) { | ||
| return gitLfs; | ||
| return GIT_UPLOAD_PACK; | ||
| } else if (suffix.startsWith(GIT_LFS)) { | ||
| return GIT_LFS; | ||
| } else if (suffix.startsWith(CLONE_BUNDLE)) { | ||
| return CLONE_BUNDLE; | ||
| } else { | ||
| return gitUploadPack; | ||
| return GIT_UPLOAD_PACK; | ||
| } | ||
@@ -156,8 +166,14 @@ } | ||
| protected boolean isCreationAllowed(String action) { | ||
| // Unknown action, don't allow creation | ||
| if (action == null) return false; | ||
| //Repository must already exist before large files can be deposited | ||
| if (action.equals(gitLfs)) { | ||
| if (GIT_LFS.equals(action)) { | ||
| return false; | ||
| } | ||
| // Action is not implemened. | ||
| if (CLONE_BUNDLE.equals(action)) { | ||
| return false; | ||
| } | ||
| return settings.getBoolean(Keys.git.allowCreateOnPush, true); | ||
@@ -177,3 +193,3 @@ } | ||
| // error messages | ||
| if (gitLfs.equals(action)) { | ||
| if (GIT_LFS.equals(action)) { | ||
| if (!method.matches("GET|POST|PUT|HEAD")) { | ||
@@ -202,9 +218,9 @@ return false; | ||
| protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) { | ||
| if (gitUploadPack.equals(action)) { | ||
| if (GIT_UPLOAD_PACK.equals(action)) { | ||
| // send to client | ||
| return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE); | ||
| } else if (gitReceivePack.equals(action)) { | ||
| } else if (GIT_RECEIVE_PACK.equals(action)) { | ||
| // receive from client | ||
| return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH); | ||
| } else if (gitLfs.equals(action)) { | ||
| } else if (GIT_LFS.equals(action)) { | ||
@@ -236,6 +252,6 @@ if (method.matches("GET|HEAD")) { | ||
| } | ||
| if (action.equals(gitReceivePack)) { | ||
| if (GIT_RECEIVE_PACK.equals(action)) { | ||
| // push permissions are enforced in the receive pack | ||
| return true; | ||
| } else if (action.equals(gitUploadPack)) { | ||
| } else if (GIT_UPLOAD_PACK.equals(action)) { | ||
| // Clone request | ||
@@ -265,5 +281,5 @@ if (user.canClone(repository)) { | ||
| protected RepositoryModel createRepository(UserModel user, String repository, String action) { | ||
| boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action); | ||
| boolean isPush = !StringUtils.isEmpty(action) && GIT_RECEIVE_PACK.equals(action); | ||
| if (action.equals(gitLfs)) { | ||
| if (GIT_LFS.equals(action)) { | ||
| //Repository must already exist for any filestore actions | ||
@@ -336,3 +352,3 @@ return null; | ||
| if (action.equals(gitLfs)) { | ||
| if (GIT_LFS.equals(action)) { | ||
| if (hasContentInRequestHeader(httpRequest, "Accept", FilestoreServlet.GIT_LFS_META_MIME)) { | ||
@@ -356,3 +372,3 @@ return "LFS-Authenticate"; | ||
| if (action.equals(gitLfs) && request.getMethod().equals("POST")) { | ||
| if (GIT_LFS.equals(action) && request.getMethod().equals("POST")) { | ||
| if ( !hasContentInRequestHeader(request, "Accept", FilestoreServlet.GIT_LFS_META_MIME) | ||
@@ -359,0 +375,0 @@ || !hasContentInRequestHeader(request, "Content-Type", FilestoreServlet.GIT_LFS_META_MIME)) { |
@@ -136,2 +136,6 @@ /* | ||
| } | ||
| if(!StringUtils.isEmpty(objectName) && !objectName.equals(model.name)) { | ||
| // skip repository if a name was submitted and it doesn't match | ||
| continue; | ||
| } | ||
| // get local branches | ||
@@ -138,0 +142,0 @@ Repository repository = gitblit.getRepository(model.name); |
@@ -113,5 +113,4 @@ /* | ||
| @Override | ||
| public BranchTicketService start() { | ||
| public void onStart() { | ||
| log.info("{} started", getClass().getSimpleName()); | ||
| return this; | ||
| } | ||
@@ -118,0 +117,0 @@ |
@@ -83,5 +83,4 @@ /* | ||
| @Override | ||
| public FileTicketService start() { | ||
| public void onStart() { | ||
| log.info("{} started", getClass().getSimpleName()); | ||
| return this; | ||
| } | ||
@@ -88,0 +87,0 @@ |
@@ -184,5 +184,22 @@ /* | ||
| @Override | ||
| public abstract ITicketService start(); | ||
| public final ITicketService start() { | ||
| onStart(); | ||
| if (shouldReindex()) { | ||
| log.info("Re-indexing all tickets..."); | ||
| // long startTime = System.currentTimeMillis(); | ||
| reindex(); | ||
| // float duration = (System.currentTimeMillis() - startTime) / 1000f; | ||
| // log.info("Built Lucene index over all tickets in {} secs", duration); | ||
| } | ||
| return this; | ||
| } | ||
| /** | ||
| * Start the specific ticket service implementation. | ||
| * | ||
| * @since 1.9.0 | ||
| */ | ||
| public abstract void onStart(); | ||
| /** | ||
| * Stop the service. | ||
@@ -201,2 +218,8 @@ * @since 1.4.0 | ||
| /** | ||
| * Closes any open resources used by this service. | ||
| * @since 1.4.0 | ||
| */ | ||
| protected abstract void close(); | ||
| /** | ||
| * Creates a ticket notifier. The ticket notifier is not thread-safe! | ||
@@ -279,8 +302,2 @@ * @since 1.4.0 | ||
| /** | ||
| * Closes any open resources used by this service. | ||
| * @since 1.4.0 | ||
| */ | ||
| protected abstract void close(); | ||
| /** | ||
| * Reset all caches in the service. | ||
@@ -1349,3 +1366,15 @@ * @since 1.4.0 | ||
| /** | ||
| * Checks tickets should get re-indexed. | ||
| * | ||
| * @return true if tickets should get re-indexed, false otherwise. | ||
| */ | ||
| private boolean shouldReindex() | ||
| { | ||
| return indexer.shouldReindex(); | ||
| } | ||
| /** | ||
| * Destroys an existing index and reindexes all tickets. | ||
@@ -1352,0 +1381,0 @@ * This operation may be expensive and time-consuming. |
@@ -64,5 +64,4 @@ /* | ||
| @Override | ||
| public NullTicketService start() { | ||
| public void onStart() { | ||
| log.info("{} started", getClass().getSimpleName()); | ||
| return this; | ||
| } | ||
@@ -69,0 +68,0 @@ |
@@ -86,3 +86,3 @@ /* | ||
| @Override | ||
| public RedisTicketService start() { | ||
| public void onStart() { | ||
| log.info("{} started", getClass().getSimpleName()); | ||
@@ -92,3 +92,2 @@ if (!isReady()) { | ||
| } | ||
| return this; | ||
| } | ||
@@ -95,0 +94,0 @@ |
@@ -34,2 +34,4 @@ /* | ||
| import org.apache.lucene.document.LongField; | ||
| import org.apache.lucene.document.NumericDocValuesField; | ||
| import org.apache.lucene.document.SortedDocValuesField; | ||
| import org.apache.lucene.document.TextField; | ||
@@ -53,3 +55,3 @@ import org.apache.lucene.index.DirectoryReader; | ||
| import org.apache.lucene.store.FSDirectory; | ||
| import org.apache.lucene.util.Version; | ||
| import org.apache.lucene.util.BytesRef; | ||
| import org.slf4j.Logger; | ||
@@ -65,3 +67,3 @@ import org.slf4j.LoggerFactory; | ||
| import com.gitblit.models.TicketModel.Status; | ||
| import com.gitblit.utils.FileUtils; | ||
| import com.gitblit.utils.LuceneIndexStore; | ||
| import com.gitblit.utils.StringUtils; | ||
@@ -114,2 +116,4 @@ | ||
| final static int INDEX_VERSION = 2; | ||
| final Type fieldType; | ||
@@ -174,6 +178,4 @@ | ||
| private final Version luceneVersion = Version.LUCENE_46; | ||
| private final LuceneIndexStore indexStore; | ||
| private final File luceneDir; | ||
| private IndexWriter writer; | ||
@@ -184,3 +186,4 @@ | ||
| public TicketIndexer(IRuntimeManager runtimeManager) { | ||
| this.luceneDir = runtimeManager.getFileOrFolder(Keys.tickets.indexFolder, "${baseFolder}/tickets/lucene"); | ||
| File luceneDir = runtimeManager.getFileOrFolder(Keys.tickets.indexFolder, "${baseFolder}/tickets/lucene"); | ||
| this.indexStore = new LuceneIndexStore(luceneDir, Lucene.INDEX_VERSION); | ||
| } | ||
@@ -201,3 +204,3 @@ | ||
| close(); | ||
| FileUtils.delete(luceneDir); | ||
| indexStore.delete(); | ||
| } | ||
@@ -211,6 +214,5 @@ | ||
| IndexWriter writer = getWriter(); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(luceneVersion); | ||
| QueryParser qp = new QueryParser(luceneVersion, Lucene.rid.name(), analyzer); | ||
| BooleanQuery query = new BooleanQuery(); | ||
| query.add(qp.parse(repository.getRID()), Occur.MUST); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| QueryParser qp = new QueryParser(Lucene.rid.name(), analyzer); | ||
| BooleanQuery query = new BooleanQuery.Builder().add(qp.parse(repository.getRID()), Occur.MUST).build(); | ||
@@ -236,2 +238,14 @@ int numDocsBefore = writer.numDocs(); | ||
| /** | ||
| * Checks if a tickets index exists, that is compatible with Lucene.INDEX_VERSION | ||
| * and the Lucene codec version. | ||
| * | ||
| * @return true if no tickets index is found, false otherwise. | ||
| * | ||
| * @since 1.9.0 | ||
| */ | ||
| boolean shouldReindex() { | ||
| return ! this.indexStore.hasIndex(); | ||
| } | ||
| /** | ||
| * Bulk Add/Update tickets in the Lucene index | ||
@@ -299,6 +313,5 @@ * | ||
| private boolean delete(String repository, long ticketId, IndexWriter writer) throws Exception { | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(luceneVersion); | ||
| QueryParser qp = new QueryParser(luceneVersion, Lucene.did.name(), analyzer); | ||
| BooleanQuery query = new BooleanQuery(); | ||
| query.add(qp.parse(StringUtils.getSHA1(repository + ticketId)), Occur.MUST); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| QueryParser qp = new QueryParser(Lucene.did.name(), analyzer); | ||
| BooleanQuery query = new BooleanQuery.Builder().add(qp.parse(StringUtils.getSHA1(repository + ticketId)), Occur.MUST).build(); | ||
@@ -344,26 +357,26 @@ int numDocsBefore = writer.numDocs(); | ||
| Set<QueryResult> results = new LinkedHashSet<QueryResult>(); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(luceneVersion); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| try { | ||
| // search the title, description and content | ||
| BooleanQuery query = new BooleanQuery(); | ||
| BooleanQuery.Builder bldr = new BooleanQuery.Builder(); | ||
| QueryParser qp; | ||
| qp = new QueryParser(luceneVersion, Lucene.title.name(), analyzer); | ||
| qp = new QueryParser(Lucene.title.name(), analyzer); | ||
| qp.setAllowLeadingWildcard(true); | ||
| query.add(qp.parse(text), Occur.SHOULD); | ||
| bldr.add(qp.parse(text), Occur.SHOULD); | ||
| qp = new QueryParser(luceneVersion, Lucene.body.name(), analyzer); | ||
| qp = new QueryParser(Lucene.body.name(), analyzer); | ||
| qp.setAllowLeadingWildcard(true); | ||
| query.add(qp.parse(text), Occur.SHOULD); | ||
| bldr.add(qp.parse(text), Occur.SHOULD); | ||
| qp = new QueryParser(luceneVersion, Lucene.content.name(), analyzer); | ||
| qp = new QueryParser(Lucene.content.name(), analyzer); | ||
| qp.setAllowLeadingWildcard(true); | ||
| query.add(qp.parse(text), Occur.SHOULD); | ||
| bldr.add(qp.parse(text), Occur.SHOULD); | ||
| IndexSearcher searcher = getSearcher(); | ||
| Query rewrittenQuery = searcher.rewrite(query); | ||
| Query rewrittenQuery = searcher.rewrite(bldr.build()); | ||
| log.debug(rewrittenQuery.toString()); | ||
| TopScoreDocCollector collector = TopScoreDocCollector.create(5000, true); | ||
| TopScoreDocCollector collector = TopScoreDocCollector.create(5000); | ||
| searcher.search(rewrittenQuery, collector); | ||
@@ -406,5 +419,5 @@ int offset = Math.max(0, (page - 1) * pageSize); | ||
| Set<QueryResult> results = new LinkedHashSet<QueryResult>(); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(luceneVersion); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| try { | ||
| QueryParser qp = new QueryParser(luceneVersion, Lucene.content.name(), analyzer); | ||
| QueryParser qp = new QueryParser(Lucene.content.name(), analyzer); | ||
| Query query = qp.parse(queryText); | ||
@@ -424,3 +437,3 @@ | ||
| int maxSize = 5000; | ||
| TopFieldDocs docs = searcher.search(rewrittenQuery, null, maxSize, sort, false, false); | ||
| TopFieldDocs docs = searcher.search(rewrittenQuery, maxSize, sort, false, false); | ||
| int size = (pageSize <= 0) ? maxSize : pageSize; | ||
@@ -459,10 +472,7 @@ int offset = Math.max(0, (page - 1) * size); | ||
| if (writer == null) { | ||
| Directory directory = FSDirectory.open(luceneDir); | ||
| indexStore.create(); | ||
| if (!luceneDir.exists()) { | ||
| luceneDir.mkdirs(); | ||
| } | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(luceneVersion); | ||
| IndexWriterConfig config = new IndexWriterConfig(luceneVersion, analyzer); | ||
| Directory directory = FSDirectory.open(indexStore.getPath()); | ||
| StandardAnalyzer analyzer = new StandardAnalyzer(); | ||
| IndexWriterConfig config = new IndexWriterConfig(analyzer); | ||
| config.setOpenMode(OpenMode.CREATE_OR_APPEND); | ||
@@ -571,2 +581,3 @@ writer = new IndexWriter(directory, config); | ||
| doc.add(new LongField(lucene.name(), value.getTime(), Store.YES)); | ||
| doc.add(new NumericDocValuesField(lucene.name(), value.getTime())); | ||
| } | ||
@@ -576,2 +587,3 @@ | ||
| doc.add(new LongField(lucene.name(), value, Store.YES)); | ||
| doc.add(new NumericDocValuesField(lucene.name(), value)); | ||
| } | ||
@@ -581,2 +593,3 @@ | ||
| doc.add(new IntField(lucene.name(), value, Store.YES)); | ||
| doc.add(new NumericDocValuesField(lucene.name(), value)); | ||
| } | ||
@@ -589,2 +602,3 @@ | ||
| doc.add(new org.apache.lucene.document.Field(lucene.name(), value, TextField.TYPE_STORED)); | ||
| doc.add(new SortedDocValuesField(lucene.name(), new BytesRef(value))); | ||
| } | ||
@@ -684,2 +698,2 @@ | ||
| } | ||
| } | ||
| } |
@@ -576,6 +576,6 @@ /* | ||
| 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); | ||
@@ -582,0 +582,0 @@ } |
@@ -28,2 +28,3 @@ /* | ||
| import com.gitblit.manager.IManager; | ||
| import com.gitblit.models.UserModel; | ||
| import com.google.common.cache.CacheBuilder; | ||
@@ -103,2 +104,14 @@ import com.google.common.cache.CacheLoader; | ||
| public abstract boolean removeAllKeys(String username); | ||
| public boolean supportsWritingKeys(UserModel user) { | ||
| return (user != null); | ||
| } | ||
| public boolean supportsCommentChanges(UserModel user) { | ||
| return (user != null); | ||
| } | ||
| public boolean supportsPermissionChanges(UserModel user) { | ||
| return (user != null); | ||
| } | ||
| } |
@@ -28,2 +28,3 @@ /* | ||
| import com.gitblit.Constants.AccessPermission; | ||
| import com.gitblit.models.UserModel; | ||
| import com.gitblit.transport.ssh.IPublicKeyManager; | ||
@@ -51,8 +52,16 @@ import com.gitblit.transport.ssh.SshKey; | ||
| protected void setup() { | ||
| register(AddKey.class); | ||
| register(RemoveKey.class); | ||
| IPublicKeyManager km = getContext().getGitblit().getPublicKeyManager(); | ||
| UserModel user = getContext().getClient().getUser(); | ||
| if (km != null && km.supportsWritingKeys(user)) { | ||
| register(AddKey.class); | ||
| register(RemoveKey.class); | ||
| } | ||
| register(ListKeys.class); | ||
| register(WhichKey.class); | ||
| register(CommentKey.class); | ||
| register(PermissionKey.class); | ||
| if (km != null && km.supportsCommentChanges(user)) { | ||
| register(CommentKey.class); | ||
| } | ||
| if (km != null && km.supportsPermissionChanges(user)) { | ||
| register(PermissionKey.class); | ||
| } | ||
| } | ||
@@ -59,0 +68,0 @@ |
@@ -18,4 +18,4 @@ /* | ||
| import org.apache.sshd.common.SshdSocketAddress; | ||
| import org.apache.sshd.common.session.Session; | ||
| import org.apache.sshd.common.util.net.SshdSocketAddress; | ||
| import org.apache.sshd.server.forward.ForwardingFilter; | ||
@@ -22,0 +22,0 @@ |
@@ -26,2 +26,3 @@ /* | ||
| import java.text.MessageFormat; | ||
| import java.util.List; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
@@ -34,3 +35,3 @@ | ||
| import org.apache.sshd.server.SshServer; | ||
| import org.apache.sshd.server.auth.CachingPublicKeyAuthenticator; | ||
| import org.apache.sshd.server.auth.pubkey.CachingPublicKeyAuthenticator; | ||
| import org.bouncycastle.openssl.PEMWriter; | ||
@@ -60,2 +61,9 @@ import org.eclipse.jgit.internal.JGitText; | ||
| private static final String AUTH_PUBLICKEY = "publickey"; | ||
| private static final String AUTH_PASSWORD = "password"; | ||
| private static final String AUTH_KBD_INTERACTIVE = "keyboard-interactive"; | ||
| private static final String AUTH_GSSAPI = "gssapi-with-mic"; | ||
| public static enum SshSessionBackend { | ||
@@ -103,5 +111,2 @@ MINA, NIO2 | ||
| // Client public key authenticator | ||
| SshKeyAuthenticator keyAuthenticator = | ||
| new SshKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit); | ||
@@ -132,12 +137,35 @@ // Configure the preferred SSHD backend | ||
| sshd.setKeyPairProvider(hostKeyPairProvider); | ||
| sshd.setPublickeyAuthenticator(new CachingPublicKeyAuthenticator(keyAuthenticator)); | ||
| sshd.setPasswordAuthenticator(new UsernamePasswordAuthenticator(gitblit)); | ||
| if (settings.getBoolean(Keys.git.sshWithKrb5, false)) { | ||
| List<String> authMethods = settings.getStrings(Keys.git.sshAuthenticationMethods); | ||
| if (authMethods.isEmpty()) { | ||
| authMethods.add(AUTH_PUBLICKEY); | ||
| authMethods.add(AUTH_PASSWORD); | ||
| } | ||
| // Keep backward compatibility with old setting files that use the git.sshWithKrb5 setting. | ||
| if (settings.getBoolean("git.sshWithKrb5", false) && !authMethods.contains(AUTH_GSSAPI)) { | ||
| authMethods.add(AUTH_GSSAPI); | ||
| log.warn("git.sshWithKrb5 is obsolete!"); | ||
| log.warn("Please add {} to {} in gitblit.properties!", AUTH_GSSAPI, Keys.git.sshAuthenticationMethods); | ||
| settings.overrideSetting(Keys.git.sshAuthenticationMethods, | ||
| settings.getString(Keys.git.sshAuthenticationMethods, AUTH_PUBLICKEY + " " + AUTH_PASSWORD) + " " + AUTH_GSSAPI); | ||
| } | ||
| if (authMethods.contains(AUTH_PUBLICKEY)) { | ||
| SshKeyAuthenticator keyAuthenticator = new SshKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit); | ||
| sshd.setPublickeyAuthenticator(new CachingPublicKeyAuthenticator(keyAuthenticator)); | ||
| log.info("SSH: adding public key authentication method."); | ||
| } | ||
| if (authMethods.contains(AUTH_PASSWORD) || authMethods.contains(AUTH_KBD_INTERACTIVE)) { | ||
| sshd.setPasswordAuthenticator(new UsernamePasswordAuthenticator(gitblit)); | ||
| log.info("SSH: adding password authentication method."); | ||
| } | ||
| if (authMethods.contains(AUTH_GSSAPI)) { | ||
| sshd.setGSSAuthenticator(new SshKrbAuthenticator(settings, gitblit)); | ||
| log.info("SSH: adding GSSAPI authentication method."); | ||
| } | ||
| sshd.setSessionFactory(new SshServerSessionFactory()); | ||
| sshd.setSessionFactory(new SshServerSessionFactory(sshd)); | ||
| sshd.setFileSystemFactory(new DisabledFilesystemFactory()); | ||
| sshd.setTcpipForwardingFilter(new NonForwardingFilter()); | ||
| sshd.setCommandFactory(new SshCommandFactory(gitblit, workQueue)); | ||
| sshd.setShellFactory(new WelcomeShell(settings)); | ||
| sshd.setShellFactory(new WelcomeShell(gitblit)); | ||
@@ -144,0 +172,0 @@ // Set the server id. This can be queried with: |
@@ -20,3 +20,3 @@ /* | ||
| import org.apache.sshd.common.session.Session.AttributeKey; | ||
| import org.apache.sshd.common.AttributeStore.AttributeKey; | ||
@@ -23,0 +23,0 @@ import com.gitblit.models.UserModel; |
@@ -25,3 +25,4 @@ /* | ||
| import org.apache.sshd.common.io.mina.MinaSession; | ||
| import org.apache.sshd.common.session.AbstractSession; | ||
| import org.apache.sshd.server.ServerFactoryManager; | ||
| import org.apache.sshd.server.session.ServerSessionImpl; | ||
| import org.apache.sshd.server.session.SessionFactory; | ||
@@ -40,7 +41,8 @@ import org.slf4j.Logger; | ||
| public SshServerSessionFactory() { | ||
| public SshServerSessionFactory(ServerFactoryManager server) { | ||
| super(server); | ||
| } | ||
| @Override | ||
| protected AbstractSession createSession(final IoSession io) throws Exception { | ||
| protected ServerSessionImpl createSession(final IoSession io) throws Exception { | ||
| log.info("creating ssh session from {}", io.getRemoteAddress()); | ||
@@ -71,5 +73,5 @@ | ||
| @Override | ||
| protected AbstractSession doCreateSession(IoSession ioSession) throws Exception { | ||
| protected ServerSessionImpl doCreateSession(IoSession ioSession) throws Exception { | ||
| return new SshServerSession(getServer(), ioSession); | ||
| } | ||
| } |
@@ -37,2 +37,3 @@ /* | ||
| import com.gitblit.Keys; | ||
| import com.gitblit.manager.IGitblit; | ||
| import com.gitblit.models.UserModel; | ||
@@ -49,6 +50,6 @@ import com.gitblit.transport.ssh.commands.DispatchCommand; | ||
| private final IStoredSettings settings; | ||
| private final IGitblit gitblit; | ||
| public WelcomeShell(IStoredSettings settings) { | ||
| this.settings = settings; | ||
| public WelcomeShell(IGitblit gitblit) { | ||
| this.gitblit = gitblit; | ||
| } | ||
@@ -58,3 +59,3 @@ | ||
| public Command create() { | ||
| return new SendMessage(settings); | ||
| return new SendMessage(gitblit); | ||
| } | ||
@@ -64,2 +65,3 @@ | ||
| private final IPublicKeyManager km; | ||
| private final IStoredSettings settings; | ||
@@ -73,4 +75,5 @@ private ServerSession session; | ||
| SendMessage(IStoredSettings settings) { | ||
| this.settings = settings; | ||
| SendMessage(IGitblit gitblit) { | ||
| this.km = gitblit.getPublicKeyManager(); | ||
| this.settings = gitblit.getSettings(); | ||
| } | ||
@@ -124,2 +127,6 @@ | ||
| int port = settings.getInteger(Keys.git.sshPort, 0); | ||
| boolean writeKeysIsSupported = true; | ||
| if (km != null) { | ||
| writeKeysIsSupported = km.supportsWritingKeys(user); | ||
| } | ||
@@ -168,3 +175,3 @@ final String b1 = StringUtils.rightPad("", 72, '═'); | ||
| if (client.getKey() == null) { | ||
| if (writeKeysIsSupported && client.getKey() == null) { | ||
| // user has authenticated with a password | ||
@@ -171,0 +178,0 @@ // display add public key instructions |
@@ -45,3 +45,3 @@ /* | ||
| public static boolean isEmpty(Collection<?> collection) { | ||
| return collection == null || collection.size() == 0; | ||
| return collection == null || collection.isEmpty(); | ||
| } | ||
@@ -48,0 +48,0 @@ |
@@ -22,5 +22,5 @@ /* | ||
| 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; | ||
@@ -62,3 +62,3 @@ | ||
| protected CommitCache() { | ||
| cache = new ConcurrentHashMap<String, ObjectCache<List<RepositoryCommit>>>(); | ||
| cache = new HashMap<>(); | ||
| } | ||
@@ -98,3 +98,5 @@ | ||
| public void clear() { | ||
| cache.clear(); | ||
| synchronized (cache) { | ||
| cache.clear(); | ||
| } | ||
| } | ||
@@ -109,4 +111,7 @@ | ||
| 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)); | ||
@@ -124,9 +129,13 @@ } | ||
| 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)); | ||
| } | ||
| } | ||
@@ -164,7 +173,2 @@ | ||
| 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(); | ||
@@ -175,36 +179,47 @@ | ||
| 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); | ||
| ObjectCache<List<RepositoryCommit>> repoCache; | ||
| synchronized (cache) { | ||
| repoCache = cache.get(repoKey); | ||
| if (repoCache == null) { | ||
| repoCache = new ObjectCache<>(); | ||
| cache.put(repoKey, repoCache); | ||
| } | ||
| } | ||
| 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); | ||
| 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))); | ||
| } | ||
| if (sinceDate.equals(cacheCutoffDate)) { | ||
| // Mustn't hand out the cached list; that's not thread-safe | ||
| list = new ArrayList<>(commits); | ||
| } 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; | ||
| // reduce the commits to those since the specified date | ||
| list = reduce(commits, sinceDate); | ||
| } | ||
| } 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); | ||
| } | ||
| logger.debug(MessageFormat.format("retrieved {0} commits from cache of {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs", | ||
@@ -232,4 +247,5 @@ list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start))); | ||
| 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); | ||
@@ -254,4 +270,5 @@ List<RefModel> commitRefs = allRefs.get(commitModel.getId()); | ||
| 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); | ||
@@ -273,3 +290,3 @@ List<RefModel> commitRefs = allRefs.get(commitModel.getId()); | ||
| 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) { | ||
@@ -276,0 +293,0 @@ if (commit.getCommitDate().compareTo(sinceDate) >= 0) { |
@@ -29,2 +29,3 @@ /* | ||
| import java.nio.charset.Charset; | ||
| import java.nio.file.LinkOption; | ||
| import java.nio.file.Path; | ||
@@ -306,4 +307,4 @@ import java.nio.file.Paths; | ||
| public static String getRelativePath(File basePath, File path) { | ||
| Path exactBase = Paths.get(getExactFile(basePath).toURI()); | ||
| Path exactPath = Paths.get(getExactFile(path).toURI()); | ||
| Path exactBase = getExactPath(basePath); | ||
| Path exactPath = getExactPath(path); | ||
| if (exactPath.startsWith(exactBase)) { | ||
@@ -317,4 +318,6 @@ return exactBase.relativize(exactPath).toString().replace('\\', '/'); | ||
| /** | ||
| * Returns the exact path for a file. This path will be the canonical path | ||
| * unless an exception is thrown in which case it will be the absolute path. | ||
| * Returns the exact path for a file. This path will be the real path | ||
| * with symbolic links unresolved. If that produces an IOException, | ||
| * the path will be the canonical path unless an exception is thrown | ||
| * in which case it will be the absolute path. | ||
| * | ||
@@ -324,10 +327,16 @@ * @param path | ||
| */ | ||
| public static File getExactFile(File path) { | ||
| private static Path getExactPath(File path) { | ||
| try { | ||
| return path.getCanonicalFile(); | ||
| return path.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS); | ||
| } catch (IOException e) { | ||
| return path.getAbsoluteFile(); | ||
| // ignored, try next option | ||
| } | ||
| try { | ||
| return Paths.get(path.getCanonicalPath()); | ||
| } catch (IOException e) { | ||
| return Paths.get(path.getAbsolutePath()); | ||
| } | ||
| } | ||
| public static File resolveParameter(String parameter, File aFolder, String path) { | ||
@@ -334,0 +343,0 @@ if (aFolder == null) { |
@@ -113,3 +113,5 @@ /* | ||
| || ("https".equals(scheme) && port != 443)) { | ||
| sb.append(":").append(port); | ||
| if (!host.endsWith(":" + port)) { | ||
| sb.append(":").append(port); | ||
| } | ||
| } | ||
@@ -116,0 +118,0 @@ sb.append(context); |
@@ -33,2 +33,3 @@ /* | ||
| import com.gitblit.Constants; | ||
| import com.gitblit.IStoredSettings; | ||
@@ -141,4 +142,4 @@ import com.gitblit.Keys; | ||
| // 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); | ||
@@ -145,0 +146,0 @@ // link ticket refs |
@@ -49,6 +49,2 @@ /* | ||
| public static final String MD5_TYPE = "MD5:"; | ||
| public static final String COMBINED_MD5_TYPE = "CMD5:"; | ||
| /** | ||
@@ -55,0 +51,0 @@ * Returns true if the string is null or empty. |
@@ -746,2 +746,21 @@ /* | ||
| String caKeystorePassword, X509Log x509log) { | ||
| return newClientBundle(null,clientMetadata,caKeystoreFile,caKeystorePassword,x509log); | ||
| } | ||
| /** | ||
| * Creates a new client certificate PKCS#12 and PEM store. Any existing | ||
| * stores are destroyed. After generation, the certificates are bundled | ||
| * into a zip file with a personalized README file. | ||
| * | ||
| * The zip file reference is returned. | ||
| * | ||
| * @param user | ||
| * @param clientMetadata a container for dynamic parameters needed for generation | ||
| * @param caKeystoreFile | ||
| * @param caKeystorePassword | ||
| * @param x509log | ||
| * @return a zip file containing the P12, PEM, and personalized README | ||
| */ | ||
| public static File newClientBundle(com.gitblit.models.UserModel user,X509Metadata clientMetadata, File caKeystoreFile, | ||
| String caKeystorePassword, X509Log x509log) { | ||
| try { | ||
@@ -759,4 +778,13 @@ // read the Gitblit CA key and certificate | ||
| // process template message | ||
| String readme = processTemplate(new File(caKeystoreFile.getParentFile(), "instructions.tmpl"), clientMetadata); | ||
| String readme = null; | ||
| String sInstructionsFileName = "instructions.tmpl"; | ||
| if( user == null ) | ||
| readme = processTemplate(new File(caKeystoreFile.getParentFile(),sInstructionsFileName), clientMetadata); | ||
| else{ | ||
| File fileInstructionsTmp = null; | ||
| if( (fileInstructionsTmp = new File(caKeystoreFile.getParentFile(),sInstructionsFileName+"_"+user.getPreferences().getLocale())).exists() ) | ||
| readme = processTemplate(fileInstructionsTmp,clientMetadata); | ||
| else | ||
| readme = processTemplate(new File(caKeystoreFile.getParentFile(),sInstructionsFileName),clientMetadata); | ||
| } | ||
| // Create a zip bundle with the p12, pem, and a personalized readme | ||
@@ -763,0 +791,0 @@ File zipFile = new File(targetFolder, clientMetadata.commonName + ".zip"); |
@@ -221,3 +221,4 @@ gb.repository = Repository | ||
| gb.query = Abfrage | ||
| gb.queryHelp = Standard Abfragesyntax wird unterst\u00fctzt.<p/><p/>Unter <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> finden Sie weitere Details. | ||
| gb.queryHelp = Standard Abfragesyntax wird unterst\u00fctzt.<p/><p/>Unter ${querySyntax} finden Sie weitere Details. | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = Ergebnisse {0} - {1} ({2} Treffer) | ||
@@ -224,0 +225,0 @@ gb.noHits = Keine Treffer |
@@ -221,3 +221,4 @@ gb.repository = Repositorio | ||
| gb.query = Consulta | ||
| gb.queryHelp = Se admite la sintaxis de consulta est\u00E1ndar.<p/>Por favor, lee el: <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Analizador sint\u00E1ctico de consultas de Lucene</a> para m\u00E1s detalles. | ||
| gb.queryHelp = Se admite la sintaxis de consulta est\u00E1ndar.<p/>Por favor, lee el: ${querySyntax} para m\u00E1s detalles. | ||
| gb.querySyntax = Analizador sint\u00E1ctico de consultas de Lucene | ||
| gb.queryResults = Resultados {0} - {1} ({2} coincidencias) | ||
@@ -224,0 +225,0 @@ gb.noHits = Sin coincidencias |
@@ -221,3 +221,4 @@ gb.repository = d\u00e9p\u00f4t | ||
| gb.query = recherche | ||
| gb.queryHelp = La syntaxe Lucene standard est support\u00e9e.<p/><p/>Se r\u00e9f\u00e9rer \u00e0 la <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Syntaxe de recherche Lucene</a> pour plus de d\u00e9tails. | ||
| gb.queryHelp = La syntaxe Lucene standard est support\u00e9e.<p/><p/>Se r\u00e9f\u00e9rer \u00e0 la ${querySyntax} pour plus de d\u00e9tails. | ||
| gb.querySyntax = Syntaxe de recherche Lucene | ||
| gb.queryResults = r\u00e9sultats {0} - {1} ({2} hits) | ||
@@ -224,0 +225,0 @@ gb.noHits = aucun r\u00e9sultats |
@@ -221,3 +221,4 @@ gb.repository = repository | ||
| gb.query = interrogazione | ||
| gb.queryHelp = La sintassi standard � supportata.<p/><p/>Si vedi <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> per ulteriori dettagli. | ||
| gb.queryHelp = La sintassi standard � supportata.<p/><p/>Si vedi ${querySyntax} per ulteriori dettagli. | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = risultati {0} - {1} ({2} corrispondenze) | ||
@@ -224,0 +225,0 @@ gb.noHits = nessuna corrispondenza |
@@ -220,3 +220,4 @@ gb.repository = \u30ea\u30dd\u30b8\u30c8\u30ea | ||
| gb.query = \u30af\u30a8\u30ea\u30fc | ||
| gb.queryHelp = \u6a19\u6e96\u7684\u306a\u30af\u30a8\u30ea\u30fc\u66f8\u5f0f\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u3059\u3002<p/><p/>\u8a73\u7d30\u306f <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> \u3092\u53c2\u7167\u3057\u3066\u4e0b\u3055\u3044\u3002 | ||
| gb.queryHelp = \u6a19\u6e96\u7684\u306a\u30af\u30a8\u30ea\u30fc\u66f8\u5f0f\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u3059\u3002<p/><p/>\u8a73\u7d30\u306f ${querySyntax} \u3092\u53c2\u7167\u3057\u3066\u4e0b\u3055\u3044\u3002 | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = \u691c\u7d22\u7d50\u679c {0} - {1} ({2} \u30d2\u30c3\u30c8) | ||
@@ -223,0 +224,0 @@ gb.noHits = \u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 |
@@ -1,182 +0,182 @@ | ||
| gb.repository = \uc800\uc7a5\uc18c | ||
| gb.owner = \uc18c\uc720\uc790 | ||
| gb.description = \uc124\uba85 | ||
| gb.lastChange = \ucd5c\uadfc \ubcc0\uacbd | ||
| gb.repository = \uC800\uC7A5\uC18C | ||
| gb.owner = \uC18C\uC720\uC790 | ||
| gb.description = \uC124\uBA85 | ||
| gb.lastChange = \uCD5C\uADFC \uBCC0\uACBD | ||
| gb.refs = refs | ||
| gb.tag = \ud0dc\uadf8 | ||
| gb.tags = \ud0dc\uadf8\ub4e4 | ||
| gb.author = \uc791\uc131\uc790 | ||
| gb.committer = \ucee4\ubbf8\ud130 | ||
| gb.commit = \ucee4\ubc0b | ||
| gb.age = \ub098\uc774 | ||
| gb.tree = \ud2b8\ub9ac | ||
| gb.parent = \ubd80\ubaa8 | ||
| gb.tag = \uD0DC\uADF8 | ||
| gb.tags = \uD0DC\uADF8\uB4E4 | ||
| gb.author = \uC791\uC131\uC790 | ||
| gb.committer = \uCEE4\uBBF8\uD130 | ||
| gb.commit = \uCEE4\uBC0B | ||
| gb.age = \uB098\uC774 | ||
| gb.tree = \uD2B8\uB9AC | ||
| gb.parent = \uBD80\uBAA8 | ||
| gb.url = URL | ||
| gb.history = \ud788\uc2a4\ud1a0\ub9ac | ||
| gb.history = \uD788\uC2A4\uD1A0\uB9AC | ||
| gb.raw = raw | ||
| gb.object = \uc624\ube0c\uc81d\ud2b8 | ||
| gb.ticketId = \ud2f0\ucf13 id | ||
| gb.ticketAssigned = \ud560\ub2f9 | ||
| gb.ticketOpenDate = \uc5f4\ub9b0 \ub0a0\uc790 | ||
| gb.ticketState = \uc0c1\ud0dc | ||
| gb.ticketComments = \ucf54\uba58\ud2b8 | ||
| gb.view = \ubcf4\uae30 | ||
| gb.local = \ub85c\uceec | ||
| gb.remote = \ub9ac\ubaa8\ud2b8 | ||
| gb.branches = \ube0c\ub79c\uce58 | ||
| gb.patch = \ud328\uce58 | ||
| gb.diff = \ube44\uad50 | ||
| gb.log = \ub85c\uadf8 | ||
| gb.moreLogs = \ucee4\ubc0b \ub354 \ubcf4\uae30... | ||
| gb.allTags = \ubaa8\ub4e0 \ud0dc\uadf8... | ||
| gb.allBranches = \ubaa8\ub4e0 \ube0c\ub79c\uce58... | ||
| gb.summary = \uc694\uc57d | ||
| gb.ticket = \ud2f0\ucf13 | ||
| gb.newRepository = \uc0c8 \uc800\uc7a5\uc18c | ||
| gb.newUser = \uc0c8 \uc0ac\uc6a9\uc790 | ||
| gb.commitdiff = \ucee4\ubc0b\ube44\uad50 | ||
| gb.tickets = \ud2f0\ucf13 | ||
| gb.pageFirst = \ucc98\uc74c | ||
| gb.pagePrevious = \uc774\uc804 | ||
| gb.pageNext = \ub2e4\uc74c | ||
| gb.object = \uC624\uBE0C\uC81D\uD2B8 | ||
| gb.ticketId = \uD2F0\uCF13 id | ||
| gb.ticketAssigned = \uD560\uB2F9 | ||
| gb.ticketOpenDate = \uC5F4\uB9B0 \uB0A0\uC790 | ||
| gb.ticketStatus = \uC0C1\uD0DC | ||
| gb.ticketComments = \uCF54\uBA58\uD2B8 | ||
| gb.view = \uBCF4\uAE30 | ||
| gb.local = \uB85C\uCEEC | ||
| gb.remote = \uB9AC\uBAA8\uD2B8 | ||
| gb.branches = \uBE0C\uB79C\uCE58 | ||
| gb.patch = \uD328\uCE58 | ||
| gb.diff = \uBE44\uAD50 | ||
| gb.log = \uB85C\uADF8 | ||
| gb.moreLogs = \uCEE4\uBC0B \uB354 \uBCF4\uAE30... | ||
| gb.allTags = \uBAA8\uB4E0 \uD0DC\uADF8... | ||
| gb.allBranches = \uBAA8\uB4E0 \uBE0C\uB79C\uCE58... | ||
| gb.summary = \uC694\uC57D | ||
| gb.ticket = \uD2F0\uCF13 | ||
| gb.newRepository = \uC0C8 \uC800\uC7A5\uC18C | ||
| gb.newUser = \uC0C8 \uC0AC\uC6A9\uC790 | ||
| gb.commitdiff = \uCEE4\uBC0B\uBE44\uAD50 | ||
| gb.tickets = \uD2F0\uCF13 | ||
| gb.pageFirst = \uCC98\uC74C | ||
| gb.pagePrevious = \uC774\uC804 | ||
| gb.pageNext = \uB2E4\uC74C | ||
| gb.head = HEAD | ||
| gb.blame = blame | ||
| gb.login = \ub85c\uadf8\uc778 | ||
| gb.logout = \ub85c\uadf8\uc544\uc6c3 | ||
| gb.username = \uc720\uc800\ub124\uc784 | ||
| gb.password = \ud328\uc2a4\uc6cc\ub4dc | ||
| gb.tagger = \ud0dc\uac70 | ||
| gb.moreHistory = \ud788\uc2a4\ud1a0\ub9ac \ub354 \ubcf4\uae30... | ||
| gb.difftocurrent = \ud604\uc7ac\uc640 \ube44\uad50 | ||
| gb.search = \uac80\uc0c9 | ||
| gb.searchForAuthor = \ucee4\ubc0b\uc744 \uc791\uc131\uc790\ub85c \uac80\uc0c9 | ||
| gb.searchForCommitter = \ucee4\ubc0b\uc744 \ucee4\ubc0b\ud130\ub85c \uac80\uc0c9 | ||
| gb.addition = \ucd94\uac00 | ||
| gb.modification = \ubcc0\uacbd | ||
| gb.deletion = \uc0ad\uc81c | ||
| gb.rename = \uc774\ub984\ubcc0\uacbd | ||
| gb.metrics = \uba54\ud2b8\ub9ad | ||
| gb.stats = \uc0c1\ud0dc | ||
| gb.markdown = \ub9c8\ud06c\ub2e4\uc6b4 | ||
| gb.changedFiles = \ud30c\uc77c \ubcc0\uacbd\ub428 | ||
| gb.filesAdded = {0}\uac1c \ud30c\uc77c \ucd94\uac00\ub428 | ||
| gb.filesModified = {0}\uac1c \ud30c\uc77c \ubcc0\uacbd\ub428 | ||
| gb.filesDeleted = {0}\uac1c \ud30c\uc77c \uc0ad\uc81c\ub428 | ||
| gb.filesCopied = {0}\uac1c \ud30c\uc77c \ubcf5\uc0ac\ub428 | ||
| gb.filesRenamed = {0}\uac1c \ud30c\uc77c \uc774\ub984 \ubcc0\uacbd\ub428 | ||
| gb.missingUsername = \uc720\uc800\ub124\uc784 \ub204\ub77d | ||
| gb.edit = \uc218\uc815 | ||
| gb.searchTypeTooltip = \uac80\uc0c9 \ud0c0\uc785 \uc120\ud0dd | ||
| gb.searchTooltip = {0} \uac80\uc0c9 | ||
| gb.delete = \uc0ad\uc81c | ||
| gb.docs = \ubb38\uc11c | ||
| gb.accessRestriction = \uc811\uc18d \uc81c\ud55c | ||
| gb.name = \uc774\ub984 | ||
| gb.enableTickets = \ud2f0\ucf13 \uc0ac\uc6a9 | ||
| gb.enableDocs = \ubb38\uc11c \uc0ac\uc6a9 | ||
| gb.save = \uc800\uc7a5 | ||
| gb.showRemoteBranches = \ub9ac\ubaa8\ud2b8 \ube0c\ub79c\uce58 \ubcf4\uae30 | ||
| gb.editUsers = \uc720\uc800 \uc218\uc815 | ||
| gb.confirmPassword = \ud328\uc2a4\uc6cc\ub4dc \ud655\uc778 | ||
| gb.restrictedRepositories = \uc81c\ud55c\ub41c \uc800\uc7a5\uc18c | ||
| gb.canAdmin = \uad00\ub9ac \uac00\ub2a5 | ||
| gb.notRestricted = \uc775\uba85 \ubdf0, \ud074\ub860, & \ud478\uc2dc | ||
| gb.pushRestricted = \uc778\uc99d\ub41c \uc720\uc800\ub9cc \ud478\uc2dc | ||
| gb.cloneRestricted = \uc778\uc99d\ub41c \uc720\uc800\ub9cc \ud074\ub860 & \ud478\uc2dc | ||
| gb.viewRestricted = \uc778\uc99d\ub41c \uc720\uc800\ub9cc \ubdf0, \ud074\ub860, & \ud478\uc2dc | ||
| gb.useTicketsDescription = Ticgit(\ubd84\uc0b0 \ud2f0\ucf13 \uc2dc\uc2a4\ud15c) \uc774\uc288 \uc0ac\uc6a9 | ||
| gb.useDocsDescription = \uc800\uc7a5\uc18c \uc788\ub294 \ub9c8\ud06c\ub2e4\uc6b4 \ubb38\uc11c \uc0ac\uc6a9 | ||
| gb.showRemoteBranchesDescription = \ub9ac\ubaa8\ud2b8 \ube0c\ub79c\uce58 \ubcf4\uae30 | ||
| gb.canAdminDescription = Gitblit \uad00\ub9ac \uad8c\ud55c \ubd80\uc5ec | ||
| gb.permittedUsers = \ud5c8\uc6a9\ub41c \uc0ac\uc6a9\uc790 | ||
| gb.isFrozen = \ud504\ub9ac\uc9d5\ub428 | ||
| gb.isFrozenDescription = \ud478\uc2dc \ucc28\ub2e8 | ||
| gb.login = \uB85C\uADF8\uC778 | ||
| gb.logout = \uB85C\uADF8\uC544\uC6C3 | ||
| gb.username = \uC720\uC800\uB124\uC784 | ||
| gb.password = \uD328\uC2A4\uC6CC\uB4DC | ||
| gb.tagger = \uD0DC\uAC70 | ||
| gb.moreHistory = \uD788\uC2A4\uD1A0\uB9AC \uB354 \uBCF4\uAE30... | ||
| gb.difftocurrent = \uD604\uC7AC\uC640 \uBE44\uAD50 | ||
| gb.search = \uAC80\uC0C9 | ||
| gb.searchForAuthor = \uCEE4\uBC0B\uC744 \uC791\uC131\uC790\uB85C \uAC80\uC0C9 | ||
| gb.searchForCommitter = \uCEE4\uBC0B\uC744 \uCEE4\uBC0B\uD130\uB85C \uAC80\uC0C9 | ||
| gb.addition = \uCD94\uAC00 | ||
| gb.modification = \uBCC0\uACBD | ||
| gb.deletion = \uC0AD\uC81C | ||
| gb.rename = \uC774\uB984\uBCC0\uACBD | ||
| gb.metrics = \uBA54\uD2B8\uB9AD | ||
| gb.stats = \uC0C1\uD0DC | ||
| gb.markdown = \uB9C8\uD06C\uB2E4\uC6B4 | ||
| gb.changedFiles = \uD30C\uC77C \uBCC0\uACBD\uB428 | ||
| gb.filesAdded = {0}\uAC1C \uD30C\uC77C \uCD94\uAC00\uB428 | ||
| gb.filesModified = {0}\uAC1C \uD30C\uC77C \uBCC0\uACBD\uB428 | ||
| gb.filesDeleted = {0}\uAC1C \uD30C\uC77C \uC0AD\uC81C\uB428 | ||
| gb.filesCopied = {0}\uAC1C \uD30C\uC77C \uBCF5\uC0AC\uB428 | ||
| gb.filesRenamed = {0}\uAC1C \uD30C\uC77C \uC774\uB984 \uBCC0\uACBD\uB428 | ||
| gb.missingUsername = \uC720\uC800\uB124\uC784 \uB204\uB77D | ||
| gb.edit = \uC218\uC815 | ||
| gb.searchTypeTooltip = \uAC80\uC0C9 \uD0C0\uC785 \uC120\uD0DD | ||
| gb.searchTooltip = {0} \uAC80\uC0C9 | ||
| gb.delete = \uC0AD\uC81C | ||
| gb.docs = \uBB38\uC11C | ||
| gb.accessRestriction = \uC811\uC18D \uC81C\uD55C | ||
| gb.name = \uC774\uB984 | ||
| gb.enableTickets = \uD2F0\uCF13 \uC0AC\uC6A9 | ||
| gb.enableDocs = \uBB38\uC11C \uC0AC\uC6A9 | ||
| gb.save = \uC800\uC7A5 | ||
| gb.showRemoteBranches = \uB9AC\uBAA8\uD2B8 \uBE0C\uB79C\uCE58 \uBCF4\uAE30 | ||
| gb.editUsers = \uC720\uC800 \uC218\uC815 | ||
| gb.confirmPassword = \uD328\uC2A4\uC6CC\uB4DC \uD655\uC778 | ||
| gb.restrictedRepositories = \uC81C\uD55C\uB41C \uC800\uC7A5\uC18C | ||
| gb.canAdmin = \uAD00\uB9AC \uAC00\uB2A5 | ||
| gb.notRestricted = \uC775\uBA85 \uBDF0, \uD074\uB860, & \uD478\uC2DC | ||
| gb.pushRestricted = \uC778\uC99D\uB41C \uC720\uC800\uB9CC \uD478\uC2DC | ||
| gb.cloneRestricted = \uC778\uC99D\uB41C \uC720\uC800\uB9CC \uD074\uB860 & \uD478\uC2DC | ||
| gb.viewRestricted = \uC778\uC99D\uB41C \uC720\uC800\uB9CC \uBDF0, \uD074\uB860, & \uD478\uC2DC | ||
| gb.useTicketsDescription = Ticgit(\uBD84\uC0B0 \uD2F0\uCF13 \uC2DC\uC2A4\uD15C) \uC774\uC288 \uC0AC\uC6A9 | ||
| gb.useDocsDescription = \uC800\uC7A5\uC18C \uC788\uB294 \uB9C8\uD06C\uB2E4\uC6B4 \uBB38\uC11C \uC0AC\uC6A9 | ||
| gb.showRemoteBranchesDescription = \uB9AC\uBAA8\uD2B8 \uBE0C\uB79C\uCE58 \uBCF4\uAE30 | ||
| gb.canAdminDescription = Gitblit \uAD00\uB9AC \uAD8C\uD55C \uBD80\uC5EC | ||
| gb.permittedUsers = \uD5C8\uC6A9\uB41C \uC0AC\uC6A9\uC790 | ||
| gb.isFrozen = \uD504\uB9AC\uC9D5\uB428 | ||
| gb.isFrozenDescription = \uD478\uC2DC \uCC28\uB2E8 | ||
| gb.zip = zip | ||
| gb.showReadme = \ub9ac\ub4dc\ubbf8(readme) \ubcf4\uae30 | ||
| gb.showReadmeDescription = \uc694\uc57d\ud398\uc774\uc9c0\uc5d0\uc11c \"readme\" \ub9c8\ud06c\ub2e4\uc6b4 \ud30c\uc77c \ubcf4\uae30 | ||
| gb.nameDescription = \uc800\uc7a5\uc18c\ub97c \uadf8\ub8f9\uc73c\ub85c \ubb36\uc73c\ub824\uba74 '/' \ub97c \uc0ac\uc6a9. \uc608) libraries/reponame.git | ||
| gb.ownerDescription = \uc18c\uc720\uc790\ub294 \uc800\uc7a5\uc18c \uc124\uc815\uc744 \ubcc0\uacbd\ud560 \uc218 \uc788\uc74c | ||
| gb.showReadme = \uB9AC\uB4DC\uBBF8(readme) \uBCF4\uAE30 | ||
| gb.showReadmeDescription = \uC694\uC57D\uD398\uC774\uC9C0\uC5D0\uC11C \"readme\" \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uBCF4\uAE30 | ||
| gb.nameDescription = \uC800\uC7A5\uC18C\uB97C \uADF8\uB8F9\uC73C\uB85C \uBB36\uC73C\uB824\uBA74 '/' \uB97C \uC0AC\uC6A9. \uC608) libraries/reponame.git | ||
| gb.ownerDescription = \uC18C\uC720\uC790\uB294 \uC800\uC7A5\uC18C \uC124\uC815\uC744 \uBCC0\uACBD\uD560 \uC218 \uC788\uC74C | ||
| gb.blob = blob | ||
| gb.commitActivityTrend = \ucee4\ubc0b \ud65c\ub3d9 \ud2b8\ub79c\ub4dc | ||
| gb.commitActivityDOW = 1\uc8fc\uc77c\uc758 \uc77c\ub2e8\uc704 \ucee4\ubc0b \ud65c\ub3d9 | ||
| gb.commitActivityAuthors = \ucee4\ubc0b \ud65c\ub3d9\uc758 \uc8fc \uc791\uc131\uc790 | ||
| gb.feed = \ud53c\ub4dc | ||
| gb.cancel = \ucde8\uc18c | ||
| gb.changePassword = \ud328\uc2a4\uc6cc\ub4dc \ubcc0\uacbd | ||
| gb.isFederated = \ud398\ub354\ub808\uc774\uc158\ub428 | ||
| gb.federateThis = \uc774 \uc800\uc7a5\uc18c\ub97c \ud398\ub354\ub808\uc774\uc158\ud568 | ||
| gb.federateOrigin = origin \uc5d0 \ud398\ub354\ub808\uc774\uc158 | ||
| gb.excludeFromFederation = \ud398\ub354\ub808\uc774\uc158 \uc81c\uc678 | ||
| gb.excludeFromFederationDescription = \uc774 \uacc4\uc815\uc73c\ub85c \ud480\ub9c1\ub418\ub294 \ud398\ub7ec\ub808\uc774\uc158 \ub41c Gitblit \uc778\uc2a4\ud134\uc2a4 \ucc28\ub2e8 | ||
| gb.tokens = \ud398\ub354\ub808\uc774\uc158 \ud1a0\ud070 | ||
| gb.tokenAllDescription = \ubaa8\ub4e0 \uc800\uc7a5\uc18c, \ud1a0\ud070, \uc0ac\uc6a9\uc790 & \uc124\uc815 | ||
| gb.tokenUnrDescription = \ubaa8\ub4e0 \uc800\uc7a5\uc18c & \uc0ac\uc6a9\uc790 | ||
| gb.tokenJurDescription = \ubaa8\ub4e0 \uc800\uc7a5\uc18c | ||
| gb.federatedRepositoryDefinitions = \uc800\uc7a5\uc18c \uc815\uc758 | ||
| gb.federatedUserDefinitions = \uc0ac\uc6a9\uc790 \uc815\uc758 | ||
| gb.federatedSettingDefinitions = \uc124\uc815 \uc815\uc758 | ||
| gb.proposals = \ud398\ub354\ub808\uc774\uc158 \uc81c\uc548 | ||
| gb.received = \uc218\uc2e0\ud568 | ||
| gb.type = \ud0c0\uc785 | ||
| gb.token = \ud1a0\ud070 | ||
| gb.repositories = \uc800\uc7a5\uc18c | ||
| gb.proposal = \uc81c\uc548 | ||
| gb.frequency = \ube48\ub3c4 | ||
| gb.folder = \ud3f4\ub354 | ||
| gb.lastPull = \ub9c8\uc9c0\ub9c9 \ud480 | ||
| gb.nextPull = \ub2e4\uc74c \ud480 | ||
| gb.inclusions = \ud3ec\ud568 | ||
| gb.exclusions = \uc81c\uc678 | ||
| gb.registration = \ub4f1\ub85d | ||
| gb.registrations = \ud398\ub354\ub808\uc774\uc158 \ub4f1\ub85d | ||
| gb.sendProposal = \uc81c\uc548\ud558\uae30 | ||
| gb.status = \uc0c1\ud0dc | ||
| gb.commitActivityTrend = \uCEE4\uBC0B \uD65C\uB3D9 \uD2B8\uB79C\uB4DC | ||
| gb.commitActivityDOW = 1\uC8FC\uC77C\uC758 \uC77C\uB2E8\uC704 \uCEE4\uBC0B \uD65C\uB3D9 | ||
| gb.commitActivityAuthors = \uCEE4\uBC0B \uD65C\uB3D9\uC758 \uC8FC \uC791\uC131\uC790 | ||
| gb.feed = \uD53C\uB4DC | ||
| gb.cancel = \uCDE8\uC18C | ||
| gb.changePassword = \uD328\uC2A4\uC6CC\uB4DC \uBCC0\uACBD | ||
| gb.isFederated = \uD398\uB354\uB808\uC774\uC158\uB428 | ||
| gb.federateThis = \uC774 \uC800\uC7A5\uC18C\uB97C \uD398\uB354\uB808\uC774\uC158\uD568 | ||
| gb.federateOrigin = origin \uC5D0 \uD398\uB354\uB808\uC774\uC158 | ||
| gb.excludeFromFederation = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC678 | ||
| gb.excludeFromFederationDescription = \uC774 \uACC4\uC815\uC73C\uB85C \uD480\uB9C1\uB418\uB294 \uD398\uB7EC\uB808\uC774\uC158 \uB41C Gitblit \uC778\uC2A4\uD134\uC2A4 \uCC28\uB2E8 | ||
| gb.tokens = \uD398\uB354\uB808\uC774\uC158 \uD1A0\uD070 | ||
| gb.tokenAllDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C, \uD1A0\uD070, \uC0AC\uC6A9\uC790 & \uC124\uC815 | ||
| gb.tokenUnrDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C & \uC0AC\uC6A9\uC790 | ||
| gb.tokenJurDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C | ||
| gb.federatedRepositoryDefinitions = \uC800\uC7A5\uC18C \uC815\uC758 | ||
| gb.federatedUserDefinitions = \uC0AC\uC6A9\uC790 \uC815\uC758 | ||
| gb.federatedSettingDefinitions = \uC124\uC815 \uC815\uC758 | ||
| gb.proposals = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548 | ||
| gb.received = \uC218\uC2E0\uD568 | ||
| gb.type = \uD0C0\uC785 | ||
| gb.token = \uD1A0\uD070 | ||
| gb.repositories = \uC800\uC7A5\uC18C | ||
| gb.proposal = \uC81C\uC548 | ||
| gb.frequency = \uBE48\uB3C4 | ||
| gb.folder = \uD3F4\uB354 | ||
| gb.lastPull = \uB9C8\uC9C0\uB9C9 \uD480 | ||
| gb.nextPull = \uB2E4\uC74C \uD480 | ||
| gb.inclusions = \uD3EC\uD568 | ||
| gb.exclusions = \uC81C\uC678 | ||
| gb.registration = \uB4F1\uB85D | ||
| gb.registrations = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D | ||
| gb.sendProposal = \uC81C\uC548\uD558\uAE30 | ||
| gb.status = \uC0C1\uD0DC | ||
| gb.origin = origin | ||
| gb.headRef = \ub514\ud3f4\ud2b8 \ube0c\ub79c\uce58(HEAD) | ||
| gb.headRefDescription = \ub514\ud3f4\ud2b8 \ube0c\ub79c\uce58\ub97c \uc785\ub825. \uc608) refs/heads/master | ||
| gb.federationStrategy = \ud398\ub354\ub808\uc774\uc158 \uc815\ucc45 | ||
| gb.federationRegistration = \ud398\ub354\ub808\uc774\uc158 \ub4f1\ub85d | ||
| gb.federationResults = \ud398\ub354\ub808\uc774\uc158 \ud480 \uacb0\uacfc | ||
| gb.federationSets = \ud398\ub354\ub808\uc774\uc158 \uc14b | ||
| gb.message = \uba54\uc2dc\uc9c0 | ||
| gb.myUrlDescription = \uacf5\uac1c\ub418\uc5b4 \uc811\uc18d\ud560 \uc218 \uc788\ub294 Gitblit \uc778\uc2a4\ud134\uc2a4 url | ||
| gb.destinationUrl = \ub85c \ubcf4\ub0c4 | ||
| gb.destinationUrlDescription = \uc81c\uc548\uc744 \uc804\uc1a1\ud560 \ub300\uc0c1 Gitblit \uc778\uc2a4\ud134\uc2a4\uc758 url | ||
| gb.users = \uc720\uc800 | ||
| gb.federation = \ud398\ub354\ub808\uc774\uc158 | ||
| gb.error = \uc5d0\ub7ec | ||
| gb.refresh = \uc0c8\ub85c\uace0\uce68 | ||
| gb.browse = \ube0c\ub77c\uc6b0\uc988 | ||
| gb.headRef = \uB514\uD3F4\uD2B8 \uBE0C\uB79C\uCE58(HEAD) | ||
| gb.headRefDescription = \uB514\uD3F4\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uC785\uB825. \uC608) refs/heads/master | ||
| gb.federationStrategy = \uD398\uB354\uB808\uC774\uC158 \uC815\uCC45 | ||
| gb.federationRegistration = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D | ||
| gb.federationResults = \uD398\uB354\uB808\uC774\uC158 \uD480 \uACB0\uACFC | ||
| gb.federationSets = \uD398\uB354\uB808\uC774\uC158 \uC14B | ||
| gb.message = \uBA54\uC2DC\uC9C0 | ||
| gb.myUrlDescription = \uACF5\uAC1C\uB418\uC5B4 \uC811\uC18D\uD560 \uC218 \uC788\uB294 Gitblit \uC778\uC2A4\uD134\uC2A4 url | ||
| gb.destinationUrl = \uB85C \uBCF4\uB0C4 | ||
| gb.destinationUrlDescription = \uC81C\uC548\uC744 \uC804\uC1A1\uD560 \uB300\uC0C1 Gitblit \uC778\uC2A4\uD134\uC2A4\uC758 url | ||
| gb.users = \uC720\uC800 | ||
| gb.federation = \uD398\uB354\uB808\uC774\uC158 | ||
| gb.error = \uC5D0\uB7EC | ||
| gb.refresh = \uC0C8\uB85C\uACE0\uCE68 | ||
| gb.browse = \uBE0C\uB77C\uC6B0\uC988 | ||
| gb.clone = clone | ||
| gb.filter = \ud544\ud130 | ||
| gb.create = \uc0dd\uc131 | ||
| gb.servers = \uc11c\ubc84 | ||
| gb.filter = \uD544\uD130 | ||
| gb.create = \uC0DD\uC131 | ||
| gb.servers = \uC11C\uBC84 | ||
| gb.recent = recent | ||
| gb.available = \uac00\ub2a5\ud55c | ||
| gb.selected = \uc120\ud0dd\ub41c | ||
| gb.size = \ud06c\uae30 | ||
| gb.downloading = \ub2e4\uc6b4\ub85c\ub4dc\uc911 | ||
| gb.loading = \ub85c\ub529\uc911 | ||
| gb.starting = \uc2dc\uc791\uc911 | ||
| gb.general = \uc77c\ubc18 | ||
| gb.settings = \uc138\ud305 | ||
| gb.manage = \uad00\ub9ac | ||
| gb.lastLogin = \ub9c8\uc9c0\ub9c9 \ub85c\uadf8\uc778 | ||
| gb.skipSizeCalculation = \ud06c\uae30 \uacc4\uc0b0 \ubb34\uc2dc | ||
| gb.skipSizeCalculationDescription = \uc800\uc7a5\uc18c \ud06c\uae30 \uacc4\uc0b0\ud558\uc9c0 \uc54a\uc74c (\ud398\uc774\uc9c0 \ub85c\ub529 \uc2dc\uac04 \ub2e8\ucd95\ub428) | ||
| gb.skipSummaryMetrics = \uba54\ud2b8\ub9ad \uc694\uc57d \ubb34\uc2dc | ||
| gb.skipSummaryMetricsDescription = \uc694\uc57d \ud398\uc9c0\uc774\uc5d0\uc11c \uba54\ud2b8\ub9ad \uacc4\uc0b0\ud558\uc9c0 \uc54a\uc74c (\ud398\uc774\uc9c0 \ub85c\ub529 \uc2dc\uac04 \ub2e8\ucd95\ub428) | ||
| gb.accessLevel = \uc811\uc18d \ub808\ubca8 | ||
| gb.default = \ub514\ud3f4\ud2b8 | ||
| gb.setDefault = \ub514\ud3f4\ud2b8 \uc124\uc815 | ||
| gb.available = \uAC00\uB2A5\uD55C | ||
| gb.selected = \uC120\uD0DD\uB41C | ||
| gb.size = \uD06C\uAE30 | ||
| gb.downloading = \uB2E4\uC6B4\uB85C\uB4DC\uC911 | ||
| gb.loading = \uB85C\uB529\uC911 | ||
| gb.starting = \uC2DC\uC791\uC911 | ||
| gb.general = \uC77C\uBC18 | ||
| gb.settings = \uC138\uD305 | ||
| gb.manage = \uAD00\uB9AC | ||
| gb.lastLogin = \uB9C8\uC9C0\uB9C9 \uB85C\uADF8\uC778 | ||
| gb.skipSizeCalculation = \uD06C\uAE30 \uACC4\uC0B0 \uBB34\uC2DC | ||
| gb.skipSizeCalculationDescription = \uC800\uC7A5\uC18C \uD06C\uAE30 \uACC4\uC0B0\uD558\uC9C0 \uC54A\uC74C (\uD398\uC774\uC9C0 \uB85C\uB529 \uC2DC\uAC04 \uB2E8\uCD95\uB428) | ||
| gb.skipSummaryMetrics = \uBA54\uD2B8\uB9AD \uC694\uC57D \uBB34\uC2DC | ||
| gb.skipSummaryMetricsDescription = \uC694\uC57D \uD398\uC9C0\uC774\uC5D0\uC11C \uBA54\uD2B8\uB9AD \uACC4\uC0B0\uD558\uC9C0 \uC54A\uC74C (\uD398\uC774\uC9C0 \uB85C\uB529 \uC2DC\uAC04 \uB2E8\uCD95\uB428) | ||
| gb.accessLevel = \uC811\uC18D \uB808\uBCA8 | ||
| gb.default = \uB514\uD3F4\uD2B8 | ||
| gb.setDefault = \uB514\uD3F4\uD2B8 \uC124\uC815 | ||
| gb.since = since | ||
| gb.status = \uc0c1\ud0dc | ||
| gb.bootDate = \ubd80\ud305 \uc77c\uc790 | ||
| gb.servletContainer = \uc11c\ube14\ub9bf \ucee8\ud14c\uc774\ub108 | ||
| gb.heapMaximum = \ucd5c\ub300 \ud799 | ||
| gb.heapAllocated = \ud560\ub2f9\ub41c \ud799 | ||
| gb.heapUsed = \uc0ac\uc6a9\ub41c \ud799 | ||
| gb.free = \ud504\ub9ac | ||
| gb.version = \ubc84\uc804 | ||
| gb.releaseDate = \ub9b4\ub9ac\uc988 \ub0a0\uc9dc | ||
| gb.status = \uC0C1\uD0DC | ||
| gb.bootDate = \uBD80\uD305 \uC77C\uC790 | ||
| gb.servletContainer = \uC11C\uBE14\uB9BF \uCEE8\uD14C\uC774\uB108 | ||
| gb.heapMaximum = \uCD5C\uB300 \uD799 | ||
| gb.heapAllocated = \uD560\uB2F9\uB41C \uD799 | ||
| gb.heapUsed = \uC0AC\uC6A9\uB41C \uD799 | ||
| gb.free = \uD504\uB9AC | ||
| gb.version = \uBC84\uC804 | ||
| gb.releaseDate = \uB9B4\uB9AC\uC988 \uB0A0\uC9DC | ||
| gb.date = date | ||
@@ -221,3 +221,4 @@ gb.activity = \uc561\ud2f0\ube44\ud2f0 | ||
| gb.query = \ucffc\ub9ac | ||
| gb.queryHelp = \ud45c\uc900 \ucffc\ub9ac \ubb38\ubc95\uc744 \uc9c0\uc6d0.<p/><p/>\uc790\uc138\ud55c \uac83\uc744 \uc6d0\ud55c\ub2e4\uba74 <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">\ub8e8\uc2e0 \ucffc\ub9ac \ud30c\uc11c \ubb38\ubc95</a> \uc744 \ubc29\ubb38\ud574 \uc8fc\uc138\uc694. | ||
| gb.queryHelp = \ud45c\uc900 \ucffc\ub9ac \ubb38\ubc95\uc744 \uc9c0\uc6d0.<p/><p/>\uc790\uc138\ud55c \uac83\uc744 \uc6d0\ud55c\ub2e4\uba74 ${querySyntax} \uc744 \ubc29\ubb38\ud574 \uc8fc\uc138\uc694. | ||
| gb.querySyntax = \ub8e8\uc2e0 \ucffc\ub9ac \ud30c\uc11c \ubb38\ubc95 | ||
| gb.queryResults = \uac80\uc0c9\uacb0\uacfc {0} - {1} ({2}\uac1c \uac80\uc0c9\ub428) | ||
@@ -289,175 +290,175 @@ gb.noHits = \uac80\uc0c9 \uacb0\uacfc \uc5c6\uc74c | ||
| gb.none = none | ||
| gb.line = \ub77c\uc778 | ||
| gb.content = \ub0b4\uc6a9 | ||
| gb.line = \uB77C\uC778 | ||
| gb.content = \uB0B4\uC6A9 | ||
| gb.empty = empty | ||
| gb.inherited = \uc0c1\uc18d | ||
| gb.deleteRepository = \uc800\uc7a5\uc18c \"{0}\" \ub97c \uc0ad\uc81c\ud560\uae4c\uc694? | ||
| gb.repositoryDeleted = \uc800\uc7a5\uc18c ''{0}'' \uc0ad\uc81c\ub428. | ||
| gb.repositoryDeleteFailed = \uc800\uc7a5\uc18c ''{0}'' \uc0ad\uc81c \uc2e4\ud328! | ||
| gb.deleteUser = \uc0ac\uc6a9\uc790 \"{0}\"\ub97c \uc0ad\uc81c\ud560\uae4c\uc694? | ||
| gb.userDeleted = \uc0ac\uc6a9\uc790 ''{0}'' \uc0ad\uc81c\ub428. | ||
| gb.userDeleteFailed = \uc0ac\uc6a9\uc790 ''{0}'' \uc0ad\uc81c \uc2e4\ud328! | ||
| gb.time.justNow = \uc9c0\uae08 | ||
| gb.time.today = \uc624\ub298 | ||
| gb.time.yesterday = \uc5b4\uc81c | ||
| gb.time.minsAgo = {0}\ubd84 \uc804 | ||
| gb.time.hoursAgo = {0}\uc2dc\uac04 \uc804 | ||
| gb.time.daysAgo = {0}\uc77c \uc804 | ||
| gb.time.weeksAgo = {0}\uc8fc \uc804 | ||
| gb.time.monthsAgo = {0}\ub2ec \uc804 | ||
| gb.time.oneYearAgo = 1\ub144 \uc804 | ||
| gb.time.yearsAgo = {0}\ub144 \uc804 | ||
| gb.duration.oneDay = 1\uc77c | ||
| gb.duration.days = {0}\uc77c | ||
| gb.duration.oneMonth = 1\uac1c\uc6d4 | ||
| gb.duration.months = {0}\uac1c\uc6d4 | ||
| gb.duration.oneYear = 1\ub144 | ||
| gb.duration.years = {0}\ub144 | ||
| gb.authorizationControl = \uc778\uc99d \uc81c\uc5b4 | ||
| gb.allowAuthenticatedDescription = \ubaa8\ub4e0 \uc778\uc99d\ub41c \uc720\uc800\uc5d0\uac8c \uad8c\ud55c \ubd80\uc5ec | ||
| gb.allowNamedDescription = \uc774\ub984\uc73c\ub85c \uc720\uc800\ub098 \ud300\uc5d0\uac8c \uad8c\ud55c \ubd80\uc5ec | ||
| gb.markdownFailure = \ub9c8\ud06c\ub2e4\uc6b4 \ucee8\ud150\uce20 \ud30c\uc2f1 \uc624\ub958! | ||
| gb.clearCache = \uce90\uc2dc \uc9c0\uc6b0\uae30 | ||
| gb.projects = \ud504\ub85c\uc81d\ud2b8 | ||
| gb.project = \ud504\ub85c\uc81d\ud2b8 | ||
| gb.allProjects = \ubaa8\ub4e0 \ud504\ub85c\uc81d\ud2b8 | ||
| gb.copyToClipboard = \ud074\ub9bd\ubcf4\ub4dc\uc5d0 \ubcf5\uc0ac | ||
| gb.fork = \ud3ec\ud06c | ||
| gb.forks = \ud3ec\ud06c | ||
| gb.forkRepository = {0}\ub97c \ud3ec\ud06c\ud560\uae4c\uc694? | ||
| gb.repositoryForked = {0} \ud3ec\ud06c\ub428 | ||
| gb.repositoryForkFailed= \ud3ec\ud06c\uc2e4\ud328 | ||
| gb.personalRepositories = \uac1c\uc778 \uc800\uc7a5\uc18c | ||
| gb.allowForks = \ud3ec\ud06c \ud5c8\uc6a9 | ||
| gb.allowForksDescription = \uc774 \uc800\uc7a5\uc18c\ub97c \uc778\uc99d\ub41c \uc720\uc800\uc5d0\uac70 \ud3ec\ud06c \ud5c8\uc6a9 | ||
| gb.forkedFrom = \ub85c\ubd80\ud130 \ud3ec\ud06c\ub428 | ||
| gb.canFork = \ud3ec\ud06c \uac00\ub2a5 | ||
| gb.canForkDescription = \ud5c8\uc6a9\ub41c \uc800\uc7a5\uc18c\ub97c \uac1c\uc778 \uc800\uc7a5\uc18c\uc5d0 \ud3ec\ud06c\ud560 \uc218 \uc788\uc74c | ||
| gb.myFork = \ub0b4 \ud3ec\ud06c \ubcf4\uae30 | ||
| gb.forksProhibited = \ud3ec\ud06c \ucc28\ub2e8\ub428 | ||
| gb.forksProhibitedWarning = \uc774 \uc800\uc7a5\uc18c\ub294 \ud3ec\ud06c \ucc28\ub2e8\ub418\uc5b4 \uc788\uc74c | ||
| gb.noForks = {0} \ub294 \ud3ec\ud06c \uc5c6\uc74c | ||
| gb.forkNotAuthorized = \uc8c4\uc1a1\ud569\ub2c8\ub2e4. {0} \ud3ec\ud06c\uc5d0 \uc811\uc18d\uc774 \uc778\uc99d\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. | ||
| gb.forkInProgress = \ud504\ud06c \uc9c4\ud589 \uc911 | ||
| gb.preparingFork = \ud3ec\ud06c \uc900\ube44 \uc911... | ||
| gb.isFork = \ud3ec\ud06c\ud55c | ||
| gb.canCreate = \uc0dd\uc131 \uac00\ub2a5 | ||
| gb.canCreateDescription = \uac1c\uc778 \uc800\uc7a5\uc18c\ub97c \ub9cc\ub4e4 \uc218 \uc788\uc74c | ||
| gb.illegalPersonalRepositoryLocation = \uac1c\uc778 \uc800\uc7a5\uc18c\ub294 \ubc18\ub4dc\uc2dc \"{0}\" \uc5d0 \uc704\uce58\ud574\uc57c \ud569\ub2c8\ub2e4. | ||
| gb.verifyCommitter = \ucee4\ubbf8\ud130 \ud655\uc778 | ||
| gb.verifyCommitterDescription = \ucee4\ubbf8\ud130 ID \ub294 Gitblit ID \uc640 \ub9e4\uce58\ub418\uc5b4\uc57c \ud568 | ||
| gb.verifyCommitterNote = \ubaa8\ub4e0 \uba38\uc9c0\ub294 \ucee4\ubbf8\ud130 ID \ub97c \uc801\uc6a9\ud558\uae30 \uc704\ud574 "--no-ff" \uc635\uc158 \ud544\uc694 | ||
| gb.repositoryPermissions = \uc800\uc7a5\uc18c \uad8c\ud55c | ||
| gb.userPermissions = \uc720\uc800 \uad8c\ud55c | ||
| gb.teamPermissions = \ud300 \uad8c\ud55c | ||
| gb.add = \ucd94\uac00 | ||
| gb.noPermission = \uc774 \uad8c\ud55c \uc0ad\uc81c | ||
| gb.excludePermission = {0} (\uc81c\uc678) | ||
| gb.viewPermission = {0} (\ubcf4\uae30) | ||
| gb.clonePermission = {0} (\ud074\ub860) | ||
| gb.pushPermission = {0} (\ud478\uc2dc) | ||
| gb.createPermission = {0} (\ud478\uc2dc, ref \uc0dd\uc131) | ||
| gb.deletePermission = {0} (\ud478\uc2dc, ref \uc0dd\uc131+\uc0ad\uc81c) | ||
| gb.rewindPermission = {0} (\ud478\uc2dc, ref \uc0dd\uc131+\uc0ad\uc81c+\ub418\ub3cc\ub9ac\uae30) | ||
| gb.permission = \uad8c\ud55c | ||
| gb.regexPermission = \uc774 \uad8c\ud55c\uc740 \uc815\uaddc\uc2dd \"{0}\" \ub85c\ubd80\ud130 \uc124\uc815\ub428 | ||
| gb.accessDenied = \uc811\uc18d \uac70\ubd80 | ||
| gb.busyCollectingGarbage = \uc8c4\uc1a1\ud569\ub2c8\ub2e4. Gitblit \uc740 \uac00\ube44\uc9c0 \uceec\ub809\uc158 \uc911\uc785\ub2c8\ub2e4. {0} | ||
| gb.gcPeriod = GC \uc8fc\uae30 | ||
| gb.gcPeriodDescription = \uac00\ube44\uc9c0 \ud074\ub809\uc158\uac04\uc758 \uc2dc\uac04 \uac04\uaca9 | ||
| gb.gcThreshold = GC \uae30\uc900\uc810 | ||
| gb.gcThresholdDescription = \uc870\uae30 \uac00\ube44\uc9c0 \uceec\ub809\uc158\uc744 \ubc1c\uc0dd\uc2dc\ud0a4\uae30 \uc704\ud55c \uc624\ube0c\uc81d\ud2b8\ub4e4\uc758 \ucd5c\uc18c \uc804\uccb4 \ud06c\uae30 | ||
| gb.ownerPermission = \uc800\uc7a5\uc18c \uc624\ub108 | ||
| gb.administrator = \uad00\ub9ac\uc790 | ||
| gb.administratorPermission = Gitblit \uad00\ub9ac\uc790 | ||
| gb.team = \ud300 | ||
| gb.teamPermission = \"{0}\" \ud300 \uba64\ubc84\uc5d0 \uad8c\ud55c \uc124\uc815\ub428 | ||
| gb.missing = \ub204\ub77d! | ||
| gb.missingPermission = \uc774 \uad8c\ud55c\uc744 \uc704\ud55c \uc800\uc7a5\uc18c \ub204\ub77d! | ||
| gb.mutable = \uac00\ubcc0 | ||
| gb.specified = \uc9c0\uc815\ub41c | ||
| gb.effective = \ud6a8\uacfc\uc801 | ||
| gb.organizationalUnit = \uc870\uc9c1 | ||
| gb.organization = \uae30\uad00 | ||
| gb.locality = \uc704\uce58 | ||
| gb.stateProvince = \ub3c4 \ub610\ub294 \uc8fc | ||
| gb.countryCode = \uad6d\uac00\ucf54\ub4dc | ||
| gb.properties = \uc18d\uc131 | ||
| gb.issued = \ubc1c\uae09\ub428 | ||
| gb.expires = \ub9cc\ub8cc | ||
| gb.expired = \ub9cc\ub8cc\ub428 | ||
| gb.expiring = \ub9cc\ub8cc\uc911 | ||
| gb.revoked = \ud3d0\uae30\ub428 | ||
| gb.serialNumber = \uc77c\ub828\ubc88\ud638 | ||
| gb.certificates = \uc778\uc99d\uc11c | ||
| gb.newCertificate = \uc0c8 \uc778\uc99d\uc11c | ||
| gb.revokeCertificate = \uc778\uc99d\uc11c \ud3d0\uae30 | ||
| gb.sendEmail = \uba54\uc77c \ubcf4\ub0b4\uae30 | ||
| gb.passwordHint = \ud328\uc2a4\uc6cc\ub4dc \ud78c\ud2b8 | ||
| gb.inherited = \uC0C1\uC18D | ||
| gb.deleteRepository = \uC800\uC7A5\uC18C \"{0}\" \uB97C \uC0AD\uC81C\uD560\uAE4C\uC694? | ||
| gb.repositoryDeleted = \uC800\uC7A5\uC18C ''{0}'' \uC0AD\uC81C\uB428. | ||
| gb.repositoryDeleteFailed = \uC800\uC7A5\uC18C ''{0}'' \uC0AD\uC81C \uC2E4\uD328! | ||
| gb.deleteUser = \uC0AC\uC6A9\uC790 \"{0}\"\uB97C \uC0AD\uC81C\uD560\uAE4C\uC694? | ||
| gb.userDeleted = \uC0AC\uC6A9\uC790 ''{0}'' \uC0AD\uC81C\uB428. | ||
| gb.userDeleteFailed = \uC0AC\uC6A9\uC790 ''{0}'' \uC0AD\uC81C \uC2E4\uD328! | ||
| gb.time.justNow = \uC9C0\uAE08 | ||
| gb.time.today = \uC624\uB298 | ||
| gb.time.yesterday = \uC5B4\uC81C | ||
| gb.time.minsAgo = {0}\uBD84 \uC804 | ||
| gb.time.hoursAgo = {0}\uC2DC\uAC04 \uC804 | ||
| gb.time.daysAgo = {0}\uC77C \uC804 | ||
| gb.time.weeksAgo = {0}\uC8FC \uC804 | ||
| gb.time.monthsAgo = {0}\uB2EC \uC804 | ||
| gb.time.oneYearAgo = 1\uB144 \uC804 | ||
| gb.time.yearsAgo = {0}\uB144 \uC804 | ||
| gb.duration.oneDay = 1\uC77C | ||
| gb.duration.days = {0}\uC77C | ||
| gb.duration.oneMonth = 1\uAC1C\uC6D4 | ||
| gb.duration.months = {0}\uAC1C\uC6D4 | ||
| gb.duration.oneYear = 1\uB144 | ||
| gb.duration.years = {0}\uB144 | ||
| gb.authorizationControl = \uC778\uC99D \uC81C\uC5B4 | ||
| gb.allowAuthenticatedDescription = \uBAA8\uB4E0 \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC | ||
| gb.allowNamedDescription = \uC774\uB984\uC73C\uB85C \uC720\uC800\uB098 \uD300\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC | ||
| gb.markdownFailure = \uB9C8\uD06C\uB2E4\uC6B4 \uCEE8\uD150\uCE20 \uD30C\uC2F1 \uC624\uB958! | ||
| gb.clearCache = \uCE90\uC2DC \uC9C0\uC6B0\uAE30 | ||
| gb.projects = \uD504\uB85C\uC81D\uD2B8 | ||
| gb.project = \uD504\uB85C\uC81D\uD2B8 | ||
| gb.allProjects = \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8 | ||
| gb.copyToClipboard = \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC | ||
| gb.fork = \uD3EC\uD06C | ||
| gb.forks = \uD3EC\uD06C | ||
| gb.forkRepository = {0}\uB97C \uD3EC\uD06C\uD560\uAE4C\uC694? | ||
| gb.repositoryForked = {0} \uD3EC\uD06C\uB428 | ||
| gb.repositoryForkFailed= \uD3EC\uD06C\uC2E4\uD328 | ||
| gb.personalRepositories = \uAC1C\uC778 \uC800\uC7A5\uC18C | ||
| gb.allowForks = \uD3EC\uD06C \uD5C8\uC6A9 | ||
| gb.allowForksDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC70 \uD3EC\uD06C \uD5C8\uC6A9 | ||
| gb.forkedFrom = \uB85C\uBD80\uD130 \uD3EC\uD06C\uB428 | ||
| gb.canFork = \uD3EC\uD06C \uAC00\uB2A5 | ||
| gb.canForkDescription = \uD5C8\uC6A9\uB41C \uC800\uC7A5\uC18C\uB97C \uAC1C\uC778 \uC800\uC7A5\uC18C\uC5D0 \uD3EC\uD06C\uD560 \uC218 \uC788\uC74C | ||
| gb.myFork = \uB0B4 \uD3EC\uD06C \uBCF4\uAE30 | ||
| gb.forksProhibited = \uD3EC\uD06C \uCC28\uB2E8\uB428 | ||
| gb.forksProhibitedWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uD3EC\uD06C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC74C | ||
| gb.noForks = {0} \uB294 \uD3EC\uD06C \uC5C6\uC74C | ||
| gb.forkNotAuthorized = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. {0} \uD3EC\uD06C\uC5D0 \uC811\uC18D\uC774 \uC778\uC99D\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. | ||
| gb.forkInProgress = \uD504\uD06C \uC9C4\uD589 \uC911 | ||
| gb.preparingFork = \uD3EC\uD06C \uC900\uBE44 \uC911... | ||
| gb.isFork = \uD3EC\uD06C\uD55C | ||
| gb.canCreate = \uC0DD\uC131 \uAC00\uB2A5 | ||
| gb.canCreateDescription = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4 \uC218 \uC788\uC74C | ||
| gb.illegalPersonalRepositoryLocation = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB294 \uBC18\uB4DC\uC2DC \"{0}\" \uC5D0 \uC704\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4. | ||
| gb.verifyCommitter = \uCEE4\uBBF8\uD130 \uD655\uC778 | ||
| gb.verifyCommitterDescription = \uCEE4\uBBF8\uD130 ID \uB294 Gitblit ID \uC640 \uB9E4\uCE58\uB418\uC5B4\uC57C \uD568 | ||
| gb.verifyCommitterNote = \uBAA8\uB4E0 \uBA38\uC9C0\uB294 \uCEE4\uBBF8\uD130 ID \uB97C \uC801\uC6A9\uD558\uAE30 \uC704\uD574 "--no-ff" \uC635\uC158 \uD544\uC694 | ||
| gb.repositoryPermissions = \uC800\uC7A5\uC18C \uAD8C\uD55C | ||
| gb.userPermissions = \uC720\uC800 \uAD8C\uD55C | ||
| gb.teamPermissions = \uD300 \uAD8C\uD55C | ||
| gb.add = \uCD94\uAC00 | ||
| gb.noPermission = \uC774 \uAD8C\uD55C \uC0AD\uC81C | ||
| gb.excludePermission = {0} (\uC81C\uC678) | ||
| gb.viewPermission = {0} (\uBCF4\uAE30) | ||
| gb.clonePermission = {0} (\uD074\uB860) | ||
| gb.pushPermission = {0} (\uD478\uC2DC) | ||
| gb.createPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131) | ||
| gb.deletePermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C) | ||
| gb.rewindPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C+\uB418\uB3CC\uB9AC\uAE30) | ||
| gb.permission = \uAD8C\uD55C | ||
| gb.regexPermission = \uC774 \uAD8C\uD55C\uC740 \uC815\uADDC\uC2DD \"{0}\" \uB85C\uBD80\uD130 \uC124\uC815\uB428 | ||
| gb.accessDenied = \uC811\uC18D \uAC70\uBD80 | ||
| gb.busyCollectingGarbage = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. Gitblit \uC740 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158 \uC911\uC785\uB2C8\uB2E4. {0} | ||
| gb.gcPeriod = GC \uC8FC\uAE30 | ||
| gb.gcPeriodDescription = \uAC00\uBE44\uC9C0 \uD074\uB809\uC158\uAC04\uC758 \uC2DC\uAC04 \uAC04\uACA9 | ||
| gb.gcThreshold = GC \uAE30\uC900\uC810 | ||
| gb.gcThresholdDescription = \uC870\uAE30 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158\uC744 \uBC1C\uC0DD\uC2DC\uD0A4\uAE30 \uC704\uD55C \uC624\uBE0C\uC81D\uD2B8\uB4E4\uC758 \uCD5C\uC18C \uC804\uCCB4 \uD06C\uAE30 | ||
| gb.ownerPermission = \uC800\uC7A5\uC18C \uC624\uB108 | ||
| gb.administrator = \uAD00\uB9AC\uC790 | ||
| gb.administratorPermission = Gitblit \uAD00\uB9AC\uC790 | ||
| gb.team = \uD300 | ||
| gb.teamPermission = \"{0}\" \uD300 \uBA64\uBC84\uC5D0 \uAD8C\uD55C \uC124\uC815\uB428 | ||
| gb.missing = \uB204\uB77D! | ||
| gb.missingPermission = \uC774 \uAD8C\uD55C\uC744 \uC704\uD55C \uC800\uC7A5\uC18C \uB204\uB77D! | ||
| gb.mutable = \uAC00\uBCC0 | ||
| gb.specified = \uC9C0\uC815\uB41C | ||
| gb.effective = \uD6A8\uACFC\uC801 | ||
| gb.organizationalUnit = \uC870\uC9C1 | ||
| gb.organization = \uAE30\uAD00 | ||
| gb.locality = \uC704\uCE58 | ||
| gb.stateProvince = \uB3C4 \uB610\uB294 \uC8FC | ||
| gb.countryCode = \uAD6D\uAC00\uCF54\uB4DC | ||
| gb.properties = \uC18D\uC131 | ||
| gb.issued = \uBC1C\uAE09\uB428 | ||
| gb.expires = \uB9CC\uB8CC | ||
| gb.expired = \uB9CC\uB8CC\uB428 | ||
| gb.expiring = \uB9CC\uB8CC\uC911 | ||
| gb.revoked = \uD3D0\uAE30\uB428 | ||
| gb.serialNumber = \uC77C\uB828\uBC88\uD638 | ||
| gb.certificates = \uC778\uC99D\uC11C | ||
| gb.newCertificate = \uC0C8 \uC778\uC99D\uC11C | ||
| gb.revokeCertificate = \uC778\uC99D\uC11C \uD3D0\uAE30 | ||
| gb.sendEmail = \uBA54\uC77C \uBCF4\uB0B4\uAE30 | ||
| gb.passwordHint = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 | ||
| gb.ok = ok | ||
| gb.invalidExpirationDate = \ub9d0\ub8cc\uc77c\uc790 \uc624\ub958! | ||
| gb.passwordHintRequired = \ud328\uc2a4\uc6cc\ub4dc \ud78c\ud2b8 \ud544\uc218! | ||
| gb.viewCertificate = \uc778\uc99d\uc11c \ubcf4\uae30 | ||
| gb.subject = \uc774\ub984 | ||
| gb.issuer = \ubc1c\uae09\uc790 | ||
| gb.validFrom = \uc720\ud6a8\uae30\uac04 (\uc2dc\uc791) | ||
| gb.validUntil = \uc720\ud6a8\uae30\uac04 (\ub05d) | ||
| gb.publicKey = \uacf5\uac1c\ud0a4 | ||
| gb.signatureAlgorithm = \uc11c\uba85 \uc54c\uace0\ub9ac\uc998 | ||
| gb.sha1FingerPrint = SHA-1 \uc9c0\ubb38 \uc54c\uace0\ub9ac\uc998 | ||
| gb.md5FingerPrint = MD5 \uc9c0\ubb38 \uc54c\uace0\ub9ac\uc998 | ||
| gb.reason = \uc774\uc720 | ||
| gb.revokeCertificateReason = \uc778\uc99d\uc11c \ud574\uc9c0\uc774\uc720\ub97c \uc120\ud0dd\ud558\uc138\uc694 | ||
| gb.unspecified = \ud45c\uc2dc\ud558\uc9c0 \uc54a\uc74c | ||
| gb.keyCompromise = \ud0a4 \uc190\uc0c1 | ||
| gb.caCompromise = CA \uc190\uc0c1 | ||
| gb.affiliationChanged = \uad00\uacc4 \ubcc0\uacbd\ub428 | ||
| gb.superseded = \ub300\uccb4\ub428 | ||
| gb.cessationOfOperation = \uc6b4\uc601 \uc911\uc9c0 | ||
| gb.privilegeWithdrawn = \uad8c\ud55c \ucca0\ud68c\ub428 | ||
| gb.time.inMinutes = {0} \ubd84 | ||
| gb.time.inHours = {0} \uc2dc\uac04 | ||
| gb.time.inDays = {0} \uc77c | ||
| gb.hostname = \ud638\uc2a4\ud2b8\uba85 | ||
| gb.hostnameRequired = \ud638\uc2a4\ud2b8\uba85\uc744 \uc785\ub825\ud558\uc138\uc694 | ||
| gb.newSSLCertificate = \uc0c8 \uc11c\ubc84 SSL \uc778\uc99d\uc11c | ||
| gb.newCertificateDefaults = \uc0c8 \uc778\uc99d\uc11c \uae30\ubcf8 | ||
| gb.duration = \uae30\uac04 | ||
| gb.certificateRevoked = \uc778\uc99d\uc11c {0,number,0} \ub294 \ud3d0\uae30\ub418\uc5c8\uc2b5\ub2c8\ub2e4 | ||
| gb.clientCertificateGenerated = {0} \uc744(\ub97c) \uc704\ud55c \uc0c8\ub85c\uc6b4 \ud074\ub77c\uc774\uc5b8\ud2b8 \uc778\uc99d\uc11c \uc0dd\uc131 \uc131\uacf5 | ||
| gb.sslCertificateGenerated = {0} \uc744(\ub97c) \uc704\ud55c \uc0c8\ub85c\uc6b4 \uc11c\ubc84 SSL \uc778\uc99d\uc11c \uc0dd\uc131 \uc131\uacf5 | ||
| gb.newClientCertificateMessage = \ub178\ud2b8:\n'\ud328\uc2a4\uc6cc\ub4dc' \ub294 \uc720\uc800\uc758 \ud328\uc2a4\uc6cc\ub4dc\uac00 \uc544\ub2c8\ub77c \uc720\uc800\uc758 \ud0a4\uc2a4\ud1a0\uc5b4 \ub97c \ubcf4\ud638\ud558\uae30 \uc704\ud55c \uac83\uc785\ub2c8\ub2e4. \uc774 \ud328\uc2a4\uc6cc\ub4dc\ub294 \uc800\uc7a5\ub418\uc9c0 \uc54a\uc73c\ubbc0\ub85c \uc0ac\uc6a9\uc790 README \uc9c0\uce68\uc5d0 \ud3ec\ud568\ub420 '\ud78c\ud2b8' \ub97c \ubc18\ub4dc\uc2dc \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. | ||
| gb.certificate = \uc778\uc99d\uc11c | ||
| gb.emailCertificateBundle = \uc774\uba54\uc77c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc778\uc99d\uc11c \ubc88\ub4e4 | ||
| gb.pleaseGenerateClientCertificate = {0} \uc744(\ub97c) \uc704\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc778\uc99d\uc11c\ub97c \uc0dd\uc131\ud558\uc138\uc694 | ||
| gb.clientCertificateBundleSent = {0} \uc744(\ub97c) \uc704\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc778\uc99d\uc11c \ubc88\ub4e4 \ubc1c\uc1a1\ub428 | ||
| gb.enterKeystorePassword = Gitblit \ud0a4\uc2a4\ud1a0\uc5b4 \ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud558\uc138\uc694 | ||
| gb.warning = \uacbd\uace0 | ||
| gb.jceWarning = \uc790\ubc14 \uc2e4\ud589\ud658\uacbd\uc5d0 \"JCE Unlimited Strength Jurisdiction Policy\" \ud30c\uc77c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.\n\uc774\uac83\uc740 \ud0a4\uc800\uc7a5\uc18c \uc554\ud638\ud654\uc5d0 \uc0ac\uc6a9\ub418\ub294 \ud328\uc2a4\uc6cc\ub4dc\uc758 \uae38\uc774\ub294 7\uc790\ub85c \uc81c\ud55c\ud569\ub2c8\ub2e4.\n\uc774 \uc815\ucc45 \ud30c\uc77c\uc740 Oracle \uc5d0\uc11c \uc120\ud0dd\uc801\uc73c\ub85c \ub2e4\uc6b4\ub85c\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4.\n\n\ubb34\uc2dc\ud558\uace0 \uc778\uc99d\uc11c \uc778\ud504\ub77c\ub97c \uc0dd\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\uc544\ub2c8\uc624(No) \ub77c\uace0 \ub2f5\ud558\uba74 \uc815\ucc45\ud30c\uc77c\uc744 \ub2e4\uc6b4\ubc1b\uc744 \uc218 \uc788\ub294 Oracle \ub2e4\uc6b4\ub85c\ub4dc \ud398\uc774\uc9c0\ub97c \ube0c\ub77c\uc6b0\uc800\ub85c \uc548\ub0b4\ud560 \uac83\uc785\ub2c8\ub2e4. | ||
| gb.maxActivityCommits = \ucd5c\ub300 \uc561\ud2f0\ube44\ud2f0 \ucee4\ubc0b | ||
| gb.maxActivityCommitsDescription = \uc561\ud2f0\ube44\ud2f0 \ud398\uc774\uc9c0\uc5d0 \ud45c\uc2dc\ud560 \ucd5c\ub300 \ucee4\ubc0b \uc218 | ||
| gb.noMaximum = \ubb34\uc81c\ud55c | ||
| gb.attributes = \uc18d\uc131 | ||
| gb.serveCertificate = \uc774 \uc778\uc99d\uc11c\ub85c https \uc81c\uacf5 | ||
| gb.sslCertificateGeneratedRestart = {0} \uc744(\ub97c) \uc704\ud55c \uc0c8 \uc11c\ubc84 SSL \uc778\uc99d\uc11c\ub97c \uc131\uacf5\uc801\uc73c\ub85c \uc0dd\uc131\ud558\uc600\uc2b5\ub2c8\ub2e4. \n\uc0c8 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 Gitblit \uc744 \uc7ac\uc2dc\uc791 \ud574\uc57c \ud569\ub2c8\ub2e4.\n\n'--alias' \ud30c\ub77c\ubbf8\ud130\ub85c \uc2e4\ud589\ud55c\ub2e4\uba74 ''--alias {0}'' \ub85c \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. | ||
| gb.validity = \uc720\ud6a8\uc131 | ||
| gb.siteName = \uc0ac\uc774\ud2b8 \uc774\ub984 | ||
| gb.siteNameDescription = \uc11c\ubc84\uc758 \uc9e6\uc740 \uc124\uba85\uc774 \ud3ec\ud568\ub41c \uc774\ub984 | ||
| gb.excludeFromActivity = \uc561\ud2f0\ube44\ud2f0 \ud398\uc774\uc9c0\uc5d0\uc11c \uc81c\uc678 | ||
| gb.isSparkleshared = \uc800\uc7a5\uc18c\ub294 Sparkleshare \ub428 | ||
| gb.owners = \uc624\ub108 | ||
| gb.sessionEnded = \uc138\uc158\uc774 \uc885\ub8cc\ub428 | ||
| gb.closeBrowser = \uc815\ud655\ud788 \uc138\uc158\uc744 \uc885\ub8cc\ud558\ub824\uba74 \ube0c\ub77c\uc6b0\uc800\ub97c \ub2eb\uc544 \uc8fc\uc138\uc694. | ||
| gb.doesNotExistInTree = {1} \ud2b8\ub9ac\uc5d0 {0} \uac00 \uc5c6\uc74c | ||
| gb.enableIncrementalPushTags = \uc99d\uac00\ud558\ub294 \ud478\uc2dc \ud0dc\uadf8 \uac00\ub2a5 | ||
| gb.useIncrementalPushTagsDescription = \ud478\uc2dc\ud560 \ub54c, \uc99d\uac00\ud558\ub294 \ub9ac\ube44\uc804 \ubc88\ud638\uac00 \uc790\ub3d9\uc73c\ub85c \ud0dc\uadf8\ub428 | ||
| gb.incrementalPushTagMessage = \ud478\uc2dc\ud560 \ub54c [{0}] \ube0c\ub79c\uce58\uc5d0 \uc790\ub3d9\uc73c\ub85c \ud0dc\uadf8\ub428 | ||
| gb.externalPermissions = {0} \uc811\uc18d \uad8c\ud55c\uc740 \uc678\ubd80\uc5d0\uc11c \uad00\ub9ac\ub428 | ||
| gb.viewAccess = Gitblit \uc5d0 \uc77d\uae30 \ub610\ub294 \uc4f0\uae30 \uad8c\ud55c\uc774 \uc5c6\uc74c | ||
| gb.overview = \uac1c\uc694 | ||
| gb.dashboard = \ub300\uc2dc\ubcf4\ub4dc | ||
| gb.monthlyActivity = \uc6d4\ubcc4 \uc561\ud2f0\ube44 | ||
| gb.myProfile = \ub0b4 \ud504\ub85c\ud544 | ||
| gb.compare = \ube44\uad50 | ||
| gb.manual = \uc124\uba85\uc11c | ||
| gb.invalidExpirationDate = \uB9D0\uB8CC\uC77C\uC790 \uC624\uB958! | ||
| gb.passwordHintRequired = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 \uD544\uC218! | ||
| gb.viewCertificate = \uC778\uC99D\uC11C \uBCF4\uAE30 | ||
| gb.subject = \uC774\uB984 | ||
| gb.issuer = \uBC1C\uAE09\uC790 | ||
| gb.validFrom = \uC720\uD6A8\uAE30\uAC04 (\uC2DC\uC791) | ||
| gb.validUntil = \uC720\uD6A8\uAE30\uAC04 (\uB05D) | ||
| gb.publicKey = \uACF5\uAC1C\uD0A4 | ||
| gb.signatureAlgorithm = \uC11C\uBA85 \uC54C\uACE0\uB9AC\uC998 | ||
| gb.sha1FingerPrint = SHA-1 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998 | ||
| gb.md5FingerPrint = MD5 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998 | ||
| gb.reason = \uC774\uC720 | ||
| gb.revokeCertificateReason = \uC778\uC99D\uC11C \uD574\uC9C0\uC774\uC720\uB97C \uC120\uD0DD\uD558\uC138\uC694 | ||
| gb.unspecified = \uD45C\uC2DC\uD558\uC9C0 \uC54A\uC74C | ||
| gb.keyCompromise = \uD0A4 \uC190\uC0C1 | ||
| gb.caCompromise = CA \uC190\uC0C1 | ||
| gb.affiliationChanged = \uAD00\uACC4 \uBCC0\uACBD\uB428 | ||
| gb.superseded = \uB300\uCCB4\uB428 | ||
| gb.cessationOfOperation = \uC6B4\uC601 \uC911\uC9C0 | ||
| gb.privilegeWithdrawn = \uAD8C\uD55C \uCCA0\uD68C\uB428 | ||
| gb.time.inMinutes = {0} \uBD84 | ||
| gb.time.inHours = {0} \uC2DC\uAC04 | ||
| gb.time.inDays = {0} \uC77C | ||
| gb.hostname = \uD638\uC2A4\uD2B8\uBA85 | ||
| gb.hostnameRequired = \uD638\uC2A4\uD2B8\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694 | ||
| gb.newSSLCertificate = \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C | ||
| gb.newCertificateDefaults = \uC0C8 \uC778\uC99D\uC11C \uAE30\uBCF8 | ||
| gb.duration = \uAE30\uAC04 | ||
| gb.certificateRevoked = \uC778\uC99D\uC11C {0,number,0} \uB294 \uD3D0\uAE30\uB418\uC5C8\uC2B5\uB2C8\uB2E4 | ||
| gb.clientCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5 | ||
| gb.sslCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uC11C\uBC84 SSL \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5 | ||
| gb.newClientCertificateMessage = \uB178\uD2B8:\n'\uD328\uC2A4\uC6CC\uB4DC' \uB294 \uC720\uC800\uC758 \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uC544\uB2C8\uB77C \uC720\uC800\uC758 \uD0A4\uC2A4\uD1A0\uC5B4 \uB97C \uBCF4\uD638\uD558\uAE30 \uC704\uD55C \uAC83\uC785\uB2C8\uB2E4. \uC774 \uD328\uC2A4\uC6CC\uB4DC\uB294 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uC0AC\uC6A9\uC790 README \uC9C0\uCE68\uC5D0 \uD3EC\uD568\uB420 '\uD78C\uD2B8' \uB97C \uBC18\uB4DC\uC2DC \uC785\uB825\uD574\uC57C \uD569\uB2C8\uB2E4. | ||
| gb.certificate = \uC778\uC99D\uC11C | ||
| gb.emailCertificateBundle = \uC774\uBA54\uC77C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 | ||
| gb.pleaseGenerateClientCertificate = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C\uB97C \uC0DD\uC131\uD558\uC138\uC694 | ||
| gb.clientCertificateBundleSent = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 \uBC1C\uC1A1\uB428 | ||
| gb.enterKeystorePassword = Gitblit \uD0A4\uC2A4\uD1A0\uC5B4 \uD328\uC2A4\uC6CC\uB4DC\uB97C \uC785\uB825\uD558\uC138\uC694 | ||
| gb.warning = \uACBD\uACE0 | ||
| gb.jceWarning = \uC790\uBC14 \uC2E4\uD589\uD658\uACBD\uC5D0 \"JCE Unlimited Strength Jurisdiction Policy\" \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uC774\uAC83\uC740 \uD0A4\uC800\uC7A5\uC18C \uC554\uD638\uD654\uC5D0 \uC0AC\uC6A9\uB418\uB294 \uD328\uC2A4\uC6CC\uB4DC\uC758 \uAE38\uC774\uB294 7\uC790\uB85C \uC81C\uD55C\uD569\uB2C8\uB2E4.\n\uC774 \uC815\uCC45 \uD30C\uC77C\uC740 Oracle \uC5D0\uC11C \uC120\uD0DD\uC801\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD574\uC57C \uD569\uB2C8\uB2E4.\n\n\uBB34\uC2DC\uD558\uACE0 \uC778\uC99D\uC11C \uC778\uD504\uB77C\uB97C \uC0DD\uC131\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?\n\n\uC544\uB2C8\uC624(No) \uB77C\uACE0 \uB2F5\uD558\uBA74 \uC815\uCC45\uD30C\uC77C\uC744 \uB2E4\uC6B4\uBC1B\uC744 \uC218 \uC788\uB294 Oracle \uB2E4\uC6B4\uB85C\uB4DC \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC548\uB0B4\uD560 \uAC83\uC785\uB2C8\uB2E4. | ||
| gb.maxActivityCommits = \uCD5C\uB300 \uC561\uD2F0\uBE44\uD2F0 \uCEE4\uBC0B | ||
| gb.maxActivityCommitsDescription = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0 \uD45C\uC2DC\uD560 \uCD5C\uB300 \uCEE4\uBC0B \uC218 | ||
| gb.noMaximum = \uBB34\uC81C\uD55C | ||
| gb.attributes = \uC18D\uC131 | ||
| gb.serveCertificate = \uC774 \uC778\uC99D\uC11C\uB85C https \uC81C\uACF5 | ||
| gb.sslCertificateGeneratedRestart = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C\uB97C \uC131\uACF5\uC801\uC73C\uB85C \uC0DD\uC131\uD558\uC600\uC2B5\uB2C8\uB2E4. \n\uC0C8 \uC778\uC99D\uC11C\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 Gitblit \uC744 \uC7AC\uC2DC\uC791 \uD574\uC57C \uD569\uB2C8\uB2E4.\n\n'--alias' \uD30C\uB77C\uBBF8\uD130\uB85C \uC2E4\uD589\uD55C\uB2E4\uBA74 ''--alias {0}'' \uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4. | ||
| gb.validity = \uC720\uD6A8\uC131 | ||
| gb.siteName = \uC0AC\uC774\uD2B8 \uC774\uB984 | ||
| gb.siteNameDescription = \uC11C\uBC84\uC758 \uC9E6\uC740 \uC124\uBA85\uC774 \uD3EC\uD568\uB41C \uC774\uB984 | ||
| gb.excludeFromActivity = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0\uC11C \uC81C\uC678 | ||
| gb.isSparkleshared = \uC800\uC7A5\uC18C\uB294 Sparkleshare \uB428 | ||
| gb.owners = \uC624\uB108 | ||
| gb.sessionEnded = \uC138\uC158\uC774 \uC885\uB8CC\uB428 | ||
| gb.closeBrowser = \uC815\uD655\uD788 \uC138\uC158\uC744 \uC885\uB8CC\uD558\uB824\uBA74 \uBE0C\uB77C\uC6B0\uC800\uB97C \uB2EB\uC544 \uC8FC\uC138\uC694. | ||
| gb.doesNotExistInTree = {1} \uD2B8\uB9AC\uC5D0 {0} \uAC00 \uC5C6\uC74C | ||
| gb.enableIncrementalPushTags = \uC99D\uAC00\uD558\uB294 \uD478\uC2DC \uD0DC\uADF8 \uAC00\uB2A5 | ||
| gb.useIncrementalPushTagsDescription = \uD478\uC2DC\uD560 \uB54C, \uC99D\uAC00\uD558\uB294 \uB9AC\uBE44\uC804 \uBC88\uD638\uAC00 \uC790\uB3D9\uC73C\uB85C \uD0DC\uADF8\uB428 | ||
| gb.incrementalPushTagMessage = \uD478\uC2DC\uD560 \uB54C [{0}] \uBE0C\uB79C\uCE58\uC5D0 \uC790\uB3D9\uC73C\uB85C \uD0DC\uADF8\uB428 | ||
| gb.externalPermissions = {0} \uC811\uC18D \uAD8C\uD55C\uC740 \uC678\uBD80\uC5D0\uC11C \uAD00\uB9AC\uB428 | ||
| gb.viewAccess = Gitblit \uC5D0 \uC77D\uAE30 \uB610\uB294 \uC4F0\uAE30 \uAD8C\uD55C\uC774 \uC5C6\uC74C | ||
| gb.overview = \uAC1C\uC694 | ||
| gb.dashboard = \uB300\uC2DC\uBCF4\uB4DC | ||
| gb.monthlyActivity = \uC6D4\uBCC4 \uC561\uD2F0\uBE44 | ||
| gb.myProfile = \uB0B4 \uD504\uB85C\uD544 | ||
| gb.compare = \uBE44\uAD50 | ||
| gb.manual = \uC124\uBA85\uC11C | ||
| gb.from = from | ||
@@ -468,280 +469,321 @@ gb.to = to | ||
| gb.in = in | ||
| gb.moreChanges = \ubaa8\ub4e0 \ubcc0\uacbd... | ||
| gb.pushedNCommitsTo = {0} \uac1c \ucee4\ubc0b\uc774 \ud478\uc2dc\ub428 | ||
| gb.pushedOneCommitTo = 1 \uac1c \ucee4\ubc0b\uc774 \ud478\uc2dc\ub428 | ||
| gb.commitsTo = {0} \uac1c \ucee4\ubc0b | ||
| gb.oneCommitTo = 1 \uac1c \ucee4\ubc0b | ||
| gb.byNAuthors = {0} \uba85\uc758 \uc791\uc131\uc790 | ||
| gb.byOneAuthor = {0} \uc5d0 \uc758\ud574 | ||
| gb.viewComparison = {0} \ucee4\ubc0b\uc758 \ube44\uad50 \ubcf4\uae30 \u00bb | ||
| gb.nMoreCommits = {0} \uac1c \ub354 \u00bb | ||
| gb.oneMoreCommit = 1 \uac1c \ub354 \u00bb | ||
| gb.pushedNewTag = \uc0c8 \ud0dc\uadf8\uac00 \ud478\uc2dc\ub428 | ||
| gb.createdNewTag = \uc0c8 \ud0dc\uadf8\uac00 \uc0dd\uc131\ub428 | ||
| gb.deletedTag = \ud0dc\uadf8\uac00 \uc0ad\uc81c\ub428 | ||
| gb.pushedNewBranch = \uc0c8 \ube0c\ub79c\uce58\uac00 \ud478\uc2dc\ub428 | ||
| gb.createdNewBranch = \uc0c8 \ube0c\ub79c\uce58\uac00 \uc0dd\uc131\ub428 | ||
| gb.deletedBranch = \ube0c\ub79c\uce58\uac00 \uc0ad\uc81c\ub428 | ||
| gb.createdNewPullRequest = \ud480 \ub9ac\ud018\uc2a4\ud2b8\uac00 \uc0dd\uc131\ub428 | ||
| gb.mergedPullRequest = \ud480 \ub9ac\ud018\uc2a4\ud2b8\uac00 \uba38\uc9c0\ub428 | ||
| gb.moreChanges = \uBAA8\uB4E0 \uBCC0\uACBD... | ||
| gb.pushedNCommitsTo = {0} \uAC1C \uCEE4\uBC0B\uC774 \uD478\uC2DC\uB428 | ||
| gb.pushedOneCommitTo = 1 \uAC1C \uCEE4\uBC0B\uC774 \uD478\uC2DC\uB428 | ||
| gb.commitsTo = {0} \uAC1C \uCEE4\uBC0B | ||
| gb.oneCommitTo = 1 \uAC1C \uCEE4\uBC0B | ||
| gb.byNAuthors = {0} \uBA85\uC758 \uC791\uC131\uC790 | ||
| gb.byOneAuthor = {0} \uC5D0 \uC758\uD574 | ||
| gb.viewComparison = {0} \uCEE4\uBC0B\uC758 \uBE44\uAD50 \uBCF4\uAE30 \u00BB | ||
| gb.nMoreCommits = {0} \uAC1C \uB354 \u00BB | ||
| gb.oneMoreCommit = 1 \uAC1C \uB354 \u00BB | ||
| gb.pushedNewTag = \uC0C8 \uD0DC\uADF8\uAC00 \uD478\uC2DC\uB428 | ||
| gb.createdNewTag = \uC0C8 \uD0DC\uADF8\uAC00 \uC0DD\uC131\uB428 | ||
| gb.deletedTag = \uD0DC\uADF8\uAC00 \uC0AD\uC81C\uB428 | ||
| gb.pushedNewBranch = \uC0C8 \uBE0C\uB79C\uCE58\uAC00 \uD478\uC2DC\uB428 | ||
| gb.createdNewBranch = \uC0C8 \uBE0C\uB79C\uCE58\uAC00 \uC0DD\uC131\uB428 | ||
| gb.deletedBranch = \uBE0C\uB79C\uCE58\uAC00 \uC0AD\uC81C\uB428 | ||
| gb.createdNewPullRequest = \uD480 \uB9AC\uD018\uC2A4\uD2B8\uAC00 \uC0DD\uC131\uB428 | ||
| gb.mergedPullRequest = \uD480 \uB9AC\uD018\uC2A4\uD2B8\uAC00 \uBA38\uC9C0\uB428 | ||
| gb.rewind = REWIND | ||
| gb.star = \ubcc4 | ||
| gb.unstar = \ubcc4\uc81c\uac70 | ||
| gb.stargazers = \uad00\uce21\uc790 | ||
| gb.starredRepositories = \ubcc4 \ud45c\uc2dc\ub41c \uc800\uc7a5\uc18c | ||
| gb.failedToUpdateUser = \uacc4\uc815 \uc5c5\ub370\uc774\ud2b8 \uc2e4\ud328! | ||
| gb.myRepositories = \ub0b4 \uc800\uc7a5\uc18c | ||
| gb.noActivity = \uc9c0\ub09c {0} \uc77c\uac04 \uc561\ud2f0\ube44\ud2f0 \uc5c6\uc74c | ||
| gb.findSomeRepositories = \uc800\uc7a5\uc18c \ucc3e\uae30 | ||
| gb.metricAuthorExclusions = \uc791\uc131\uc790 \uba54\ud2b8\ub9ad \uc81c\uc678 | ||
| gb.myDashboard = \ub0b4 \ub300\uc2dc\ubcf4\ub4dc | ||
| gb.failedToFindAccount = ''{0}'' \uacc4\uc815 \ucc3e\uae30 \uc2e4\ud328 | ||
| gb.star = \uBCC4 | ||
| gb.unstar = \uBCC4\uC81C\uAC70 | ||
| gb.stargazers = \uAD00\uCE21\uC790 | ||
| gb.starredRepositories = \uBCC4 \uD45C\uC2DC\uB41C \uC800\uC7A5\uC18C | ||
| gb.failedToUpdateUser = \uACC4\uC815 \uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328! | ||
| gb.myRepositories = \uB0B4 \uC800\uC7A5\uC18C | ||
| gb.noActivity = \uC9C0\uB09C {0} \uC77C\uAC04 \uC561\uD2F0\uBE44\uD2F0 \uC5C6\uC74C | ||
| gb.findSomeRepositories = \uC800\uC7A5\uC18C \uCC3E\uAE30 | ||
| gb.metricAuthorExclusions = \uC791\uC131\uC790 \uBA54\uD2B8\uB9AD \uC81C\uC678 | ||
| gb.myDashboard = \uB0B4 \uB300\uC2DC\uBCF4\uB4DC | ||
| gb.failedToFindAccount = ''{0}'' \uACC4\uC815 \uCC3E\uAE30 \uC2E4\uD328 | ||
| gb.reflog = reflog | ||
| gb.active = \ud65c\uc131 | ||
| gb.starred = \ubcc4\ud45c | ||
| gb.owned = \uc18c\uc720\ud568 | ||
| gb.starredAndOwned = \ubcc4\ud45c & \uc18c\uc720\ud568 | ||
| gb.reviewPatchset = \ub9ac\ubdf0 {0} \ud328\uce58\uc14b {1} | ||
| gb.todaysActivityStats = \uc624\ub298 / {2} \uc791\uc131\uc790\uac00 {1} \ucee4\ubc0b\uc0dd\uc131 | ||
| gb.todaysActivityNone = \uc624\ub298 / \uc5c6\uc74c | ||
| gb.noActivityToday = \uc624\ub298\uc740 \uc561\ud2f0\ube44\ud2f0\uac00 \uc5c6\uc74c | ||
| gb.anonymousUser= \uc775\uba85 | ||
| gb.commitMessageRenderer = \ucee4\ubc0b \uba54\uc2dc\uc9c0 \ub79c\ub354\ub7ec | ||
| gb.diffStat = {0}\uac1c \ucd94\uac00 & {1}\uac1c \uc0ad\uc81c | ||
| gb.home = \ud648 | ||
| gb.isMirror = \ubbf8\ub7ec \uc800\uc7a5\uc18c | ||
| gb.mirrorOf = {0} \uc758 \ubbf8\ub7ec | ||
| gb.mirrorWarning = \uc774 \uc800\uc7a5\uc18c\ub294 \ubbf8\ub7ec\uc774\uace0 \ud478\uc2dc\ub97c \ubc1b\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. | ||
| gb.docsWelcome1 = \uc800\uc7a5\uc18c\ub97c \ubb38\uc11c\ud654\ud558\uae30 \uc704\ud574 \ubb38\uc11c\ub4e4\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.docsWelcome2 = \uc2dc\uc791\ud558\uae30 \uc704\ud574 README.md \ub610\ub294 HOME.md \ud30c\uc77c\uc744 \ucee4\ubc0b\ud558\uc138\uc694. | ||
| gb.createReadme = README \uc0dd\uc131 | ||
| gb.responsible = \ub2f4\ub2f9\uc790 | ||
| gb.createdThisTicket = \uc774 \ud2f0\ucf13 \uc0dd\uc131 | ||
| gb.proposedThisChange = \uc774 \ubcc0\uacbd \uc81c\uc548 | ||
| gb.uploadedPatchsetN = {0} \ud328\uce58\uc14b \uc5c5\ub85c\ub4dc | ||
| gb.uploadedPatchsetNRevisionN = \ub9ac\ube44\uc804 {1} \ud328\uce58\uc14b {0} \uc5c5\ub85c\ub4dc | ||
| gb.mergedPatchset = \ud328\uce58\uc14b \uba38\uc9c0 | ||
| gb.commented = \ucf54\uba58\ud2b8 | ||
| gb.noDescriptionGiven = \uc124\uba85 \uc5c6\uc74c | ||
| gb.toBranch = {0}\uc5d0\uac8c | ||
| gb.createdBy = \uc0dd\uc131\uc790 | ||
| gb.oneParticipant = \ucc38\uac00\uc790 {0} | ||
| gb.nParticipants = \ucc38\uac00\uc790 {0} | ||
| gb.noComments = \ucf54\uba58\ud2b8 \uc5c6\uc74c | ||
| gb.oneComment = \ucf54\uba58\ud2b8 {0} | ||
| gb.nComments = \ucf54\uba58\ud2b8 {0} | ||
| gb.oneAttachment = \ucca8\ubd80\ud30c\uc77c {0} | ||
| gb.nAttachments = \ucca8\ubd80\ud30c\uc77c {0} | ||
| gb.milestone = \ub9c8\uc77c\uc2a4\ud1a4 | ||
| gb.compareToMergeBase = \uba38\uc9c0\ubca0\uc774\uc2a4\uc640 \ube44\uad50 | ||
| gb.compareToN = {0}\uc640 \ube44\uad50 | ||
| gb.open = \uc5f4\ub9bc | ||
| gb.closed = \ub2eb\ud798 | ||
| gb.merged = \uba38\uc9c0\ud568 | ||
| gb.ticketPatchset = {0} \ud2f0\ucf13, {1} \ud328\uce58\uc14b | ||
| gb.patchsetMergeable = \uc774 \ud328\uce58\uc14b\uc740 {0}\uc5d0 \uc790\ub3d9\uc73c\ub85c \uba38\uc9c0\ub420 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.patchsetMergeableMore = \uc774 \ud328\uce58\uc14b\uc740 \ucee4\ub9e8\ub4dc \ub77c\uc778\uc5d0\uc11c {0}\uc5d0 \uba38\uc9c0\ub420 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.patchsetAlreadyMerged = \uc774 \ud328\uce58\uc14b\uc740 {0}\uc5d0 \uba38\uc9c0\ub428. | ||
| gb.patchsetNotMergeable = \uc774 \ud328\uce58\uc14b\uc740 {0}\uc5d0 \uc790\ub3d9\uc73c\ub85c \uba38\uc9c0\ub420 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. | ||
| gb.patchsetNotMergeableMore = \ucda9\ub3cc\uc744 \ud574\uacb0\ud558\uae30 \uc704\ud574 \uc774 \ud328\uce58\uc14b\uc740 \ub9ac\ubca0\uc774\uc2a4 \ud558\uac70\ub098 {0}\uc5d0 \uba38\uc9c0\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4. | ||
| gb.patchsetNotApproved = \uc774 \ud328\uce58\uc14b \ub9ac\ube44\uc804\uc740 {0}\uc5d0 \uba38\uc9c0\ub418\ub294\uac83\uc774 \uc2b9\uc778\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. | ||
| gb.patchsetNotApprovedMore = \ub9ac\ubdf0\uc5b4\uac00 \uc774 \ud328\uce58\uc14b\uc744 \uc2b9\uc778\ud574\uc57c \ud569\ub2c8\ub2e4. | ||
| gb.patchsetVetoedMore = \ub9ac\ubdf0\uc5b4\uac00 \uc774 \ud328\uce58\uc14b\uc744 \uac70\ubd80\ud558\uc600\uc2b5\ub2c8\ub2e4. | ||
| gb.write = \uc4f0\uae30 | ||
| gb.comment = \ucf54\uba58\ud2b8 | ||
| gb.preview = \ubbf8\ub9ac\ubcf4\uae30 | ||
| gb.leaveComment = \ucf54\uba58\ud2b8 \ub0a8\uae30\uae30... | ||
| gb.showHideDetails = \uc0c1\uc138 \ubcf4\uae30/\uc228\uae30\uae30 | ||
| gb.acceptNewPatchsets = \ud328\uce58\uc14b \uc2b9\uc778 | ||
| gb.acceptNewPatchsetsDescription = \uc774 \uc800\uc7a5\uc18c\uc5d0 \ud328\uce58\uc14b\uc744 \ud478\uc2dc\ud558\ub294\uac83\uc744 \uc2b9\uc778 | ||
| gb.acceptNewTickets = \uc0c8 \ud2f0\ucf13 \ud5c8\uc6a9 | ||
| gb.acceptNewTicketsDescription = \ubc84\uadf8, \uac1c\uc120, \ud0c0\uc2a4\ud06c, \ub4f1\uc758 \ud2f0\ucf13 \uc0dd\uc131 \ud5c8\uc6a9 | ||
| gb.requireApproval = \uc2b9\uc778 \ud544\uc694 | ||
| gb.requireApprovalDescription = \uba38\uc9c0\ubc84\ud2bc \ud65c\uc131\ud654 \uc804 \ud328\uce58\uc14b\uc774 \uc2b9\uc778\ub418\uc5b4\uc57c \ud568 | ||
| gb.topic = \ud1a0\ud53d | ||
| gb.proposalTickets = \ubcc0\uacbd \uc81c\uc548 | ||
| gb.bugTickets = \ubc84\uadf8 | ||
| gb.enhancementTickets = \uac1c\uc120 | ||
| gb.taskTickets = \ud0c0\uc2a4\ud06c | ||
| gb.questionTickets = \uc9c8\ubb38 | ||
| gb.requestTickets = \uac1c\uc120 & \ud0c0\uc2a4\ud06c | ||
| gb.yourCreatedTickets = \ub0b4\uac00 \uc0dd\uc131\ud568 | ||
| gb.yourWatchedTickets = \ub0b4\uac00 \uc9c0\ucf1c\ubd04 | ||
| gb.mentionsMeTickets = \ub098\ub97c \ub9e8\uc158 \uc911 | ||
| gb.updatedBy = \uc5c5\ub370\uc774\ud2b8 | ||
| gb.sort = \uc815\ub82c | ||
| gb.sortNewest = \ucd5c\uc2e0 \uc21c | ||
| gb.sortOldest = \uc624\ub798\ub41c \uc21c | ||
| gb.sortMostRecentlyUpdated = \ucd5c\uadfc \uc5c5\ub370\uc774\ud2b8 \uc21c | ||
| gb.sortLeastRecentlyUpdated = \uc624\ub798\ub41c \uc5c5\ub370\uc774\ud2b8\ub41c \uc21c | ||
| gb.sortMostComments = \ucf54\uba58\ud2b8 \ub9ce\uc740 \uc21c | ||
| gb.sortLeastComments = \ucf54\uba58\ud2b8 \uc801\uc740 \uc21c | ||
| gb.sortMostPatchsetRevisions = \ud328\uce58\uc14b \ub9ac\ube44\uc804 \ub9ce\uc740 \uc21c | ||
| gb.sortLeastPatchsetRevisions = \ud328\uce58\uc14b \ub9ac\ube44\uc804 \uc801\uc740 \uc21c | ||
| gb.sortMostVotes = \uac70\ubd80 \ub9ce\uc740 \uc21c | ||
| gb.sortLeastVotes = \uac70\ubd80 \uc801\uc740 \uc21c | ||
| gb.topicsAndLabels = \ud1a0\ud53d & \ub77c\ubca8 | ||
| gb.milestones = \ub9c8\uc77c\uc2a4\ud1a4 | ||
| gb.noMilestoneSelected = \uc120\ud0dd\ub41c \ub9c8\uc77c\uc2a4\ud1a4 \uc5c6\uc74c | ||
| gb.notSpecified = \uc9c0\uc815\ub418\uc9c0 \uc54a\uc74c | ||
| gb.active = \uD65C\uC131 | ||
| gb.starred = \uBCC4\uD45C | ||
| gb.owned = \uC18C\uC720\uD568 | ||
| gb.starredAndOwned = \uBCC4\uD45C & \uC18C\uC720\uD568 | ||
| gb.reviewPatchset = \uB9AC\uBDF0 {0} \uD328\uCE58\uC14B {1} | ||
| gb.todaysActivityStats = \uC624\uB298 / {2} \uC791\uC131\uC790\uAC00 {1} \uCEE4\uBC0B\uC0DD\uC131 | ||
| gb.todaysActivityNone = \uC624\uB298 / \uC5C6\uC74C | ||
| gb.noActivityToday = \uC624\uB298\uC740 \uC561\uD2F0\uBE44\uD2F0\uAC00 \uC5C6\uC74C | ||
| gb.anonymousUser= \uC775\uBA85 | ||
| gb.commitMessageRenderer = \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uB79C\uB354\uB7EC | ||
| gb.diffStat = {0}\uAC1C \uCD94\uAC00 & {1}\uAC1C \uC0AD\uC81C | ||
| gb.home = \uD648 | ||
| gb.isMirror = \uBBF8\uB7EC \uC800\uC7A5\uC18C | ||
| gb.mirrorOf = {0} \uC758 \uBBF8\uB7EC | ||
| gb.mirrorWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uBBF8\uB7EC\uC774\uACE0 \uD478\uC2DC\uB97C \uBC1B\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. | ||
| gb.docsWelcome1 = \uC800\uC7A5\uC18C\uB97C \uBB38\uC11C\uD654\uD558\uAE30 \uC704\uD574 \uBB38\uC11C\uB4E4\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.docsWelcome2 = \uC2DC\uC791\uD558\uAE30 \uC704\uD574 README.md \uB610\uB294 HOME.md \uD30C\uC77C\uC744 \uCEE4\uBC0B\uD558\uC138\uC694. | ||
| gb.createReadme = README \uC0DD\uC131 | ||
| gb.responsible = \uB2F4\uB2F9\uC790 | ||
| gb.createdThisTicket = \uC774 \uD2F0\uCF13 \uC0DD\uC131 | ||
| gb.proposedThisChange = \uC774 \uBCC0\uACBD \uC81C\uC548 | ||
| gb.uploadedPatchsetN = {0} \uD328\uCE58\uC14B \uC5C5\uB85C\uB4DC | ||
| gb.uploadedPatchsetNRevisionN = \uB9AC\uBE44\uC804 {1} \uD328\uCE58\uC14B {0} \uC5C5\uB85C\uB4DC | ||
| gb.mergedPatchset = \uD328\uCE58\uC14B \uBA38\uC9C0 | ||
| gb.commented = \uCF54\uBA58\uD2B8 | ||
| gb.noDescriptionGiven = \uC124\uBA85 \uC5C6\uC74C | ||
| gb.toBranch = {0}\uC5D0\uAC8C | ||
| gb.createdBy = \uC0DD\uC131\uC790 | ||
| gb.oneParticipant = \uCC38\uAC00\uC790 {0} | ||
| gb.nParticipants = \uCC38\uAC00\uC790 {0} | ||
| gb.noComments = \uCF54\uBA58\uD2B8 \uC5C6\uC74C | ||
| gb.oneComment = \uCF54\uBA58\uD2B8 {0} | ||
| gb.nComments = \uCF54\uBA58\uD2B8 {0} | ||
| gb.oneAttachment = \uCCA8\uBD80\uD30C\uC77C {0} | ||
| gb.nAttachments = \uCCA8\uBD80\uD30C\uC77C {0} | ||
| gb.milestone = \uB9C8\uC77C\uC2A4\uD1A4 | ||
| gb.compareToMergeBase = \uBA38\uC9C0\uBCA0\uC774\uC2A4\uC640 \uBE44\uAD50 | ||
| gb.compareToN = {0}\uC640 \uBE44\uAD50 | ||
| gb.open = \uC5F4\uB9BC | ||
| gb.closed = \uB2EB\uD798 | ||
| gb.merged = \uBA38\uC9C0\uD568 | ||
| gb.ticketPatchset = {0} \uD2F0\uCF13, {1} \uD328\uCE58\uC14B | ||
| gb.patchsetMergeable = \uC774 \uD328\uCE58\uC14B\uC740 {0}\uC5D0 \uC790\uB3D9\uC73C\uB85C \uBA38\uC9C0\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.patchsetMergeableMore = \uC774 \uD328\uCE58\uC14B\uC740 \uCEE4\uB9E8\uB4DC \uB77C\uC778\uC5D0\uC11C {0}\uC5D0 \uBA38\uC9C0\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.patchsetAlreadyMerged = \uC774 \uD328\uCE58\uC14B\uC740 {0}\uC5D0 \uBA38\uC9C0\uB428. | ||
| gb.patchsetNotMergeable = \uC774 \uD328\uCE58\uC14B\uC740 {0}\uC5D0 \uC790\uB3D9\uC73C\uB85C \uBA38\uC9C0\uB420 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. | ||
| gb.patchsetNotMergeableMore = \uCDA9\uB3CC\uC744 \uD574\uACB0\uD558\uAE30 \uC704\uD574 \uC774 \uD328\uCE58\uC14B\uC740 \uB9AC\uBCA0\uC774\uC2A4 \uD558\uAC70\uB098 {0}\uC5D0 \uBA38\uC9C0\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4. | ||
| gb.patchsetNotApproved = \uC774 \uD328\uCE58\uC14B \uB9AC\uBE44\uC804\uC740 {0}\uC5D0 \uBA38\uC9C0\uB418\uB294\uAC83\uC774 \uC2B9\uC778\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. | ||
| gb.patchsetNotApprovedMore = \uB9AC\uBDF0\uC5B4\uAC00 \uC774 \uD328\uCE58\uC14B\uC744 \uC2B9\uC778\uD574\uC57C \uD569\uB2C8\uB2E4. | ||
| gb.patchsetVetoedMore = \uB9AC\uBDF0\uC5B4\uAC00 \uC774 \uD328\uCE58\uC14B\uC744 \uAC70\uBD80\uD558\uC600\uC2B5\uB2C8\uB2E4. | ||
| gb.write = \uC4F0\uAE30 | ||
| gb.comment = \uCF54\uBA58\uD2B8 | ||
| gb.preview = \uBBF8\uB9AC\uBCF4\uAE30 | ||
| gb.leaveComment = \uCF54\uBA58\uD2B8 \uB0A8\uAE30\uAE30... | ||
| gb.showHideDetails = \uC0C1\uC138 \uBCF4\uAE30/\uC228\uAE30\uAE30 | ||
| gb.acceptNewPatchsets = \uD328\uCE58\uC14B \uC2B9\uC778 | ||
| gb.acceptNewPatchsetsDescription = \uC774 \uC800\uC7A5\uC18C\uC5D0 \uD328\uCE58\uC14B\uC744 \uD478\uC2DC\uD558\uB294\uAC83\uC744 \uC2B9\uC778 | ||
| gb.acceptNewTickets = \uC0C8 \uD2F0\uCF13 \uD5C8\uC6A9 | ||
| gb.acceptNewTicketsDescription = \uBC84\uADF8, \uAC1C\uC120, \uD0C0\uC2A4\uD06C, \uB4F1\uC758 \uD2F0\uCF13 \uC0DD\uC131 \uD5C8\uC6A9 | ||
| gb.requireApproval = \uC2B9\uC778 \uD544\uC694 | ||
| gb.requireApprovalDescription = \uBA38\uC9C0\uBC84\uD2BC \uD65C\uC131\uD654 \uC804 \uD328\uCE58\uC14B\uC774 \uC2B9\uC778\uB418\uC5B4\uC57C \uD568 | ||
| gb.topic = \uD1A0\uD53D | ||
| gb.proposalTickets = \uBCC0\uACBD \uC81C\uC548 | ||
| gb.bugTickets = \uBC84\uADF8 | ||
| gb.enhancementTickets = \uAC1C\uC120 | ||
| gb.taskTickets = \uD0C0\uC2A4\uD06C | ||
| gb.questionTickets = \uC9C8\uBB38 | ||
| gb.maintenanceTickets = \uC720\uC9C0\uBCF4\uC218 | ||
| gb.requestTickets = \uAC1C\uC120 & \uD0C0\uC2A4\uD06C | ||
| gb.yourCreatedTickets = \uB0B4\uAC00 \uC0DD\uC131\uD568 | ||
| gb.yourWatchedTickets = \uB0B4\uAC00 \uC9C0\uCF1C\uBD04 | ||
| gb.mentionsMeTickets = \uB098\uB97C \uB9E8\uC158 \uC911 | ||
| gb.updatedBy = \uC5C5\uB370\uC774\uD2B8 | ||
| gb.sort = \uC815\uB82C | ||
| gb.sortNewest = \uCD5C\uC2E0 \uC21C | ||
| gb.sortOldest = \uC624\uB798\uB41C \uC21C | ||
| gb.sortMostRecentlyUpdated = \uCD5C\uADFC \uC5C5\uB370\uC774\uD2B8 \uC21C | ||
| gb.sortLeastRecentlyUpdated = \uC624\uB798\uB41C \uC5C5\uB370\uC774\uD2B8\uB41C \uC21C | ||
| gb.sortMostComments = \uCF54\uBA58\uD2B8 \uB9CE\uC740 \uC21C | ||
| gb.sortLeastComments = \uCF54\uBA58\uD2B8 \uC801\uC740 \uC21C | ||
| gb.sortMostPatchsetRevisions = \uD328\uCE58\uC14B \uB9AC\uBE44\uC804 \uB9CE\uC740 \uC21C | ||
| gb.sortLeastPatchsetRevisions = \uD328\uCE58\uC14B \uB9AC\uBE44\uC804 \uC801\uC740 \uC21C | ||
| gb.sortMostVotes = \uAC70\uBD80 \uB9CE\uC740 \uC21C | ||
| gb.sortLeastVotes = \uAC70\uBD80 \uC801\uC740 \uC21C | ||
| gb.topicsAndLabels = \uD1A0\uD53D & \uB77C\uBCA8 | ||
| gb.milestones = \uB9C8\uC77C\uC2A4\uD1A4 | ||
| gb.noMilestoneSelected = \uC120\uD0DD\uB41C \uB9C8\uC77C\uC2A4\uD1A4 \uC5C6\uC74C | ||
| gb.notSpecified = \uC9C0\uC815\uB418\uC9C0 \uC54A\uC74C | ||
| gb.due = due | ||
| gb.queries = \ucffc\ub9ac | ||
| gb.searchTicketsTooltip = {0} \ud2f0\ucf13 \uac80\uc0c9 | ||
| gb.searchTickets = \ud2f0\ucf13 \uac80\uc0c9 | ||
| gb.new = \uc0c8 | ||
| gb.newTicket = \uc0c8 \ud2f0\ucf13 | ||
| gb.editTicket = \ud2f0\ucf13 \uc218\uc815 | ||
| gb.ticketsWelcome = \ud560\uc77c \ubaa9\ub85d, \ubc84\uadf8 \ud1a0\ub860\uc744 \uc815\ub9ac\ud558\uace0 \ud328\uce58\uc14b\uc73c\ub85c \ud611\uc5c5\ud558\uae30 \uc704\ud574 \ud2f0\ucf13\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.createFirstTicket = \uccab\ubc88\uc9f8 \ud2f0\ucf13\uc744 \ub9cc\ub4dc\uc138\uc694. | ||
| gb.title = \uc81c\ubaa9 | ||
| gb.changedStatus = \uc0c1\ud0dc \ubcc0\uacbd\ub428 | ||
| gb.discussion = \ud1a0\ub860 | ||
| gb.updated = \uc5c5\ub370\uc774\ud2b8\ub428 | ||
| gb.proposePatchset = \ud328\uce58\uc14b\uc758 \uc81c\uc548 | ||
| gb.proposePatchsetNote = \uc774 \ud2f0\ucf13\uc5d0 \ub300\ud55c \ud328\uce58\uc14b \uc81c\uc548\uc744 \ud658\uc601\ud569\ub2c8\ub2e4. | ||
| gb.proposeInstructions = \ub9e8 \uba3c\uc800, \ud328\uce58\uc14b\uc744 \ub9cc\ub4e4\uace0 Git\uc73c\ub85c \uc5c5\ub85c\ub4dc \ud558\uc138\uc694. Gitblit \uc774 id \ub85c \uc774 \ud2f0\ucf13\uacfc \uc5f0\uacb0\ud560 \uac83\uc785\ub2c8\ub2e4. | ||
| gb.proposeWith = \uc774\uac83\uc73c\ub85c \ud328\uce58\uc14b \uc81c\uc548 - {0} | ||
| gb.revisionHistory = \ub9ac\ube44\uc804 \ud788\uc2a4\ud1a0\ub9ac | ||
| gb.merge = \uba38\uc9c0 | ||
| gb.action = \uc561\uc158 | ||
| gb.patchset = \ud328\uce58\uc14b | ||
| gb.all = \ubaa8\ub450 | ||
| gb.mergeBase = \uba38\uc9c0 \ubca0\uc774\uc2a4 | ||
| gb.checkout = \uccb4\ud06c\uc544\uc6c3 | ||
| gb.checkoutViaCommandLine = \ucee4\ub9e8\ub4dc \ub77c\uc778\uc73c\ub85c \uccb4\ud06c\uc544\uc6c3 | ||
| gb.checkoutViaCommandLineNote = \uc774 \uc800\uc7a5\uc18c\uc758 \ud074\ub860\uc5d0\uc11c \ub85c\uceec \ubcc0\uacbd\uc0ac\ud56d\uc744 \uccb4\ud06c\uc544\uc6c3\ud558\uace0 \ud14c\uc2a4\ud2b8\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.checkoutStep1 = \ud604\uc7ac \ud328\uce58\uc14b\uc744 \uac00\uc838\uc624\uc138\uc694. - \ud504\ub85c\uc81d\ud2b8 \ub514\ub809\ud1a0\ub9ac\uc5d0\uc11c \uc774 \uc791\uc5c5\uc744 \uc2e4\ud589\ud558\uc138\uc694. | ||
| gb.checkoutStep2 = \uc0c8 \ube0c\ub79c\uce58\uc640 \ub9ac\ubdf0\uc5d0 \ud328\uce58\uc14b\uc744 \uccb4\ud06c\uc544\uc6c3 \ud558\uc138\uc694. | ||
| gb.mergingViaCommandLine = \ucee4\ub9e8\ub4dc \ub77c\uc778\uc5d0\uc11c \uba38\uc9d5 | ||
| gb.mergingViaCommandLineNote = \uba38\uc9c0 \ubc84\ud2bc \uc0ac\uc6a9\uc744 \uc6d0\ud558\uc9c0 \uc54a\uac70\ub098 \uc790\ub3d9 \uba38\uc9c0\uac00 \ub3d9\uc791\ud558\uc9c0 \ub418\uc9c0 \uc54a\ub294\ub2e4\uba74, \ucee4\ub9e8\ub4dc \ub77c\uc778\uc5d0\uc11c \uc218\ub3d9\uc73c\ub85c \uba38\uc9c0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.mergeStep1 = \ubcc0\uacbd\uc0ac\ud56d\uc744 \ub9ac\ubdf0\ud558\uae30\uc704\ud574 \uc0c8 \ube0c\ub79c\uce58\ub97c \uccb4\ud06c\uc544\uc6c3 \ud558\uc138\uc694. \u2014 \ud504\ub85c\uc81d\ud2b8 \ub514\ub809\ud1a0\ub9ac\uc5d0\uc11c \uc774 \uc791\uc5c5\uc744 \uc2e4\ud589\ud558\uc138\uc694. | ||
| gb.mergeStep2 = \uc81c\uc548\ub41c \ubcc0\uacbd\uc0ac\ud56d\uacfc \ub9ac\ubdf0\ub97c \uac00\uc838\uc624\uc138\uc694. | ||
| gb.mergeStep3 = \uc81c\uc548\ub41c \ubcc0\uacbd\uc0ac\ud56d\uc744 \uba38\uc9c0\ud558\uace0 \uc11c\ubc84\uc5d0 \uc5c5\ub370\uc774\ud2b8 \ud558\uc138\uc694. | ||
| gb.download = \ub2e4\uc6b4\ub85c\ub4dc | ||
| gb.ptDescription = Gitblit \ud328\uce58\uc14b \ub3c4\uad6c | ||
| gb.ptCheckout = \ub9ac\ubdf0 \ube0c\ub79c\uce58\uc5d0 \ud604\uc7ac \ud328\uce58\uc14b\uc744 \uac00\uc838\uc624\uace0 \uccb4\ud06c\uc544\uc6c3\ud558\uc138\uc694. | ||
| gb.ptMerge = \ub85c\uceec \ube0c\ub79c\uce58\uc5d0 \ud604\uc7ac \ud328\uce58\uc14b\uc744 \uac00\uc838\uc624\uace0 \uba38\uc9c0\ud558\uc138\uc694. | ||
| gb.ptDescription1 = Barnum \uc740 Gitblit \ud2f0\ucf13\uacfc \ud328\uce58\uc14b\uc744 \uc0ac\uc6a9\ud558\uae30 \uc704\ud55c \uad6c\ubb38\uc744 \ub2e8\uc21c\ud558\uac8c \ud574\uc904 Git \ucee4\ub9e8\ub4dc \ub77c\uc778 \ub3c4\uad6c \uc785\ub2c8\ub2e4. | ||
| gb.ptSimplifiedCollaboration = \ub2e8\uc21c\ud654\ub41c \uacf5\ub3d9\uc791\uc5c5 \uad6c\ubb38 | ||
| gb.ptSimplifiedMerge = \ub2e8\uc21c\ud654\ub41c \uba38\uc9c0 \uad6c\ubb38 | ||
| gb.ptDescription2 = Barnum \uc740 \ud30c\uc774\uc36c 3 \uc640 \ub124\uc774\ud2f0\ube0c Git \uc774 \ud544\uc694\ud569\ub2c8\ub2e4. Windows, Linux, and Mac OS X \uc5d0\uc11c \ub3d9\uc791\ud569\ub2c8\ub2e4. | ||
| gb.stepN = {0} \ub2e8\uacc4 | ||
| gb.watchers = \uc9c0\ucf1c\ubcf4\ub294 \uc774 | ||
| gb.votes = \ud22c\ud45c | ||
| gb.vote = {0} \uc5d0 \ud22c\ud45c\ud558\uae30 | ||
| gb.watch = {0} \uc9c0\ucf1c\ubcf4\uae30 | ||
| gb.removeVote = \ud22c\ud45c \uc81c\uac70 | ||
| gb.stopWatching = \uc9c0\ucf1c\ubcf4\uae30 \uc911\uc9c0 | ||
| gb.watching = \uc9c0\ucf1c\ubcf4\ub294 \uc911 | ||
| gb.comments = \ucf54\uba58\ud2b8 | ||
| gb.addComment = \ucf54\uba58\ud2b8 \ucd94\uac00 | ||
| gb.export = \ub0b4\ubcf4\ub0b4\uae30 | ||
| gb.oneCommit = 1\uac1c \ucee4\ubc0b | ||
| gb.nCommits = {0}\uac1c \ucee4\ubc0b | ||
| gb.addedOneCommit = 1\uac1c \ucee4\ubc0b \ucd94\uac00 | ||
| gb.addedNCommits = {0}\uac1c \ucee4\ubc0b \ucd94\uac00 | ||
| gb.commitsInPatchsetN = {0} \ud328\uce58\uc14b\uc758 \ucee4\ubc0b | ||
| gb.patchsetN = \ud328\uce58\uc14b {0} | ||
| gb.reviewedPatchsetRev = \ud328\uce58\uc14b {0} \ub9ac\ube44\uc804 {1}: {2} \ub9ac\ubdf0 | ||
| gb.review = \ub9ac\ubdf0 | ||
| gb.reviews = \ub9ac\ubdf0 | ||
| gb.veto = \uac70\ubd80 | ||
| gb.needsImprovement = \uac1c\uc120 \ud544\uc694 | ||
| gb.looksGood = \uc88b\uc544 \ubcf4\uc784 | ||
| gb.approve = \uc2b9\uc778 | ||
| gb.hasNotReviewed = \ub9ac\ubdf0\ub418\uc9c0 \uc54a\uc74c | ||
| gb.queries = \uCFFC\uB9AC | ||
| gb.searchTicketsTooltip = {0} \uD2F0\uCF13 \uAC80\uC0C9 | ||
| gb.searchTickets = \uD2F0\uCF13 \uAC80\uC0C9 | ||
| gb.new = \uC0C8 | ||
| gb.newTicket = \uC0C8 \uD2F0\uCF13 | ||
| gb.editTicket = \uD2F0\uCF13 \uC218\uC815 | ||
| gb.ticketsWelcome = \uD560\uC77C \uBAA9\uB85D, \uBC84\uADF8 \uD1A0\uB860\uC744 \uC815\uB9AC\uD558\uACE0 \uD328\uCE58\uC14B\uC73C\uB85C \uD611\uC5C5\uD558\uAE30 \uC704\uD574 \uD2F0\uCF13\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.createFirstTicket = \uCCAB\uBC88\uC9F8 \uD2F0\uCF13\uC744 \uB9CC\uB4DC\uC138\uC694. | ||
| gb.title = \uC81C\uBAA9 | ||
| gb.changedStatus = \uC0C1\uD0DC \uBCC0\uACBD\uB428 | ||
| gb.discussion = \uD1A0\uB860 | ||
| gb.updated = \uC5C5\uB370\uC774\uD2B8\uB428 | ||
| gb.proposePatchset = \uD328\uCE58\uC14B\uC758 \uC81C\uC548 | ||
| gb.proposePatchsetNote = \uC774 \uD2F0\uCF13\uC5D0 \uB300\uD55C \uD328\uCE58\uC14B \uC81C\uC548\uC744 \uD658\uC601\uD569\uB2C8\uB2E4. | ||
| gb.proposeInstructions = \uB9E8 \uBA3C\uC800, \uD328\uCE58\uC14B\uC744 \uB9CC\uB4E4\uACE0 Git\uC73C\uB85C \uC5C5\uB85C\uB4DC \uD558\uC138\uC694. Gitblit \uC774 id \uB85C \uC774 \uD2F0\uCF13\uACFC \uC5F0\uACB0\uD560 \uAC83\uC785\uB2C8\uB2E4. | ||
| gb.proposeWith = \uC774\uAC83\uC73C\uB85C \uD328\uCE58\uC14B \uC81C\uC548 - {0} | ||
| gb.revisionHistory = \uB9AC\uBE44\uC804 \uD788\uC2A4\uD1A0\uB9AC | ||
| gb.merge = \uBA38\uC9C0 | ||
| gb.action = \uC561\uC158 | ||
| gb.patchset = \uD328\uCE58\uC14B | ||
| gb.all = \uBAA8\uB450 | ||
| gb.mergeBase = \uBA38\uC9C0 \uBCA0\uC774\uC2A4 | ||
| gb.checkout = \uCCB4\uD06C\uC544\uC6C3 | ||
| gb.checkoutViaCommandLine = \uCEE4\uB9E8\uB4DC \uB77C\uC778\uC73C\uB85C \uCCB4\uD06C\uC544\uC6C3 | ||
| gb.checkoutViaCommandLineNote = \uC774 \uC800\uC7A5\uC18C\uC758 \uD074\uB860\uC5D0\uC11C \uB85C\uCEEC \uBCC0\uACBD\uC0AC\uD56D\uC744 \uCCB4\uD06C\uC544\uC6C3\uD558\uACE0 \uD14C\uC2A4\uD2B8\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.checkoutStep1 = \uD604\uC7AC \uD328\uCE58\uC14B\uC744 \uAC00\uC838\uC624\uC138\uC694. - \uD504\uB85C\uC81D\uD2B8 \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC774 \uC791\uC5C5\uC744 \uC2E4\uD589\uD558\uC138\uC694. | ||
| gb.checkoutStep2 = \uC0C8 \uBE0C\uB79C\uCE58\uC640 \uB9AC\uBDF0\uC5D0 \uD328\uCE58\uC14B\uC744 \uCCB4\uD06C\uC544\uC6C3 \uD558\uC138\uC694. | ||
| gb.mergingViaCommandLine = \uCEE4\uB9E8\uB4DC \uB77C\uC778\uC5D0\uC11C \uBA38\uC9D5 | ||
| gb.mergingViaCommandLineNote = \uBA38\uC9C0 \uBC84\uD2BC \uC0AC\uC6A9\uC744 \uC6D0\uD558\uC9C0 \uC54A\uAC70\uB098 \uC790\uB3D9 \uBA38\uC9C0\uAC00 \uB3D9\uC791\uD558\uC9C0 \uB418\uC9C0 \uC54A\uB294\uB2E4\uBA74, \uCEE4\uB9E8\uB4DC \uB77C\uC778\uC5D0\uC11C \uC218\uB3D9\uC73C\uB85C \uBA38\uC9C0\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.mergeStep1 = \uBCC0\uACBD\uC0AC\uD56D\uC744 \uB9AC\uBDF0\uD558\uAE30\uC704\uD574 \uC0C8 \uBE0C\uB79C\uCE58\uB97C \uCCB4\uD06C\uC544\uC6C3 \uD558\uC138\uC694. \u2014 \uD504\uB85C\uC81D\uD2B8 \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC774 \uC791\uC5C5\uC744 \uC2E4\uD589\uD558\uC138\uC694. | ||
| gb.mergeStep2 = \uC81C\uC548\uB41C \uBCC0\uACBD\uC0AC\uD56D\uACFC \uB9AC\uBDF0\uB97C \uAC00\uC838\uC624\uC138\uC694. | ||
| gb.mergeStep3 = \uC81C\uC548\uB41C \uBCC0\uACBD\uC0AC\uD56D\uC744 \uBA38\uC9C0\uD558\uACE0 \uC11C\uBC84\uC5D0 \uC5C5\uB370\uC774\uD2B8 \uD558\uC138\uC694. | ||
| gb.download = \uB2E4\uC6B4\uB85C\uB4DC | ||
| gb.ptDescription = Gitblit \uD328\uCE58\uC14B \uB3C4\uAD6C | ||
| gb.ptCheckout = \uB9AC\uBDF0 \uBE0C\uB79C\uCE58\uC5D0 \uD604\uC7AC \uD328\uCE58\uC14B\uC744 \uAC00\uC838\uC624\uACE0 \uCCB4\uD06C\uC544\uC6C3\uD558\uC138\uC694. | ||
| gb.ptMerge = \uB85C\uCEEC \uBE0C\uB79C\uCE58\uC5D0 \uD604\uC7AC \uD328\uCE58\uC14B\uC744 \uAC00\uC838\uC624\uACE0 \uBA38\uC9C0\uD558\uC138\uC694. | ||
| gb.ptDescription1 = Barnum \uC740 Gitblit \uD2F0\uCF13\uACFC \uD328\uCE58\uC14B\uC744 \uC0AC\uC6A9\uD558\uAE30 \uC704\uD55C \uAD6C\uBB38\uC744 \uB2E8\uC21C\uD558\uAC8C \uD574\uC904 Git \uCEE4\uB9E8\uB4DC \uB77C\uC778 \uB3C4\uAD6C \uC785\uB2C8\uB2E4. | ||
| gb.ptSimplifiedCollaboration = \uB2E8\uC21C\uD654\uB41C \uACF5\uB3D9\uC791\uC5C5 \uAD6C\uBB38 | ||
| gb.ptSimplifiedMerge = \uB2E8\uC21C\uD654\uB41C \uBA38\uC9C0 \uAD6C\uBB38 | ||
| gb.ptDescription2 = Barnum \uC740 \uD30C\uC774\uC36C 3 \uC640 \uB124\uC774\uD2F0\uBE0C Git \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Windows, Linux, and Mac OS X \uC5D0\uC11C \uB3D9\uC791\uD569\uB2C8\uB2E4. | ||
| gb.stepN = {0} \uB2E8\uACC4 | ||
| gb.watchers = \uC9C0\uCF1C\uBCF4\uB294 \uC774 | ||
| gb.votes = \uD22C\uD45C | ||
| gb.vote = {0} \uC5D0 \uD22C\uD45C\uD558\uAE30 | ||
| gb.watch = {0} \uC9C0\uCF1C\uBCF4\uAE30 | ||
| gb.removeVote = \uD22C\uD45C \uC81C\uAC70 | ||
| gb.stopWatching = \uC9C0\uCF1C\uBCF4\uAE30 \uC911\uC9C0 | ||
| gb.watching = \uC9C0\uCF1C\uBCF4\uB294 \uC911 | ||
| gb.comments = \uCF54\uBA58\uD2B8 | ||
| gb.addComment = \uCF54\uBA58\uD2B8 \uCD94\uAC00 | ||
| gb.export = \uB0B4\uBCF4\uB0B4\uAE30 | ||
| gb.oneCommit = 1\uAC1C \uCEE4\uBC0B | ||
| gb.nCommits = {0}\uAC1C \uCEE4\uBC0B | ||
| gb.addedOneCommit = 1\uAC1C \uCEE4\uBC0B \uCD94\uAC00 | ||
| gb.addedNCommits = {0}\uAC1C \uCEE4\uBC0B \uCD94\uAC00 | ||
| gb.commitsInPatchsetN = {0} \uD328\uCE58\uC14B\uC758 \uCEE4\uBC0B | ||
| gb.patchsetN = \uD328\uCE58\uC14B {0} | ||
| gb.reviewedPatchsetRev = \uD328\uCE58\uC14B {0} \uB9AC\uBE44\uC804 {1}: {2} \uB9AC\uBDF0 | ||
| gb.review = \uB9AC\uBDF0 | ||
| gb.reviews = \uB9AC\uBDF0 | ||
| gb.veto = \uAC70\uBD80 | ||
| gb.needsImprovement = \uAC1C\uC120 \uD544\uC694 | ||
| gb.looksGood = \uC88B\uC544 \uBCF4\uC784 | ||
| gb.approve = \uC2B9\uC778 | ||
| gb.hasNotReviewed = \uB9AC\uBDF0\uB418\uC9C0 \uC54A\uC74C | ||
| gb.about = about | ||
| gb.ticketN = \ud2f0\ucf13 #{0} | ||
| gb.disableUser = \uc0ac\uc6a9\uc790 \ube44\ud65c\uc131\ud654 | ||
| gb.disableUserDescription = \uc778\uc99d\uc5d0\uc11c \uc774 \uacc4\uc815 \ucc28\ub2e8 | ||
| gb.any = \ubaa8\ub450 | ||
| gb.milestoneProgress = {0}\uac1c \uc5f4\ub9bc, {1}\uac1c \ub2eb\ud798 | ||
| gb.nOpenTickets = {0}\uac1c \uc5f4\ub9bc | ||
| gb.nClosedTickets = {0}\uac1c \ub2eb\ud798 | ||
| gb.nTotalTickets = \ubaa8\ub450 {0}\uac1c | ||
| gb.body = \ub0b4\uc6a9 | ||
| gb.ticketN = \uD2F0\uCF13 #{0} | ||
| gb.disableUser = \uC0AC\uC6A9\uC790 \uBE44\uD65C\uC131\uD654 | ||
| gb.disableUserDescription = \uC778\uC99D\uC5D0\uC11C \uC774 \uACC4\uC815 \uCC28\uB2E8 | ||
| gb.any = \uBAA8\uB450 | ||
| gb.milestoneProgress = {0}\uAC1C \uC5F4\uB9BC, {1}\uAC1C \uB2EB\uD798 | ||
| gb.nOpenTickets = {0}\uAC1C \uC5F4\uB9BC | ||
| gb.nClosedTickets = {0}\uAC1C \uB2EB\uD798 | ||
| gb.nTotalTickets = \uBAA8\uB450 {0}\uAC1C | ||
| gb.body = \uB0B4\uC6A9 | ||
| gb.mergeSha = mergeSha | ||
| gb.mergeTo = \uba38\uc9c0\ub300\uc0c1 | ||
| gb.labels = \ub77c\ubca8 | ||
| gb.reviewers = \uac80\ud1a0\uc790 | ||
| gb.voters = \ud22c\ud45c\uc790 | ||
| gb.mentions = \ub9e8\uc158 | ||
| gb.canNotProposePatchset = \ud328\uce58\uc14b\uc744 \uc81c\uc548\ud560 \uc218 \uc5c6\uc74c | ||
| gb.repositoryIsMirror = \uc774 \uc800\uc7a5\uc18c\ub294 \uc77d\uae30\uc804\uc6a9 \ubbf8\ub7ec\uc785\ub2c8\ub2e4. | ||
| gb.repositoryIsFrozen = \uc774 \uc800\uc7a5\uc18c\ub294 \ud504\ub85c\uc98c \uc0c1\ud0dc\uc785\ub2c8\ub2e4. | ||
| gb.repositoryDoesNotAcceptPatchsets = \uc774 \uc800\uc7a5\uc18c\ub294 \ud328\uce58\uc14b\uc744 \uc218\uc6a9\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. | ||
| gb.serverDoesNotAcceptPatchsets = \uc774 \uc11c\ubc84\ub294 \ud328\uce58\uc14b\uc744 \uc218\uc6a9\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. | ||
| gb.ticketIsClosed = \uc774 \ud2f0\ucf13\uc740 \ub2eb\ud600 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.mergeToDescription = \ud2f0\ucf13 \ud328\uce58\uc14b\uc744 \uba38\uc9c0\ud560 \uae30\ubcf8 \ud1b5\ud569 \ube0c\ub79c\uce58 | ||
| gb.anonymousCanNotPropose = \uc775\uba85 \uc0ac\uc6a9\uc790\ub294 \ud328\uce58\uc14b\uc744 \uc81c\uc548\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. | ||
| gb.youDoNotHaveClonePermission = \ub2f9\uc2e0\uc740 \uc774 \uc800\uc7a5\uc18c\ub97c \ud074\ub860\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. | ||
| gb.myTickets = \ub0b4 \ud2f0\ucf13 | ||
| gb.yourAssignedTickets = \ub098\uc5d0\uac8c \ud560\ub2f9\ub41c | ||
| gb.newMilestone = \uc0c8 \ub9c8\uc77c\uc2a4\ud1a4 | ||
| gb.editMilestone = \ub9c8\uc77c\uc2a4\ud1a4 \uc218\uc815 | ||
| gb.deleteMilestone = \ub9c8\uc77c\uc2a4\ud1a4 \"{0}\"\uc744(\ub97c) \uc0ad\uc81c\ud560\uae4c\uc694? | ||
| gb.milestoneDeleteFailed = \ub9c8\uc77c\uc2a4\ud1a4 ''{0}'' \uc0ad\uc81c \uc2e4\ud328! | ||
| gb.notifyChangedOpenTickets = \uc5f0 \ud2f0\ucf13\uc758 \ubcc0\uacbd \uc54c\ub9bc \uc804\uc1a1 | ||
| gb.overdue = \uc9c0\uc5f0 | ||
| gb.openMilestones = \ub9c8\uc77c\uc2a4\ud1a4 \uc5f4\uae30 | ||
| gb.closedMilestones = \ub2eb\ud78c \ub9c8\uc77c\uc2a4\ud1a4 | ||
| gb.administration = \uad00\ub9ac | ||
| gb.plugins = \ud50c\ub7ec\uadf8\uc778 | ||
| gb.extensions = \ud655\uc7a5\uae30\ub2a5 | ||
| gb.pleaseSelectProject = \ud504\ub85c\uc81d\ud2b8\ub97c \uc120\ud0dd\ud574 \uc8fc\uc138\uc694! | ||
| gb.accessPolicy = \uc811\uadfc \uc815\ucc45 | ||
| gb.accessPolicyDescription = \uc800\uc7a5\uc18c \ubcf4\uae30\uc640 git \uad8c\ud55c\uc744 \uc81c\uc5b4\ud558\uae30 \uc704\ud574 \uc811\uadfc \uc815\ucc45\uc744 \uc120\ud0dd\ud558\uc138\uc694. | ||
| gb.anonymousPolicy = \uc775\uba85 \ubcf4\uae30, \ud074\ub860 \uadf8\ub9ac\uace0 \ud478\uc2dc | ||
| gb.anonymousPolicyDescription = \ub204\uad6c\ub098 \uc774 \uc800\uc7a5\uc18c\ub97c \ubcf4\uae30, \ud074\ub860, \uadf8\ub9ac\uace0 \ud478\uc2dc\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.authenticatedPushPolicy = \uc81c\ud55c\ub41c \ud478\uc2dc (\uc778\uc99d\ub41c) | ||
| gb.authenticatedPushPolicyDescription = \ub204\uad6c\ub098 \uc774 \uc800\uc7a5\uc18c\ub97c \ubcf4\uac70\ub098 \ud074\ub860\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubaa8\ub4e0 \uc778\uc99d\ub41c \uc720\uc800\ub294 RW+ \ud478\uc2dc \uad8c\ud55c\uc744 \uac00\uc9d1\ub2c8\ub2e4. | ||
| gb.namedPushPolicy = \uc774\ub984\uc73c\ub85c \ud478\uc2dc \uc81c\ud55c | ||
| gb.namedPushPolicyDescription = \ub204\uad6c\ub098 \uc774 \uc800\uc7a5\uc18c\ub97c \ubcf4\uac70\ub098 \ud074\ub860\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc120\ud0dd\ud55c \uc720\uc800\ub9cc \ud478\uc2dc\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.clonePolicy = \uc81c\ud55c\ub41c \ud074\ub860 & \ud478\uc2dc | ||
| gb.clonePolicyDescription = \ub204\uad6c\ub098 \uc774 \uc800\uc7a5\uc18c\ub97c \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc120\ud0dd\ud55c \uc720\uc800\ub9cc \ud074\ub860\uacfc \ud478\uc2dc\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.viewPolicy = \uc81c\ud55c\ub41c \ubcf4\uae30, \ud074\ub860 & \ud478\uc2dc | ||
| gb.viewPolicyDescription = \uc120\ud0dd\ud55c \uc720\uc800\ub9cc \uc774 \uc800\uc7a5\uc18c\uc5d0 \ub300\ud574 \ubcf4\uae30, \ud074\ub860 \uadf8\ub9ac\uace0 \ud478\uc2dc \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.initialCommit = \ucd5c\ucd08 \ucee4\ubc0b | ||
| gb.initialCommitDescription = \uc774 \uc800\uc7a5\uc18c\ub97c \uc989\uc2dc <code>git clone</code> \ud560 \uc218 \uc788\ub3c4\ub85d \ud569\ub2c8\ub2e4. \ub85c\uceec\uc5d0\uc11c <code>git init</code> \ud588\ub2e4\uba74 \uc774 \ub2e8\uacc4\ub97c \uac74\ub108\ub6f0\uc138\uc694. | ||
| gb.initWithReadme = README \ud3ec\ud568 | ||
| gb.initWithReadmeDescription = \uc800\uc7a5\uc18c\uc758 \uac04\ub2e8\ud55c README \ubb38\uc11c\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4. | ||
| gb.initWithGitignore = .gitignore \ud30c\uc77c \ud3ec\ud568 | ||
| gb.initWithGitignoreDescription = Git \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uc815\uc758\ub41c \ud328\ud134\uc5d0 \ub530\ub77c \ud30c\uc77c\uc774\ub098 \ub514\ub809\ud1a0\ub9ac\ub97c \ubb34\uc2dc\ud558\ub3c4\ub85d \uc9c0\uc815\ud55c \uc124\uc815\ud30c\uc77c\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4. | ||
| gb.pleaseSelectGitIgnore = .gitignore \ud30c\uc77c\uc744 \uc120\ud0dd\ud558\uc138\uc694. | ||
| gb.receive = \uc218\uc2e0 | ||
| gb.permissions = \uad8c\ud55c | ||
| gb.ownersDescription = \uc18c\uc720\uc790\ub294 \uc800\uc7a5\uc18c\uc758 \ubaa8\ub4e0 \uc124\uc815\uc744 \uad00\ub9ac\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uadf8\ub7ec\ub098, \uac1c\uc778 \uc800\uc7a5\uc18c\ub97c \uc81c\uc678\ud558\uace0\ub294 \uc800\uc7a5\uc18c \uc774\ub984\uc744 \ubcc0\uacbd\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. | ||
| gb.userPermissionsDescription = \uac1c\ubcc4 \uc0ac\uc6a9\uc790 \uad8c\ud55c\uc744 \uc9c0\uc815\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc774 \uc124\uc815\uc740 \ud300\uc774\ub098 \uc815\uaddc\uc2dd \uad8c\ud55c\uc744 \ubb34\uc2dc\ud569\ub2c8\ub2e4. | ||
| gb.teamPermissionsDescription = \uac1c\ubcc4 \ud300 \uad8c\ud55c\uc744 \uc9c0\uc815\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc774 \uc124\uc815\uc740 \uc815\uaddc\uc2dd \uad8c\ud55c\uc744 \ubb34\uc2dc\ud569\ub2c8\ub2e4. | ||
| gb.ticketSettings = \ud2f0\ucf13 \uc124\uc815 | ||
| gb.receiveSettings = \uc218\uc2e0 \uc124\uc815 | ||
| gb.receiveSettingsDescription = \uc218\uc2e0 \uc124\uc815\uc740 \uc800\uc7a5\uc18c\uc5d0 \ud478\uc2dc\ud558\ub294 \uac83\uc744 \uc81c\uc5b4\ud569\ub2c8\ub2e4. | ||
| gb.preReceiveDescription = Pre-receive \ud6c5\uc740 \ucee4\ubc0b\uc744 \uc218\uc2e0\ud588\uc9c0\ub9cc, refs \uac00 \uc5c5\ub370\uc774\ud2b8 \ub418\uae30 <em>\uc804</em> \uc5d0 \uc2e4\ud589\ub429\ub2c8\ub2e4.<p>\uc774\uac83\uc740 \ud478\uc2dc\ub97c \uac70\ubd80\ud558\uae30\uc5d0 \uc801\uc808\ud55c \ud6c5 \uc785\ub2c8\ub2e4.</p> | ||
| gb.postReceiveDescription = Post-receive \ud639\uc740 \ucee4\ubc0b\uc744 \uc218\uc2e0\ud558\uace0, refs \uac00 \uc5c5\ub370\uc774\ud2b8 \ub41c <em>\ud6c4</em> \uc5d0 \uc2e4\ud589\ub429\ub2c8\ub2e4.<p>\uc774\uac83\uc740 \uc54c\ub9bc, \ube4c\ub4dc \ud2b8\ub9ac\uac70 \ub4f1\uc744 \ud558\uae30\uc5d0 \uc801\uc808\ud55c \ud6c5 \uc785\ub2c8\ub2e4.</p> | ||
| gb.federationStrategyDescription = \ub2e4\ub978 Gitblit \uacfc \ud398\ub354\ub808\uc774\uc158 \ud558\ub294 \ubc29\ubc95\uc744 \uc81c\uc5b4\ud569\ub2c8\ub2e4. | ||
| gb.federationSetsDescription = \uc774 \uc800\uc7a5\uc18c\ub294 \uc120\ud0dd\ub41c \ud398\ub354\ub808\uc774\uc158 \uc14b\uc5d0 \ud3ec\ud568\ub429\ub2c8\ub2e4. | ||
| gb.miscellaneous = \uae30\ud0c0 | ||
| gb.originDescription = \uc774 \uc800\uc7a5\uc18c\uac00 \ud074\ub860\ub41c \uacf3\uc758 url | ||
| gb.mergeTo = \uBA38\uC9C0\uB300\uC0C1 | ||
| gb.mergeType = \uBA38\uC9C0\uD0C0\uC785 | ||
| gb.labels = \uB77C\uBCA8 | ||
| gb.reviewers = \uAC80\uD1A0\uC790 | ||
| gb.voters = \uD22C\uD45C\uC790 | ||
| gb.mentions = \uB9E8\uC158 | ||
| gb.canNotProposePatchset = \uD328\uCE58\uC14B\uC744 \uC81C\uC548\uD560 \uC218 \uC5C6\uC74C | ||
| gb.repositoryIsMirror = \uC774 \uC800\uC7A5\uC18C\uB294 \uC77D\uAE30\uC804\uC6A9 \uBBF8\uB7EC\uC785\uB2C8\uB2E4. | ||
| gb.repositoryIsFrozen = \uC774 \uC800\uC7A5\uC18C\uB294 \uD504\uB85C\uC98C \uC0C1\uD0DC\uC785\uB2C8\uB2E4. | ||
| gb.repositoryDoesNotAcceptPatchsets = \uC774 \uC800\uC7A5\uC18C\uB294 \uD328\uCE58\uC14B\uC744 \uC218\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. | ||
| gb.serverDoesNotAcceptPatchsets = \uC774 \uC11C\uBC84\uB294 \uD328\uCE58\uC14B\uC744 \uC218\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. | ||
| gb.ticketIsClosed = \uC774 \uD2F0\uCF13\uC740 \uB2EB\uD600 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.mergeToDescription = \uD2F0\uCF13 \uD328\uCE58\uC14B\uC744 \uBA38\uC9C0\uD560 \uAE30\uBCF8 \uD1B5\uD569 \uBE0C\uB79C\uCE58 | ||
| gb.mergeTypeDescription = fast-forward \uB9CC \uBA38\uC9C0, \uD544\uC694\uD558\uBA74, \uB610\uB294 \uD56D\uC0C1 \uD1B5\uD569\uBE0C\uB79C\uCE58\uC5D0 \uCEE4\uBC0B \uBA38\uC9C0 | ||
| gb.anonymousCanNotPropose = \uC775\uBA85 \uC0AC\uC6A9\uC790\uB294 \uD328\uCE58\uC14B\uC744 \uC81C\uC548\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. | ||
| gb.youDoNotHaveClonePermission = \uB2F9\uC2E0\uC740 \uC774 \uC800\uC7A5\uC18C\uB97C \uD074\uB860\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. | ||
| gb.myTickets = \uB0B4 \uD2F0\uCF13 | ||
| gb.yourAssignedTickets = \uB098\uC5D0\uAC8C \uD560\uB2F9\uB41C | ||
| gb.newMilestone = \uC0C8 \uB9C8\uC77C\uC2A4\uD1A4 | ||
| gb.editMilestone = \uB9C8\uC77C\uC2A4\uD1A4 \uC218\uC815 | ||
| gb.deleteMilestone = \uB9C8\uC77C\uC2A4\uD1A4 \"{0}\"\uC744(\uB97C) \uC0AD\uC81C\uD560\uAE4C\uC694? | ||
| gb.milestoneDeleteFailed = \uB9C8\uC77C\uC2A4\uD1A4 ''{0}'' \uC0AD\uC81C \uC2E4\uD328! | ||
| gb.notifyChangedOpenTickets = \uC5F0 \uD2F0\uCF13\uC758 \uBCC0\uACBD \uC54C\uB9BC \uC804\uC1A1 | ||
| gb.overdue = \uC9C0\uC5F0 | ||
| gb.openMilestones = \uB9C8\uC77C\uC2A4\uD1A4 \uC5F4\uAE30 | ||
| gb.closedMilestones = \uB2EB\uD78C \uB9C8\uC77C\uC2A4\uD1A4 | ||
| gb.administration = \uAD00\uB9AC | ||
| gb.plugins = \uD50C\uB7EC\uADF8\uC778 | ||
| gb.extensions = \uD655\uC7A5\uAE30\uB2A5 | ||
| gb.pleaseSelectProject = \uD504\uB85C\uC81D\uD2B8\uB97C \uC120\uD0DD\uD574 \uC8FC\uC138\uC694! | ||
| gb.accessPolicy = \uC811\uADFC \uC815\uCC45 | ||
| gb.accessPolicyDescription = \uC800\uC7A5\uC18C \uBCF4\uAE30\uC640 git \uAD8C\uD55C\uC744 \uC81C\uC5B4\uD558\uAE30 \uC704\uD574 \uC811\uADFC \uC815\uCC45\uC744 \uC120\uD0DD\uD558\uC138\uC694. | ||
| gb.anonymousPolicy = \uC775\uBA85 \uBCF4\uAE30, \uD074\uB860 \uADF8\uB9AC\uACE0 \uD478\uC2DC | ||
| gb.anonymousPolicyDescription = \uB204\uAD6C\uB098 \uC774 \uC800\uC7A5\uC18C\uB97C \uBCF4\uAE30, \uD074\uB860, \uADF8\uB9AC\uACE0 \uD478\uC2DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.authenticatedPushPolicy = \uC81C\uD55C\uB41C \uD478\uC2DC (\uC778\uC99D\uB41C) | ||
| gb.authenticatedPushPolicyDescription = \uB204\uAD6C\uB098 \uC774 \uC800\uC7A5\uC18C\uB97C \uBCF4\uAC70\uB098 \uD074\uB860\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uBAA8\uB4E0 \uC778\uC99D\uB41C \uC720\uC800\uB294 RW+ \uD478\uC2DC \uAD8C\uD55C\uC744 \uAC00\uC9D1\uB2C8\uB2E4. | ||
| gb.namedPushPolicy = \uC774\uB984\uC73C\uB85C \uD478\uC2DC \uC81C\uD55C | ||
| gb.namedPushPolicyDescription = \uB204\uAD6C\uB098 \uC774 \uC800\uC7A5\uC18C\uB97C \uBCF4\uAC70\uB098 \uD074\uB860\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC120\uD0DD\uD55C \uC720\uC800\uB9CC \uD478\uC2DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.clonePolicy = \uC81C\uD55C\uB41C \uD074\uB860 & \uD478\uC2DC | ||
| gb.clonePolicyDescription = \uB204\uAD6C\uB098 \uC774 \uC800\uC7A5\uC18C\uB97C \uBCFC \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC120\uD0DD\uD55C \uC720\uC800\uB9CC \uD074\uB860\uACFC \uD478\uC2DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.viewPolicy = \uC81C\uD55C\uB41C \uBCF4\uAE30, \uD074\uB860 & \uD478\uC2DC | ||
| gb.viewPolicyDescription = \uC120\uD0DD\uD55C \uC720\uC800\uB9CC \uC774 \uC800\uC7A5\uC18C\uC5D0 \uB300\uD574 \uBCF4\uAE30, \uD074\uB860 \uADF8\uB9AC\uACE0 \uD478\uC2DC \uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.initialCommit = \uCD5C\uCD08 \uCEE4\uBC0B | ||
| gb.initialCommitDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uC989\uC2DC <code>git clone</code> \uD560 \uC218 \uC788\uB3C4\uB85D \uD569\uB2C8\uB2E4. \uB85C\uCEEC\uC5D0\uC11C <code>git init</code> \uD588\uB2E4\uBA74 \uC774 \uB2E8\uACC4\uB97C \uAC74\uB108\uB6F0\uC138\uC694. | ||
| gb.initWithReadme = README \uD3EC\uD568 | ||
| gb.initWithReadmeDescription = \uC800\uC7A5\uC18C\uC758 \uAC04\uB2E8\uD55C README \uBB38\uC11C\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. | ||
| gb.initWithGitignore = .gitignore \uD30C\uC77C \uD3EC\uD568 | ||
| gb.initWithGitignoreDescription = Git \uD074\uB77C\uC774\uC5B8\uD2B8\uAC00 \uC815\uC758\uB41C \uD328\uD134\uC5D0 \uB530\uB77C \uD30C\uC77C\uC774\uB098 \uB514\uB809\uD1A0\uB9AC\uB97C \uBB34\uC2DC\uD558\uB3C4\uB85D \uC9C0\uC815\uD55C \uC124\uC815\uD30C\uC77C\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4. | ||
| gb.pleaseSelectGitIgnore = .gitignore \uD30C\uC77C\uC744 \uC120\uD0DD\uD558\uC138\uC694. | ||
| gb.receive = \uC218\uC2E0 | ||
| gb.permissions = \uAD8C\uD55C | ||
| gb.ownersDescription = \uC18C\uC720\uC790\uB294 \uC800\uC7A5\uC18C\uC758 \uBAA8\uB4E0 \uC124\uC815\uC744 \uAD00\uB9AC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uADF8\uB7EC\uB098, \uAC1C\uC778 \uC800\uC7A5\uC18C\uB97C \uC81C\uC678\uD558\uACE0\uB294 \uC800\uC7A5\uC18C \uC774\uB984\uC744 \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. | ||
| gb.userPermissionsDescription = \uAC1C\uBCC4 \uC0AC\uC6A9\uC790 \uAD8C\uD55C\uC744 \uC9C0\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uC124\uC815\uC740 \uD300\uC774\uB098 \uC815\uADDC\uC2DD \uAD8C\uD55C\uC744 \uBB34\uC2DC\uD569\uB2C8\uB2E4. | ||
| gb.teamPermissionsDescription = \uAC1C\uBCC4 \uD300 \uAD8C\uD55C\uC744 \uC9C0\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uC124\uC815\uC740 \uC815\uADDC\uC2DD \uAD8C\uD55C\uC744 \uBB34\uC2DC\uD569\uB2C8\uB2E4. | ||
| gb.ticketSettings = \uD2F0\uCF13 \uC124\uC815 | ||
| gb.receiveSettings = \uC218\uC2E0 \uC124\uC815 | ||
| gb.receiveSettingsDescription = \uC218\uC2E0 \uC124\uC815\uC740 \uC800\uC7A5\uC18C\uC5D0 \uD478\uC2DC\uD558\uB294 \uAC83\uC744 \uC81C\uC5B4\uD569\uB2C8\uB2E4. | ||
| gb.preReceiveDescription = Pre-receive \uD6C5\uC740 \uCEE4\uBC0B\uC744 \uC218\uC2E0\uD588\uC9C0\uB9CC, refs \uAC00 \uC5C5\uB370\uC774\uD2B8 \uB418\uAE30 <em>\uC804</em> \uC5D0 \uC2E4\uD589\uB429\uB2C8\uB2E4.<p>\uC774\uAC83\uC740 \uD478\uC2DC\uB97C \uAC70\uBD80\uD558\uAE30\uC5D0 \uC801\uC808\uD55C \uD6C5 \uC785\uB2C8\uB2E4.</p> | ||
| gb.postReceiveDescription = Post-receive \uD639\uC740 \uCEE4\uBC0B\uC744 \uC218\uC2E0\uD558\uACE0, refs \uAC00 \uC5C5\uB370\uC774\uD2B8 \uB41C <em>\uD6C4</em> \uC5D0 \uC2E4\uD589\uB429\uB2C8\uB2E4.<p>\uC774\uAC83\uC740 \uC54C\uB9BC, \uBE4C\uB4DC \uD2B8\uB9AC\uAC70 \uB4F1\uC744 \uD558\uAE30\uC5D0 \uC801\uC808\uD55C \uD6C5 \uC785\uB2C8\uB2E4.</p> | ||
| gb.federationStrategyDescription = \uB2E4\uB978 Gitblit \uACFC \uD398\uB354\uB808\uC774\uC158 \uD558\uB294 \uBC29\uBC95\uC744 \uC81C\uC5B4\uD569\uB2C8\uB2E4. | ||
| gb.federationSetsDescription = \uC774 \uC800\uC7A5\uC18C\uB294 \uC120\uD0DD\uB41C \uD398\uB354\uB808\uC774\uC158 \uC14B\uC5D0 \uD3EC\uD568\uB429\uB2C8\uB2E4. | ||
| gb.miscellaneous = \uAE30\uD0C0 | ||
| gb.originDescription = \uC774 \uC800\uC7A5\uC18C\uAC00 \uD074\uB860\uB41C \uACF3\uC758 url | ||
| gb.gc = GC | ||
| gb.garbageCollection = \uac00\ube44\uc9c0 \uceec\ub809\uc158 | ||
| gb.garbageCollectionDescription = \uac00\ube44\uc9c0 \uceec\ub809\ud130\ub294 \ud074\ub77c\uc774\uc5b8\ud2b8\uc5d0\uc11c \ud478\uc2dc\ud55c \ub290\uc2a8\ud55c \uc624\ube0c\uc81d\ud2b8\ub97c \ud328\ud0b9\ud558\uace0, \uc800\uc7a5\uc18c\uc5d0\uc11c \ucc38\uc870\ud558\uc9c0 \uc54a\ub294 \uc624\ube0c\uc81d\ud2b8\ub97c \uc0ad\uc81c\ud569\ub2c8\ub2e4. | ||
| gb.commitMessageRendererDescription = \ucee4\ubc0b \uba54\uc2dc\uc9c0\ub294 \ud3c9\ubb38 \ub610\ub294 \ub9c8\ud06c\uc5c5\uc73c\ub85c \ub80c\ub354\ub9c1\ud558\uc5ec \ud45c\uc2dc\ub420 \uc218 \uc788\uc2b5\ub2c8\ub2e4. | ||
| gb.preferences = \uc124\uc815 | ||
| gb.accountPreferences = \uacc4\uc815 \uc124\uc815 | ||
| gb.accountPreferencesDescription = \uacc4\uc815 \uc124\uc815\uc744 \uc9c0\uc815\ud569\ub2c8\ub2e4. | ||
| gb.languagePreference = \uc5b8\uc5b4 \uc124\uc815 | ||
| gb.languagePreferenceDescription = \uc120\ud638\ud558\ub294 \uc5b8\uc5b4\ub97c \uc120\ud0dd\ud558\uc138\uc694. | ||
| gb.emailMeOnMyTicketChanges = \ub0b4 \ud2f0\ucf13\uc774 \ubcc0\uacbd\ub418\uba74 \uc774\uba54\uc77c\ub85c \uc54c\ub9bc | ||
| gb.emailMeOnMyTicketChangesDescription = \ub0b4\uac00 \ub9cc\ub4e0 \ud2f0\ucf13\uc758 \ubcc0\uacbd\ub418\uba74 \ubcc0\uacbd\uc0ac\ud56d\uc744 \ub098\uc758 \uc774\uba54\uc77c\ub85c \uc54c\ub824\uc90c | ||
| gb.displayNameDescription = \ud45c\uc2dc\ub420 \uc774\ub984 | ||
| gb.emailAddressDescription = \uc54c\ub9bc\uc744 \ubc1b\uae30\uc704\ud55c \uc8fc \uc774\uba54\uc77c | ||
| gb.sshKeys = SSH \ud0a4 | ||
| gb.sshKeysDescription = SSH \uacf5\uac1c\ud0a4 \uc778\uc99d\uc740 \ud328\uc2a4\uc6cc\ub4dc \uc778\uc99d\uc744 \ub300\uccb4\ud558\ub294 \uc548\uc804\ud55c \ub300\uc548\uc785\ub2c8\ub2e4. | ||
| gb.addSshKey = SSH \ud0a4 \ucd94\uac00 | ||
| gb.key = \ud0a4 | ||
| gb.comment = \uc124\uba85 | ||
| gb.sshKeyCommentDescription = \uc0ac\uc6a9\uc790 \uc120\ud0dd\uc778 \uc124\uba85\uc744 \ucd94\uac00\ud558\uc138\uc694. \ube44\uc6cc \ub450\uba74 \ud0a4 \ub370\uc774\ud130\uc5d0\uc11c \ucd94\ucd9c\ud558\uc5ec \ucc44\uc6cc\uc9d1\ub2c8\ub2e4. | ||
| gb.permission = \uad8c\ud55c | ||
| gb.sshKeyPermissionDescription = SSH \ud0a4\uc758 \uc811\uc18d \uad8c\ud55c\uc744 \uc9c0\uc815\ud558\uc138\uc694. | ||
| gb.transportPreference = \uc804\uc1a1 \uc124\uc815 | ||
| gb.transportPreferenceDescription = \ud074\ub860\uc2dc \uc0ac\uc6a9\ud560 \uc124\uc815\uc744 \uc9c0\uc815\ud558\uc138\uc694. | ||
| gb.garbageCollection = \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158 | ||
| gb.garbageCollectionDescription = \uAC00\uBE44\uC9C0 \uCEEC\uB809\uD130\uB294 \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C \uD478\uC2DC\uD55C \uB290\uC2A8\uD55C \uC624\uBE0C\uC81D\uD2B8\uB97C \uD328\uD0B9\uD558\uACE0, \uC800\uC7A5\uC18C\uC5D0\uC11C \uCC38\uC870\uD558\uC9C0 \uC54A\uB294 \uC624\uBE0C\uC81D\uD2B8\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4. | ||
| gb.commitMessageRendererDescription = \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD3C9\uBB38 \uB610\uB294 \uB9C8\uD06C\uC5C5\uC73C\uB85C \uB80C\uB354\uB9C1\uD558\uC5EC \uD45C\uC2DC\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | ||
| gb.preferences = \uC124\uC815 | ||
| gb.accountPreferences = \uACC4\uC815 \uC124\uC815 | ||
| gb.accountPreferencesDescription = \uACC4\uC815 \uC124\uC815\uC744 \uC9C0\uC815\uD569\uB2C8\uB2E4. | ||
| gb.languagePreference = \uC5B8\uC5B4 \uC124\uC815 | ||
| gb.languagePreferenceDescription = \uC120\uD638\uD558\uB294 \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694. | ||
| gb.emailMeOnMyTicketChanges = \uB0B4 \uD2F0\uCF13\uC774 \uBCC0\uACBD\uB418\uBA74 \uC774\uBA54\uC77C\uB85C \uC54C\uB9BC | ||
| gb.emailMeOnMyTicketChangesDescription = \uB0B4\uAC00 \uB9CC\uB4E0 \uD2F0\uCF13\uC758 \uBCC0\uACBD\uB418\uBA74 \uBCC0\uACBD\uC0AC\uD56D\uC744 \uB098\uC758 \uC774\uBA54\uC77C\uB85C \uC54C\uB824\uC90C | ||
| gb.displayNameDescription = \uD45C\uC2DC\uB420 \uC774\uB984 | ||
| gb.emailAddressDescription = \uC54C\uB9BC\uC744 \uBC1B\uAE30\uC704\uD55C \uC8FC \uC774\uBA54\uC77C | ||
| gb.sshKeys = SSH \uD0A4 | ||
| gb.sshKeysDescription = SSH \uACF5\uAC1C\uD0A4 \uC778\uC99D\uC740 \uD328\uC2A4\uC6CC\uB4DC \uC778\uC99D\uC744 \uB300\uCCB4\uD558\uB294 \uC548\uC804\uD55C \uB300\uC548\uC785\uB2C8\uB2E4. | ||
| gb.addSshKey = SSH \uD0A4 \uCD94\uAC00 | ||
| gb.key = \uD0A4 | ||
| gb.comment = \uC124\uBA85 | ||
| gb.sshKeyCommentDescription = \uC0AC\uC6A9\uC790 \uC120\uD0DD\uC778 \uC124\uBA85\uC744 \uCD94\uAC00\uD558\uC138\uC694. \uBE44\uC6CC \uB450\uBA74 \uD0A4 \uB370\uC774\uD130\uC5D0\uC11C \uCD94\uCD9C\uD558\uC5EC \uCC44\uC6CC\uC9D1\uB2C8\uB2E4. | ||
| gb.permission = \uAD8C\uD55C | ||
| gb.sshKeyPermissionDescription = SSH \uD0A4\uC758 \uC811\uC18D \uAD8C\uD55C\uC744 \uC9C0\uC815\uD558\uC138\uC694. | ||
| gb.transportPreference = \uC804\uC1A1 \uC124\uC815 | ||
| gb.transportPreferenceDescription = \uD074\uB860\uC2DC \uC0AC\uC6A9\uD560 \uC124\uC815\uC744 \uC9C0\uC815\uD558\uC138\uC694. | ||
| gb.priority = \uC6B0\uC120\uC21C\uC704 | ||
| gb.severity = \uC911\uC694\uB3C4 | ||
| gb.sortHighestPriority = \uCD5C\uACE0 \uC6B0\uC120\uC21C\uC704 | ||
| gb.sortLowestPriority = \uCD5C\uC800 \uC6B0\uC120\uC21C\uC704 | ||
| gb.sortHighestSeverity = \uCD5C\uACE0 \uC911\uC694\uB3C4 | ||
| gb.sortLowestSeverity = \uCD5C\uC800 \uC911\uC694\uB3C4 | ||
| gb.missingIntegrationBranchMore = \uD1B5\uD569\uB300\uC0C1 \uBE0C\uB79C\uCE58\uAC00 \uC800\uC7A5\uC18C\uC5D0 \uC5C6\uC5B4\uC694! | ||
| gb.diffDeletedFileSkipped = (\uC0AD\uC81C\uB428) | ||
| gb.diffFileDiffTooLarge = \uBE44\uAD50\uD558\uAE30\uC5D0 \uB108\uBB34 \uD07C | ||
| gb.diffNewFile = \uC0C8 \uD30C\uC77C | ||
| gb.diffDeletedFile = \uD30C\uC77C\uC774 \uC0AD\uC81C\uB428 | ||
| gb.diffRenamedFile = {0} \uC5D0\uC11C \uC774\uB984\uC774 \uBCC0\uACBD\uB428 | ||
| gb.diffCopiedFile = {0} \uC5D0\uC11C \uBCF5\uC0AC\uB428 | ||
| gb.diffTruncated = \uC704 \uD30C\uC77C\uC774\uD6C4 \uCC28\uC774 \uC81C\uAC70\uB428 | ||
| gb.opacityAdjust = \uBD88\uD22C\uBA85\uB3C4 \uC870\uC815 | ||
| gb.blinkComparator = \uC810\uBA78 \uBE44\uAD50\uAE30 | ||
| gb.imgdiffSubtract = Subtract (black = identical) | ||
| gb.deleteRepositoryHeader = \uC800\uC7A5\uC18C \uC0AD\uC81C | ||
| gb.deleteRepositoryDescription = \uC0AD\uC81C\uB41C \uC800\uC7A5\uC18C\uB294 \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. | ||
| gb.show_whitespace = \uACF5\uBC31 \uBCF4\uAE30 | ||
| gb.ignore_whitespace = \uACF5\uBC31 \uBB34\uC2DC | ||
| gb.allRepositories = \uBAA8\uB4E0 \uC800\uC7A5\uC18C | ||
| gb.oid = \uC624\uBE0C\uC81D\uD2B8 id | ||
| gb.filestore = \uD30C\uC77C\uC2A4\uD1A0\uC5B4 | ||
| gb.filestoreStats = \uD30C\uC77C\uC2A4\uD1A0\uC5B4\uC5D0 {1} \uC6A9\uB7C9\uC73C\uB85C {0} \uAC1C \uD30C\uC77C\uC774 \uC788\uC74C. ({2} \uB0A8\uC74C) | ||
| gb.statusChangedOn = \uC0C1\uD0DC \uBCC0\uACBD\uC77C | ||
| gb.statusChangedBy = \uC0C1\uD0DC \uBCC0\uACBD\uC790 | ||
| gb.filestoreHelp = \uD30C\uC77C\uC2A4\uD1A0\uC5B4 \uC0AC\uC6A9\uBC95? | ||
| gb.editFile = \uD30C\uC77C \uC218\uC815 | ||
| gb.continueEditing = \uACC4\uC18D \uC218\uC815 | ||
| gb.commitChanges = \uCEE4\uBC0B \uBCC0\uD654 | ||
| gb.fileNotMergeable = {0} \uC744(\uB97C) \uCEE4\uBC0B\uD560 \uC218 \uC5C6\uC74C. \uC774 \uD30C\uC77C\uC740 \uC790\uB3D9\uC73C\uB85C \uBA38\uC9C0 \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. | ||
| gb.fileCommitted = {0} \uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uCEE4\uBC0B\uB418\uC5C8\uC5B4\uC694. | ||
| gb.deletePatchset = {0} \uD328\uCE58\uC14B \uC0AD\uC81C | ||
| gb.deletePatchsetSuccess = {0} \uD328\uCE58\uC14B\uC774 \uC0AD\uC81C\uB418\uC5C8\uC5B4\uC694. | ||
| gb.deletePatchsetFailure = {0} \uD328\uCE58\uC14B \uC0AD\uC81C \uC624\uB958. | ||
| gb.referencedByCommit = \uCEE4\uBC0B\uC5D0 \uCC38\uC870\uB428. | ||
| gb.referencedByTicket = \uD2F0\uCF13\uC5D0 \uCC38\uC870\uB428. |
@@ -221,3 +221,4 @@ gb.repository = repositorie | ||
| gb.query = query | ||
| gb.queryHelp = Standaard query syntax wordt ondersteund.<p/><p/>Zie aub <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> voor informatie. | ||
| gb.queryHelp = Standaard query syntax wordt ondersteund.<p/><p/>Zie aub ${querySyntax} voor informatie. | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = resultaten {0} - {1} ({2} hits) | ||
@@ -480,3 +481,3 @@ gb.noHits = geen hits | ||
| gb.deletedTag = tag verwijderd | ||
| gb.pushedNewBranch = push neuwe branch | ||
| gb.pushedNewBranch = push nieuwe branch | ||
| gb.createdNewBranch = nieuwe branch gemaakt | ||
@@ -483,0 +484,0 @@ gb.deletedBranch = branch verwijderd |
@@ -221,3 +221,4 @@ gb.repository = repository | ||
| gb.query = sp\u00F8rring | ||
| gb.queryHelp = Standard lucene sp\u00F8rringssyntaks st\u00F8ttes.<p/><p/>Se <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> for mer info. | ||
| gb.queryHelp = Standard lucene sp\u00F8rringssyntaks st\u00F8ttes.<p/><p/>Se ${querySyntax} for mer info. | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = resultater {0} - {1} ({2} treff) | ||
@@ -224,0 +225,0 @@ gb.noHits = ingen treff |
@@ -220,3 +220,4 @@ gb.repository = Repozytorium | ||
| gb.query = Szukaj | ||
| gb.queryHelp = Standardowa sk\u0142adnia wyszukiwa\u0144 jest wspierana.<p/><p/>Na stronie <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> dost\u0119pne s\u0105 dalsze szczeg\u00F3\u0142y. | ||
| gb.queryHelp = Standardowa sk\u0142adnia wyszukiwa\u0144 jest wspierana.<p/><p/>Na stronie ${querySyntax} dost\u0119pne s\u0105 dalsze szczeg\u00F3\u0142y. | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = Wyniki {0} - {1} ({2} wynik\u00F3w) | ||
@@ -223,0 +224,0 @@ gb.noHits = Brak wynik\u00F3w |
@@ -220,3 +220,4 @@ gb.repository = reposit�rio | ||
| gb.query = query | ||
| gb.queryHelp = Standard query syntax � suportada.<p/><p/>Por favor veja <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> para mais detalhes. | ||
| gb.queryHelp = Standard query syntax � suportada.<p/><p/>Por favor veja ${querySyntax} para mais detalhes. | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = resultados {0} - {1} ({2} hits) | ||
@@ -223,0 +224,0 @@ gb.noHits = sem hits |
@@ -221,3 +221,4 @@ gb.repository = \u7248\u672c\u5e93 | ||
| gb.query = \u67e5\u8be2 | ||
| gb.queryHelp = \u652f\u6301\u6807\u51c6\u67e5\u8be2\u683c\u5f0f.<p/><p/>\u8bf7\u67e5\u770b <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene \u67e5\u8be2\u5904\u7406\u5668\u683c\u5f0f</a> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u5185\u5bb9\u3002 | ||
| gb.queryHelp = \u652f\u6301\u6807\u51c6\u67e5\u8be2\u683c\u5f0f.<p/><p/>\u8bf7\u67e5\u770b ${querySyntax} \u4ee5\u83b7\u53d6\u8be6\u7ec6\u5185\u5bb9\u3002 | ||
| gb.querySyntax = Lucene \u67e5\u8be2\u5904\u7406\u5668\u683c\u5f0f | ||
| gb.queryResults = \u7ed3\u679c {0} - {1} ({2} \u6b21\u547d\u4e2d) | ||
@@ -224,0 +225,0 @@ gb.noHits = \u672a\u547d\u4e2d |
| #! | ||
| #! 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.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.all = \u5168\u90e8 | ||
| 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.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.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.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.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.users = \u4f7f\u7528\u8005 | ||
| gb.federation = federation | ||
| gb.error = \u932f\u8aa4 | ||
| gb.refresh = \u91cd\u6574 | ||
| gb.browse = \u700f\u89bd | ||
| 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.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 ${querySyntax} | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| 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.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.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.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.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.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.confirmPassword = \u78ba\u8a8d\u5bc6\u78bc | ||
| gb.none = \u7121 | ||
| gb.line = \u884c | ||
| 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.empty = \u7a7a\u7684 | ||
| gb.inherited = \u7e7c\u627f | ||
| 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.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.deletion = \u522a\u9664 | ||
| gb.description = \u6982\u8ff0 | ||
| 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.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.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.error = \u932f\u8aa4 | ||
| gb.errorAdministrationDisabled = \u7ba1\u7406\u6b0a\u9650\u5df2\u53d6\u6d88 | ||
| 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.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.forks = \u5206\u652f(forks) | ||
| gb.forkRepository = \u7248\u672c\u5eab{0}\u5efa\u7acb\u5206\u652f(fork)? | ||
| gb.forks = \u5206\u652f(forks) | ||
| 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 | ||
@@ -271,503 +370,417 @@ gb.gcPeriodDescription = \u56de\u6536\u9031\u671f | ||
| 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.noMaximum = \u7121\u6700\u5927\u503c | ||
| gb.attributes = \u5c6c\u6027 | ||
| gb.serveCertificate = \u555f\u7528\u4f7f\u7528\u6b64\u8b49\u66f8\u7684https\u529f\u80fd | ||
| 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.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.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.unstar = \u53d6\u6d88 | ||
| 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.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.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.noComments = \u6c92\u6709\u5099\u8a3b | ||
| 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.mergedPatchset = \u5c07\u88dc\u4e01\u5408\u4f75 | ||
| gb.commented = \u5df2\u8a3b\u89e3 | ||
| 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.toBranch = \u5230\u5206\u652f {0} | ||
| gb.createdBy = \u5efa\u7acb\u8005 | ||
| gb.oneParticipant = {0}\u53c3\u8207 | ||
| 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.noComments = \u6c92\u6709\u5099\u8a3b | ||
| 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.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.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.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.patchsetN = \u88dc\u4e01{0} | ||
| 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.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.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.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.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.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.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\u55ae\u63d0\u4f9b\u88dc\u4e01 | ||
| gb.proposeWith = propose a patchset with {0} | ||
| 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.ptDescription = the Gitblit patchset tool | ||
| 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.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.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.watch = \u76e3\u770b{0} | ||
| 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.stopWatching = \u505c\u6b62\u8ffd\u8e64(watching) | ||
| gb.watching = \u76e3\u770b\u4e2d | ||
| 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.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.serveCertificate = \u555f\u7528\u4f7f\u7528\u6b64\u8b49\u66f8\u7684https\u529f\u80fd | ||
| 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.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.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.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.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.sortHighestPriority = \u6700\u9ad8 | ||
| 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.sortLowestPriority = \u6700\u4f4e | ||
| 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.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.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.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.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.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.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. | ||
| gb.emailClientCertificateSubject = \u4F3A\u670D\u5668 {0} \u9023\u7DDA\u6191\u8B49 |
@@ -221,3 +221,4 @@ gb.repository = repository | ||
| gb.query = query | ||
| gb.queryHelp = Standard query syntax is supported.<p/><p/>Please see <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> for details. | ||
| gb.queryHelp = Standard query syntax is supported.<p/><p/>Please see ${querySyntax} for details. | ||
| gb.querySyntax = Lucene Query Parser Syntax | ||
| gb.queryResults = results {0} - {1} ({2} hits) | ||
@@ -664,2 +665,3 @@ gb.noHits = no hits | ||
| gb.mergeTo = merge to | ||
| gb.mergeType = merge type | ||
| gb.labels = labels | ||
@@ -676,2 +678,3 @@ gb.reviewers = reviewers | ||
| 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. | ||
@@ -787,1 +790,2 @@ gb.youDoNotHaveClonePermission = You are not permitted to clone this repository. | ||
| gb.referencedByTicket = Referenced by ticket. | ||
| gb.emailClientCertificateSubject = Your Gitblit client certificate for {0} |
@@ -20,2 +20,3 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| <link rel="stylesheet" type="text/css" href="gitblit.css"/> | ||
| <link rel="stylesheet" type="text/css" href="bootstrap-fixes.css"/> | ||
| </wicket:head> | ||
@@ -54,5 +55,6 @@ | ||
| <script type="text/javascript" src="bootstrap/js/jquery.js"></script> | ||
| <script type="text/javascript" src="bootstrap/js/bootstrap.js"></script> | ||
| <script type="text/javascript" src="bootstrap/js/bootstrap.js"></script> | ||
| <script type="text/javascript" src="gitblit/js/collapsible-table.js"></script> | ||
| <wicket:container wicket:id="bottomScripts"></wicket:container> | ||
| </body> | ||
| </html> |
@@ -31,3 +31,3 @@ /* | ||
| import com.gitblit.models.UserModel; | ||
| import com.gitblit.utils.StringUtils; | ||
| import com.gitblit.utils.PasswordHash; | ||
| import com.gitblit.wicket.GitBlitWebSession; | ||
@@ -38,4 +38,4 @@ import com.gitblit.wicket.NonTrimmedPasswordTextField; | ||
| IModel<String> password = new Model<String>(""); | ||
| IModel<String> confirmPassword = new Model<String>(""); | ||
| private IModel<String> password = new Model<String>(""); | ||
| private IModel<String> confirmPassword = new Model<String>(""); | ||
@@ -90,11 +90,7 @@ public ChangePasswordPage() { | ||
| // convert to MD5 digest, if appropriate | ||
| String type = app().settings().getString(Keys.realm.passwordStorage, "md5"); | ||
| if (type.equalsIgnoreCase("md5")) { | ||
| // store MD5 digest of password | ||
| password = StringUtils.MD5_TYPE + StringUtils.getMD5(password); | ||
| } else if (type.equalsIgnoreCase("combined-md5")) { | ||
| // store MD5 digest of username+password | ||
| password = StringUtils.COMBINED_MD5_TYPE | ||
| + StringUtils.getMD5(user.username.toLowerCase() + password); | ||
| // convert to digest, if appropriate | ||
| String type = app().settings().getString(Keys.realm.passwordStorage, PasswordHash.getDefaultType().name()); | ||
| PasswordHash pwdHash = PasswordHash.instanceOf(type); | ||
| if (pwdHash != null) { | ||
| password = pwdHash.toHashedEntry(password, user.username); | ||
| } | ||
@@ -101,0 +97,0 @@ |
@@ -54,2 +54,7 @@ /* | ||
| RevCommit commit = JGitUtils.getCommit(r, objectId); | ||
| if (commit == null) { | ||
| setResponsePage(NoDocsPage.class, params); | ||
| return; | ||
| } | ||
| String [] encodings = getEncodings(); | ||
@@ -56,0 +61,0 @@ |
@@ -65,2 +65,6 @@ /* | ||
| RevCommit head = JGitUtils.getCommit(r, objectId); | ||
| if (head == null) { | ||
| setResponsePage(NoDocsPage.class, params); | ||
| return; | ||
| } | ||
| final String commitId = getBestCommitId(head); | ||
@@ -67,0 +71,0 @@ |
@@ -126,3 +126,4 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| <div wicket:id="mergeTo"></div> | ||
| <div wicket:id="mergeType"></div> | ||
| </div> | ||
@@ -129,0 +130,0 @@ |
@@ -59,2 +59,3 @@ /* | ||
| import com.gitblit.Constants.FederationStrategy; | ||
| import com.gitblit.Constants.MergeType; | ||
| import com.gitblit.Constants.RegistrantType; | ||
@@ -462,2 +463,7 @@ import com.gitblit.GitBlitException; | ||
| availableBranches)); | ||
| form.add(new ChoiceOption<MergeType>("mergeType", | ||
| getString("gb.mergeType"), | ||
| getString("gb.mergeTypeDescription"), | ||
| new PropertyModel<MergeType>(repositoryModel, "mergeType"), | ||
| Arrays.asList(MergeType.values()))); | ||
@@ -464,0 +470,0 @@ // |
@@ -34,7 +34,8 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| <tr><th><wicket:message key="gb.emailAddress"></wicket:message></th><td class="edit"><input type="text" wicket:id="emailAddress" size="30" tabindex="5" /></td></tr> | ||
| <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> <span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="7" /> <span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="8" /> <span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="9" /> <span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.disableUser"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="disabled" tabindex="10" /> <span class="help-inline"><wicket:message key="gb.disableUserDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.languagePreference"></wicket:message></th><td class="edit"><select wicket:id="language" ></select></td></tr> | ||
| <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="7" /> <span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="8" /> <span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="9" /> <span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="10" /> <span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr> | ||
| <tr><th><wicket:message key="gb.disableUser"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="disabled" tabindex="11" /> <span class="help-inline"><wicket:message key="gb.disableUserDescription"></wicket:message></span></label></td></tr> | ||
| </tbody> | ||
@@ -41,0 +42,0 @@ </table> |
@@ -23,3 +23,5 @@ /* | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import com.gitblit.utils.PasswordHash; | ||
| import org.apache.wicket.PageParameters; | ||
@@ -30,5 +32,7 @@ import org.apache.wicket.behavior.SimpleAttributeModifier; | ||
| import org.apache.wicket.markup.html.form.CheckBox; | ||
| import org.apache.wicket.markup.html.form.DropDownChoice; | ||
| import org.apache.wicket.markup.html.form.Form; | ||
| import org.apache.wicket.markup.html.form.TextField; | ||
| import org.apache.wicket.model.CompoundPropertyModel; | ||
| import org.apache.wicket.model.IModel; | ||
| import org.apache.wicket.model.Model; | ||
@@ -113,2 +117,7 @@ import org.apache.wicket.model.util.CollectionModel; | ||
| .getAllTeamNames()), new StringChoiceRenderer(), 10, false); | ||
| Locale locale = userModel.getPreferences().getLocale(); | ||
| List<Language> languages = UserPage.getLanguages(); | ||
| Language preferredLanguage = UserPage.getPreferredLanguage(locale, languages); | ||
| final IModel<Language> language = Model.of(preferredLanguage); | ||
| Form<UserModel> form = new Form<UserModel>("editForm", model) { | ||
@@ -129,2 +138,6 @@ | ||
| } | ||
| Language lang = language.getObject(); | ||
| if (lang != null) { | ||
| userModel.getPreferences().setLocale(lang.code); | ||
| } | ||
| // force username to lower-case | ||
@@ -148,4 +161,3 @@ userModel.username = userModel.username.toLowerCase(); | ||
| String password = userModel.password; | ||
| if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE) | ||
| && !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | ||
| if (!PasswordHash.isHashedEntry(password)) { | ||
| // This is a plain text password. | ||
@@ -157,3 +169,3 @@ // Check length. | ||
| } | ||
| if (password.trim().length() < minLength) { | ||
| if (password.trim().length() < minLength) { // TODO: Why do we trim here, but not in EditUserDialog and ChangePasswordPage? | ||
| error(MessageFormat.format(getString("gb.passwordTooShort"), | ||
@@ -165,17 +177,12 @@ minLength)); | ||
| // 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"); | ||
| if (type.equalsIgnoreCase("md5")) { | ||
| // store MD5 digest of password | ||
| userModel.password = StringUtils.MD5_TYPE | ||
| + StringUtils.getMD5(userModel.password); | ||
| } else if (type.equalsIgnoreCase("combined-md5")) { | ||
| // store MD5 digest of username+password | ||
| userModel.password = StringUtils.COMBINED_MD5_TYPE | ||
| + StringUtils.getMD5(username + userModel.password); | ||
| String type = app().settings().getString(Keys.realm.passwordStorage, PasswordHash.getDefaultType().name()); | ||
| PasswordHash pwdh = PasswordHash.instanceOf(type); | ||
| if (pwdh != null) { // Hash the password | ||
| userModel.password = pwdh.toHashedEntry(password, username); | ||
| } | ||
| } else if (rename | ||
| && password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | ||
| && password.toUpperCase().startsWith(PasswordHash.Type.CMD5.name())) { | ||
| error(getString("gb.combinedMd5Rename")); | ||
@@ -261,3 +268,6 @@ return; | ||
| form.add(new TextField<String>("emailAddress").setEnabled(editEmailAddress)); | ||
| DropDownChoice<Language> choice = new DropDownChoice<Language>("language",language,languages ); | ||
| form.add( choice.setEnabled(languages.size()>0) ); | ||
| if (userModel.canAdmin() && !userModel.canAdmin) { | ||
@@ -264,0 +274,0 @@ // user inherits Admin permission |
@@ -55,3 +55,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| <div style="margin-left:10px;" class="span2"> | ||
| <wicket:message key="gb.queryHelp"></wicket:message> | ||
| <wicket:message key="gb.queryHelp"> | ||
| <a target="_new" wicket:id="querySyntax"><wicket:message key="gb.querySyntax"/></a> | ||
| </wicket:message> | ||
| </div> | ||
@@ -58,0 +60,0 @@ </div> |
@@ -30,2 +30,3 @@ /* | ||
| import org.apache.wicket.markup.html.form.TextField; | ||
| import org.apache.wicket.markup.html.link.ExternalLink; | ||
| import org.apache.wicket.markup.html.panel.Fragment; | ||
@@ -54,2 +55,4 @@ import org.apache.wicket.markup.repeater.Item; | ||
| private final static String LUCENE_QUERY_SYNTAX_LINK = "https://lucene.apache.org/core/5_5_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description"; | ||
| public LuceneSearchPage() { | ||
@@ -172,2 +175,3 @@ super(); | ||
| form.add(new CheckBox("allrepos", allreposModel)); | ||
| form.add(new ExternalLink("querySyntax", LUCENE_QUERY_SYNTAX_LINK)); | ||
| add(form.setEnabled(luceneEnabled)); | ||
@@ -174,0 +178,0 @@ |
@@ -524,3 +524,9 @@ /* | ||
| public int compare(TicketMilestone o1, TicketMilestone o2) { | ||
| return o2.due.compareTo(o1.due); | ||
| if (o1.due == null) { | ||
| return (o2.due == null) ? 0 : 1; | ||
| } else if (o2.due == null) { | ||
| return -1; | ||
| } else { | ||
| return o1.due.compareTo(o2.due); | ||
| } | ||
| } | ||
@@ -532,3 +538,9 @@ }); | ||
| public int compare(TicketMilestone o1, TicketMilestone o2) { | ||
| return o2.due.compareTo(o1.due); | ||
| if (o1.due == null) { | ||
| return (o2.due == null) ? 0 : 1; | ||
| } else if (o2.due == null) { | ||
| return -1; | ||
| } else { | ||
| return o1.due.compareTo(o2.due); | ||
| } | ||
| } | ||
@@ -535,0 +547,0 @@ }); |
@@ -18,3 +18,2 @@ /* | ||
| import java.io.Serializable; | ||
| import java.util.ArrayList; | ||
@@ -170,8 +169,6 @@ import java.util.Arrays; | ||
| } | ||
| private void addPreferences(UserModel user) { | ||
| // add preferences | ||
| Form<Void> prefs = new Form<Void>("prefsForm"); | ||
| List<Language> languages = Arrays.asList( | ||
| static List<Language> getLanguages(){ | ||
| return Arrays.asList( | ||
| new Language("Česky","cs"), | ||
| new Language("Deutsch","de"), | ||
@@ -190,20 +187,5 @@ new Language("English","en"), | ||
| new Language("正體中文", "zh_TW")); | ||
| } | ||
| Locale locale = user.getPreferences().getLocale(); | ||
| if (locale == null) { | ||
| // user has not specified language preference | ||
| // try server default preference | ||
| String lc = app().settings().getString(Keys.web.forceDefaultLocale, null); | ||
| if (StringUtils.isEmpty(lc)) { | ||
| // server default language is not configured | ||
| // try browser preference | ||
| Locale sessionLocale = GitBlitWebSession.get().getLocale(); | ||
| if (sessionLocale != null) { | ||
| locale = sessionLocale; | ||
| } | ||
| } else { | ||
| } | ||
| } | ||
| static Language getPreferredLanguage(Locale locale, List<Language> languages) { | ||
| Language preferredLanguage = null; | ||
@@ -220,3 +202,3 @@ if (locale != null) { | ||
| preferredLanguage = language; | ||
| } else if (preferredLanguage != null && language.code.startsWith(locale.getLanguage())) { | ||
| } else if (preferredLanguage == null && language.code.startsWith(locale.getLanguage())) { | ||
| // language match | ||
@@ -227,3 +209,27 @@ preferredLanguage = language; | ||
| } | ||
| return preferredLanguage; | ||
| } | ||
| private void addPreferences(UserModel user) { | ||
| // add preferences | ||
| Form<Void> prefs = new Form<Void>("prefsForm"); | ||
| Locale locale = user.getPreferences().getLocale(); | ||
| if (locale == null) { | ||
| // user has not specified language preference | ||
| // try server default preference | ||
| String lc = app().settings().getString(Keys.web.forceDefaultLocale, null); | ||
| if (StringUtils.isEmpty(lc)) { | ||
| // server default language is not configured | ||
| // try browser preference | ||
| Locale sessionLocale = GitBlitWebSession.get().getLocale(); | ||
| if (sessionLocale != null) { | ||
| locale = sessionLocale; | ||
| } | ||
| } | ||
| } | ||
| List<Language> languages = getLanguages(); | ||
| Language preferredLanguage = getPreferredLanguage(locale, languages); | ||
| final IModel<String> displayName = Model.of(user.getDisplayName()); | ||
@@ -323,20 +329,2 @@ final IModel<String> emailAddress = Model.of(user.emailAddress == null ? "" : user.emailAddress); | ||
| } | ||
| private class Language implements Serializable { | ||
| private static final long serialVersionUID = 1L; | ||
| final String name; | ||
| final String code; | ||
| public Language(String name, String code) { | ||
| this.name = name; | ||
| this.code = code; | ||
| } | ||
| @Override | ||
| public String toString() { | ||
| return name + " (" + code +")"; | ||
| } | ||
| } | ||
| } |
@@ -51,3 +51,3 @@ /* | ||
| if (totalPages > 0) { | ||
| if (totalPages > 0 && currentPage > 1) { | ||
| pages.add(new PageObject("\u2190", currentPage - 1)); | ||
@@ -61,3 +61,3 @@ } | ||
| } | ||
| if (totalPages > 0) { | ||
| if (totalPages > 0 && currentPage < totalPages) { | ||
| pages.add(new PageObject("\u2192", currentPage + 1)); | ||
@@ -80,2 +80,3 @@ } | ||
| WicketUtils.setCssClass(item, "disabled"); | ||
| link.setEnabled(false); | ||
| } | ||
@@ -82,0 +83,0 @@ } |
@@ -21,2 +21,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| <wicket:fragment wicket:id="emptyFragment"> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="repoIconFragment"> | ||
@@ -76,5 +79,11 @@ <span class="octicon octicon-centered octicon-repo"></span> | ||
| <wicket:fragment wicket:id="tableAllCollapsible"> | ||
| <i title="Click to expand all" class="fa fa-plus-square-o table-openall-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i> | ||
| <i title="Click to collapse all" class="fa fa-minus-square-o table-closeall-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="groupRepositoryHeader"> | ||
| <tr> | ||
| <th class="left"> | ||
| <span wicket:id="allCollapsible"></span> | ||
| <img style="vertical-align: middle;" src="git-black-16x16.png"/> | ||
@@ -91,4 +100,12 @@ <wicket:message key="gb.repository">Repository</wicket:message> | ||
| <wicket:fragment wicket:id="tableGroupMinusCollapsible"> | ||
| <i title="Click to expand/collapse" class="fa fa-minus-square-o table-group-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="tableGroupPlusCollapsible"> | ||
| <i title="Click to expand/collapse" class="fa fa-plus-square-o table-group-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i> | ||
| </wicket:fragment> | ||
| <wicket:fragment wicket:id="groupRepositoryRow"> | ||
| <td colspan="1"><span wicket:id="groupName">[group name]</span></td> | ||
| <td colspan="1"><span wicket:id="groupCollapsible"></span><span wicket:id="groupName">[group name]</span></td> | ||
| <td colspan="6" style="padding: 2px;"><span class="hidden-phone" style="font-weight:normal;color:#666;" wicket:id="groupDescription">[description]</span></td> | ||
@@ -108,2 +125,2 @@ </wicket:fragment> | ||
| </body> | ||
| </html> | ||
| </html> |
@@ -31,2 +31,3 @@ /* | ||
| import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider; | ||
| import org.apache.wicket.markup.html.WebMarkupContainer; | ||
| import org.apache.wicket.markup.html.basic.Label; | ||
@@ -47,2 +48,3 @@ import org.apache.wicket.markup.html.link.BookmarkablePageLink; | ||
| import com.gitblit.models.RepositoryModel; | ||
| import com.gitblit.models.TreeNodeModel; | ||
| import com.gitblit.models.UserModel; | ||
@@ -64,2 +66,22 @@ import com.gitblit.utils.ArrayUtils; | ||
| private enum CollapsibleRepositorySetting { | ||
| DISABLED, | ||
| EXPANDED, | ||
| COLLAPSED; | ||
| public static CollapsibleRepositorySetting get(String name) { | ||
| CollapsibleRepositorySetting returnVal = CollapsibleRepositorySetting.DISABLED; | ||
| for (CollapsibleRepositorySetting setting : values()) { | ||
| if (setting.name().equalsIgnoreCase(name)) { | ||
| returnVal = setting; | ||
| break; | ||
| } | ||
| } | ||
| return returnVal; | ||
| } | ||
| } | ||
| public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement, | ||
@@ -72,6 +94,8 @@ List<RepositoryModel> models, boolean enableLinks, | ||
| final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true); | ||
| final String collapsibleRespositorySetting = app().settings().getString(Keys.web.collapsibleRepositoryGroups, null); | ||
| final CollapsibleRepositorySetting collapsibleRepoGroups = CollapsibleRepositorySetting.get(collapsibleRespositorySetting); | ||
| final UserModel user = GitBlitWebSession.get().getUser(); | ||
| final IDataProvider<RepositoryModel> dp; | ||
| IDataProvider<RepositoryModel> dp = null; | ||
@@ -104,3 +128,24 @@ Fragment managementLinks; | ||
| if (app().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) { | ||
| if (app().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("tree")) { | ||
| TreeNodeModel tree = new TreeNodeModel(); | ||
| for (RepositoryModel model : models) { | ||
| String rootPath = StringUtils.getRootPath(model.name); | ||
| if (StringUtils.isEmpty(rootPath)) { | ||
| tree.add(model); | ||
| } else { | ||
| // create folder structure | ||
| tree.add(rootPath, model); | ||
| } | ||
| } | ||
| WebMarkupContainer container = new WebMarkupContainer("row"); | ||
| add(container); | ||
| container.add(new NestedRepositoryTreePanel("rowContent", Model.of(tree), accessRestrictionTranslations, enableLinks)); | ||
| Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this); | ||
| Fragment allCollapsible = new Fragment("allCollapsible", "tableAllCollapsible", this); | ||
| fragment.add(allCollapsible); | ||
| add(fragment); | ||
| } else if (app().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) { | ||
| List<RepositoryModel> rootRepositories = new ArrayList<RepositoryModel>(); | ||
@@ -148,2 +193,3 @@ Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>(); | ||
| if (dp != null) { | ||
| final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true); | ||
@@ -169,2 +215,12 @@ | ||
| Fragment row = new Fragment("rowContent", "groupRepositoryRow", this); | ||
| if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED) { | ||
| Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupMinusCollapsible", this); | ||
| row.add(groupCollapsible); | ||
| } else if(collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) { | ||
| Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupPlusCollapsible", this); | ||
| row.add(groupCollapsible); | ||
| } else { | ||
| Fragment groupCollapsible = new Fragment("groupCollapsible", "emptyFragment", this); | ||
| row.add(groupCollapsible); | ||
| } | ||
| item.add(row); | ||
@@ -184,3 +240,3 @@ | ||
| } | ||
| WicketUtils.setCssClass(item, "group"); | ||
| WicketUtils.setCssClass(item, "group collapsible"); | ||
| // reset counter so that first row is light background | ||
@@ -330,4 +386,13 @@ counter = 0; | ||
| Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this); | ||
| if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED || | ||
| collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) { | ||
| Fragment allCollapsible = new Fragment("allCollapsible", "tableAllCollapsible", this); | ||
| fragment.add(allCollapsible); | ||
| } else { | ||
| Fragment allCollapsible = new Fragment("allCollapsible", "emptyFragment", this); | ||
| fragment.add(allCollapsible); | ||
| } | ||
| add(fragment); | ||
| } | ||
| } | ||
| } | ||
@@ -334,0 +399,0 @@ |
@@ -51,2 +51,3 @@ /* | ||
| private final UserModel user; | ||
| private final boolean canWriteKeys; | ||
@@ -57,2 +58,3 @@ public SshKeysPanel(String wicketId, UserModel user) { | ||
| this.user = user; | ||
| this.canWriteKeys = app().keys().supportsWritingKeys(user); | ||
| } | ||
@@ -95,2 +97,5 @@ | ||
| }; | ||
| if (!canWriteKeys) { | ||
| delete.setVisibilityAllowed(false); | ||
| } | ||
| item.add(delete); | ||
@@ -170,4 +175,8 @@ } | ||
| if (! canWriteKeys) { | ||
| addKeyForm.setVisibilityAllowed(false); | ||
| } | ||
| add(addKeyForm); | ||
| } | ||
| } |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <web-app version="2.4" | ||
| xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd"> | ||
| <web-app version="3.0" | ||
| xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> | ||
@@ -52,2 +52,2 @@ <!-- The base folder is used to specify the root location of your Gitblit data. | ||
| </session-config> | ||
| </web-app> | ||
| </web-app> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <wls:weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wls="http://www.bea.com/ns/weblogic/90" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-web-app.xsd"> | ||
| <wls:weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wls="http://www.bea.com/ns/weblogic/90" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-web-app.xsd"> | ||
| <wls:weblogic-version>12.1.1</wls:weblogic-version> | ||
@@ -9,2 +9,2 @@ <wls:context-root>gitblit</wls:context-root> | ||
| </wls:container-descriptor> | ||
| </wls:weblogic-web-app> | ||
| </wls:weblogic-web-app> |
@@ -48,2 +48,3 @@ body { | ||
| width: 16px; | ||
| padding-left: 17px; | ||
| } | ||
@@ -1967,2 +1968,3 @@ | ||
| width: 13em; | ||
| white-space: nowrap; | ||
| } | ||
@@ -1969,0 +1971,0 @@ |
@@ -0,3 +1,4 @@ | ||
| [user "admin"] | ||
| password = admin | ||
| password = MD5:21232f297a57a5a743894a0e4a801fc3 | ||
| cookie = dd94709528bb1c83d08f3088d4043f4742891f4f | ||
@@ -4,0 +5,0 @@ accountType = LOCAL |
@@ -46,2 +46,3 @@ /* | ||
| import com.gitblit.utils.PasswordHash; | ||
| import org.junit.Test; | ||
@@ -669,3 +670,34 @@ | ||
| @Test | ||
| public void testAuthenticateUpgradePlaintext() throws Exception { | ||
| IAuthenticationManager auth = newAuthenticationManager(); | ||
| UserModel user = new UserModel("sunnyjim"); | ||
| user.password = "password"; | ||
| users.updateUserModel(user); | ||
| assertNotNull(auth.authenticate(user.username, user.password.toCharArray(), null)); | ||
| // validate that plaintext password was automatically updated to hashed one | ||
| assertTrue(user.password.startsWith(PasswordHash.getDefaultType().name() + ":")); | ||
| } | ||
| @Test | ||
| public void testAuthenticateUpgradeMD5() throws Exception { | ||
| IAuthenticationManager auth = newAuthenticationManager(); | ||
| UserModel user = new UserModel("sunnyjim"); | ||
| user.password = "MD5:5F4DCC3B5AA765D61D8327DEB882CF99"; | ||
| users.updateUserModel(user); | ||
| assertNotNull(auth.authenticate(user.username, "password".toCharArray(), null)); | ||
| // validate that MD5 password was automatically updated to hashed one | ||
| assertTrue(user.password.startsWith(PasswordHash.getDefaultType().name() + ":")); | ||
| } | ||
| @Test | ||
| public void testContenairAuthenticate() throws Exception { | ||
@@ -672,0 +704,0 @@ settings.put(Keys.realm.container.autoCreateAccounts, "true"); |
@@ -62,3 +62,3 @@ /* | ||
| BranchTicketService service = new BranchTicketService( | ||
| BranchTicketService service = (BranchTicketService) new BranchTicketService( | ||
| runtimeManager, | ||
@@ -65,0 +65,0 @@ pluginManager, |
@@ -43,3 +43,3 @@ /* | ||
| RevCommit commit = JGitUtils.getCommit(repository, | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second)); | ||
| String diff = DiffUtils.getCommitDiff(repository, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content; | ||
@@ -56,5 +56,5 @@ repository.close(); | ||
| RevCommit baseCommit = JGitUtils.getCommit(repository, | ||
| "8baf6a833b5579384d9b9ceb8a16b5d0ea2ec4ca"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.first)); | ||
| RevCommit commit = JGitUtils.getCommit(repository, | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second)); | ||
| String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content; | ||
@@ -71,3 +71,3 @@ repository.close(); | ||
| RevCommit commit = JGitUtils.getCommit(repository, | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second)); | ||
| String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content; | ||
@@ -84,3 +84,3 @@ repository.close(); | ||
| RevCommit commit = JGitUtils.getCommit(repository, | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second)); | ||
| String patch = DiffUtils.getCommitPatch(repository, null, commit, "java.java"); | ||
@@ -97,5 +97,5 @@ repository.close(); | ||
| RevCommit baseCommit = JGitUtils.getCommit(repository, | ||
| "8baf6a833b5579384d9b9ceb8a16b5d0ea2ec4ca"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.first)); | ||
| RevCommit commit = JGitUtils.getCommit(repository, | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second)); | ||
| String patch = DiffUtils.getCommitPatch(repository, baseCommit, commit, "java.java"); | ||
@@ -112,5 +112,5 @@ repository.close(); | ||
| RevCommit baseCommit = JGitUtils.getCommit(repository, | ||
| "8baf6a833b5579384d9b9ceb8a16b5d0ea2ec4ca"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.first)); | ||
| RevCommit commit = JGitUtils.getCommit(repository, | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second)); | ||
| String patch = DiffUtils.getCommitPatch(repository, baseCommit, commit, null); | ||
@@ -127,7 +127,7 @@ repository.close(); | ||
| List<AnnotatedLine> lines = DiffUtils.blame(repository, "java.java", | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second)); | ||
| repository.close(); | ||
| assertTrue(lines.size() > 0); | ||
| assertEquals("c6d31dccf5cc75e8e46299fc62d38f60ec6d41e0", lines.get(0).commitId); | ||
| assertEquals(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.first), lines.get(0).commitId); | ||
| } | ||
| } |
@@ -18,2 +18,3 @@ /* | ||
| import java.io.IOException; | ||
| import java.util.Date; | ||
@@ -49,2 +50,8 @@ import java.util.HashMap; | ||
| //test data | ||
| static final String testUser = "test"; | ||
| static final String testUserPwd = "whocares"; | ||
| static final String testTeam = "testteam"; | ||
| static final String testTeamRepository = "helloworld.git"; | ||
| private static final AtomicBoolean started = new AtomicBoolean(false); | ||
@@ -59,2 +66,6 @@ | ||
| public static void stopGitblit() throws Exception { | ||
| //clean up test user and team if left over | ||
| deleteTestUser(); | ||
| deleteTestTeam(); | ||
| if (started.get()) { | ||
@@ -65,2 +76,14 @@ GitBlitSuite.stopGitblit(); | ||
| private static void deleteTestUser() throws IOException { | ||
| UserModel user = new UserModel(testUser); | ||
| user.password = testUserPwd; | ||
| RpcUtils.deleteUser(user, GitBlitSuite.url, GitBlitSuite.account, GitBlitSuite.password.toCharArray()); | ||
| } | ||
| private static void deleteTestTeam() throws IOException { | ||
| TeamModel team = new TeamModel(testTeam); | ||
| team.addRepositoryPermission(testTeamRepository); | ||
| RpcUtils.deleteTeam(team, GitBlitSuite.url, GitBlitSuite.account, GitBlitSuite.password.toCharArray()); | ||
| } | ||
| @Test | ||
@@ -128,14 +151,18 @@ public void testProposal() throws Exception { | ||
| public void testPullUsers() throws Exception { | ||
| //clean up test user and team left over from previous run, if any | ||
| deleteTestUser(); | ||
| deleteTestTeam(); | ||
| List<UserModel> users = FederationUtils.getUsers(getRegistration()); | ||
| assertNotNull(users); | ||
| // admin is excluded | ||
| assertEquals(0, users.size()); | ||
| // admin is excluded, hence there should be no other users in the list | ||
| assertEquals("Gitblit server still contains " + users + " user account(s).", 0, users.size()); | ||
| UserModel newUser = new UserModel("test"); | ||
| newUser.password = "whocares"; | ||
| UserModel newUser = new UserModel(testUser); | ||
| newUser.password = testUserPwd; | ||
| assertTrue(RpcUtils.createUser(newUser, url, account, password.toCharArray())); | ||
| TeamModel team = new TeamModel("testteam"); | ||
| team.addUser("test"); | ||
| team.addRepositoryPermission("helloworld.git"); | ||
| TeamModel team = new TeamModel(testTeam); | ||
| team.addUser(testUser); | ||
| team.addRepositoryPermission(testTeamRepository); | ||
| assertTrue(RpcUtils.createTeam(team, url, account, password.toCharArray())); | ||
@@ -148,3 +175,3 @@ | ||
| newUser = users.get(0); | ||
| assertTrue(newUser.isTeamMember("testteam")); | ||
| assertTrue(newUser.isTeamMember(testTeam)); | ||
@@ -157,5 +184,8 @@ assertTrue(RpcUtils.deleteUser(newUser, url, account, password.toCharArray())); | ||
| public void testPullTeams() throws Exception { | ||
| TeamModel team = new TeamModel("testteam"); | ||
| team.addUser("test"); | ||
| team.addRepositoryPermission("helloworld.git"); | ||
| //clean up test team left over from previous run, if any | ||
| deleteTestTeam(); | ||
| TeamModel team = new TeamModel(testTeam); | ||
| team.addUser(testUser); | ||
| team.addRepositoryPermission(testTeamRepository); | ||
| assertTrue(RpcUtils.createTeam(team, url, account, password.toCharArray())); | ||
@@ -162,0 +192,0 @@ |
@@ -61,3 +61,3 @@ /* | ||
| FileTicketService service = new FileTicketService( | ||
| FileTicketService service = (FileTicketService) new FileTicketService( | ||
| runtimeManager, | ||
@@ -64,0 +64,0 @@ pluginManager, |
@@ -19,2 +19,5 @@ /* | ||
| import java.io.File; | ||
| import java.io.FileInputStream; | ||
| import java.io.FileOutputStream; | ||
| import java.io.IOException; | ||
| import java.lang.reflect.Field; | ||
@@ -24,2 +27,4 @@ import java.util.concurrent.Executors; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.zip.ZipEntry; | ||
| import java.util.zip.ZipInputStream; | ||
@@ -32,2 +37,3 @@ import org.eclipse.jgit.api.Git; | ||
| import org.eclipse.jgit.util.FS; | ||
| import org.eclipse.jgit.util.FileUtils; | ||
| import org.junit.AfterClass; | ||
@@ -39,2 +45,3 @@ import org.junit.BeforeClass; | ||
| import com.gitblit.FileSettings; | ||
| import com.gitblit.GitBlitException; | ||
@@ -84,2 +91,14 @@ import com.gitblit.GitBlitServer; | ||
| private static final File AMBITION_REPO_SOURCE = new File("src/test/data/ambition.git"); | ||
| private static final File TICGIT_REPO_SOURCE = new File("src/test/data/ticgit.git"); | ||
| private static final File GITECTIVE_REPO_SOURCE = new File("src/test/data/gitective.git"); | ||
| private static final File HELLOWORLD_REPO_SOURCE = new File("src/test/data/hello-world.git"); | ||
| private static final File HELLOWORLD_REPO_PROPERTIES = new File("src/test/data/hello-world.properties"); | ||
| public static final FileSettings helloworldSettings = new FileSettings(HELLOWORLD_REPO_PROPERTIES.getAbsolutePath()); | ||
| static int port = 8280; | ||
@@ -175,13 +194,35 @@ static int gitPort = 8300; | ||
| public static void deleteRefChecksFolder() throws IOException { | ||
| File refChecks = new File(GitBlitSuite.REPOSITORIES, "refchecks"); | ||
| if (refChecks.exists()) { | ||
| FileUtils.delete(refChecks, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| } | ||
| @BeforeClass | ||
| public static void setUp() throws Exception { | ||
| //"refchecks" folder is used in GitServletTest; | ||
| //need be deleted before Gitblit server instance is started | ||
| deleteRefChecksFolder(); | ||
| startGitblit(); | ||
| if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) { | ||
| cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git"); | ||
| cloneOrFetch("ticgit.git", "https://github.com/schacon/ticgit.git"); | ||
| if (!HELLOWORLD_REPO_SOURCE.exists()) { | ||
| unzipRepository(HELLOWORLD_REPO_SOURCE.getPath() + ".zip", HELLOWORLD_REPO_SOURCE.getParentFile()); | ||
| } | ||
| if (!TICGIT_REPO_SOURCE.exists()) { | ||
| unzipRepository(TICGIT_REPO_SOURCE.getPath() + ".zip", TICGIT_REPO_SOURCE.getParentFile()); | ||
| } | ||
| if (!AMBITION_REPO_SOURCE.exists()) { | ||
| unzipRepository(AMBITION_REPO_SOURCE.getPath() + ".zip", AMBITION_REPO_SOURCE.getParentFile()); | ||
| } | ||
| if (!GITECTIVE_REPO_SOURCE.exists()) { | ||
| unzipRepository(GITECTIVE_REPO_SOURCE.getPath() + ".zip", GITECTIVE_REPO_SOURCE.getParentFile()); | ||
| } | ||
| cloneOrFetch("helloworld.git", HELLOWORLD_REPO_SOURCE.getAbsolutePath()); | ||
| cloneOrFetch("ticgit.git", TICGIT_REPO_SOURCE.getAbsolutePath()); | ||
| cloneOrFetch("test/jgit.git", "https://github.com/eclipse/jgit.git"); | ||
| cloneOrFetch("test/helloworld.git", "https://github.com/git/hello-world.git"); | ||
| cloneOrFetch("test/ambition.git", "https://github.com/defunkt/ambition.git"); | ||
| cloneOrFetch("test/gitective.git", "https://github.com/kevinsawicki/gitective.git"); | ||
| cloneOrFetch("test/helloworld.git", HELLOWORLD_REPO_SOURCE.getAbsolutePath()); | ||
| cloneOrFetch("test/ambition.git", AMBITION_REPO_SOURCE.getAbsolutePath()); | ||
| cloneOrFetch("test/gitective.git", GITECTIVE_REPO_SOURCE.getAbsolutePath()); | ||
@@ -263,2 +304,45 @@ showRemoteBranches("ticgit.git"); | ||
| } | ||
| private static void unzipRepository(String zippedRepo, File destDir) throws IOException { | ||
| System.out.print("Unzipping " + zippedRepo + "... "); | ||
| if (!destDir.exists()) { | ||
| destDir.mkdir(); | ||
| } | ||
| byte[] buffer = new byte[1024]; | ||
| ZipInputStream zis = new ZipInputStream(new FileInputStream(zippedRepo)); | ||
| ZipEntry zipEntry = zis.getNextEntry(); | ||
| while (zipEntry != null) { | ||
| File newFile = newFile(destDir, zipEntry); | ||
| if (zipEntry.isDirectory()) { | ||
| newFile.mkdirs(); | ||
| } | ||
| else { | ||
| FileOutputStream fos = new FileOutputStream(newFile); | ||
| int len; | ||
| while ((len = zis.read(buffer)) > 0) { | ||
| fos.write(buffer, 0, len); | ||
| } | ||
| fos.close(); | ||
| } | ||
| zipEntry = zis.getNextEntry(); | ||
| } | ||
| zis.closeEntry(); | ||
| zis.close(); | ||
| System.out.println("done."); | ||
| } | ||
| private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { | ||
| File destFile = new File(destinationDir, zipEntry.getName()); | ||
| String destDirPath = destinationDir.getCanonicalPath(); | ||
| String destFilePath = destFile.getCanonicalPath(); | ||
| //guards against writing files to the file system outside of the target folder | ||
| //to prevent Zip Slip exploit | ||
| if (!destFilePath.startsWith(destDirPath + File.separator)) { | ||
| throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); | ||
| } | ||
| return destFile; | ||
| } | ||
| } |
@@ -28,2 +28,7 @@ /* | ||
| import org.apache.commons.io.IOUtils; | ||
| import org.apache.http.HttpResponse; | ||
| import org.apache.http.client.HttpClient; | ||
| import org.apache.http.client.methods.HttpGet; | ||
| import org.apache.http.impl.client.HttpClientBuilder; | ||
| import org.eclipse.jgit.api.CloneCommand; | ||
@@ -93,2 +98,5 @@ import org.eclipse.jgit.api.Git; | ||
| public static void startGitblit() throws Exception { | ||
| //"refchecks" folder is used in this test class; | ||
| //need be deleted before Gitblit server instance is started | ||
| GitBlitSuite.deleteRefChecksFolder(); | ||
| started.set(GitBlitSuite.startGitblit()); | ||
@@ -112,15 +120,15 @@ | ||
| GitBlitSuite.close(ticgitFolder); | ||
| FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE); | ||
| FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| if (ticgit2Folder.exists()) { | ||
| GitBlitSuite.close(ticgit2Folder); | ||
| FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE); | ||
| FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| if (jgitFolder.exists()) { | ||
| GitBlitSuite.close(jgitFolder); | ||
| FileUtils.delete(jgitFolder, FileUtils.RECURSIVE); | ||
| FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| if (jgit2Folder.exists()) { | ||
| GitBlitSuite.close(jgit2Folder); | ||
| FileUtils.delete(jgit2Folder, FileUtils.RECURSIVE); | ||
| FileUtils.delete(jgit2Folder, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
@@ -406,3 +414,3 @@ } | ||
| if (verification.exists()) { | ||
| FileUtils.delete(verification, FileUtils.RECURSIVE); | ||
| FileUtils.delete(verification, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
@@ -492,3 +500,3 @@ CloneCommand clone = Git.cloneRepository(); | ||
| if (verification.exists()) { | ||
| FileUtils.delete(verification, FileUtils.RECURSIVE); | ||
| FileUtils.delete(verification, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
@@ -638,3 +646,3 @@ CloneCommand clone = Git.cloneRepository(); | ||
| if (refChecks.exists()) { | ||
| FileUtils.delete(refChecks, FileUtils.RECURSIVE); | ||
| FileUtils.delete(refChecks, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
@@ -672,3 +680,3 @@ CloneCommand clone = Git.cloneRepository(); | ||
| if (local.exists()) { | ||
| FileUtils.delete(local, FileUtils.RECURSIVE); | ||
| FileUtils.delete(local, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
@@ -935,2 +943,63 @@ clone = Git.cloneRepository(); | ||
| } | ||
| @Test | ||
| public void testInvalidURLNoRepoName() throws IOException { | ||
| final String testURL = GitBlitSuite.gitServletUrl + "/?service=git-upload-pack"; | ||
| HttpClient client = HttpClientBuilder.create().build(); | ||
| HttpGet request = new HttpGet(testURL); | ||
| HttpResponse response = client.execute(request); | ||
| assertEquals("Expected BAD REQUEST due to missing repository string", 400, response.getStatusLine().getStatusCode()); | ||
| } | ||
| @Test | ||
| public void testInvalidURLNoRepoName2() throws IOException { | ||
| final String testURL = GitBlitSuite.gitServletUrl + "//info/refs"; | ||
| HttpClient client = HttpClientBuilder.create().build(); | ||
| HttpGet request = new HttpGet(testURL); | ||
| HttpResponse response = client.execute(request); | ||
| assertEquals("Expected BAD REQUEST due to missing repository string", 400, response.getStatusLine().getStatusCode()); | ||
| } | ||
| @Test | ||
| public void testURLUnknownRepo() throws IOException { | ||
| final String testURL = GitBlitSuite.url + "/r/foobar.git/info/refs"; | ||
| HttpClient client = HttpClientBuilder.create().build(); | ||
| HttpGet request = new HttpGet(testURL); | ||
| HttpResponse response = client.execute(request); | ||
| assertEquals(401, response.getStatusLine().getStatusCode()); | ||
| } | ||
| @Test | ||
| public void testURLUnknownAction() throws IOException { | ||
| final String testURL = GitBlitSuite.gitServletUrl + "/helloworld.git/something/unknown"; | ||
| HttpClient client = HttpClientBuilder.create().build(); | ||
| HttpGet request = new HttpGet(testURL); | ||
| HttpResponse response = client.execute(request); | ||
| assertEquals(400, response.getStatusLine().getStatusCode()); | ||
| } | ||
| @Test | ||
| public void testInvalidURLCloneBundle() throws IOException { | ||
| final String testURL = GitBlitSuite.gitServletUrl + "/helloworld.git/clone.bundle"; | ||
| HttpClient client = HttpClientBuilder.create().build(); | ||
| HttpGet request = new HttpGet(testURL); | ||
| HttpResponse response = client.execute(request); | ||
| assertEquals(501, response.getStatusLine().getStatusCode()); | ||
| String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); | ||
| assertNotNull(content); | ||
| } | ||
| } |
@@ -77,7 +77,7 @@ /* | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master")); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master2")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master2")); | ||
@@ -100,7 +100,7 @@ RepositoryModel repository = repositories().getRepositoryModel("helloworld.git"); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master")); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master2")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master2")); | ||
@@ -126,7 +126,7 @@ RepositoryModel repository = repositories().getRepositoryModel("helloworld.git"); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master")); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master2")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master2")); | ||
@@ -151,3 +151,3 @@ RepositoryModel repository = repositories().getRepositoryModel("helloworld.git"); | ||
| commands.add(new ReceiveCommand(ObjectId.zeroId(), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master")); | ||
@@ -166,3 +166,3 @@ RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date()); | ||
| commands.add(new ReceiveCommand(ObjectId.zeroId(), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/tags/v1.0")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/tags/v1.0")); | ||
@@ -182,4 +182,4 @@ RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date()); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master")); | ||
@@ -199,3 +199,3 @@ RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date()); | ||
| ReceiveCommand command = new ReceiveCommand(ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), ObjectId.zeroId(), | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), ObjectId.zeroId(), | ||
| "refs/heads/master"); | ||
@@ -218,3 +218,3 @@ commands.add(command); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), ObjectId.zeroId(), | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), ObjectId.zeroId(), | ||
| "refs/heads/other")); | ||
@@ -235,3 +235,3 @@ | ||
| ReceiveCommand command = new ReceiveCommand(ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), ObjectId.zeroId(), | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), ObjectId.zeroId(), | ||
| "refs/tags/v1.0"); | ||
@@ -254,4 +254,4 @@ commands.add(command); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master")); | ||
@@ -275,4 +275,4 @@ RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date()); | ||
| commands.add(new ReceiveCommand(ObjectId | ||
| .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId | ||
| .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master")); | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifth)), ObjectId | ||
| .fromString(GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)), "refs/heads/master")); | ||
@@ -279,0 +279,0 @@ RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date()); |
@@ -203,7 +203,7 @@ /* | ||
| MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); | ||
| UserModel user = auth.authenticate("user1", "pass1".toCharArray(), null); | ||
| UserModel user = auth.authenticate("user1", "#externalAccount".toCharArray(), null); | ||
| assertNotNull(user); | ||
| assertEquals("user1", user.username); | ||
| user = auth.authenticate("user2", "pass2".toCharArray(), null); | ||
| user = auth.authenticate("user2", "#externalAccount".toCharArray(), null); | ||
| assertNotNull(user); | ||
@@ -210,0 +210,0 @@ assertEquals("user2", user.username); |
@@ -20,2 +20,6 @@ /* | ||
| import java.io.FileOutputStream; | ||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Paths; | ||
| import java.text.SimpleDateFormat; | ||
@@ -55,2 +59,4 @@ import java.util.Arrays; | ||
| import static org.junit.Assume.assumeTrue; | ||
| public class JGitUtilsTest extends GitblitUnitTest { | ||
@@ -80,2 +86,113 @@ | ||
| @Test | ||
| public void testFindRepositoriesSymLinked() { | ||
| String reposdirName = "gitrepos"; | ||
| String repositoryName = "test-linked.git"; | ||
| File extrepodir = new File(GitBlitSuite.REPOSITORIES.getParent(), reposdirName); | ||
| Path symlink = null; | ||
| Path alink = null; | ||
| try { | ||
| Path link = Paths.get( GitBlitSuite.REPOSITORIES.toString(), "test-rln.git"); | ||
| Path target = Paths.get("../" + reposdirName,repositoryName); | ||
| symlink = Files.createSymbolicLink(link, target); | ||
| link = Paths.get( GitBlitSuite.REPOSITORIES.toString(), "test-ln.git"); | ||
| target = Paths.get(extrepodir.getCanonicalPath(), repositoryName); | ||
| alink = Files.createSymbolicLink(link, target); | ||
| } | ||
| catch (UnsupportedOperationException e) { | ||
| assumeTrue("No symbolic links supported.", false); | ||
| } | ||
| catch (IOException ioe) { | ||
| try { | ||
| if (symlink != null) Files.delete(symlink); | ||
| if (alink != null) Files.delete(alink); | ||
| } | ||
| catch (IOException ignored) {} | ||
| fail(ioe.getMessage()); | ||
| } | ||
| Path extDir = null; | ||
| Repository repository = null; | ||
| String testDirName = "test-linked"; | ||
| String testTestDirName = "test-linked/test"; | ||
| Path testDir = Paths.get(GitBlitSuite.REPOSITORIES.toString(),testDirName); | ||
| Path testTestDir = Paths.get(GitBlitSuite.REPOSITORIES.toString(),testTestDirName); | ||
| Path linkTestRepo = null; | ||
| Path linkTestTestRepo = null; | ||
| Path linkExtDir = null; | ||
| try { | ||
| List<String> list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, true, true, -1, null); | ||
| int preSize = list.size(); | ||
| // Create test repo. This will make the link targets exist, so that the number of repos increases by two. | ||
| extDir = Files.createDirectory(extrepodir.toPath()); | ||
| repository = JGitUtils.createRepository(extrepodir, repositoryName); | ||
| list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, true, true, -1, null); | ||
| assertEquals("No linked repositories found in " + GitBlitSuite.REPOSITORIES, 2, (list.size() - preSize)); | ||
| list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, true, true, -1, Arrays.asList(".*ln\\.git")); | ||
| assertEquals("Filtering out linked repos failed.", preSize, list.size()); | ||
| // Create subdirectories and place links into them | ||
| Files.createDirectories(testTestDir); | ||
| Path target = Paths.get(extrepodir.getCanonicalPath(), repositoryName); | ||
| Path link = Paths.get(testDir.toString(), "test-ln-one.git"); | ||
| linkTestRepo = Files.createSymbolicLink(link, target); | ||
| link = Paths.get(testTestDir.toString(), "test-ln-two.git"); | ||
| linkTestTestRepo = Files.createSymbolicLink(link, target); | ||
| list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, true, true, -1, null); | ||
| assertEquals("No linked repositories found in subdirectories of " + GitBlitSuite.REPOSITORIES, 4, (list.size() - preSize)); | ||
| assertTrue("Did not find linked repo test-ln-one.git", list.contains(testDirName + "/test-ln-one.git")); | ||
| assertTrue("Did not find linked repo test-ln-two.git", list.contains(testTestDirName + "/test-ln-two.git")); | ||
| list = JGitUtils.getRepositoryList(new File(testDir.toString()), true, true, -1, null); | ||
| assertEquals("No linked repositories found in subdirectories of " + testDir, 2, list.size()); | ||
| assertTrue("Did not find linked repo test-ln-one.git", list.contains("test-ln-one.git")); | ||
| assertTrue("Did not find linked repo test-ln-two.git", list.contains("test/test-ln-two.git")); | ||
| // Create link to external directory with repos | ||
| target = Paths.get(extrepodir.getCanonicalPath()); | ||
| link = Paths.get(testDir.toString(), "test-linked"); | ||
| linkExtDir = Files.createSymbolicLink(link, target); | ||
| list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, true, true, -1, null); | ||
| assertEquals("No repositories found in linked subdirectories of " + GitBlitSuite.REPOSITORIES, 5, (list.size() - preSize)); | ||
| assertTrue("Did not find repo in linked subfolder.", list.contains(testDirName + "/test-linked/" + repositoryName)); | ||
| list = JGitUtils.getRepositoryList(new File(testDir.toString()), true, true, -1, null); | ||
| assertEquals("No repositories found in linked subdirectories of " + testDir, 3, list.size()); | ||
| assertTrue("Did not find repo in linked subfolder.", list.contains("test-linked/" + repositoryName)); | ||
| } catch (IOException e) { | ||
| fail(e.toString()); | ||
| } finally { | ||
| try { | ||
| if (repository != null) { | ||
| repository.close(); | ||
| RepositoryCache.close(repository); | ||
| FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE); | ||
| } | ||
| if (extDir != null) Files.delete(extDir); | ||
| if (symlink != null) Files.delete(symlink); | ||
| if (alink != null) Files.delete(alink); | ||
| if (linkExtDir != null) Files.deleteIfExists(linkExtDir); | ||
| if (linkTestTestRepo != null) Files.deleteIfExists(linkTestTestRepo); | ||
| if (linkTestRepo != null) Files.deleteIfExists(linkTestRepo); | ||
| Files.deleteIfExists(testTestDir); | ||
| Files.deleteIfExists(testDir); | ||
| } | ||
| catch (IOException ignored) {} | ||
| } | ||
| } | ||
| @Test | ||
| public void testFindExclusions() { | ||
@@ -114,3 +231,4 @@ List<String> list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, null); | ||
| assertNotNull("Could not get first commit!", commit); | ||
| assertEquals("Incorrect first commit!", "f554664a346629dc2b839f7292d06bad2db4aece", | ||
| assertEquals("Incorrect first commit!", | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.first), | ||
| commit.getName()); | ||
@@ -449,6 +567,6 @@ assertTrue(firstChange.equals(new Date(commit.getCommitTime() * 1000L))); | ||
| RevCommit commit = JGitUtils.getCommit(repository, | ||
| "1d0c2933a4ae69c362f76797d42d6bd182d05176"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifteen)); | ||
| List<PathChangeModel> paths = JGitUtils.getFilesInCommit(repository, commit); | ||
| commit = JGitUtils.getCommit(repository, "af0e9b2891fda85afc119f04a69acf7348922830"); | ||
| commit = JGitUtils.getCommit(repository, GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.deleted)); | ||
| List<PathChangeModel> deletions = JGitUtils.getFilesInCommit(repository, commit); | ||
@@ -486,6 +604,18 @@ | ||
| assertEquals(0, JGitUtils.getFilesInPath2(null, null, null).size()); | ||
| Repository repository = GitBlitSuite.getHelloworldRepository(); | ||
| List<PathModel> files = JGitUtils.getFilesInPath2(repository, null, null); | ||
| assertEquals(GitBlitSuite.helloworldSettings.getInteger(HelloworldKeys.files.top, 15), files.size()); | ||
| files = JGitUtils.getFilesInPath2(repository, "C", null); | ||
| assertEquals(GitBlitSuite.helloworldSettings.getInteger(HelloworldKeys.files.C.top, 1), files.size()); | ||
| files = JGitUtils.getFilesInPath2(repository, "[C++]", null); | ||
| assertEquals(GitBlitSuite.helloworldSettings.getInteger(HelloworldKeys.files.Cpp, 1), files.size()); | ||
| files = JGitUtils.getFilesInPath2(repository, "C/C (K&R)", null); | ||
| assertEquals(GitBlitSuite.helloworldSettings.getInteger(HelloworldKeys.files.C.KnR, 1), files.size()); | ||
| repository.close(); | ||
| assertTrue(files.size() > 10); | ||
| } | ||
@@ -535,6 +665,7 @@ | ||
| // grab the commits since 2008-07-15 | ||
| // grab the commits since 2019-06-05 | ||
| commits = JGitUtils.getRevLog(repository, null, | ||
| new SimpleDateFormat("yyyy-MM-dd").parse("2008-07-15")); | ||
| assertEquals(12, commits.size()); | ||
| new SimpleDateFormat("yyyy-MM-dd").parse("2019-06-05")); | ||
| assertEquals("Wrong number of commits since 2019-06-05.", | ||
| GitBlitSuite.helloworldSettings.getInteger(HelloworldKeys.commits.since_20190605, -1), commits.size()); | ||
| repository.close(); | ||
@@ -547,4 +678,4 @@ } | ||
| List<RevCommit> commits = JGitUtils.getRevLog(repository, | ||
| "fbd14fa6d1a01d4aefa1fca725792683800fc67e", | ||
| "85a0e4087b8439c0aa6b1f4f9e08c26052ab7e87"); | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.second), | ||
| GitBlitSuite.helloworldSettings.getRequiredString(HelloworldKeys.commit.fifteen)); | ||
| repository.close(); | ||
@@ -551,0 +682,0 @@ assertEquals(14, commits.size()); |
@@ -20,3 +20,3 @@ /* | ||
| import java.util.Date; | ||
| import java.util.HashMap; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
@@ -33,3 +33,4 @@ | ||
| public void testSerialization() { | ||
| Map<String, String> map = new HashMap<String, String>(); | ||
| Map<String, String> map = new LinkedHashMap<String, String>(); | ||
| //LinkedHashMap preserves the order of insertion | ||
| map.put("a", "alligator"); | ||
@@ -42,3 +43,3 @@ map.put("b", "bear"); | ||
| assertEquals( | ||
| "{\"d\":\"dingo\",\"e\":\"eagle\",\"b\":\"bear\",\"c\":\"caterpillar\",\"a\":\"alligator\"}", | ||
| "{\"a\":\"alligator\",\"b\":\"bear\",\"c\":\"caterpillar\",\"d\":\"dingo\",\"e\":\"eagle\"}", | ||
| json); | ||
@@ -62,2 +63,2 @@ Map<String, String> map2 = JsonUtils.fromJsonString(json, | ||
| } | ||
| } | ||
| } |
@@ -19,14 +19,8 @@ /* | ||
| import java.io.File; | ||
| import java.io.FileInputStream; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import static org.junit.Assume.*; | ||
| import org.apache.commons.io.FileUtils; | ||
| import org.junit.Before; | ||
| import org.junit.BeforeClass; | ||
| import org.junit.Rule; | ||
| import org.junit.Test; | ||
| import org.junit.rules.TemporaryFolder; | ||
| import org.junit.runner.RunWith; | ||
| import org.junit.runners.Parameterized; | ||
| import com.gitblit.Constants.AccountType; | ||
@@ -45,5 +39,2 @@ import com.gitblit.IStoredSettings; | ||
| import com.gitblit.utils.XssFilter.AllowXssFilter; | ||
| import com.unboundid.ldap.listener.InMemoryDirectoryServer; | ||
| import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; | ||
| import com.unboundid.ldap.listener.InMemoryListenerConfig; | ||
| import com.unboundid.ldap.sdk.SearchResult; | ||
@@ -60,16 +51,7 @@ import com.unboundid.ldap.sdk.SearchScope; | ||
| */ | ||
| public class LdapAuthenticationTest extends GitblitUnitTest { | ||
| @Rule | ||
| public TemporaryFolder folder = new TemporaryFolder(); | ||
| @RunWith(Parameterized.class) | ||
| public class LdapAuthenticationTest extends LdapBasedUnitTest { | ||
| private static final String RESOURCE_DIR = "src/test/resources/ldap/"; | ||
| private LdapAuthProvider ldap; | ||
| private File usersConf; | ||
| private LdapAuthProvider ldap; | ||
| static int ldapPort = 1389; | ||
| private static InMemoryDirectoryServer ds; | ||
| private IUserManager userManager; | ||
@@ -79,22 +61,5 @@ | ||
| private MemorySettings settings; | ||
| @BeforeClass | ||
| public static void createInMemoryLdapServer() throws Exception { | ||
| InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=MyDomain"); | ||
| config.addAdditionalBindCredentials("cn=Directory Manager", "password"); | ||
| config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", ldapPort)); | ||
| config.setSchema(null); | ||
| ds = new InMemoryDirectoryServer(config); | ||
| ds.startListening(); | ||
| } | ||
| @Before | ||
| public void init() throws Exception { | ||
| ds.clear(); | ||
| ds.importFromLDIF(true, new LDIFReader(new FileInputStream(RESOURCE_DIR + "sampledata.ldif"))); | ||
| usersConf = folder.newFile("users.conf"); | ||
| FileUtils.copyFile(new File(RESOURCE_DIR + "users.conf"), usersConf); | ||
| settings = getSettings(); | ||
| public void setup() throws Exception { | ||
| ldap = newLdapAuthentication(settings); | ||
@@ -121,24 +86,2 @@ auth = newAuthenticationManager(settings); | ||
| private MemorySettings getSettings() { | ||
| Map<String, Object> backingMap = new HashMap<String, Object>(); | ||
| backingMap.put(Keys.realm.userService, usersConf.getAbsolutePath()); | ||
| backingMap.put(Keys.realm.ldap.server, "ldap://localhost:" + ldapPort); | ||
| // backingMap.put(Keys.realm.ldap.domain, ""); | ||
| backingMap.put(Keys.realm.ldap.username, "cn=Directory Manager"); | ||
| backingMap.put(Keys.realm.ldap.password, "password"); | ||
| // backingMap.put(Keys.realm.ldap.backingUserService, "users.conf"); | ||
| backingMap.put(Keys.realm.ldap.maintainTeams, "true"); | ||
| backingMap.put(Keys.realm.ldap.accountBase, "OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"); | ||
| backingMap.put(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); | ||
| backingMap.put(Keys.realm.ldap.groupBase, "OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain"); | ||
| backingMap.put(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))"); | ||
| backingMap.put(Keys.realm.ldap.admins, "UserThree @Git_Admins \"@Git Admins\""); | ||
| backingMap.put(Keys.realm.ldap.displayName, "displayName"); | ||
| backingMap.put(Keys.realm.ldap.email, "email"); | ||
| backingMap.put(Keys.realm.ldap.uid, "sAMAccountName"); | ||
| MemorySettings ms = new MemorySettings(backingMap); | ||
| return ms; | ||
| } | ||
| @Test | ||
@@ -150,3 +93,2 @@ public void testAuthenticate() { | ||
| assertNotNull(userOneModel.getTeam("git_users")); | ||
| assertTrue(userOneModel.canAdmin); | ||
@@ -161,3 +103,2 @@ UserModel userOneModelFailedAuth = ldap.authenticate("UserOne", "userTwoPassword".toCharArray()); | ||
| assertNotNull(userTwoModel.getTeam("git admins")); | ||
| assertTrue(userTwoModel.canAdmin); | ||
@@ -168,6 +109,95 @@ UserModel userThreeModel = ldap.authenticate("UserThree", "userThreePassword".toCharArray()); | ||
| assertNull(userThreeModel.getTeam("git_admins")); | ||
| UserModel userFourModel = ldap.authenticate("UserFour", "userFourPassword".toCharArray()); | ||
| assertNotNull(userFourModel); | ||
| assertNotNull(userFourModel.getTeam("git_users")); | ||
| assertNull(userFourModel.getTeam("git_admins")); | ||
| assertNull(userFourModel.getTeam("git admins")); | ||
| } | ||
| @Test | ||
| public void testAdminPropertyTeamsInLdap() { | ||
| UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray()); | ||
| assertNotNull(userOneModel); | ||
| assertNotNull(userOneModel.getTeam("git_admins")); | ||
| assertNull(userOneModel.getTeam("git admins")); | ||
| assertNotNull(userOneModel.getTeam("git_users")); | ||
| assertFalse(userOneModel.canAdmin); | ||
| assertTrue(userOneModel.canAdmin()); | ||
| assertTrue(userOneModel.getTeam("git_admins").canAdmin); | ||
| assertFalse(userOneModel.getTeam("git_users").canAdmin); | ||
| UserModel userTwoModel = ldap.authenticate("UserTwo", "userTwoPassword".toCharArray()); | ||
| assertNotNull(userTwoModel); | ||
| assertNotNull(userTwoModel.getTeam("git_users")); | ||
| assertNull(userTwoModel.getTeam("git_admins")); | ||
| assertNotNull(userTwoModel.getTeam("git admins")); | ||
| assertFalse(userTwoModel.canAdmin); | ||
| assertTrue(userTwoModel.canAdmin()); | ||
| assertTrue(userTwoModel.getTeam("git admins").canAdmin); | ||
| assertFalse(userTwoModel.getTeam("git_users").canAdmin); | ||
| UserModel userThreeModel = ldap.authenticate("UserThree", "userThreePassword".toCharArray()); | ||
| assertNotNull(userThreeModel); | ||
| assertNotNull(userThreeModel.getTeam("git_users")); | ||
| assertNull(userThreeModel.getTeam("git_admins")); | ||
| assertNull(userThreeModel.getTeam("git admins")); | ||
| assertTrue(userThreeModel.canAdmin); | ||
| assertTrue(userThreeModel.canAdmin()); | ||
| assertFalse(userThreeModel.getTeam("git_users").canAdmin); | ||
| UserModel userFourModel = ldap.authenticate("UserFour", "userFourPassword".toCharArray()); | ||
| assertNotNull(userFourModel); | ||
| assertNotNull(userFourModel.getTeam("git_users")); | ||
| assertNull(userFourModel.getTeam("git_admins")); | ||
| assertNull(userFourModel.getTeam("git admins")); | ||
| assertFalse(userFourModel.canAdmin); | ||
| assertFalse(userFourModel.canAdmin()); | ||
| assertFalse(userFourModel.getTeam("git_users").canAdmin); | ||
| } | ||
| @Test | ||
| public void testAdminPropertyTeamsNotInLdap() { | ||
| settings.put(Keys.realm.ldap.maintainTeams, "false"); | ||
| UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray()); | ||
| assertNotNull(userOneModel); | ||
| assertNotNull(userOneModel.getTeam("git_admins")); | ||
| assertNull(userOneModel.getTeam("git admins")); | ||
| assertNotNull(userOneModel.getTeam("git_users")); | ||
| assertTrue(userOneModel.canAdmin); | ||
| assertTrue(userOneModel.canAdmin()); | ||
| assertFalse(userOneModel.getTeam("git_admins").canAdmin); | ||
| assertFalse(userOneModel.getTeam("git_users").canAdmin); | ||
| UserModel userTwoModel = ldap.authenticate("UserTwo", "userTwoPassword".toCharArray()); | ||
| assertNotNull(userTwoModel); | ||
| assertNotNull(userTwoModel.getTeam("git_users")); | ||
| assertNull(userTwoModel.getTeam("git_admins")); | ||
| assertNotNull(userTwoModel.getTeam("git admins")); | ||
| assertFalse(userTwoModel.canAdmin); | ||
| assertTrue(userTwoModel.canAdmin()); | ||
| assertTrue(userTwoModel.getTeam("git admins").canAdmin); | ||
| assertFalse(userTwoModel.getTeam("git_users").canAdmin); | ||
| UserModel userThreeModel = ldap.authenticate("UserThree", "userThreePassword".toCharArray()); | ||
| assertNotNull(userThreeModel); | ||
| assertNotNull(userThreeModel.getTeam("git_users")); | ||
| assertNull(userThreeModel.getTeam("git_admins")); | ||
| assertNull(userThreeModel.getTeam("git admins")); | ||
| assertFalse(userThreeModel.canAdmin); | ||
| assertFalse(userThreeModel.canAdmin()); | ||
| assertFalse(userThreeModel.getTeam("git_users").canAdmin); | ||
| UserModel userFourModel = ldap.authenticate("UserFour", "userFourPassword".toCharArray()); | ||
| assertNotNull(userFourModel); | ||
| assertNotNull(userFourModel.getTeam("git_users")); | ||
| assertNull(userFourModel.getTeam("git_admins")); | ||
| assertNull(userFourModel.getTeam("git admins")); | ||
| assertFalse(userFourModel.canAdmin); | ||
| assertFalse(userFourModel.canAdmin()); | ||
| assertFalse(userFourModel.getTeam("git_users").canAdmin); | ||
| } | ||
| @Test | ||
| public void testDisplayName() { | ||
@@ -215,3 +245,3 @@ UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray()); | ||
| public void checkIfUsersConfContainsAllUsersFromSampleDataLdif() throws Exception { | ||
| SearchResult searchResult = ds.search("OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain", SearchScope.SUB, "objectClass=person"); | ||
| SearchResult searchResult = getDS().search(ACCOUNT_BASE, SearchScope.SUB, "objectClass=person"); | ||
| assertEquals("Number of ldap users in gitblit user model", searchResult.getEntryCount(), countLdapUsersInUserManager()); | ||
@@ -222,3 +252,3 @@ } | ||
| public void addingUserInLdapShouldNotUpdateGitBlitUsersAndGroups() throws Exception { | ||
| ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); | ||
| getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); | ||
| ldap.sync(); | ||
@@ -231,3 +261,3 @@ assertEquals("Number of ldap users in gitblit user model", 5, countLdapUsersInUserManager()); | ||
| settings.put(Keys.realm.ldap.synchronize, "true"); | ||
| ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); | ||
| getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); | ||
| ldap.sync(); | ||
@@ -239,3 +269,3 @@ assertEquals("Number of ldap users in gitblit user model", 6, countLdapUsersInUserManager()); | ||
| public void addingGroupsInLdapShouldNotUpdateGitBlitUsersAndGroups() throws Exception { | ||
| ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); | ||
| getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); | ||
| ldap.sync(); | ||
@@ -246,5 +276,19 @@ assertEquals("Number of ldap groups in gitblit team model", 0, countLdapTeamsInUserManager()); | ||
| @Test | ||
| public void addingGroupsInLdapShouldUpdateGitBlitUsersNotGroups2() throws Exception { | ||
| settings.put(Keys.realm.ldap.synchronize, "true"); | ||
| settings.put(Keys.realm.ldap.maintainTeams, "false"); | ||
| getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); | ||
| getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); | ||
| ldap.sync(); | ||
| assertEquals("Number of ldap users in gitblit user model", 6, countLdapUsersInUserManager()); | ||
| assertEquals("Number of ldap groups in gitblit team model", 0, countLdapTeamsInUserManager()); | ||
| } | ||
| @Test | ||
| public void addingGroupsInLdapShouldUpdateGitBlitUsersAndGroups() throws Exception { | ||
| // This test only makes sense if the authentication mode allows for synchronization. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS || authMode == AuthMode.DS_MANAGER); | ||
| settings.put(Keys.realm.ldap.synchronize, "true"); | ||
| ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); | ||
| getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); | ||
| ldap.sync(); | ||
@@ -255,2 +299,82 @@ assertEquals("Number of ldap groups in gitblit team model", 1, countLdapTeamsInUserManager()); | ||
| @Test | ||
| public void syncUpdateUsersAndGroupsAdminProperty() throws Exception { | ||
| // This test only makes sense if the authentication mode allows for synchronization. | ||
| assumeTrue(authMode == AuthMode.ANONYMOUS || authMode == AuthMode.DS_MANAGER); | ||
| settings.put(Keys.realm.ldap.synchronize, "true"); | ||
| ldap.sync(); | ||
| UserModel user = userManager.getUserModel("UserOne"); | ||
| assertNotNull(user); | ||
| assertFalse(user.canAdmin); | ||
| assertTrue(user.canAdmin()); | ||
| user = userManager.getUserModel("UserTwo"); | ||
| assertNotNull(user); | ||
| assertFalse(user.canAdmin); | ||
| assertTrue(user.canAdmin()); | ||
| user = userManager.getUserModel("UserThree"); | ||
| assertNotNull(user); | ||
| assertTrue(user.canAdmin); | ||
| assertTrue(user.canAdmin()); | ||
| user = userManager.getUserModel("UserFour"); | ||
| assertNotNull(user); | ||
| assertFalse(user.canAdmin); | ||
| assertFalse(user.canAdmin()); | ||
| TeamModel team = userManager.getTeamModel("Git_Admins"); | ||
| assertNotNull(team); | ||
| assertTrue(team.canAdmin); | ||
| team = userManager.getTeamModel("Git Admins"); | ||
| assertNotNull(team); | ||
| assertTrue(team.canAdmin); | ||
| team = userManager.getTeamModel("Git_Users"); | ||
| assertNotNull(team); | ||
| assertFalse(team.canAdmin); | ||
| } | ||
| @Test | ||
| public void syncNotUpdateUsersAndGroupsAdminProperty() throws Exception { | ||
| settings.put(Keys.realm.ldap.synchronize, "true"); | ||
| settings.put(Keys.realm.ldap.maintainTeams, "false"); | ||
| ldap.sync(); | ||
| UserModel user = userManager.getUserModel("UserOne"); | ||
| assertNotNull(user); | ||
| assertTrue(user.canAdmin); | ||
| assertTrue(user.canAdmin()); | ||
| user = userManager.getUserModel("UserTwo"); | ||
| assertNotNull(user); | ||
| assertFalse(user.canAdmin); | ||
| assertTrue(user.canAdmin()); | ||
| user = userManager.getUserModel("UserThree"); | ||
| assertNotNull(user); | ||
| assertFalse(user.canAdmin); | ||
| assertFalse(user.canAdmin()); | ||
| user = userManager.getUserModel("UserFour"); | ||
| assertNotNull(user); | ||
| assertFalse(user.canAdmin); | ||
| assertFalse(user.canAdmin()); | ||
| TeamModel team = userManager.getTeamModel("Git_Admins"); | ||
| assertNotNull(team); | ||
| assertFalse(team.canAdmin); | ||
| team = userManager.getTeamModel("Git Admins"); | ||
| assertNotNull(team); | ||
| assertTrue(team.canAdmin); | ||
| team = userManager.getTeamModel("Git_Users"); | ||
| assertNotNull(team); | ||
| assertFalse(team.canAdmin); | ||
| } | ||
| @Test | ||
| public void testAuthenticationManager() { | ||
@@ -261,3 +385,2 @@ UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null); | ||
| assertNotNull(userOneModel.getTeam("git_users")); | ||
| assertTrue(userOneModel.canAdmin); | ||
@@ -272,3 +395,2 @@ UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null); | ||
| assertNotNull(userTwoModel.getTeam("git admins")); | ||
| assertTrue(userTwoModel.canAdmin); | ||
@@ -279,8 +401,106 @@ UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray(), null); | ||
| assertNull(userThreeModel.getTeam("git_admins")); | ||
| UserModel userFourModel = auth.authenticate("UserFour", "userFourPassword".toCharArray(), null); | ||
| assertNotNull(userFourModel); | ||
| assertNotNull(userFourModel.getTeam("git_users")); | ||
| assertNull(userFourModel.getTeam("git_admins")); | ||
| assertNull(userFourModel.getTeam("git admins")); | ||
| } | ||
| @Test | ||
| public void testAuthenticationManagerAdminPropertyTeamsInLdap() { | ||
| UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null); | ||
| assertNotNull(userOneModel); | ||
| assertNotNull(userOneModel.getTeam("git_admins")); | ||
| assertNull(userOneModel.getTeam("git admins")); | ||
| assertNotNull(userOneModel.getTeam("git_users")); | ||
| assertFalse(userOneModel.canAdmin); | ||
| assertTrue(userOneModel.canAdmin()); | ||
| assertTrue(userOneModel.getTeam("git_admins").canAdmin); | ||
| assertFalse(userOneModel.getTeam("git_users").canAdmin); | ||
| UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null); | ||
| assertNull(userOneModelFailedAuth); | ||
| UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray(), null); | ||
| assertNotNull(userTwoModel); | ||
| assertNotNull(userTwoModel.getTeam("git_users")); | ||
| assertNull(userTwoModel.getTeam("git_admins")); | ||
| assertNotNull(userTwoModel.getTeam("git admins")); | ||
| assertFalse(userTwoModel.canAdmin); | ||
| assertTrue(userTwoModel.canAdmin()); | ||
| assertTrue(userTwoModel.getTeam("git admins").canAdmin); | ||
| assertFalse(userTwoModel.getTeam("git_users").canAdmin); | ||
| UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray(), null); | ||
| assertNotNull(userThreeModel); | ||
| assertNotNull(userThreeModel.getTeam("git_users")); | ||
| assertNull(userThreeModel.getTeam("git_admins")); | ||
| assertNull(userThreeModel.getTeam("git admins")); | ||
| assertTrue(userThreeModel.canAdmin); | ||
| assertTrue(userThreeModel.canAdmin()); | ||
| assertFalse(userThreeModel.getTeam("git_users").canAdmin); | ||
| UserModel userFourModel = auth.authenticate("UserFour", "userFourPassword".toCharArray(), null); | ||
| assertNotNull(userFourModel); | ||
| assertNotNull(userFourModel.getTeam("git_users")); | ||
| assertNull(userFourModel.getTeam("git_admins")); | ||
| assertNull(userFourModel.getTeam("git admins")); | ||
| assertFalse(userFourModel.canAdmin); | ||
| assertFalse(userFourModel.canAdmin()); | ||
| assertFalse(userFourModel.getTeam("git_users").canAdmin); | ||
| } | ||
| @Test | ||
| public void testAuthenticationManagerAdminPropertyTeamsNotInLdap() { | ||
| settings.put(Keys.realm.ldap.maintainTeams, "false"); | ||
| UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null); | ||
| assertNotNull(userOneModel); | ||
| assertNotNull(userOneModel.getTeam("git_admins")); | ||
| assertNull(userOneModel.getTeam("git admins")); | ||
| assertNotNull(userOneModel.getTeam("git_users")); | ||
| assertTrue(userOneModel.canAdmin); | ||
| assertTrue(userOneModel.canAdmin()); | ||
| assertFalse(userOneModel.getTeam("git_admins").canAdmin); | ||
| assertFalse(userOneModel.getTeam("git_users").canAdmin); | ||
| UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null); | ||
| assertNull(userOneModelFailedAuth); | ||
| UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray(), null); | ||
| assertNotNull(userTwoModel); | ||
| assertNotNull(userTwoModel.getTeam("git_users")); | ||
| assertNull(userTwoModel.getTeam("git_admins")); | ||
| assertNotNull(userTwoModel.getTeam("git admins")); | ||
| assertFalse(userTwoModel.canAdmin); | ||
| assertTrue(userTwoModel.canAdmin()); | ||
| assertTrue(userTwoModel.getTeam("git admins").canAdmin); | ||
| assertFalse(userTwoModel.getTeam("git_users").canAdmin); | ||
| UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray(), null); | ||
| assertNotNull(userThreeModel); | ||
| assertNotNull(userThreeModel.getTeam("git_users")); | ||
| assertNull(userThreeModel.getTeam("git_admins")); | ||
| assertNull(userThreeModel.getTeam("git admins")); | ||
| assertFalse(userThreeModel.canAdmin); | ||
| assertFalse(userThreeModel.canAdmin()); | ||
| assertFalse(userThreeModel.getTeam("git_users").canAdmin); | ||
| UserModel userFourModel = auth.authenticate("UserFour", "userFourPassword".toCharArray(), null); | ||
| assertNotNull(userFourModel); | ||
| assertNotNull(userFourModel.getTeam("git_users")); | ||
| assertNull(userFourModel.getTeam("git_admins")); | ||
| assertNull(userFourModel.getTeam("git admins")); | ||
| assertFalse(userFourModel.canAdmin); | ||
| assertFalse(userFourModel.canAdmin()); | ||
| assertFalse(userFourModel.getTeam("git_users").canAdmin); | ||
| } | ||
| @Test | ||
| public void testBindWithUser() { | ||
| settings.put(Keys.realm.ldap.bindpattern, "CN=${username},OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"); | ||
| // This test only makes sense if the user is not prevented from reading users and teams. | ||
| assumeTrue(authMode != AuthMode.DS_MANAGER); | ||
| settings.put(Keys.realm.ldap.bindpattern, "CN=${username},OU=US," + ACCOUNT_BASE); | ||
| settings.put(Keys.realm.ldap.username, ""); | ||
@@ -296,2 +516,10 @@ settings.put(Keys.realm.ldap.password, ""); | ||
| private int countLdapUsersInUserManager() { | ||
@@ -298,0 +526,0 @@ int ldapAccountCount = 0; |
@@ -18,4 +18,10 @@ /* | ||
| 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; | ||
@@ -43,2 +49,68 @@ | ||
| } | ||
| } | ||
| @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:jane@doe\">jane@doe</a> yesterday." | ||
| + "</p>" | ||
| + "<p>" + String.format(mentionHtml, "jack.daniels") + " can vote for " | ||
| + "<a href=\"mailto:john@wayne.name\">john@wayne.name</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; | ||
| } | ||
| } |
@@ -48,5 +48,5 @@ /* | ||
| repository.close(); | ||
| assertEquals("No author metrics found!", 9, byEmail.size()); | ||
| assertEquals("No author metrics found!", 8, byName.size()); | ||
| assertEquals("No author metrics found!", GitBlitSuite.helloworldSettings.getInteger(HelloworldKeys.users.byEmail, -1), byEmail.size()); | ||
| assertEquals("No author metrics found!", GitBlitSuite.helloworldSettings.getInteger(HelloworldKeys.users.byName, -1), byName.size()); | ||
| } | ||
| } |
@@ -18,2 +18,4 @@ /* | ||
| import org.junit.Ignore; | ||
| import com.gitblit.IStoredSettings; | ||
@@ -43,2 +45,3 @@ import com.gitblit.Keys; | ||
| */ | ||
| @Ignore("Redis tests currently broken, no service running.") | ||
| public class RedisTicketServiceTest extends TicketServiceTest { | ||
@@ -71,3 +74,3 @@ | ||
| RedisTicketService service = new RedisTicketService( | ||
| RedisTicketService service = (RedisTicketService) new RedisTicketService( | ||
| runtimeManager, | ||
@@ -74,0 +77,0 @@ pluginManager, |
@@ -71,2 +71,7 @@ /* | ||
| public static void stopGitblit() throws Exception { | ||
| //clean up the "A-Team" if left over | ||
| TeamModel aTeam = new TeamModel("A-Team"); | ||
| aTeam.addRepositoryPermission("helloworld.git"); | ||
| RpcUtils.deleteTeam(aTeam, GitBlitSuite.url, GitBlitSuite.account, GitBlitSuite.password.toCharArray()); | ||
| if (started.get()) { | ||
@@ -269,7 +274,13 @@ GitBlitSuite.stopGitblit(); | ||
| public void testTeamAdministration() throws IOException { | ||
| //clean up the "A-Team" left over from previous run, if any | ||
| TeamModel aTeam = new TeamModel("A-Team"); | ||
| aTeam.addRepositoryPermission("helloworld.git"); | ||
| RpcUtils.deleteTeam(aTeam, url, account, password.toCharArray()); | ||
| List<TeamModel> teams = RpcUtils.getTeams(url, account, password.toCharArray()); | ||
| assertEquals(1, teams.size()); | ||
| //should be just the admins team | ||
| assertEquals("In addition to 'admins', too many left-over team(s) in Gitblit server: " + teams, 1, teams.size()); | ||
| // Create the A-Team | ||
| TeamModel aTeam = new TeamModel("A-Team"); | ||
| aTeam = new TeamModel("A-Team"); | ||
| aTeam.users.add("admin"); | ||
@@ -276,0 +287,0 @@ aTeam.addRepositoryPermission("helloworld.git"); |
@@ -47,5 +47,5 @@ /* | ||
| SshClient client = getClient(); | ||
| ClientSession session = client.connect(username, "localhost", GitBlitSuite.sshPort).await().getSession(); | ||
| ClientSession session = client.connect(username, "localhost", GitBlitSuite.sshPort).verify().getSession(); | ||
| session.addPublicKeyIdentity(rwKeyPair); | ||
| assertTrue(session.auth().await().isSuccess()); | ||
| assertTrue(session.auth().await()); | ||
| } | ||
@@ -68,2 +68,3 @@ | ||
| RepositoryModel model = repositories().getRepositoryModel("ticgit.git"); | ||
| assertNotNull("Could not get repository modle for ticgit.git", model); | ||
| model.accessRestriction = AccessRestrictionType.CLONE; | ||
@@ -70,0 +71,0 @@ model.authorizationControl = AuthorizationControl.NAMED; |
@@ -40,3 +40,3 @@ /* | ||
| assertEquals(String.format("There are %d keys!", keys.size()), 2, keys.size()); | ||
| assertEquals(keys.get(0).getRawData() + "\n" + keys.get(1).getRawData(), result); | ||
| assertEquals(String.format("%s%n%s", keys.get(0).getRawData(), keys.get(1).getRawData()), result); | ||
| } | ||
@@ -68,5 +68,5 @@ | ||
| testSshCommand("keys ls -L"); | ||
| assertTrue("Authentication worked without a public key?!", false); | ||
| fail("Authentication worked without a public key?!"); | ||
| } catch (AssertionError e) { | ||
| assertTrue(true); | ||
| // expected | ||
| } | ||
@@ -82,5 +82,5 @@ } | ||
| testSshCommand("keys ls -L"); | ||
| assertTrue("Authentication worked without a public key?!", false); | ||
| fail("Authentication worked without a public key?!"); | ||
| } catch (AssertionError e) { | ||
| assertTrue(true); | ||
| // expected | ||
| } | ||
@@ -102,5 +102,5 @@ } | ||
| sb.append(sk.getRawData()); | ||
| sb.append('\n'); | ||
| sb.append(System.getProperty("line.separator", "\n")); | ||
| } | ||
| sb.setLength(sb.length() - 1); | ||
| sb.setLength(sb.length() - System.getProperty("line.separator", "\n").length()); | ||
| assertEquals(sb.toString(), result); | ||
@@ -107,0 +107,0 @@ } |
@@ -24,12 +24,22 @@ /* | ||
| import java.net.SocketAddress; | ||
| import java.security.GeneralSecurityException; | ||
| import java.security.KeyPair; | ||
| import java.security.KeyPairGenerator; | ||
| import java.security.PublicKey; | ||
| import java.util.EnumSet; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import org.apache.sshd.client.ServerKeyVerifier; | ||
| import org.apache.sshd.client.SshClient; | ||
| import org.apache.sshd.client.channel.ClientChannel; | ||
| import org.apache.sshd.client.channel.ClientChannelEvent; | ||
| import org.apache.sshd.client.config.keys.ClientIdentityLoader; | ||
| import org.apache.sshd.client.future.AuthFuture; | ||
| import org.apache.sshd.client.keyverifier.ServerKeyVerifier; | ||
| import org.apache.sshd.client.session.ClientSession; | ||
| import org.apache.sshd.common.config.keys.FilePasswordProvider; | ||
| import org.apache.sshd.common.util.SecurityUtils; | ||
| import org.eclipse.jgit.lib.Config; | ||
| import org.eclipse.jgit.storage.file.FileBasedConfig; | ||
| import org.eclipse.jgit.util.FS; | ||
| import org.eclipse.jgit.util.SystemReader; | ||
| import org.junit.After; | ||
@@ -61,2 +71,53 @@ import org.junit.AfterClass; | ||
| started.set(GitBlitSuite.startGitblit()); | ||
| final SystemReader dsr = SystemReader.getInstance(); | ||
| SystemReader.setInstance(new SystemReader() | ||
| { | ||
| final SystemReader defaultsr = dsr; | ||
| @Override | ||
| public String getHostname() | ||
| { | ||
| return defaultsr.getHostname(); | ||
| } | ||
| @Override | ||
| public String getenv(String variable) | ||
| { | ||
| if ("GIT_SSH".equalsIgnoreCase(variable)) { | ||
| return null; | ||
| } | ||
| return defaultsr.getenv(variable); | ||
| } | ||
| @Override | ||
| public String getProperty(String key) | ||
| { | ||
| return defaultsr.getProperty(key); | ||
| } | ||
| @Override | ||
| public FileBasedConfig openUserConfig(Config parent, FS fs) | ||
| { | ||
| return defaultsr.openUserConfig(parent, fs); | ||
| } | ||
| @Override | ||
| public FileBasedConfig openSystemConfig(Config parent, FS fs) | ||
| { | ||
| return defaultsr.openSystemConfig(parent, fs); | ||
| } | ||
| @Override | ||
| public long getCurrentTime() | ||
| { | ||
| return defaultsr.getCurrentTime(); | ||
| } | ||
| @Override | ||
| public int getTimezone(long when) | ||
| { | ||
| return defaultsr.getTimezone(when); | ||
| } | ||
| }); | ||
| } | ||
@@ -101,2 +162,12 @@ | ||
| SshClient client = SshClient.setUpDefaultClient(); | ||
| client.setClientIdentityLoader(new ClientIdentityLoader() { // Ignore the files under ~/.ssh | ||
| @Override | ||
| public boolean isValidLocation(String location) throws IOException { | ||
| return true; | ||
| } | ||
| @Override | ||
| public KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException { | ||
| return null; | ||
| } | ||
| }); | ||
| client.setServerKeyVerifier(new ServerKeyVerifier() { | ||
@@ -118,5 +189,7 @@ @Override | ||
| SshClient client = getClient(); | ||
| ClientSession session = client.connect(username, "localhost", GitBlitSuite.sshPort).await().getSession(); | ||
| ClientSession session = client.connect(username, "localhost", GitBlitSuite.sshPort).verify().getSession(); | ||
| session.addPublicKeyIdentity(rwKeyPair); | ||
| assertTrue(session.auth().await().isSuccess()); | ||
| AuthFuture authFuture = session.auth(); | ||
| assertTrue(authFuture.await()); | ||
| assertTrue(authFuture.isSuccess()); | ||
@@ -138,3 +211,3 @@ ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_EXEC, cmd); | ||
| channel.waitFor(ClientChannel.CLOSED, 0); | ||
| channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED, ClientChannelEvent.EOF), 0); | ||
@@ -141,0 +214,0 @@ String result = out.toString().trim(); |
@@ -60,2 +60,3 @@ /* | ||
| static final String repoName = "TicketReferenceTest.git"; | ||
| static File workingCopy = new File(GitBlitSuite.REPOSITORIES, "working/TicketReferenceTest.git-wc"); | ||
@@ -77,3 +78,3 @@ | ||
| public static void configure() throws Exception { | ||
| File repositoryName = new File("TicketReferenceTest.git");; | ||
| File repositoryName = new File(repoName); | ||
@@ -84,3 +85,3 @@ GitBlitSuite.close(repositoryName); | ||
| } | ||
| repo = new RepositoryModel("TicketReferenceTest.git", null, null, null); | ||
| repo = new RepositoryModel(repoName, null, null, null); | ||
@@ -147,3 +148,19 @@ if (gitblit().hasRepository(repo.name)) { | ||
| public static void cleanup() throws Exception { | ||
| //clean up the test user account if left over | ||
| if (gitblit().getUserModel(user.username) != null) { | ||
| gitblit().deleteUser(user.username); | ||
| } | ||
| GitBlitSuite.close(git); | ||
| //clean up the TicketReferenceTest.git repo | ||
| File repositoryName = new File(repoName); | ||
| GitBlitSuite.close(repositoryName); | ||
| if (repositoryName.exists()) { | ||
| FileUtils.delete(repositoryName, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| RepositoryModel repo = new RepositoryModel(repoName, null, null, null); | ||
| if (gitblit().hasRepository(repo.name)) { | ||
| gitblit().deleteRepositoryModel(repo); | ||
| } | ||
| } | ||
@@ -150,0 +167,0 @@ |
@@ -24,4 +24,4 @@ /* | ||
| import org.apache.commons.io.FileUtils; | ||
| import org.bouncycastle.util.Arrays; | ||
| import org.eclipse.jgit.util.FileUtils; | ||
| import org.junit.After; | ||
@@ -69,3 +69,5 @@ import org.junit.Before; | ||
| if (deleteAll) { | ||
| FileUtils.deleteDirectory(dir); | ||
| if (dir.exists()) { | ||
| FileUtils.delete(dir, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, getRepository().name).close(); | ||
@@ -72,0 +74,0 @@ } |
@@ -19,11 +19,9 @@ /* | ||
| import java.io.File; | ||
| import java.util.Date; | ||
| import java.io.IOException; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import org.apache.commons.io.FileUtils; | ||
| import org.bouncycastle.util.Arrays; | ||
| import org.eclipse.jgit.lib.Repository; | ||
| import org.eclipse.jgit.util.FileUtils; | ||
| import org.junit.After; | ||
| import org.junit.AfterClass; | ||
| import org.junit.Before; | ||
@@ -44,22 +42,12 @@ import org.junit.Test; | ||
| import com.gitblit.manager.UserManager; | ||
| import com.gitblit.models.Mailing; | ||
| import com.gitblit.models.RepositoryModel; | ||
| import com.gitblit.models.TicketModel; | ||
| import com.gitblit.models.TicketModel.Attachment; | ||
| import com.gitblit.models.TicketModel.Change; | ||
| import com.gitblit.models.TicketModel.Field; | ||
| import com.gitblit.models.TicketModel.Patchset; | ||
| import com.gitblit.models.TicketModel.Priority; | ||
| import com.gitblit.models.TicketModel.Severity; | ||
| import com.gitblit.models.TicketModel.Status; | ||
| import com.gitblit.models.TicketModel.Type; | ||
| import com.gitblit.tests.mock.MemorySettings; | ||
| import com.gitblit.tickets.ITicketService; | ||
| import com.gitblit.tickets.ITicketService.TicketFilter; | ||
| import com.gitblit.tickets.QueryResult; | ||
| import com.gitblit.tickets.TicketIndexer.Lucene; | ||
| import com.gitblit.tickets.BranchTicketService; | ||
| import com.gitblit.tickets.TicketLabel; | ||
| import com.gitblit.tickets.TicketMilestone; | ||
| import com.gitblit.tickets.TicketNotifier; | ||
| import com.gitblit.utils.JGitUtils; | ||
@@ -75,3 +63,3 @@ import com.gitblit.utils.XssFilter; | ||
| private ITicketService service; | ||
| final String repoName = "UITicketTest.git"; | ||
| static final String repoName = "UITicketTest.git"; | ||
| final RepositoryModel repo = new RepositoryModel(repoName, null, null, null); | ||
@@ -89,3 +77,3 @@ | ||
| BranchTicketService service = new BranchTicketService( | ||
| BranchTicketService service = (BranchTicketService) new BranchTicketService( | ||
| runtimeManager, | ||
@@ -106,3 +94,5 @@ pluginManager, | ||
| if (deleteAll) { | ||
| FileUtils.deleteDirectory(dir); | ||
| if (dir.exists()) { | ||
| FileUtils.delete(dir, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repoName).close(); | ||
@@ -122,2 +112,11 @@ } | ||
| @AfterClass | ||
| public static void deleteUITicketTestRepo() throws IOException { | ||
| //delete the UITicketTest.git folder, at end of the test | ||
| File dir = new File(GitBlitSuite.REPOSITORIES, repoName); | ||
| if (dir.exists()) { | ||
| FileUtils.delete(dir, FileUtils.RECURSIVE | FileUtils.RETRY); | ||
| } | ||
| } | ||
| @Before | ||
@@ -157,2 +156,2 @@ public void setup() throws Exception { | ||
| } | ||
| } |
@@ -13,3 +13,3 @@ [user "admin"] | ||
| accountType = LDAP | ||
| role = "#admin" | ||
| role = "#none" | ||
| [user "userfive"] | ||
@@ -35,3 +35,3 @@ password = "#externalAccount" | ||
| accountType = LDAP | ||
| role = "#admin" | ||
| role = "#none" | ||
| [user "basic"] | ||
@@ -68,4 +68,4 @@ password = MD5:f17aaabc20bfe045075927934fed52d2 | ||
| [team "Git Admins"] | ||
| role = "#none" | ||
| role = "#admin" | ||
| accountType = LOCAL | ||
| user = usertwo |
| /* | ||
| * Copyright 2012 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.authority; | ||
| import java.awt.Color; | ||
| import java.awt.EventQueue; | ||
| import java.awt.FontMetrics; | ||
| import java.awt.Graphics2D; | ||
| import java.awt.SplashScreen; | ||
| import java.io.File; | ||
| import java.io.FileFilter; | ||
| import java.io.IOException; | ||
| import java.lang.reflect.Method; | ||
| import java.net.URL; | ||
| import java.net.URLClassLoader; | ||
| import java.text.MessageFormat; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import com.gitblit.Constants; | ||
| import com.gitblit.client.Translation; | ||
| /** | ||
| * Downloads dependencies and launches Gitblit Authority. | ||
| * | ||
| * @author James Moger | ||
| * | ||
| */ | ||
| public class Launcher { | ||
| public static final boolean DEBUG = false; | ||
| /** | ||
| * Parameters of the method to add an URL to the System classes. | ||
| */ | ||
| private static final Class<?>[] PARAMETERS = new Class[] { URL.class }; | ||
| public static void main(String[] args) { | ||
| final SplashScreen splash = SplashScreen.getSplashScreen(); | ||
| File libFolder = new File("ext"); | ||
| List<File> jars = findJars(libFolder.getAbsoluteFile()); | ||
| // sort the jars by name and then reverse the order so the newer version | ||
| // of the library gets loaded in the event that this is an upgrade | ||
| Collections.sort(jars); | ||
| Collections.reverse(jars); | ||
| for (File jar : jars) { | ||
| try { | ||
| updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "..."); | ||
| addJarFile(jar); | ||
| } catch (IOException e) { | ||
| } | ||
| } | ||
| updateSplash(splash, Translation.get("gb.starting") + " Gitblit Authority..."); | ||
| GitblitAuthority.main(args); | ||
| } | ||
| private static void updateSplash(final SplashScreen splash, final String string) { | ||
| if (splash == null) { | ||
| return; | ||
| } | ||
| try { | ||
| EventQueue.invokeAndWait(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| Graphics2D g = splash.createGraphics(); | ||
| if (g != null) { | ||
| // Splash is 320x120 | ||
| FontMetrics fm = g.getFontMetrics(); | ||
| // paint startup status | ||
| g.setColor(Color.darkGray); | ||
| int h = fm.getHeight() + fm.getMaxDescent(); | ||
| int x = 5; | ||
| int y = 115; | ||
| int w = 320 - 2 * x; | ||
| g.fillRect(x, y - h, w, h); | ||
| g.setColor(Color.lightGray); | ||
| g.drawRect(x, y - h, w, h); | ||
| g.setColor(Color.WHITE); | ||
| int xw = fm.stringWidth(string); | ||
| g.drawString(string, x + ((w - xw) / 2), y - 5); | ||
| // paint version | ||
| String ver = "v" + Constants.getVersion(); | ||
| int vw = g.getFontMetrics().stringWidth(ver); | ||
| g.drawString(ver, 320 - vw - 5, 34); | ||
| g.dispose(); | ||
| splash.update(); | ||
| } | ||
| } | ||
| }); | ||
| } catch (Throwable t) { | ||
| t.printStackTrace(); | ||
| } | ||
| } | ||
| public static List<File> findJars(File folder) { | ||
| List<File> jars = new ArrayList<File>(); | ||
| if (folder.exists()) { | ||
| File[] libs = folder.listFiles(new FileFilter() { | ||
| @Override | ||
| public boolean accept(File file) { | ||
| return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar"); | ||
| } | ||
| }); | ||
| if (libs != null && libs.length > 0) { | ||
| jars.addAll(Arrays.asList(libs)); | ||
| if (DEBUG) { | ||
| for (File jar : jars) { | ||
| System.out.println("found " + jar); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return jars; | ||
| } | ||
| /** | ||
| * Adds a file to the classpath | ||
| * | ||
| * @param f | ||
| * the file to be added | ||
| * @throws IOException | ||
| */ | ||
| public static void addJarFile(File f) throws IOException { | ||
| if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) { | ||
| // don't add source or javadoc jars to runtime classpath | ||
| return; | ||
| } | ||
| URL u = f.toURI().toURL(); | ||
| if (DEBUG) { | ||
| System.out.println("load=" + u.toExternalForm()); | ||
| } | ||
| URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); | ||
| Class<?> sysclass = URLClassLoader.class; | ||
| try { | ||
| Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS); | ||
| method.setAccessible(true); | ||
| method.invoke(sysloader, new Object[] { u }); | ||
| } catch (Throwable t) { | ||
| throw new IOException(MessageFormat.format( | ||
| "Error, could not add {0} to system classloader", f.getPath()), t); | ||
| } | ||
| } | ||
| } |
| /* | ||
| * Copyright 2011 gitblit.com. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.gitblit.client; | ||
| import java.awt.Color; | ||
| import java.awt.EventQueue; | ||
| import java.awt.FontMetrics; | ||
| import java.awt.Graphics2D; | ||
| import java.awt.SplashScreen; | ||
| import java.io.File; | ||
| import java.io.FileFilter; | ||
| import java.io.IOException; | ||
| import java.lang.reflect.Method; | ||
| import java.net.URL; | ||
| import java.net.URLClassLoader; | ||
| import java.text.MessageFormat; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import com.gitblit.Constants; | ||
| /** | ||
| * Downloads dependencies and launches Gitblit Manager. | ||
| * | ||
| * @author James Moger | ||
| * | ||
| */ | ||
| public class GitblitManagerLauncher { | ||
| public static final boolean DEBUG = false; | ||
| /** | ||
| * Parameters of the method to add an URL to the System classes. | ||
| */ | ||
| private static final Class<?>[] PARAMETERS = new Class[] { URL.class }; | ||
| public static void main(String[] args) { | ||
| final SplashScreen splash = SplashScreen.getSplashScreen(); | ||
| File libFolder = new File("ext"); | ||
| List<File> jars = findJars(libFolder.getAbsoluteFile()); | ||
| // sort the jars by name and then reverse the order so the newer version | ||
| // of the library gets loaded in the event that this is an upgrade | ||
| Collections.sort(jars); | ||
| Collections.reverse(jars); | ||
| for (File jar : jars) { | ||
| try { | ||
| updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "..."); | ||
| addJarFile(jar); | ||
| } catch (IOException e) { | ||
| } | ||
| } | ||
| updateSplash(splash, Translation.get("gb.starting") + " Gitblit Manager..."); | ||
| GitblitManager.main(args); | ||
| } | ||
| private static void updateSplash(final SplashScreen splash, final String string) { | ||
| if (splash == null) { | ||
| return; | ||
| } | ||
| try { | ||
| EventQueue.invokeAndWait(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| Graphics2D g = splash.createGraphics(); | ||
| if (g != null) { | ||
| // Splash is 320x120 | ||
| FontMetrics fm = g.getFontMetrics(); | ||
| // paint startup status | ||
| g.setColor(Color.darkGray); | ||
| int h = fm.getHeight() + fm.getMaxDescent(); | ||
| int x = 5; | ||
| int y = 115; | ||
| int w = 320 - 2 * x; | ||
| g.fillRect(x, y - h, w, h); | ||
| g.setColor(Color.lightGray); | ||
| g.drawRect(x, y - h, w, h); | ||
| g.setColor(Color.WHITE); | ||
| int xw = fm.stringWidth(string); | ||
| g.drawString(string, x + ((w - xw) / 2), y - 5); | ||
| // paint version | ||
| String ver = "v" + Constants.getVersion(); | ||
| int vw = g.getFontMetrics().stringWidth(ver); | ||
| g.drawString(ver, 320 - vw - 5, 34); | ||
| g.dispose(); | ||
| splash.update(); | ||
| } | ||
| } | ||
| }); | ||
| } catch (Throwable t) { | ||
| t.printStackTrace(); | ||
| } | ||
| } | ||
| public static List<File> findJars(File folder) { | ||
| List<File> jars = new ArrayList<File>(); | ||
| if (folder.exists()) { | ||
| File[] libs = folder.listFiles(new FileFilter() { | ||
| @Override | ||
| public boolean accept(File file) { | ||
| return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar"); | ||
| } | ||
| }); | ||
| if (libs != null && libs.length > 0) { | ||
| jars.addAll(Arrays.asList(libs)); | ||
| if (DEBUG) { | ||
| for (File jar : jars) { | ||
| System.out.println("found " + jar); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return jars; | ||
| } | ||
| /** | ||
| * Adds a file to the classpath | ||
| * | ||
| * @param f | ||
| * the file to be added | ||
| * @throws IOException | ||
| */ | ||
| public static void addJarFile(File f) throws IOException { | ||
| if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) { | ||
| // don't add source or javadoc jars to runtime classpath | ||
| return; | ||
| } | ||
| URL u = f.toURI().toURL(); | ||
| if (DEBUG) { | ||
| System.out.println("load=" + u.toExternalForm()); | ||
| } | ||
| URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); | ||
| Class<?> sysclass = URLClassLoader.class; | ||
| try { | ||
| Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS); | ||
| method.setAccessible(true); | ||
| method.invoke(sysloader, new Object[] { u }); | ||
| } catch (Throwable t) { | ||
| throw new IOException(MessageFormat.format( | ||
| "Error, could not add {0} to system classloader", f.getPath()), t); | ||
| } | ||
| } | ||
| } |
| /* | ||
| * Copyright 2011 gitblit.com. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.gitblit; | ||
| import java.io.File; | ||
| import java.io.FileFilter; | ||
| import java.io.IOException; | ||
| import java.lang.reflect.Method; | ||
| import java.net.URL; | ||
| import java.net.URLClassLoader; | ||
| import java.security.ProtectionDomain; | ||
| import java.text.MessageFormat; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| /** | ||
| * Launch helper class that adds all jars found in the local "lib" & "ext" | ||
| * folders and then calls the application main. Using this technique we do not | ||
| * have to specify a classpath and we can dynamically add jars to the | ||
| * distribution. | ||
| * | ||
| * @author James Moger | ||
| * | ||
| */ | ||
| public class Launcher { | ||
| public static final boolean DEBUG = false; | ||
| /** | ||
| * Parameters of the method to add an URL to the System classes. | ||
| */ | ||
| private static final Class<?>[] PARAMETERS = new Class[] { URL.class }; | ||
| public static void main(String[] args) { | ||
| if (DEBUG) { | ||
| System.out.println("jcp=" + System.getProperty("java.class.path")); | ||
| ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain(); | ||
| System.out.println("launcher=" | ||
| + protectionDomain.getCodeSource().getLocation().toExternalForm()); | ||
| } | ||
| // Load the JARs in the lib and ext folder | ||
| String[] folders = new String[] { "lib", "ext" }; | ||
| List<File> jars = new ArrayList<File>(); | ||
| for (String folder : folders) { | ||
| if (folder == null) { | ||
| continue; | ||
| } | ||
| File libFolder = new File(folder); | ||
| if (!libFolder.exists()) { | ||
| continue; | ||
| } | ||
| List<File> found = findJars(libFolder.getAbsoluteFile()); | ||
| jars.addAll(found); | ||
| } | ||
| // sort the jars by name and then reverse the order so the newer version | ||
| // of the library gets loaded in the event that this is an upgrade | ||
| Collections.sort(jars); | ||
| Collections.reverse(jars); | ||
| if (jars.size() == 0) { | ||
| for (String folder : folders) { | ||
| File libFolder = new File(folder); | ||
| // this is a test of adding a comment | ||
| // more really interesting things | ||
| System.err.println("Failed to find any JARs in " + libFolder.getPath()); | ||
| } | ||
| System.exit(-1); | ||
| } else { | ||
| for (File jar : jars) { | ||
| try { | ||
| jar.canRead(); | ||
| addJarFile(jar); | ||
| } catch (Throwable t) { | ||
| t.printStackTrace(); | ||
| } | ||
| } | ||
| } | ||
| // Start Server | ||
| GitBlitServer.main(args); | ||
| } | ||
| public static List<File> findJars(File folder) { | ||
| List<File> jars = new ArrayList<File>(); | ||
| if (folder.exists()) { | ||
| File[] libs = folder.listFiles(new FileFilter() { | ||
| @Override | ||
| public boolean accept(File file) { | ||
| return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar"); | ||
| } | ||
| }); | ||
| if (libs != null && libs.length > 0) { | ||
| jars.addAll(Arrays.asList(libs)); | ||
| if (DEBUG) { | ||
| for (File jar : jars) { | ||
| System.out.println("found " + jar); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return jars; | ||
| } | ||
| /** | ||
| * Adds a file to the classpath | ||
| * | ||
| * @param f | ||
| * the file to be added | ||
| * @throws IOException | ||
| */ | ||
| public static void addJarFile(File f) throws IOException { | ||
| if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) { | ||
| // don't add source or javadoc jars to runtime classpath | ||
| return; | ||
| } | ||
| URL u = f.toURI().toURL(); | ||
| if (DEBUG) { | ||
| System.out.println("load=" + u.toExternalForm()); | ||
| } | ||
| URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); | ||
| Class<?> sysclass = URLClassLoader.class; | ||
| try { | ||
| Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS); | ||
| method.setAccessible(true); | ||
| method.invoke(sysloader, new Object[] { u }); | ||
| } catch (Throwable t) { | ||
| throw new IOException(MessageFormat.format( | ||
| "Error, could not add {0} to system classloader", f.getPath()), t); | ||
| } | ||
| } | ||
| } |
| /* | ||
| * Copyright 2013 gitblit.com. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.gitblit.tests; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.util.List; | ||
| import org.eclipse.jgit.lib.Repository; | ||
| import org.eclipse.jgit.lib.RepositoryCache.FileKey; | ||
| import org.eclipse.jgit.storage.file.FileRepositoryBuilder; | ||
| import org.eclipse.jgit.util.FS; | ||
| import org.junit.Test; | ||
| import com.gitblit.models.RefLogEntry; | ||
| import com.gitblit.utils.RefLogUtils; | ||
| public class PushLogTest extends GitblitUnitTest { | ||
| @Test | ||
| public void testPushLog() throws IOException { | ||
| String name = "~james/helloworld.git"; | ||
| File gitDir = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, name), FS.DETECTED); | ||
| Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build(); | ||
| List<RefLogEntry> pushes = RefLogUtils.getRefLog(name, repository); | ||
| GitBlitSuite.close(repository); | ||
| } | ||
| } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet