.classpath
@@ -9,8 +9,8 @@ <classpathentry kind="lib" path="ext/slf4j-log4j12-1.6.6.jar" sourcepath="ext/src/slf4j-log4j12-1.6.6-sources.jar" /> <classpathentry kind="lib" path="ext/mail-1.4.3.jar" sourcepath="ext/src/mail-1.4.3-sources.jar" /> <classpathentry kind="lib" path="ext/javax.servlet-api-3.0.1.jar" sourcepath="ext/src/javax.servlet-api-3.0.1-sources.jar" /> <classpathentry kind="lib" path="ext/jetty-webapp-7.6.7.v20120910.jar" sourcepath="ext/src/jetty-webapp-7.6.7.v20120910-sources.jar" /> <classpathentry kind="lib" path="ext/jetty-ajp-7.6.7.v20120910.jar" sourcepath="ext/src/jetty-ajp-7.6.7.v20120910-sources.jar" /> <classpathentry kind="lib" path="ext/jetty-webapp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-webapp-7.6.8.v20121106-sources.jar" /> <classpathentry kind="lib" path="ext/jetty-ajp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-ajp-7.6.8.v20121106-sources.jar" /> <classpathentry kind="lib" path="ext/wicket-1.4.21.jar" sourcepath="ext/src/wicket-1.4.21-sources.jar" /> <classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.21.jar" sourcepath="ext/src/wicket-auth-roles-1.4.21-sources.jar" /> <classpathentry kind="lib" path="ext/wicket-extensions-1.4.21.jar" sourcepath="ext/src/wicket-extensions-1.4.21-sources.jar" /> @@ -20,7 +20,7 @@ <classpathentry kind="lib" path="ext/lucene-memory-3.6.1.jar" sourcepath="ext/src/lucene-memory-3.6.1-sources.jar" /> <classpathentry kind="lib" path="ext/lucene-queries-3.6.1.jar" sourcepath="ext/src/lucene-queries-3.6.1-sources.jar" /> <classpathentry kind="lib" path="ext/jakarta-regexp-1.4.jar" /> <classpathentry kind="lib" path="ext/markdownpapers-core-1.2.7.jar" sourcepath="ext/src/markdownpapers-core-1.2.7-sources.jar" /> <classpathentry kind="lib" path="ext/markdownpapers-core-1.3.2.jar" sourcepath="ext/src/markdownpapers-core-1.3.2-sources.jar" /> <classpathentry kind="lib" path="ext/org.eclipse.jgit-2.1.0.201209190230-r.jar" sourcepath="ext/src/org.eclipse.jgit-2.1.0.201209190230-r-sources.jar" /> <classpathentry kind="lib" path="ext/jsch-0.1.44-1.jar" sourcepath="ext/src/jsch-0.1.44-1-sources.jar" /> <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-2.1.0.201209190230-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-2.1.0.201209190230-r-sources.jar" /> build.xml
@@ -171,7 +171,7 @@ Build Gitblit GO ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildGO" depends="compile" description="Build Gitblit GO distribution"> <target name="buildGO" depends="compile,buildAuthority" description="Build Gitblit GO distribution"> <echo>Building Gitblit GO ${gb.version}</echo> @@ -768,6 +768,7 @@ <resource file="${basedir}/resources/user_16x16.png" /> <resource file="${basedir}/resources/users_16x16.png" /> <resource file="${basedir}/resources/rosette_16x16.png" /> <resource file="${basedir}/resources/rosette_32x32.png" /> <resource file="${basedir}/resources/vcard_16x16.png" /> <resource file="${basedir}/resources/settings_16x16.png" /> <resource file="${basedir}/resources/settings_32x32.png" /> distrib/gitblit.properties
@@ -504,6 +504,12 @@ # SINCE 0.9.0 web.allowLuceneIndexing = true # Allows an authenticated user to create forks of a repository # # set this to false if you want to disable all fork controls on the web site # web.allowForking = true # Controls the length of shortened commit hash ids # # SINCE 1.2.0 @@ -515,6 +521,14 @@ # # SINCE 0.8.0 web.allowFlashCopyToClipboard = true # Default maximum number of commits that a repository may contribute to the # activity page, regardless of the selected duration. This setting may be valuable # for an extremely busy server. This value may also be configed per-repository # in Edit Repository. 0 disables this throttle. # # SINCE 1.2.0 web.maxActivityCommits = 0 # Default number of entries to include in RSS Syndication links # @@ -1141,6 +1155,13 @@ # RESTART REQUIRED server.ajpBindInterface = localhost # Alias of certificate to use for https/SSL serving. If blank the first # certificate found in the keystore will be used. # # SINCE 1.2.0 # RESTART REQUIRED server.certificateAlias = localhost # Password for SSL keystore. # Keystore password and certificate password must match. # This is provided for convenience, its probably more secure to set this value @@ -1179,4 +1200,4 @@ # # SINCE 0.5.0 # RESTART REQUIRED server.shutdownPort = 8081 server.shutdownPort = 8081 docs/01_features.mkd
@@ -17,7 +17,7 @@ - Optional feature to allow users to create personal repositories - Optional feature to fork a repository to a personal repository - Optional feature to create a repository on push - Experimental built-in Garbage Collection - *Experimental* built-in Garbage Collection - Ability to federate with one or more other Gitblit instances - RSS/JSON RPC interface - Java/Swing Gitblit Manager tool @@ -59,8 +59,8 @@ ## Gitblit GO Features - Out-of-the-box integrated stack requiring minimal configuration - Automatically generates a self-signed certificate for *localhost* https communications (includes script to generate a self-signed certificate for *your hostname*, see [setup](/setup.html) for details) - Automatic generation of ssl certificate for https communications - Integrated GUI tool to facilitate x509 PKI including ssl and client certificate generation, client certificate revocation, and client certificate distribution - Single text file for configuring server and gitblit - A Windows service installation script and configuration tool - Built-in AJP connector for Apache httpd docs/01_setup.mkd
@@ -27,7 +27,6 @@ - *git.repositoryFolder* (path may be relative or absolute) - *groovy.scriptsFolder* (path may be relative or absolute) - *groovy.grapeFolder* (path may be relative or absolute) - *web.siteName* (used in certificate generation, etc) - *server.tempFolder* (path may be relative or absolute) - *server.httpPort* and *server.httpsPort* - *server.httpBindInterface* and *server.httpsBindInterface* @@ -36,10 +35,10 @@ - *git.packedGitLimit* (set larger than the size of your largest repository) - *git.streamFileThreshold* (set larger than the size of your largest committed file) 3. Execute `authority.cmd` or `java -jar authority.jar` from a command-line a. enter default values for your generated certificates in the *new certificate defaults* dialog b. enter the store password used in *server.storePassword* when prompted *(this generates an SSL certificate for **localhost**)* c. you will also want to generate SSL certificates for the hostnames or ip addresses you serve from d. exit the authority app 1. fill out the fields in the *new certificate defaults* dialog 2. enter the store password used in *server.storePassword* when prompted. This generates an SSL certificate for **localhost**. 3. you may want to generate an SSL certificate for the hostname or ip address hostnames you are serving from<br/>**NOTE:** You can only have **one** SSL certificate specified for a port. 5. exit the authority app 4. Execute `gitblit.cmd` or `java -jar gitblit.jar` from a command-line 5. Open your browser to <http://localhost:8080> or <https://localhost:8443> depending on your chosen configuration. 6. Enter the default administrator credentials: **admin / admin** and click the *Login* button @@ -55,18 +54,21 @@ Cannot get remote repository refs. Reason: https:/myserver.com/git/myrepo.git: cannot open git-upload-pack If you want to serve your repositories to another machine over https then you will want to generate your own certificate. If you want to serve your repositories to another machine over https then you will want to generate a new certificate for the hostname or ip address you are serving from. 1. `authority.cmd` or `java -jar authority.jar` 2. Click the *new ssl certificate* button (red rosette in the toolbar in upper left of window) 3. Enter the hostname or ip address 4. Enter the *server.storePassword* password 4. Make sure the checkbox *serve https with this certificate* is checked 5. In the keystore password prompt, enter the *server.storePassword* password Additionally, if you want to change the value of *server.storePassword* (recommended) you will have to delete the following files and then start the Gitblit Certificate Authority app: If you decide to change the value of *server.storePassword* (recommended) <u>after</u> you have already started Gitblit or Gitblit Certificate Authority, then you will have to delete the following files and then restart the Gitblit Certificate Authority app: 1. serverKeyStore.jks 2. serverTrustStore.jks 3. certs/caKeyStore.jks 4. certs/ca.crt 5. certs/caRevocationList.crl (optional) ### Client SSL Certificates SINCE 1.2.0 @@ -94,8 +96,12 @@ 1. `authority.cmd` or `java -jar authority.jar` 2. Select the user for which to generate the certificate 3. Click the *new certificate* button and enter the expiration date of the certificate. You must also enter a password for the generated keystore. This password is not the same as the user's login password. This password is used to protect the privatekey and public certificate you will generate for the selected user. You must also enter a password hint for the user. 3. Click the *new certificate* button and enter the expiration date of the certificate. You must also enter a password for the generated keystore. This password is *not* the same as the user's login password. This password is used to protect the privatekey and public certificate you will generate for the selected user. You must also enter a password hint for the user. 4. If your mail server settings are properly configured you will have a *send email* checkbox which you can use to immediately send the generated certificate bundle to the user. #### Certificate Inspection and Advanced Troubleshooting X509 certificates can be confusing and tricky even with the simplified Gitblit Certificate Authority tool. If you find you need more tooling to understand your keystores, certificates, and certificate revocation lists (CRLs), I highly recommend [Portecle](http://portecle.sourceforge.net) which can be conveniently launched as a [Java Web Start app](http://portecle.sourceforge.net/webstart/portecle.jnlp). ### Running as a Windows Service Gitblit uses [Apache Commons Daemon](http://commons.apache.org/daemon) to install and configure its Windows service. @@ -130,6 +136,7 @@ --httpPort HTTP port for to serve. (port <= 0 will disable this connector) --httpsPort HTTPS port to serve. (port <= 0 will disable this connector) --ajpPort AJP port to serve. (port <= 0 will disable this connector) --alias Alias in keystore of SSL cert to use for https serving --storePassword Password for SSL (https) keystore. --shutdownPort Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor) --tempFolder Folder for server to extract built-in webapp docs/04_releases.mkd
@@ -12,6 +12,7 @@ #### fixes - Set subjectAlternativeName on generated SSL cert if CN is an ip address (issue 170) - Fixed incorrect links on history page for files not in the current/active commit (issue 166) - Empty repository page failed to handle missing repository (issue 160) - Fixed broken ticgit urls (issue 157) @@ -32,17 +33,18 @@ - RW (clone and push) - RWC (clone and push with ref creation) - RWD (clone and push with ref creation, deletion) - RW+ (clone and push with ref creation, deletion, rewind) - RW+ (clone and push with ref creation, deletion, rewind) While not as sophisticated as Gitolite, this does give finer access controls. These permissions fit in cleanly with the existing users.conf and users.properties files. In Gitblit <= 1.1.0, all your existing user accounts have RW+ access. If you are upgrading to 1.2.0, the RW+ access is *preserved* and you will have to lower/adjust accordingly. - Implemented *case-insensitive* regex repository permission matching (issue 36) This allows you to specify a permission like `RW:mygroup/.*` to grant push privileges to all repositories within the *mygroup* project/folder. - Added DELETE, CREATE, and NON-FAST-FORWARD ref change logging - Added support for personal repositories. - Added support for personal repositories. Personal repositories can be created by accounts with the *create* permission and are stored in *git.repositoriesFolder/~username*. Each user with personal repositories will have a user page, something like the GitHub profile page. Personal repositories have all the same features as common repositories, except personal repositories can be renamed by their owner. - Added support for server-side forking of a repository to a personal repository (issue 137) In order to fork a repository, the user account must have the *fork* permission **and** the repository must *allow forks*. The clone inherits the access list of its origin. i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork. This is to facilitate collaboration. The fork owner may change access to the fork and add/remove users/teams, etc as required <u>however</u> it should be noted that all personal forks will be enumerated in the fork network regardless of access view restrictions. If you really must have an invisible fork, the clone it locally, create a new repository for your invisible fork, and push it back to Gitblit. In order to fork a repository, the user account must have the *fork* permission **and** the repository must *allow forks*. The clone inherits the access list of its origin. i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork. This is to facilitate collaboration. The fork owner may change access to the fork and add/remove users/teams, etc as required <u>however</u> it should be noted that all personal forks will be enumerated in the fork network regardless of access view restrictions. If you really must have an invisible fork, the clone it locally, create a new repository for your invisible fork, and push it back to Gitblit. **New:** *web.allowForking=true* - Added optional *create-on-push* support **New:** *git.allowCreateOnPush=true* **New:** *git.allowCreateOnPush=true* - Added **experimental** JGit-based garbage collection service. This service is disabled by default. **New:** *git.allowGarbageCollection=false* **New:** *git.garbageCollectionHour = 0* @@ -54,12 +56,12 @@ **New:** *git.enforceCertificateValidity = true* **New:** *git.certificateUsernameOIDs = CN* - Revised clean install certificate generation to create a Gitblit GO Certificate Authority certificate; an SSL certificate signed by the CA certificate; and to create distinct server key and server trust stores. <u>The store files have been renamed!</u> - Added support for Gitblit GO to require usage of client certificates to access the entire server. - Added support for Gitblit GO to require usage of client certificates to access the entire server. This is extreme and should be considered carefully since it affects every https access. The default is to **want** client certificates. Setting this value to *true* changes that to **need** client certificates. **New:** *server.requireClientCertificates = false* - Added Gitblit Certificate Authority, an X509 certificate generation tool for Gitblit GO to encourage use of client certificate authentication. - Added **Gitblit Certificate Authority**, an x509 PKI management tool for Gitblit GO to encourage use of x509 client certificate authentication. - Added setting to control length of shortened commit ids **New:** *web.shortCommitIdLength=8* **New:** *web.shortCommitIdLength=8* - Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174) **New:** *web.compressedDownloads = zip gz* - Added simple project pages. A project is a subfolder off the *git.repositoriesFolder*. @@ -72,7 +74,10 @@ #### changes - Access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate any Gitblit cookie found in the request before resorting to BASIC authentication. - Added server setting to specify keystore alias for ssl certificate (issue 98) - Added optional global and per-repository activity page commit contribution throttle to help tame *really* active repositories (issue 173) - Added support for symlinks in tree page and commit page (issue 171) - All access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate using X509 certificates, container principals, cookies, and BASIC headers, in that order. - Added *groovy* and *scala* to *web.prettyPrintExtensions* - Added short commit id column to log and history tables (issue 168) - Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration @@ -83,15 +88,17 @@ - Emit a warning in the log file if running on a Tomcat-based servlet container which is unfriendly to %2F forward-slash url encoding AND Gitblit is configured to mount parameters with %2F forward-slash url encoding (Github/jpyeron, issue 126) - LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list. If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, then User.canAdmin() is controlled by LDAP administrative team membership. Otherwise, User.canAdmin() is controlled by Gitblit. - Support servlet container authentication for existing UserModels (issue 68) #### dependency changes - updated to Jetty 7.6.7 - updated to Jetty 7.6.8 - updated to JGit 2.1.0.201209190230-r - updated to Groovy 1.8.8 - updated to Wicket 1.4.21 - updated to Lucene 3.6.1 - updated to BouncyCastle 1.47 - updated to MarkdownPapers 1.3.2 - added JCalendar 1.3.2 - added Commons-Compress 1.4.1 - added XZ for Java 1.0 @@ -113,8 +120,8 @@ - Fixed generated urls in Groovy *sendmail* hook script for grouped repositories - Fixed generated urls in RSS feeds for grouped repositories - Fixed nullpointer exception in git servlet security filter (issue 123) - Eliminated an unnecessary repository enumeration call on the root page which should result in faster page loads (issue 103) - Gitblit could not delete a Lucene index in a working copy on index upgrade - Eliminated an unnecessary repository enumeration call on the root page which should result in faster page loads (issue 103) - Gitblit could not delete a Lucene index in a working copy on index upgrade - Do not index submodule links (issue 119) - Restore original user or team object on failure to update (issue 118) - Fixes to relative path determination in repository search algorithm for symlinks (issue 116) @@ -128,25 +135,25 @@ #### additions - Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103) - Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103) **New:** *git.cacheRepositoryList=true* - Preliminary bare repository submodule support - Preliminary bare repository submodule support **New:** *git.submoduleUrlPatterns=* - *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url. For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git* - *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url. For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git* **Note:** You may not need this control to work with submodules, but it is there if you do. - If there are no matches from *git.submoduleUrlPatterns* then the repository name is assumed to be whatever comes after the last `/` character *(e.g. gitblit.git)* - Gitblit will try to locate this repository relative to the current repository *(e.g. myfolder/myrepo.git, myfolder/mysubmodule.git)* and then at the root level *(mysubmodule.git)* if that fails. - Submodule references in a working copy will be properly identified as gitlinks, but Gitblit will not traverse into the working copy submodule repository. - Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117) NAMED is the original behavior for authorizing against a list of permitted users or permitted teams. - Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117) NAMED is the original behavior for authorizing against a list of permitted users or permitted teams. AUTHENTICATED allows restricted access for any authenticated user. This is a looser authorization control. - Added default authorization control setting (AUTHENTICATED or NAMED) **New:** *git.defaultAuthorizationControl=NAMED* - Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103) **New:** *git.searchRecursionDepth=-1* - Added setting to specify regex exclusions for repositories (issue 103) **New:** *git.searchExclusions=* - Added default authorization control setting (AUTHENTICATED or NAMED) **New:** *git.defaultAuthorizationControl=NAMED* - Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103) **New:** *git.searchRecursionDepth=-1* - Added setting to specify regex exclusions for repositories (issue 103) **New:** *git.searchExclusions=* - Blob page now supports displaying images (issue 6) - Non-image binary files can now be downloaded using the RAW link - Support StartTLS in LdapUserService (Steffen Gebert, issue 122) @@ -175,38 +182,38 @@ #### changes - **Updated Lucene index version which will force a rebuild of ALL your Lucene indexes** - **Updated Lucene index version which will force a rebuild of ALL your Lucene indexes** Make sure to properly set *web.blobEncodings* before starting Gitblit if you are updating! (issue 97) - Changed default layout for web ui from Fixed-Width layout to Responsive layout (issue 101) - IUserService interface has changed to better accomodate custom authentication and/or custom authorization The default `users.conf` now supports persisting display names and email addresses. - Changed default layout for web ui from Fixed-Width layout to Responsive layout (issue 101) - IUserService interface has changed to better accomodate custom authentication and/or custom authorization The default `users.conf` now supports persisting display names and email addresses. - Updated Japanese translation (Github/zakki) #### additions - Added setting to allow specification of a robots.txt file (issue 99) **New:** *web.robots.txt =* - Added setting to control Responsive layout or Fixed-Width layout (issue 101) Responsive layout is now the default. This layout gracefully scales the web ui from a desktop layout to a mobile layout by hiding page components. It is easy to try, just resize your browser or point your Android/iOS device to the url of your Gitblit install. **New:** *web.useResponsiveLayout = true* - Added setting to control charsets for blob string decoding. Default encodings are UTF-8, ISO-8859-1, and server's default charset. (issue 97) **New:** *web.blobEncodings = UTF-8 ISO-8859-1* - Exposed JGit's internal configuration settings in gitblit.properties/web.xml (issue 93) Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings. **New:** *git.packedGitWindowSize = 8k* **New:** *git.packedGitLimit = 10m* **New:** *git.deltaBaseCacheLimit = 10m* **New:** *git.packedGitOpenFiles = 128* **New:** *git.streamFileThreshold = 50m* **New:** *git.packedGitMmap = false* - Added default access restriction. Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88) **New:** *git.defaultAccessRestriction = NONE* - Added Ivy 2.2.0 dependency which enables Groovy Grapes, a mechanism to resolve and retrieve library dependencies from a Maven 2 repository within a Groovy push hook script - Added setting to control Groovy Grape root folder (location where resolved dependencies are stored) [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath. **New:** *groovy.grapeFolder = groovy/grape* - Added setting to allow specification of a robots.txt file (issue 99) **New:** *web.robots.txt =* - Added setting to control Responsive layout or Fixed-Width layout (issue 101) Responsive layout is now the default. This layout gracefully scales the web ui from a desktop layout to a mobile layout by hiding page components. It is easy to try, just resize your browser or point your Android/iOS device to the url of your Gitblit install. **New:** *web.useResponsiveLayout = true* - Added setting to control charsets for blob string decoding. Default encodings are UTF-8, ISO-8859-1, and server's default charset. (issue 97) **New:** *web.blobEncodings = UTF-8 ISO-8859-1* - Exposed JGit's internal configuration settings in gitblit.properties/web.xml (issue 93) Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings. **New:** *git.packedGitWindowSize = 8k* **New:** *git.packedGitLimit = 10m* **New:** *git.deltaBaseCacheLimit = 10m* **New:** *git.packedGitOpenFiles = 128* **New:** *git.streamFileThreshold = 50m* **New:** *git.packedGitMmap = false* - Added default access restriction. Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88) **New:** *git.defaultAccessRestriction = NONE* - Added Ivy 2.2.0 dependency which enables Groovy Grapes, a mechanism to resolve and retrieve library dependencies from a Maven 2 repository within a Groovy push hook script - Added setting to control Groovy Grape root folder (location where resolved dependencies are stored) [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath. **New:** *groovy.grapeFolder = groovy/grape* - Added LDAP User Service with many new *realm.ldap* keys (Github/jcrygier) - Added support for custom repository properties for Groovy hooks (Github/jcrygier) - Added support for custom repository properties for Groovy hooks (Github/jcrygier) Custom repository properties complement hook scripts by providing text field prompts in the web ui and the Gitblit Manager for the defined properties. This allows your push hooks to be parameterized. - Added script to facilitate proxy environment setup on Linux (Github/mragab) - Added Polish translation (Lukasz Jader) @@ -271,32 +278,32 @@ #### additions - Added optional Lucene branch indexing (issue 16) **New:** *web.allowLuceneIndexing = true* **New:** *web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip* Repository branches may be optionally indexed by Lucene for improved searching. To use this feature you must specify which branches to index within the *Edit Repository* page; _no repositories are automatically indexed_. Gitblit will build or incrementally update enrolled repositories on a 2 minute cycle. (i.e you will have to wait 2-3 minutes after respecifying indexed branches or pushing new commits before Gitblit will build/update the repository's Lucene index.) If a repository has Lucene-indexed branches the *search* form on the repository pages will redirect to the root-level Lucene search page and only the content of those branches can be searched. If the repository does not specify any indexed branches then repository commit-traversal search is used. **Note:** Initial indexing of an existing repository can be memory-exhaustive. Be sure to provide your Gitblit server adequate heap space to index your repositories (e.g. -Xmx1024M). - Added optional Lucene branch indexing (issue 16) **New:** *web.allowLuceneIndexing = true* **New:** *web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip* Repository branches may be optionally indexed by Lucene for improved searching. To use this feature you must specify which branches to index within the *Edit Repository* page; _no repositories are automatically indexed_. Gitblit will build or incrementally update enrolled repositories on a 2 minute cycle. (i.e you will have to wait 2-3 minutes after respecifying indexed branches or pushing new commits before Gitblit will build/update the repository's Lucene index.) If a repository has Lucene-indexed branches the *search* form on the repository pages will redirect to the root-level Lucene search page and only the content of those branches can be searched. If the repository does not specify any indexed branches then repository commit-traversal search is used. **Note:** Initial indexing of an existing repository can be memory-exhaustive. Be sure to provide your Gitblit server adequate heap space to index your repositories (e.g. -Xmx1024M). See the [setup](setup.html) page for additional details. - Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54) **New:** *web.timezone =* - Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59) **New:** *server.ajpPort = 0* - Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54) **New:** *web.timezone =* - Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59) **New:** *server.ajpPort = 0* **New:** *server.ajpBindInterface = localhost* - On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49) - On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49) Push requests to these repositories will be rejected. - On all non-bare Repository pages show *WORKING COPY* in the upper right corner (issue 49) - New setting to prevent display/serving non-bare repositories - New setting to prevent display/serving non-bare repositories **New:** *git.onlyAccessBareRepositories = false* - Added *protect-refs.groovy* (Github/plm) - Added *protect-refs.groovy* (Github/plm) - Allow setting default branch (relinking HEAD) to a branch or a tag (Github/plm) - Added Ubuntu service init script (issue 72) - Added partial Japanese translation (Github/zakki) #### fixes #### fixes - Ensure that Welcome message is parsed using UTF-8 encoding (issue 74) - Ensure that Welcome message is parsed using UTF-8 encoding (issue 74) - Activity page chart layout broken by Google (issue 73) - Uppercase repositories not selectable in edit palettes (issue 71) - Not all git notes were properly displayed on the commit page (issue 70) @@ -305,7 +312,7 @@ - Fixed possible nullpointer from the servlet container on startup (issue 67) - Fixed UTF-8 encoding bug on diff page (issue 66) - Fixed timezone bugs on the activity page (issue 54) - Prevent add/edit team with no selected repositories (issue 56) - Prevent add/edit team with no selected repositories (issue 56) - Disallow browser autocomplete on add/edit user/team/repository pages - Fixed username case-sensitivity issues (issue 43) - Disregard searching a subfolder if Gitblit does not have filesystem permissions (Github/lemval issue 51) @@ -341,31 +348,31 @@ #### additions - Platform-independent, Groovy push hook script mechanism. Hook scripts can be set per-repository, per-team, or globally for all repositories. **New:** *groovy.scriptsFolder = groovy* **New:** *groovy.preReceiveScripts =* - Platform-independent, Groovy push hook script mechanism. Hook scripts can be set per-repository, per-team, or globally for all repositories. **New:** *groovy.scriptsFolder = groovy* **New:** *groovy.preReceiveScripts =* **New:** *groovy.postReceiveScripts =* - *sendmail.groovy* for optional email notifications on push. - *sendmail.groovy* for optional email notifications on push. You must properly configure your SMTP server settings in `gitblit.properties` or `web.xml` to use *sendmail.groovy*. - New global key for mailing lists. This is used in conjunction with the *sendmail.groovy* hook script. All repositories that use the *sendmail.groovy* script will include these addresses in the notification process. Please see the Setup page for more details about configuring sendmail. - New global key for mailing lists. This is used in conjunction with the *sendmail.groovy* hook script. All repositories that use the *sendmail.groovy* script will include these addresses in the notification process. Please see the Setup page for more details about configuring sendmail. **New:** *mail.mailingLists =* - *com.gitblit.GitblitUserService*. This is a wrapper object for the built-in user service implementations. For those wanting to only implement custom authentication it is recommended to subclass GitblitUserService and override the appropriate methods. Going forward, this will help insulate custom authentication from new IUserService API and/or changes in model classes. - New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`) This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects without requiring the encoding trickery now present in FileUserService (users.properties). This will open the door for more advanced Gitblit features. For those upgrading from an earlier Gitblit version, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit <u>however</u> you will have to manually set *realm.userService=users.conf* to switch to the new user service. The original `users.properties` file and it's corresponding implementation are **deprecated**. - New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`) This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects without requiring the encoding trickery now present in FileUserService (users.properties). This will open the door for more advanced Gitblit features. For those upgrading from an earlier Gitblit version, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit <u>however</u> you will have to manually set *realm.userService=users.conf* to switch to the new user service. The original `users.properties` file and it's corresponding implementation are **deprecated**. **New:** *realm.userService = users.conf* - Teams for specifying user-repository access in bulk. Teams may also specify mailing lists addresses and pre- & post- receive hook scripts. - Gravatar integration **New:** *web.allowGravatar = true* - Activity page for aggregated repository activity. This is a timeline of commit activity over the last N days for one or more repositories. **New:** *web.activityDuration = 14* **New:** *web.timeFormat = HH:mm* **New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy* - *Filters* menu for the Repositories page and Activity page. You can filter by federation set, team, and simple custom regular expressions. Custom expressions can be stored in `gitblit.properties` or `web.xml` or directly defined in your url (issue 27) - Gravatar integration **New:** *web.allowGravatar = true* - Activity page for aggregated repository activity. This is a timeline of commit activity over the last N days for one or more repositories. **New:** *web.activityDuration = 14* **New:** *web.timeFormat = HH:mm* **New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy* - *Filters* menu for the Repositories page and Activity page. You can filter by federation set, team, and simple custom regular expressions. Custom expressions can be stored in `gitblit.properties` or `web.xml` or directly defined in your url (issue 27) **New:** *web.customFilters=* - Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy - Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy **New:** *web.allowFlashCopyToClipboard = true* - JavaScript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url in the event that you do not want to use Flash on your installation - Empty repositories now link to an *empty repository* page which gives some direction to the user for the next step in using Gitblit. This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31) @@ -375,7 +382,7 @@ #### changes - Dropped display of trailing .git from repository names - Gitblit GO is now monolithic like the WAR build. (issue 30) - Gitblit GO is now monolithic like the WAR build. (issue 30) This change helps adoption of GO in environments without an internet connection or with a restricted connection. - Unit testing framework has been migrated to JUnit4 syntax and the test suite has been redesigned to run all unit tests, including rpc, federation, and git push/clone tests @@ -387,7 +394,7 @@ #### dependency changes - updated to JGit 1.2.0 - added Groovy 1.8.5 - added Groovy 1.8.5 - added Clippy (bundled) <hr/> @@ -395,25 +402,25 @@ **0.7.0** *released 2011-11-11* - **security**: fixed security hole when cloning clone-restricted repository with TortoiseGit (issue 28) - improved: updated ui with Twitter's Bootstrap CSS toolkit - improved: updated ui with Twitter's Bootstrap CSS toolkit **New:** *web.loginMessage = gitblit* - improved: repositories list performance by caching repository sizes (issue 27) - improved: summary page performance by caching metric calculations (issue 25) - added: authenticated JSON RPC mechanism **New:** *web.enableRpcServlet = true* **New:** *web.enableRpcManagement = false* - added: authenticated JSON RPC mechanism **New:** *web.enableRpcServlet = true* **New:** *web.enableRpcManagement = false* **New:** *web.enableRpcAdministration = false* - added: Gitblit API RSS/JSON RPC library - added: Gitblit Manager (Java/Swing Application) for remote administration of a Gitblit server. - added: per-repository setting to skip size calculation (faster repositories page loading) - added: per-repository setting to skip summary metrics calculation (faster summary page loading) - added: IUserService.setup(IStoredSettings) for custom user service implementations - added: setting to control Gitblit GO context path for proxy setups *(Github/trygvis)* - added: setting to control Gitblit GO context path for proxy setups *(Github/trygvis)* **New:** *server.contextPath = /* - added: *combined-md5* password storage option which stores the hash of username+password as the password *(Github/alyandon)* - added: repository owners are automatically granted access for git, feeds, and zip downloads without explicitly selecting them *(Github/dadalar)* - added: RSS feeds now include regex substitutions on commit messages for bug trackers, etc - fixed: federation protocol timestamps. dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard. - fixed: federation protocol timestamps. dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard. **This breaks 0.6.0 federation clients/servers.** - fixed: collision on rename for repositories and users - fixed: Gitblit can now browse the Linux kernel repository (issue 25) @@ -430,15 +437,15 @@ **0.6.0** *released 2011-09-27* - added: federation feature to allow gitblit instances (or gitblit federation clients) to pull repositories and, optionally, settings and accounts from other gitblit instances. This is something like [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html) for gitblit. **New:** *federation.name =* **New:** *federation.passphrase =* **New:** *federation.allowProposals = false* **New:** *federation.proposalsFolder = proposals* **New:** *federation.defaultFrequency = 60 mins* **New:** *federation.sets =* **New:** *mail.* settings for sending emails **New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance - added: federation feature to allow gitblit instances (or gitblit federation clients) to pull repositories and, optionally, settings and accounts from other gitblit instances. This is something like [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html) for gitblit. **New:** *federation.name =* **New:** *federation.passphrase =* **New:** *federation.allowProposals = false* **New:** *federation.proposalsFolder = proposals* **New:** *federation.defaultFrequency = 60 mins* **New:** *federation.sets =* **New:** *mail.* settings for sending emails **New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance - added: google-gson dependency - added: javamail dependency - updated: MarkdownPapers 1.1.1 @@ -458,9 +465,9 @@ - fixed: users can now change their passwords (issue 1) - fixed: always show root repository group first, i.e. don't sort root group with other groups - fixed: tone-down repository group header color - added: optionally display repository on-disk size on repositories page - added: optionally display repository on-disk size on repositories page **New:** *web.showRepositorySizes = true* - added: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers - added: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers **New:** *web.forwardSlashCharacter = /* - updated: MarkdownPapers 1.1.0 - updated: Jetty 7.4.3 gitblit.iml
@@ -77,24 +77,24 @@ </library> </orderEntry> <orderEntry type="module-library"> <library name="jetty-webapp-7.6.7.v20120910.jar"> <library name="jetty-webapp-7.6.8.v20121106.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/jetty-webapp-7.6.7.v20120910.jar!/" /> <root url="jar://$MODULE_DIR$/ext/jetty-webapp-7.6.8.v20121106.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jetty-webapp-7.6.7.v20120910-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/jetty-webapp-7.6.8.v20121106-sources.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="jetty-ajp-7.6.7.v20120910.jar"> <library name="jetty-ajp-7.6.8.v20121106.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/jetty-ajp-7.6.7.v20120910.jar!/" /> <root url="jar://$MODULE_DIR$/ext/jetty-ajp-7.6.8.v20121106.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jetty-ajp-7.6.7.v20120910-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/jetty-ajp-7.6.8.v20121106-sources.jar!/" /> </SOURCES> </library> </orderEntry> @@ -196,13 +196,13 @@ </library> </orderEntry> <orderEntry type="module-library"> <library name="markdownpapers-core-1.2.7.jar"> <library name="markdownpapers-core-1.3.2.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/markdownpapers-core-1.2.7.jar!/" /> <root url="jar://$MODULE_DIR$/ext/markdownpapers-core-1.3.2.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/markdownpapers-core-1.2.7-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/markdownpapers-core-1.3.2-sources.jar!/" /> </SOURCES> </library> </orderEntry> @@ -347,6 +347,28 @@ <SOURCES /> </library> </orderEntry> <orderEntry type="module-library"> <library name="commons-compress-1.4.1.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/commons-compress-1.4.1.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/commons-compress-1.4.1-sources.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="xz-1.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/xz-1.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/xz-1.0-sources.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="junit-4.10.jar"> <CLASSES> groovy/sendmail-html.groovy
@@ -370,14 +370,15 @@ formatter.setDiffComparator(RawTextComparator.DEFAULT); def diffs RevWalk rw = new RevWalk(repository); RevWalk rw = new RevWalk(repository) if (commit.parentCount > 0) { RevCommit parent = commit.parents[0] RevCommit parent = rw.parseCommit(commit.parents[0].id) diffs = formatter.scan(parent.tree, commit.tree) } else { diffs = formatter.scan(new EmptyTreeIterator(), new CanonicalTreeParser(null, rw.objectReader, commit.tree)) } rw.dispose() // Write status table builder.table('class':"plain") { tbody() { src/com/gitblit/AuthenticationFilter.java
@@ -16,9 +16,7 @@ package com.gitblit; import java.io.IOException; import java.nio.charset.Charset; import java.security.Principal; import java.text.MessageFormat; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @@ -37,7 +35,6 @@ import org.slf4j.LoggerFactory; import com.gitblit.models.UserModel; import com.gitblit.utils.Base64; import com.gitblit.utils.StringUtils; /** @@ -51,9 +48,7 @@ */ public abstract class AuthenticationFilter implements Filter { protected static final String BASIC = "Basic"; protected static final String CHALLENGE = BASIC + " realm=\"" + Constants.NAME + "\""; protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\""; protected static final String SESSION_SECURED = "com.gitblit.secured"; @@ -103,40 +98,8 @@ * @return user */ protected UserModel getUser(HttpServletRequest httpRequest) { UserModel user = null; // try request authentication user = GitBlit.self().authenticate(httpRequest); if (user != null) { return user; } else if (requiresClientCertificate()) { // http request does not have a valid certificate // and the filter requires one return null; } // look for client authorization credentials in header final String authorization = httpRequest.getHeader("Authorization"); if (authorization != null && authorization.startsWith(BASIC)) { // Authorization: Basic base64credentials String base64Credentials = authorization.substring(BASIC.length()).trim(); String credentials = new String(Base64.decode(base64Credentials), Charset.forName("UTF-8")); // credentials = username:password final String[] values = credentials.split(":",2); if (values.length == 2) { String username = values[0]; char[] password = values[1].toCharArray(); user = GitBlit.self().authenticate(username, password); if (user != null) { return user; } } if (GitBlit.isDebugMode()) { logger.info(MessageFormat.format("AUTH: invalid credentials ({0})", credentials)); } } return null; UserModel user = GitBlit.self().authenticate(httpRequest, requiresClientCertificate()); return user; } /** src/com/gitblit/Constants.java
@@ -399,7 +399,7 @@ } public static enum AuthenticationType { CREDENTIALS, COOKIE, CERTIFICATE; CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER; public boolean isStandard() { return ordinal() <= COOKIE.ordinal(); src/com/gitblit/GitBlit.java
@@ -24,6 +24,8 @@ import java.lang.reflect.Field; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.security.Principal; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -98,6 +100,7 @@ import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.Base64; import com.gitblit.utils.ByteFormat; import com.gitblit.utils.ContainerUtils; import com.gitblit.utils.DeepCopier; @@ -567,6 +570,20 @@ * @return a user object or null */ public UserModel authenticate(HttpServletRequest httpRequest) { return authenticate(httpRequest, false); } /** * Authenticate a user based on HTTP request parameters. * * Authentication by X509Certificate, servlet container principal, cookie, * and BASIC header. * * @param httpRequest * @param requiresCertificate * @return a user object or null */ public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { // try to authenticate by certificate boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true); String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]); @@ -574,38 +591,84 @@ if (model != null) { // grab real user model and preserve certificate serial number UserModel user = getUserModel(model.username); X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest); if (user != null) { RequestCycle requestCycle = RequestCycle.get(); if (requestCycle != null) { // flag the Wicket session, if this is a Wicket request GitBlitWebSession session = GitBlitWebSession.get(); session.authenticationType = AuthenticationType.CERTIFICATE; } X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest); flagWicketSession(AuthenticationType.CERTIFICATE); logger.info(MessageFormat.format("{0} authenticated by client certificate {1} from {2}", user.username, metadata.serialNumber, httpRequest.getRemoteAddr())); return user; } else { logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}", model.username, metadata.serialNumber, httpRequest.getRemoteAddr())); } } if (requiresCertificate) { // caller requires client certificate authentication (e.g. git servlet) return null; } // try to authenticate by servlet container principal Principal principal = httpRequest.getUserPrincipal(); if (principal != null) { UserModel user = getUserModel(principal.getName()); if (user != null) { flagWicketSession(AuthenticationType.CONTAINER); logger.info(MessageFormat.format("{0} authenticated by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else { logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}", principal.getName(), httpRequest.getRemoteAddr())); } } // try to authenticate by cookie Cookie[] cookies = httpRequest.getCookies(); if (allowCookieAuthentication() && cookies != null && cookies.length > 0) { // Grab cookie from Browser Session UserModel user = authenticate(cookies); if (allowCookieAuthentication()) { UserModel user = authenticate(httpRequest.getCookies()); if (user != null) { RequestCycle requestCycle = RequestCycle.get(); if (requestCycle != null) { // flag the Wicket session, if this is a Wicket request GitBlitWebSession session = GitBlitWebSession.get(); session.authenticationType = AuthenticationType.COOKIE; } flagWicketSession(AuthenticationType.COOKIE); logger.info(MessageFormat.format("{0} authenticated by cookie from {1}", user.username, httpRequest.getRemoteAddr())); return user; } } // try to authenticate by BASIC final String authorization = httpRequest.getHeader("Authorization"); if (authorization != null && authorization.startsWith("Basic")) { // Authorization: Basic base64credentials String base64Credentials = authorization.substring("Basic".length()).trim(); String credentials = new String(Base64.decode(base64Credentials), Charset.forName("UTF-8")); // credentials = username:password final String[] values = credentials.split(":",2); if (values.length == 2) { String username = values[0]; char[] password = values[1].toCharArray(); UserModel user = authenticate(username, password); if (user != null) { flagWicketSession(AuthenticationType.CREDENTIALS); logger.info(MessageFormat.format("{0} authenticated by BASIC request header from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else { logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials ({1}) from {2}", username, credentials, httpRequest.getRemoteAddr())); } } } return null; } protected void flagWicketSession(AuthenticationType authenticationType) { RequestCycle requestCycle = RequestCycle.get(); if (requestCycle != null) { // flag the Wicket session, if this is a Wicket request GitBlitWebSession session = GitBlitWebSession.get(); session.authenticationType = authenticationType; } } /** @@ -693,6 +756,9 @@ * @return true if successful */ public boolean deleteUser(String username) { if (StringUtils.isEmpty(username)) { return false; } return userService.deleteUser(username); } @@ -704,6 +770,9 @@ * @return a user object or null */ public UserModel getUserModel(String username) { if (StringUtils.isEmpty(username)) { return null; } UserModel user = userService.getUserModel(username); return user; } @@ -1533,6 +1602,7 @@ } catch (Exception e) { model.lastGC = new Date(0); } model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0)); model.origin = config.getString("remote", "origin", "url"); if (model.origin != null) { model.origin = model.origin.replace('\\', '/'); @@ -1999,10 +2069,21 @@ repository.federationStrategy.name()); config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated); config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold); config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod); if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) { // use default from config config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod"); } else { config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod); } if (repository.lastGC != null) { config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC)); } if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) { // use default from config config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits"); } else { config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits); } updateList(config, "federationSets", repository.federationSets); updateList(config, "preReceiveScript", repository.preReceiveScripts); src/com/gitblit/GitBlitServer.java
@@ -44,7 +44,6 @@ import org.eclipse.jetty.server.ssl.SslConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jgit.storage.file.FileBasedConfig; @@ -242,7 +241,7 @@ }); if (serverKeyStore.exists()) { Connector secureConnector = createSSLConnector(serverKeyStore, serverTrustStore, params.storePassword, Connector secureConnector = createSSLConnector(params.alias, serverKeyStore, serverTrustStore, params.storePassword, caRevocationList, params.useNIO, params.securePort, params.requireClientCertificates); String bindInterface = settings.getString(Keys.server.httpsBindInterface, null); if (!StringUtils.isEmpty(bindInterface)) { @@ -413,6 +412,7 @@ * SSL renegotiation will be enabled if the JVM is 1.6.0_22 or later. * oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html * * @param certAlias * @param keyStore * @param clientTrustStore * @param storePassword @@ -422,52 +422,31 @@ * @param requireClientCertificates * @return an https connector */ private static Connector createSSLConnector(File keyStore, File clientTrustStore, private static Connector createSSLConnector(String certAlias, File keyStore, File clientTrustStore, String storePassword, File caRevocationList, boolean useNIO, int port, boolean requireClientCertificates) { SslContextFactory sslContext = new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH); GitblitSslContextFactory factory = new GitblitSslContextFactory(certAlias, keyStore, clientTrustStore, storePassword, caRevocationList); SslConnector connector; if (useNIO) { logger.info("Setting up NIO SslSelectChannelConnector on port " + port); SslSelectChannelConnector ssl = new SslSelectChannelConnector(sslContext); SslSelectChannelConnector ssl = new SslSelectChannelConnector(factory); ssl.setSoLingerTime(-1); if (requireClientCertificates) { sslContext.setNeedClientAuth(true); factory.setNeedClientAuth(true); } else { sslContext.setWantClientAuth(true); factory.setWantClientAuth(true); } ssl.setThreadPool(new QueuedThreadPool(20)); connector = ssl; } else { logger.info("Setting up NIO SslSocketConnector on port " + port); SslSocketConnector ssl = new SslSocketConnector(sslContext); SslSocketConnector ssl = new SslSocketConnector(factory); connector = ssl; } // disable renegotiation unless this is a patched JVM boolean allowRenegotiation = false; String v = System.getProperty("java.version"); if (v.startsWith("1.7")) { allowRenegotiation = true; } else if (v.startsWith("1.6")) { // 1.6.0_22 was first release with RFC-5746 implemented fix. if (v.indexOf('_') > -1) { String b = v.substring(v.indexOf('_') + 1); if (Integer.parseInt(b) >= 22) { allowRenegotiation = true; } } } if (allowRenegotiation) { logger.info(" allowing SSL renegotiation on Java " + v); sslContext.setAllowRenegotiate(allowRenegotiation); } sslContext.setKeyStorePath(keyStore.getAbsolutePath()); sslContext.setKeyStorePassword(storePassword); sslContext.setTrustStore(clientTrustStore.getAbsolutePath()); sslContext.setTrustStorePassword(storePassword); sslContext.setCrlPath(caRevocationList.getAbsolutePath()); connector.setPort(port); connector.setMaxIdleTime(30000); return connector; } @@ -596,6 +575,9 @@ @Parameter(names = "--ajpPort", description = "AJP port to serve. (port <= 0 will disable this connector)") public Integer ajpPort = FILESETTINGS.getInteger(Keys.server.ajpPort, 0); @Parameter(names = "--alias", description = "Alias of SSL certificate in keystore for serving https.") public String alias = FILESETTINGS.getString(Keys.server.certificateAlias, ""); @Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.") public String storePassword = FILESETTINGS.getString(Keys.server.storePassword, ""); src/com/gitblit/GitblitSslContextFactory.java
New file @@ -0,0 +1,94 @@ /* * 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; import java.io.File; import java.security.KeyStore; import java.security.cert.CRL; import java.util.Collection; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.utils.StringUtils; /** * Special SSL context factory that configures Gitblit GO and replaces the * primary trustmanager with a GitblitTrustManager. * * @author James Moger */ public class GitblitSslContextFactory extends SslContextFactory { private static final Logger logger = LoggerFactory.getLogger(GitblitSslContextFactory.class); private final File caRevocationList; public GitblitSslContextFactory(String certAlias, File keyStore, File clientTrustStore, String storePassword, File caRevocationList) { super(keyStore.getAbsolutePath()); this.caRevocationList = caRevocationList; // disable renegotiation unless this is a patched JVM boolean allowRenegotiation = false; String v = System.getProperty("java.version"); if (v.startsWith("1.7")) { allowRenegotiation = true; } else if (v.startsWith("1.6")) { // 1.6.0_22 was first release with RFC-5746 implemented fix. if (v.indexOf('_') > -1) { String b = v.substring(v.indexOf('_') + 1); if (Integer.parseInt(b) >= 22) { allowRenegotiation = true; } } } if (allowRenegotiation) { logger.info(" allowing SSL renegotiation on Java " + v); setAllowRenegotiate(allowRenegotiation); } if (!StringUtils.isEmpty(certAlias)) { logger.info(" certificate alias = " + certAlias); setCertAlias(certAlias); } setKeyStorePassword(storePassword); setTrustStore(clientTrustStore.getAbsolutePath()); setTrustStorePassword(storePassword); logger.info(" keyStorePath = " + keyStore.getAbsolutePath()); logger.info(" trustStorePath = " + clientTrustStore.getAbsolutePath()); logger.info(" crlPath = " + caRevocationList.getAbsolutePath()); } @Override protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception { TrustManager[] managers = super.getTrustManagers(trustStore, crls); X509TrustManager delegate = (X509TrustManager) managers[0]; GitblitTrustManager root = new GitblitTrustManager(delegate, caRevocationList); // replace first manager with the GitblitTrustManager managers[0] = root; return managers; } } src/com/gitblit/GitblitTrustManager.java
New file @@ -0,0 +1,125 @@ /* * 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; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.util.concurrent.atomic.AtomicLong; import javax.net.ssl.X509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * GitblitTrustManager is a wrapper trust manager that hot-reloads a local file * CRL and enforces client certificate revocations. The GitblitTrustManager * also implements fuzzy revocation enforcement in case of issuer mismatch BUT * serial number match. These rejecions are specially noted in the log. * * @author James Moger */ public class GitblitTrustManager implements X509TrustManager { private static final Logger logger = LoggerFactory.getLogger(GitblitTrustManager.class); private final X509TrustManager delegate; private final File caRevocationList; private final AtomicLong lastModified = new AtomicLong(0); private volatile X509CRL crl; public GitblitTrustManager(X509TrustManager delegate, File crlFile) { this.delegate = delegate; this.caRevocationList = crlFile; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { X509Certificate cert = chain[0]; if (isRevoked(cert)) { String message = MessageFormat.format("Rejecting revoked certificate {0,number,0} for {1}", cert.getSerialNumber(), cert.getSubjectDN().getName()); logger.warn(message); throw new CertificateException(message); } delegate.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { delegate.checkServerTrusted(chain, authType); } @Override public X509Certificate[] getAcceptedIssuers() { return delegate.getAcceptedIssuers(); } protected boolean isRevoked(X509Certificate cert) { if (!caRevocationList.exists()) { return false; } read(); if (crl.isRevoked(cert)) { // exact cert is revoked return true; } X509CRLEntry entry = crl.getRevokedCertificate(cert.getSerialNumber()); if (entry != null) { logger.warn("Certificate issuer does not match CRL issuer, but serial number has been revoked!"); logger.warn(" cert issuer = " + cert.getIssuerX500Principal()); logger.warn(" crl issuer = " + crl.getIssuerX500Principal()); return true; } return false; } protected synchronized void read() { if (lastModified.get() == caRevocationList.lastModified()) { return; } logger.info("Reloading CRL from " + caRevocationList.getAbsolutePath()); InputStream inStream = null; try { inStream = new FileInputStream(caRevocationList); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509CRL list = (X509CRL)cf.generateCRL(inStream); crl = list; lastModified.set(caRevocationList.lastModified()); } catch (Exception e) { } finally { if (inStream != null) { try { inStream.close(); } catch (Exception e) { } } } } } src/com/gitblit/authority/GitblitAuthority.java
@@ -21,6 +21,7 @@ import java.awt.Dimension; import java.awt.EventQueue; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Point; import java.awt.event.ActionEvent; @@ -69,6 +70,7 @@ import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.RowFilter; import javax.swing.SwingConstants; import javax.swing.UIManager; @@ -565,15 +567,26 @@ } }; JTextField durationTF = new JTextField(4); durationTF.setInputVerifier(verifier); durationTF.setVerifyInputWhenFocusTarget(true); durationTF.setText("" + certificateConfig.duration); JPanel durationPanel = Utils.newFieldPanel(Translation.get("gb.duration"), durationTF, Translation.get("gb.duration.days").replace("{0}", "").trim()); JTextField siteNameTF = new JTextField(20); siteNameTF.setText(gitblitSettings.getString(Keys.web.siteName, "Gitblit")); JPanel siteNamePanel = Utils.newFieldPanel(Translation.get("gb.siteName"), siteNameTF, Translation.get("gb.siteNameDescription")); JTextField validityTF = new JTextField(4); validityTF.setInputVerifier(verifier); validityTF.setVerifyInputWhenFocusTarget(true); validityTF.setText("" + certificateConfig.duration); JPanel validityPanel = Utils.newFieldPanel(Translation.get("gb.validity"), validityTF, Translation.get("gb.duration.days").replace("{0}", "").trim()); JPanel p1 = new JPanel(new GridLayout(0, 1, 5, 2)); p1.add(siteNamePanel); p1.add(validityPanel); DefaultOidsPanel oids = new DefaultOidsPanel(metadata); JPanel panel = new JPanel(new BorderLayout()); panel.add(durationPanel, BorderLayout.NORTH); panel.add(p1, BorderLayout.NORTH); panel.add(oids, BorderLayout.CENTER); int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, @@ -582,9 +595,13 @@ if (result == JOptionPane.OK_OPTION) { try { oids.update(metadata); certificateConfig.duration = Integer.parseInt(durationTF.getText()); certificateConfig.duration = Integer.parseInt(validityTF.getText()); certificateConfig.store(config, metadata); config.save(); Map<String, String> updates = new HashMap<String, String>(); updates.put(Keys.web.siteName, siteNameTF.getText()); gitblitSettings.saveSettings(updates); } catch (Exception e1) { Utils.showException(GitblitAuthority.this, e1); } @@ -607,7 +624,8 @@ } final Date expires = dialog.getExpiration(); final String hostname = dialog.getHostname(); final boolean serveCertificate = dialog.isServeCertificate(); AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) { @Override @@ -623,17 +641,31 @@ // generate new SSL certificate X509Metadata metadata = new X509Metadata(hostname, caKeystorePassword); setMetadataDefaults(metadata); metadata.notAfter = expires; File serverKeystoreFile = new File(folder, X509Utils.SERVER_KEY_STORE); X509Certificate cert = X509Utils.newSSLCertificate(metadata, caPrivateKey, caCert, serverKeystoreFile, GitblitAuthority.this); return cert != null; boolean hasCert = cert != null; if (hasCert && serveCertificate) { // update Gitblit https connector alias Map<String, String> updates = new HashMap<String, String>(); updates.put(Keys.server.certificateAlias, metadata.commonName); gitblitSettings.saveSettings(updates); } return hasCert; } @Override protected void onSuccess() { JOptionPane.showMessageDialog(GitblitAuthority.this, if (serveCertificate) { JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.sslCertificateGeneratedRestart"), hostname), Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.sslCertificateGenerated"), hostname), Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE); } } }; @@ -713,7 +745,8 @@ } }); JPanel buttonControls = new JPanel(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, Utils.MARGIN)); JToolBar buttonControls = new JToolBar(JToolBar.HORIZONTAL); buttonControls.setFloatable(false); buttonControls.add(certificateDefaultsButton); buttonControls.add(newSSLCertificate); buttonControls.add(emailBundle); src/com/gitblit/authority/NewSSLCertificateDialog.java
@@ -24,6 +24,7 @@ import java.util.Date; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; @@ -41,6 +42,7 @@ JDateChooser expirationDate; JTextField hostname; JCheckBox serveCertificate; boolean isCanceled = true; public NewSSLCertificateDialog(Frame owner, Date defaultExpiration) { @@ -60,6 +62,7 @@ expirationDate = new JDateChooser(defaultExpiration); hostname = new JTextField(20); serveCertificate = new JCheckBox(Translation.get("gb.serveCertificate"), true); JPanel panel = new JPanel(new GridLayout(0, 2, Utils.MARGIN, Utils.MARGIN)); @@ -68,6 +71,9 @@ panel.add(new JLabel(Translation.get("gb.expires"))); panel.add(expirationDate); panel.add(new JLabel("")); panel.add(serveCertificate); JButton ok = new JButton(Translation.get("gb.ok")); ok.addActionListener(new ActionListener() { @@ -123,7 +129,11 @@ public Date getExpiration() { return expirationDate.getDate(); } public boolean isServeCertificate() { return serveCertificate.isSelected(); } public boolean isCanceled() { return isCanceled; } src/com/gitblit/build/Build.java
@@ -565,17 +565,17 @@ "c7adc475ca40c288c93054e0f4fe58f3a98c0cb5"); public static final MavenObject JETTY = new MavenObject( "Jetty", "org/eclipse/jetty/aggregate", "jetty-webapp", "7.6.7.v20120910", "Jetty", "org/eclipse/jetty/aggregate", "jetty-webapp", "7.6.8.v20121106", 1000000, 680000, 2720000, "d621fa6419aaa37edbcab8e16a5e6b05c9527e62", "b505f7b493c5aa262d371d90754bded8b392ffb0", "6333969b4d509c4b681e05302ca7ebccb9c3efb5", "354f2752ed6544296bc0fc92e533d68a5b03045b", ""); public static final MavenObject JETTY_AJP = new MavenObject( "Jetty-AJP", "org/eclipse/jetty", "jetty-ajp", "7.6.7.v20120910", "Jetty-AJP", "org/eclipse/jetty", "jetty-ajp", "7.6.8.v20121106", 32000, 22000, 97000, "578d502bc78ed7aa1c0b6afef4cd59477041ec37", "6cfed9a1354f720fcde12ec15d5e1ae9cf97000c", "95bd1c89bb2afd4eeaabc6f4b0183a9f26a522d7", "e1fc2539202ebb240a87a080bc44a24c93d7318b", ""); public static final MavenObject SERVLET = new MavenObject( @@ -647,10 +647,10 @@ ""); public static final MavenObject MARKDOWNPAPERS = new MavenObject( "MarkdownPapers", "org/tautua/markdownpapers", "markdownpapers-core", "1.2.7", 87000, 58000, 268000, "84ac5636ac7ddfad9d2ee8456a0f4f69709b6ee0", "453cf00a289c46a0e4f6f019a28d2a2605f652c8", "MarkdownPapers", "org/tautua/markdownpapers", "markdownpapers-core", "1.3.2", 92000, 60000, 268000, "da22db6660e90b9a677bbdfc2c511c619ea5c249", "6a7228280a229144afe6c01351a8f44675d8524d", ""); public static final MavenObject BOUNCYCASTLE = new MavenObject( src/com/gitblit/client/EditRepositoryDialog.java
@@ -124,6 +124,8 @@ private JComboBox gcPeriod; private JTextField gcThreshold; private JComboBox maxActivityCommits; private RegistrantPermissionsPanel usersPalette; @@ -225,6 +227,10 @@ isFrozen = new JCheckBox(Translation.get("gb.isFrozenDescription"), anRepository.isFrozen); maxActivityCommits = new JComboBox(new Integer [] { 0, 25, 50, 75, 100, 150, 250, 500 }); maxActivityCommits.setSelectedItem(anRepository.maxActivityCommits); mailingListsField = new JTextField( ArrayUtils.isEmpty(anRepository.mailingLists) ? "" : StringUtils.flattenStrings(anRepository.mailingLists, @@ -314,6 +320,8 @@ skipSizeCalculation)); fieldsPanel.add(newFieldPanel(Translation.get("gb.skipSummaryMetrics"), skipSummaryMetrics)); fieldsPanel.add(newFieldPanel(Translation.get("gb.maxActivityCommits"), maxActivityCommits)); fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"), mailingListsField)); @@ -561,6 +569,7 @@ repository.showReadme = showReadme.isSelected(); repository.skipSizeCalculation = skipSizeCalculation.isSelected(); repository.skipSummaryMetrics = skipSummaryMetrics.isSelected(); repository.maxActivityCommits = (Integer) maxActivityCommits.getSelectedItem(); repository.isFrozen = isFrozen.isSelected(); repository.allowForks = allowForks.isSelected(); src/com/gitblit/client/EditUserDialog.java
@@ -90,7 +90,17 @@ private JCheckBox canCreateCheckbox; private JCheckBox notFederatedCheckbox; private JTextField organizationalUnitField; private JTextField organizationField; private JTextField localityField; private JTextField stateProvinceField; private JTextField countryCodeField; private RegistrantPermissionsPanel repositoryPalette; private JPalette<TeamModel> teamsPalette; @@ -142,6 +152,12 @@ Translation.get("gb.excludeFromFederationDescription"), anUser.excludeFromFederation); organizationalUnitField = new JTextField(anUser.organizationalUnit == null ? "" : anUser.organizationalUnit, 25); organizationField = new JTextField(anUser.organization == null ? "" : anUser.organization, 25); localityField = new JTextField(anUser.locality == null ? "" : anUser.locality, 25); stateProvinceField = new JTextField(anUser.stateProvince == null ? "" : anUser.stateProvince, 25); countryCodeField = new JTextField(anUser.countryCode == null ? "" : anUser.countryCode, 15); // credentials are optionally controlled by 3rd-party authentication usernameField.setEnabled(settings.supportsCredentialChanges); passwordField.setEnabled(settings.supportsCredentialChanges); @@ -149,6 +165,12 @@ displayNameField.setEnabled(settings.supportsDisplayNameChanges); emailAddressField.setEnabled(settings.supportsEmailAddressChanges); organizationalUnitField.setEnabled(settings.supportsDisplayNameChanges); organizationField.setEnabled(settings.supportsDisplayNameChanges); localityField.setEnabled(settings.supportsDisplayNameChanges); stateProvinceField.setEnabled(settings.supportsDisplayNameChanges); countryCodeField.setEnabled(settings.supportsDisplayNameChanges); JPanel fieldsPanel = new JPanel(new GridLayout(0, 1)); fieldsPanel.add(newFieldPanel(Translation.get("gb.username"), usernameField)); @@ -162,6 +184,13 @@ fieldsPanel.add(newFieldPanel(Translation.get("gb.excludeFromFederation"), notFederatedCheckbox)); JPanel attributesPanel = new JPanel(new GridLayout(0, 1, 5, 2)); attributesPanel.add(newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnitField)); attributesPanel.add(newFieldPanel(Translation.get("gb.organization") + " (O)", organizationField)); attributesPanel.add(newFieldPanel(Translation.get("gb.locality") + " (L)", localityField)); attributesPanel.add(newFieldPanel(Translation.get("gb.stateProvince") + " (ST)", stateProvinceField)); attributesPanel.add(newFieldPanel(Translation.get("gb.countryCode") + " (C)", countryCodeField)); final Insets _insets = new Insets(5, 5, 5, 5); repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY); teamsPalette = new JPalette<TeamModel>(); @@ -169,6 +198,9 @@ JPanel fieldsPanelTop = new JPanel(new BorderLayout()); fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH); JPanel attributesPanelTop = new JPanel(new BorderLayout()); attributesPanelTop.add(attributesPanel, BorderLayout.NORTH); JPanel repositoriesPanel = new JPanel(new BorderLayout()) { @@ -192,6 +224,7 @@ JTabbedPane panel = new JTabbedPane(JTabbedPane.TOP); panel.addTab(Translation.get("gb.general"), fieldsPanelTop); panel.addTab(Translation.get("gb.attributes"), attributesPanelTop); if (protocolVersion > 1) { panel.addTab(Translation.get("gb.teamMemberships"), teamsPanel); } @@ -324,6 +357,12 @@ user.canCreate = canCreateCheckbox.isSelected(); user.excludeFromFederation = notFederatedCheckbox.isSelected(); user.organizationalUnit = organizationalUnitField.getText().trim(); user.organization = organizationField.getText().trim(); user.locality = localityField.getText().trim(); user.stateProvince = stateProvinceField.getText().trim(); user.countryCode = countryCodeField.getText().trim(); for (RegistrantAccessPermission rp : repositoryPalette.getPermissions()) { user.setRepositoryPermission(rp.registrant, rp.permission); } src/com/gitblit/models/PathModel.java
@@ -48,6 +48,10 @@ this.commitId = commitId; } public boolean isSymlink() { return FileMode.SYMLINK.equals(mode); } public boolean isSubmodule() { return FileMode.GITLINK.equals(mode); } src/com/gitblit/models/RepositoryModel.java
@@ -78,6 +78,7 @@ public boolean verifyCommitter; public String gcThreshold; public int gcPeriod; public int maxActivityCommits; public transient boolean isCollectingGarbage; public Date lastGC; src/com/gitblit/models/UserModel.java
@@ -589,7 +589,7 @@ public boolean hasBranchPermission(String repositoryName, String branch) { // Default UserModel doesn't implement branch-level security. Other Realms (i.e. Gerrit) may override this method. return hasRepositoryPermission(repositoryName); return hasRepositoryPermission(repositoryName) || hasTeamRepositoryPermission(repositoryName); } public boolean isMyPersonalRepository(String repository) { src/com/gitblit/utils/ActivityUtils.java
@@ -106,7 +106,11 @@ } List<RevCommit> commits = JGitUtils.getRevLog(repository, branch, thresholdDate); for (RevCommit commit : commits) { if (model.maxActivityCommits > 0 && commits.size() > model.maxActivityCommits) { // trim commits to maximum count commits = commits.subList(0, model.maxActivityCommits); } for (RevCommit commit : commits) { Date date = JGitUtils.getCommitDate(commit); String dateStr = df.format(date); if (!activity.containsKey(dateStr)) { src/com/gitblit/utils/CompressionUtils.java
@@ -15,27 +15,25 @@ */ package com.gitblit.utils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.compressors.CompressorException; import org.apache.commons.compress.compressors.CompressorStreamFactory; import org.apache.commons.compress.utils.IOUtils; import org.apache.wicket.util.io.ByteArrayOutputStream; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; @@ -99,37 +97,35 @@ RevWalk rw = new RevWalk(repository); TreeWalk tw = new TreeWalk(repository); try { tw.reset(); tw.addTree(commit.getTree()); ZipOutputStream zos = new ZipOutputStream(os); ZipArchiveOutputStream zos = new ZipArchiveOutputStream(os); zos.setComment("Generated by Gitblit"); if (!StringUtils.isEmpty(basePath)) { PathFilter f = PathFilter.create(basePath); tw.setFilter(f); } tw.setRecursive(true); MutableObjectId id = new MutableObjectId(); ObjectReader reader = tw.getObjectReader(); long modified = commit.getAuthorIdent().getWhen().getTime(); while (tw.next()) { if (tw.getFileMode(0) == FileMode.GITLINK) { FileMode mode = tw.getFileMode(0); if (mode == FileMode.GITLINK || mode == FileMode.TREE) { continue; } ZipEntry entry = new ZipEntry(tw.getPathString()); entry.setSize(tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB)); tw.getObjectId(id, 0); ZipArchiveEntry entry = new ZipArchiveEntry(tw.getPathString()); entry.setSize(reader.getObjectSize(id, Constants.OBJ_BLOB)); entry.setComment(commit.getName()); zos.putNextEntry(entry); entry.setUnixMode(mode.getBits()); entry.setTime(modified); zos.putArchiveEntry(entry); ObjectId entid = tw.getObjectId(0); FileMode entmode = tw.getFileMode(0); RevBlob blob = (RevBlob) rw.lookupAny(entid, entmode.getObjectType()); rw.parseBody(blob); ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB); byte[] tmp = new byte[4096]; InputStream in = ldr.openStream(); int n; while ((n = in.read(tmp)) > 0) { zos.write(tmp, 0, n); } in.close(); ObjectLoader ldr = repository.open(id); ldr.copyTo(zos); zos.closeArchiveEntry(); } zos.finish(); success = true; @@ -250,6 +246,7 @@ RevWalk rw = new RevWalk(repository); TreeWalk tw = new TreeWalk(repository); try { tw.reset(); tw.addTree(commit.getTree()); TarArchiveOutputStream tos = new TarArchiveOutputStream(cos); tos.setAddPaxHeadersForNonAsciiNames(true); @@ -259,46 +256,33 @@ tw.setFilter(f); } tw.setRecursive(true); MutableObjectId id = new MutableObjectId(); long modified = commit.getAuthorIdent().getWhen().getTime(); while (tw.next()) { FileMode mode = tw.getFileMode(0); if (mode == FileMode.GITLINK) { if (mode == FileMode.GITLINK || mode == FileMode.TREE) { continue; } ObjectId id = tw.getObjectId(0); tw.getObjectId(id, 0); // new entry TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString()); entry.setSize(tw.getObjectReader().getObjectSize(id, Constants.OBJ_BLOB)); if (FileMode.SYMLINK.equals(mode)) { // symlink entry.setMode(mode.getBits()); // read the symlink target ByteArrayOutputStream bs = new ByteArrayOutputStream(); RevBlob blob = (RevBlob) rw.lookupAny(id, mode.getObjectType()); rw.parseBody(blob); ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB); IOUtils.copy(ldr.openStream(), bs); entry.setLinkName(bs.toString("UTF-8")); ObjectLoader loader = repository.open(id); if (FileMode.SYMLINK == mode) { TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString(),TarArchiveEntry.LF_SYMLINK); ByteArrayOutputStream bos = new ByteArrayOutputStream(); loader.copyTo(bos); entry.setLinkName(bos.toString()); entry.setModTime(modified); tos.putArchiveEntry(entry); tos.closeArchiveEntry(); } else { // regular file or executable file TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString()); entry.setMode(mode.getBits()); } entry.setModTime(commit.getAuthorIdent().getWhen()); tos.putArchiveEntry(entry); if (!FileMode.SYMLINK.equals(mode)) { // write the blob RevBlob blob = (RevBlob) rw.lookupAny(id, mode.getObjectType()); rw.parseBody(blob); ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB); IOUtils.copy(ldr.openStream(), tos); entry.setModTime(modified); entry.setSize(loader.getSize()); tos.putArchiveEntry(entry); loader.copyTo(tos); tos.closeArchiveEntry(); } // close entry tos.closeArchiveEntry(); } tos.finish(); tos.close(); src/com/gitblit/utils/HttpUtils.java
@@ -178,4 +178,27 @@ } return null; } public static boolean isIpAddress(String address) { if (StringUtils.isEmpty(address)) { return false; } String [] fields = address.split("\\."); if (fields.length == 4) { // IPV4 for (String field : fields) { try { int value = Integer.parseInt(field); if (value < 0 || value > 255) { return false; } } catch (Exception e) { return false; } } return true; } // TODO IPV6? return false; } } src/com/gitblit/utils/X509Utils.java
@@ -46,11 +46,13 @@ import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; @@ -556,6 +558,16 @@ certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false)); certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey())); // support alternateSubjectNames for SSL certificates List<GeneralName> altNames = new ArrayList<GeneralName>(); if (HttpUtils.isIpAddress(sslMetadata.commonName)) { altNames.add(new GeneralName(GeneralName.iPAddress, sslMetadata.commonName)); } if (altNames.size() > 0) { GeneralNames subjectAltName = new GeneralNames(altNames.toArray(new GeneralName [altNames.size()])); certBuilder.addExtension(X509Extension.subjectAlternativeName, false, subjectAltName); } ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM) .setProvider(BC).build(caPrivateKey); X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC) src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -430,4 +430,13 @@ gb.clientCertificateBundleSent = Client certificate bundle for {0} sent gb.enterKeystorePassword = Please enter the Gitblit keystore password gb.warning = warning 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.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.maxActivityCommits = max activity commits gb.maxActivityCommitsDescription = maximum number of commits to contribute to the Activity page gb.noMaximum = no maximum gb.attributes = attributes gb.serveCertificate = serve https with this certificate gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certificate for {0}.\nYou must restart Gitblit to use the new certificate.\n\nIf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''. gb.validity = validity gb.siteName = site name gb.siteNameDescription = short, descriptive name of your server src/com/gitblit/wicket/pages/CommitPage.java
@@ -169,9 +169,15 @@ WicketUtils.newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule)); } else { // blob item.add(new LinkPanel("pathName", "list", entry.path, BlobPage.class, String displayPath = entry.path; String path = entry.path; if (entry.isSymlink()) { path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path); displayPath = entry.path + " -> " + path; } item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class, WicketUtils .newPathParameter(repositoryName, entry.commitId, entry.path))); .newPathParameter(repositoryName, entry.commitId, path))); } // quick links src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -39,8 +39,9 @@ <tr><th><wicket:message key="gb.showReadme"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="showReadme" tabindex="10" /> <span class="help-inline"><wicket:message key="gb.showReadmeDescription"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.skipSizeCalculation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSizeCalculation" tabindex="11" /> <span class="help-inline"><wicket:message key="gb.skipSizeCalculationDescription"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.skipSummaryMetrics"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSummaryMetrics" tabindex="12" /> <span class="help-inline"><wicket:message key="gb.skipSummaryMetricsDescription"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.maxActivityCommits"></wicket:message></th><td class="edit"><select class="span2" wicket:id="maxActivityCommits" tabindex="13" /> <span class="help-inline"><wicket:message key="gb.maxActivityCommitsDescription"></wicket:message></span></td></tr> <tr><th colspan="2"><hr/></th></tr> <tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="13" /></td></tr> <tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="14" /></td></tr> </tbody> </table> </div> @@ -49,15 +50,15 @@ <div class="tab-pane" id="permissions"> <table class="plain"> <tbody class="settings"> <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select class="span2" wicket:id="owner" tabindex="14" /> <span class="help-inline"><wicket:message key="gb.ownerDescription"></wicket:message></span></td></tr> <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select class="span2" wicket:id="owner" tabindex="15" /> <span class="help-inline"><wicket:message key="gb.ownerDescription"></wicket:message></span></td></tr> <tr><th colspan="2"><hr/></th></tr> <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="15" /></td></tr> <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="16" /></td></tr> <tr><th colspan="2"><hr/></th></tr> <tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;"><span class="authorizationControl" wicket:id="authorizationControl"></span></td></tr> <tr><th colspan="2"><hr/></th></tr> <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="18" /> <span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="19" /> <span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="20" /> <span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="17" /> <span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="18" /> <span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr> <tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="19" /> <span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr> <tr><th colspan="2"><hr/></th></tr> <tr><th><wicket:message key="gb.userPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr> <tr><th colspan="2"><hr/></th></tr> @@ -70,7 +71,7 @@ <div class="tab-pane" id="federation"> <table class="plain"> <tbody class="settings"> <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="21" /></td></tr> <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="20" /></td></tr> <tr><th><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr> </tbody> </table> src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -379,7 +379,7 @@ form.add(new TextField<String>("description")); form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames()) .setEnabled(GitBlitWebSession.get().canAdmin() && !repositoryModel.isPersonalRepository())); form.add(new CheckBox("allowForks")); form.add(new CheckBox("allowForks").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true))); DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays .asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()); form.add(accessRestriction); @@ -414,6 +414,9 @@ form.add(new CheckBox("showReadme")); form.add(new CheckBox("skipSizeCalculation")); form.add(new CheckBox("skipSummaryMetrics")); List<Integer> maxActivityCommits = Arrays.asList(0, 25, 50, 75, 100, 150, 200, 250, 500 ); form.add(new DropDownChoice<Integer>("maxActivityCommits", maxActivityCommits, new MaxActivityCommitsRenderer())); mailingLists = new Model<String>(ArrayUtils.isEmpty(repositoryModel.mailingLists) ? "" : StringUtils.flattenStrings(repositoryModel.mailingLists, " ")); form.add(new TextField<String>("mailingLists", mailingLists)); @@ -654,4 +657,26 @@ } } private class MaxActivityCommitsRenderer implements IChoiceRenderer<Integer> { private static final long serialVersionUID = 1L; public MaxActivityCommitsRenderer() { } @Override public String getDisplayValue(Integer value) { if (value == 0) { return getString("gb.noMaximum"); } else { return value + " " + getString("gb.commits"); } } @Override public String getIdValue(Integer value, int index) { return Integer.toString(index); } } } src/com/gitblit/wicket/pages/EditTeamPage.java
@@ -38,6 +38,7 @@ import com.gitblit.GitBlit; import com.gitblit.GitBlitException; import com.gitblit.Keys; import com.gitblit.Constants.RegistrantType; import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.models.TeamModel; @@ -216,7 +217,7 @@ // field names reflective match TeamModel fields form.add(new TextField<String>("name")); form.add(new CheckBox("canAdmin")); form.add(new CheckBox("canFork")); form.add(new CheckBox("canFork").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true))); form.add(new CheckBox("canCreate")); form.add(users.setEnabled(editMemberships)); mailingLists = new Model<String>(teamModel.mailingLists == null ? "" src/com/gitblit/wicket/pages/EditUserPage.html
@@ -13,6 +13,7 @@ <!-- tab titles --> <ul class="nav nav-tabs"> <li class="active"><a href="#general" data-toggle="tab"><wicket:message key="gb.general"></wicket:message></a></li> <li><a href="#attributes" data-toggle="tab"><wicket:message key="gb.attributes"></wicket:message></a></li> <li><a href="#permissions" data-toggle="tab"><wicket:message key="gb.accessPermissions"></wicket:message></a></li> </ul> @@ -36,6 +37,19 @@ </table> </div> <!-- attributes tab --> <div class="tab-pane" id="attributes"> <table class="plain"> <tbody class="settings"> <tr><th><wicket:message key="gb.organizationalUnit"></wicket:message> (OU)</th><td class="edit"><input type="text" wicket:id="organizationalUnit" size="30" tabindex="1" /></td></tr> <tr><th><wicket:message key="gb.organization"></wicket:message> (O)</th><td class="edit"><input type="text" wicket:id="organization" size="30" tabindex="2" /></td></tr> <tr><th><wicket:message key="gb.locality"></wicket:message> (L)</th><td class="edit"><input type="text" wicket:id="locality" size="30" tabindex="3" /></td></tr> <tr><th><wicket:message key="gb.stateProvince"></wicket:message> (ST)</th><td class="edit"><input type="text" wicket:id="stateProvince" size="30" tabindex="4" /></td></tr> <tr><th><wicket:message key="gb.countryCode"></wicket:message> (C)</th><td class="edit"><input type="text" wicket:id="countryCode" size="15 " tabindex="5" /></td></tr> </tbody> </table> </div> <!-- access permissions tab --> <div class="tab-pane" id="permissions"> <table class="plain"> src/com/gitblit/wicket/pages/EditUserPage.java
@@ -233,12 +233,17 @@ form.add(new TextField<String>("displayName").setEnabled(editDisplayName)); form.add(new TextField<String>("emailAddress").setEnabled(editEmailAddress)); form.add(new CheckBox("canAdmin")); form.add(new CheckBox("canFork")); form.add(new CheckBox("canFork").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true))); form.add(new CheckBox("canCreate")); form.add(new CheckBox("excludeFromFederation")); form.add(new RegistrantPermissionsPanel("repositories", RegistrantType.REPOSITORY, repos, permissions, getAccessPermissions())); form.add(teams.setEnabled(editTeams)); form.add(new TextField<String>("organizationalUnit").setEnabled(editDisplayName)); form.add(new TextField<String>("organization").setEnabled(editDisplayName)); form.add(new TextField<String>("locality").setEnabled(editDisplayName)); form.add(new TextField<String>("stateProvince").setEnabled(editDisplayName)); form.add(new TextField<String>("countryCode").setEnabled(editDisplayName)); form.add(new Button("save")); Button cancel = new Button("cancel") { private static final long serialVersionUID = 1L; src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -154,7 +154,9 @@ pages.put("branches", new PageRegistration("gb.branches", BranchesPage.class, params)); pages.put("tags", new PageRegistration("gb.tags", TagsPage.class, params)); pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params)); pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params)); if (GitBlit.getBoolean(Keys.web.allowForking, true)) { pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params)); } // conditional links Repository r = getRepository(); @@ -191,7 +193,7 @@ } protected boolean allowForkControls() { return true; return GitBlit.getBoolean(Keys.web.allowForking, true); } @Override @@ -597,4 +599,4 @@ getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl)); } } } } src/com/gitblit/wicket/pages/TreePage.java
@@ -144,25 +144,31 @@ item.add(links); } else { // blob link String displayPath = entry.name; String path = entry.path; if (entry.isSymlink()) { path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path); displayPath = entry.name + " -> " + path; } item.add(WicketUtils.getFileImage("pathIcon", entry.name)); item.add(new Label("pathSize", byteFormat.format(entry.size))); item.add(new LinkPanel("pathName", "list", entry.name, BlobPage.class, item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class, WicketUtils.newPathParameter(repositoryName, entry.commitId, entry.path))); path))); // links Fragment links = new Fragment("pathLinks", "blobLinks", this); links.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils.newPathParameter(repositoryName, entry.commitId, entry.path))); path))); links.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils .newPathParameter(repositoryName, entry.commitId, entry.path))); .newPathParameter(repositoryName, entry.commitId, path))); links.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils.newPathParameter(repositoryName, entry.commitId, entry.path))); path))); links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils.newPathParameter(repositoryName, entry.commitId, entry.path))); path))); item.add(links); } }