From 7a399b7061d4d374f01ddaa75ae7fba53ca4cb6b Mon Sep 17 00:00:00 2001 From: Matthieu Tardy Date: Mon, 9 Jan 2017 07:38:13 +0100 Subject: Strip reference prefixes on branch creation Signed-off-by: Matthieu Tardy --- ...ranch-names-with-reference-prefixes-results-in-buggy-branches.yml | 4 ++++ lib/gitlab/git_ref_validator.rb | 3 +++ spec/lib/git_ref_validator_spec.rb | 5 +++++ 3 files changed, 12 insertions(+) create mode 100644 changelogs/unreleased/26470-branch-names-with-reference-prefixes-results-in-buggy-branches.yml diff --git a/changelogs/unreleased/26470-branch-names-with-reference-prefixes-results-in-buggy-branches.yml b/changelogs/unreleased/26470-branch-names-with-reference-prefixes-results-in-buggy-branches.yml new file mode 100644 index 00000000000..e82cbf00cfb --- /dev/null +++ b/changelogs/unreleased/26470-branch-names-with-reference-prefixes-results-in-buggy-branches.yml @@ -0,0 +1,4 @@ +--- +title: Strip reference prefixes on branch creation +merge_request: 8498 +author: Matthieu Tardy diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 4d83d8e72a8..0e87ee30c98 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -5,6 +5,9 @@ module Gitlab # # Returns true for a valid reference name, false otherwise def validate(ref_name) + return false if ref_name.start_with?('refs/heads/') + return false if ref_name.start_with?('refs/remotes/') + Gitlab::Utils.system_silent( %W(#{Gitlab.config.git.bin_path} check-ref-format refs/#{ref_name})) end diff --git a/spec/lib/git_ref_validator_spec.rb b/spec/lib/git_ref_validator_spec.rb index dc57e94f193..cc8daa535d6 100644 --- a/spec/lib/git_ref_validator_spec.rb +++ b/spec/lib/git_ref_validator_spec.rb @@ -5,6 +5,7 @@ describe Gitlab::GitRefValidator, lib: true do it { expect(Gitlab::GitRefValidator.validate('implement_@all')).to be_truthy } it { expect(Gitlab::GitRefValidator.validate('my_new_feature')).to be_truthy } it { expect(Gitlab::GitRefValidator.validate('#1')).to be_truthy } + it { expect(Gitlab::GitRefValidator.validate('feature/refs/heads/foo')).to be_truthy } it { expect(Gitlab::GitRefValidator.validate('feature/~new/')).to be_falsey } it { expect(Gitlab::GitRefValidator.validate('feature/^new/')).to be_falsey } it { expect(Gitlab::GitRefValidator.validate('feature/:new/')).to be_falsey } @@ -17,4 +18,8 @@ describe Gitlab::GitRefValidator, lib: true do it { expect(Gitlab::GitRefValidator.validate('feature\new')).to be_falsey } it { expect(Gitlab::GitRefValidator.validate('feature//new')).to be_falsey } it { expect(Gitlab::GitRefValidator.validate('feature new')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('refs/heads/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('refs/remotes/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('refs/heads/feature')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('refs/remotes/origin')).to be_falsey } end -- cgit v1.2.1 From 5852e0e0605e90949aec817293f45fabf5b116ac Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Fri, 24 Feb 2017 12:21:41 +0100 Subject: Suggest a more secure way of handling SSH host keys in docker builds --- doc/ci/ssh_keys/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 49e7ac38b26..688a69d77ba 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -38,6 +38,15 @@ following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the content of your _private_ key that you created earlier. +It is also good practice to check the server's own public key to make sure you +are not being targeted by a man-in-the-middle attack. To do this, add another +variable named `SSH_SERVER_HOSTKEYS`. To find out the hostkeys of your server, run +the `ssh-keyscan YOUR_SERVER` command from a trusted network (ideally, from the +server itself), and paste its output into the `SSH_SERVER_HOSTKEY` variable. If +you need to connect to multiple servers, concatenate all the server public keys +that you collected into the **Value** of the variable. There must be one key per +line. + Next you need to modify your `.gitlab-ci.yml` with a `before_script` action. Add it to the top: @@ -59,6 +68,11 @@ before_script: # you will overwrite your user's SSH config. - mkdir -p ~/.ssh - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' + # In order to properly check the server's host key, assuming you created the + # SSH_SERVER_HOSTKEYS variable previously, uncomment the following two lines + # instead. + # - mkdir -p ~/.ssh + # - '[[ -f /.dockerenv ]] && echo "$SSH_SERVER_HOSTKEYS" > ~/.ssh/known_hosts' ``` As a final step, add the _public_ key from the one you created earlier to the -- cgit v1.2.1 From 7951b8469d81f58132f69ad3a1e71fbd39ef1f49 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 24 Feb 2017 23:11:50 +0530 Subject: Document U2F limitations with multiple hostnames/FQDNs. --- .../28277-document-u2f-limitations-with-multiple-urls.yml | 4 ++++ doc/user/profile/account/two_factor_authentication.md | 11 +++++++++++ 2 files changed, 15 insertions(+) create mode 100644 changelogs/unreleased/28277-document-u2f-limitations-with-multiple-urls.yml diff --git a/changelogs/unreleased/28277-document-u2f-limitations-with-multiple-urls.yml b/changelogs/unreleased/28277-document-u2f-limitations-with-multiple-urls.yml new file mode 100644 index 00000000000..6e3cd8a60d8 --- /dev/null +++ b/changelogs/unreleased/28277-document-u2f-limitations-with-multiple-urls.yml @@ -0,0 +1,4 @@ +--- +title: Document U2F limitations with multiple URLs +merge_request: 9300 +author: diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index eaa39a0c4ea..63a3d3c472e 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -215,3 +215,14 @@ you may have cases where authorization always fails because of time differences. [Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en [FreeOTP]: https://freeotp.github.io/ [YubiKey]: https://www.yubico.com/products/yubikey-hardware/ + +- The GitLab U2F implementation does _not_ work when the GitLab instance is accessed from +multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at +the time of registration, and cannot be used for other hostnames/FQDNs. + + For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`: + + - The user logs in via `first.host.xyz` and registers their U2F key. + - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication suceeds. + - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because + the U2F key has only been registered on `first.host.xyz`. -- cgit v1.2.1 From 1bc5dab7b4f2650b5afb7c0e4c70e5ac9f66eba0 Mon Sep 17 00:00:00 2001 From: 3kami3 Date: Wed, 1 Mar 2017 23:16:38 +0900 Subject: Add real_ip setting to nginx example. ref) https://docs.gitlab.com/omnibus/settings/nginx.html#configuring-gitlab-trusted_proxies-and-the-nginx-real_ip-module --- lib/support/nginx/gitlab | 6 ++++++ lib/support/nginx/gitlab-ssl | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 2f7c34a3f31..78f28347d1a 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -38,6 +38,12 @@ server { ## See app/controllers/application_controller.rb for headers set + ## Real IP Module Config + ## http://nginx.org/en/docs/http/ngx_http_realip_module.html + real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol + real_ip_recursive off; ## If you enable 'on' + set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 5661394058d..1bccb1c2451 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -82,6 +82,12 @@ server { ## # ssl_dhparam /etc/ssl/certs/dhparam.pem; + ## Real IP Module Config + ## http://nginx.org/en/docs/http/ngx_http_realip_module.html + real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol + real_ip_recursive off; ## If you enable 'on' + set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; -- cgit v1.2.1 From 330d4f38041e2727a741d800b623563acef5e695 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 24 Feb 2017 22:18:08 +0600 Subject: dismissable error close is not visible enough --- app/assets/stylesheets/pages/note_form.scss | 12 ++++++++++++ .../28660-fix-dismissable-error-close-not-visible-enough.yml | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index f984b469609..47e1c7902f8 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -148,6 +148,18 @@ .error-alert > .alert { margin-top: 5px; margin-bottom: 5px; + + &.alert-dismissable { + .close { + color: $white-light; + opacity: 0.85; + font-weight: normal; + + &:hover { + opacity: 1; + } + } + } } .discussion-body, diff --git a/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml b/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml new file mode 100644 index 00000000000..8b592766bf3 --- /dev/null +++ b/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml @@ -0,0 +1,4 @@ +--- +title: Fixes dismissable error close is not visible enough +merge_request: 9516 +author: -- cgit v1.2.1 From 79c3ace80b690c9ccc2d6190fcf1f14f735f566c Mon Sep 17 00:00:00 2001 From: 3kami3 Date: Fri, 3 Mar 2017 22:20:29 +0900 Subject: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9623#note_24573655 Fixed issues pointed out. --- lib/support/nginx/gitlab | 3 ++- lib/support/nginx/gitlab-ssl | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 78f28347d1a..f25e66d54c8 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -42,7 +42,8 @@ server { ## http://nginx.org/en/docs/http/ngx_http_realip_module.html real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol real_ip_recursive off; ## If you enable 'on' - set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## If you have a trusted IP address, uncomment it and set it + # set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 1bccb1c2451..67dac676e49 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -86,7 +86,8 @@ server { ## http://nginx.org/en/docs/http/ngx_http_realip_module.html real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol real_ip_recursive off; ## If you enable 'on' - set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## If you have a trusted IP address, uncomment it and set it + # set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; -- cgit v1.2.1 From 75e78f108f850fe6c70c04a13747d2c40a511774 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 8 Mar 2017 16:45:59 +0000 Subject: The GitLab Pages external-http and external-https arguments can be specified multiple times --- config/gitlab.yml.example | 4 ++-- config/initializers/1_settings.rb | 4 ++-- doc/administration/pages/index.md | 27 ++++++++++++++++----------- features/steps/project/pages.rb | 6 +++--- lib/support/init.d/gitlab.default.example | 4 ++-- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 720df0cac2d..8d0ea603569 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -157,8 +157,8 @@ production: &base host: example.com port: 80 # Set to 443 if you serve the pages with HTTPS https: false # Set to true if you serve the pages with HTTPS - # external_http: "1.1.1.1:80" # If defined, enables custom domain support in GitLab Pages - # external_https: "1.1.1.1:443" # If defined, enables custom domain and certificate support in GitLab Pages + # external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages + # external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages ## Mattermost ## For enabling Add to Mattermost button diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index b45d0e23080..e5e90031871 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -278,8 +278,8 @@ Settings.pages['host'] ||= "example.com" Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" Settings.pages['url'] ||= Settings.send(:build_pages_url) -Settings.pages['external_http'] ||= false if Settings.pages['external_http'].nil? -Settings.pages['external_https'] ||= false if Settings.pages['external_https'].nil? +Settings.pages['external_http'] ||= false unless Settings.pages['external_http'].present? +Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present? # # Git LFS diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 62b0468da79..0c63b0b59a7 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -26,9 +26,9 @@ it works. --- -In the case of [custom domains](#custom-domains) (but not -[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on -ports `80` and/or `443`. For that reason, there is some flexibility in the way +In the case of [custom domains](#custom-domains) (but not +[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on +ports `80` and/or `443`. For that reason, there is some flexibility in the way which you can set it up: 1. Run the Pages daemon in the same server as GitLab, listening on a secondary IP. @@ -65,11 +65,13 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the host that GitLab runs. For example, an entry would look like this: ``` -*.example.io. 1800 IN A 1.1.1.1 +*.example.io. 1800 IN A 1.1.1.1 +*.example.io. 1800 IN AAAA 2001::1 ``` where `example.io` is the domain under which GitLab Pages will be served -and `1.1.1.1` is the IP address of your GitLab instance. +and `1.1.1.1` is the IPv4 address of your GitLab instance and `2001::1` is the +IPv6 address. If you don't have IPv6, you can omit the AAAA record. > **Note:** You should not use the GitLab domain to serve user pages. For more information @@ -141,7 +143,8 @@ outside world. In addition to the wildcard domains, you can also have the option to configure GitLab Pages to work with custom domains. Again, there are two options here: support custom domains with and without TLS certificates. The easiest setup is -that without TLS certificates. +that without TLS certificates. In either case, you'll need a secondary IP. If +you have IPv6 as well as IPv4 addresses, you can use them both. ### Custom domains @@ -163,11 +166,12 @@ world. Custom domains are supported, but no TLS. pages_external_url "http://example.io" nginx['listen_addresses'] = ['1.1.1.1'] pages_nginx['enable'] = false - gitlab_pages['external_http'] = '1.1.1.2:80' + gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80'] ``` where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + `1.1.1.2` and `2001::2` are the secondary IPs the GitLab Pages daemon + listens on. If you don't have IPv6, you can omit the IPv6 address. 1. [Reconfigure GitLab][reconfigure] @@ -194,12 +198,13 @@ world. Custom domains and TLS are supported. pages_nginx['enable'] = false gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" - gitlab_pages['external_http'] = '1.1.1.2:80' - gitlab_pages['external_https'] = '1.1.1.2:443' + gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80'] + gitlab_pages['external_https'] = ['1.1.1.2:443', '[2001::2]:443'] ``` where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + `1.1.1.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon + listens on. If you don't have IPv6, you can omit the IPv6 address. 1. [Reconfigure GitLab][reconfigure] diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index c80c6273807..4045955a8b9 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -53,13 +53,13 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'pages are exposed on external HTTP address' do - allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80') + allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80']) allow(Gitlab.config.pages).to receive(:external_https).and_return(nil) end step 'pages are exposed on external HTTPS address' do - allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80') - allow(Gitlab.config.pages).to receive(:external_https).and_return('1.1.1.1:443') + allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80']) + allow(Gitlab.config.pages).to receive(:external_https).and_return(['1.1.1.1:443']) end step 'I should be able to add a New Domain' do diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index e5797d8fe3c..f6642527639 100644 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -56,14 +56,14 @@ gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" # The value of -listen-http must be set to `gitlab.yml > pages > external_http` # as well. For example: # -# -listen-http 1.1.1.1:80 +# -listen-http 1.1.1.1:80 -listen-http [2001::1]:80 # # To enable HTTPS support for custom domains add the `-listen-https`, # `-root-cert` and `-root-key` directives in `gitlab_pages_options` below. # The value of -listen-https must be set to `gitlab.yml > pages > external_https` # as well. For example: # -# -listen-https 1.1.1.1:443 -root-cert /path/to/example.com.crt -root-key /path/to/example.com.key +# -listen-https 1.1.1.1:443 -listen-http [2001::1]:443 -root-cert /path/to/example.com.crt -root-key /path/to/example.com.key # # The -pages-domain must be specified the same as in `gitlab.yml > pages > host`. # Set `gitlab_pages_enabled=true` if you want to enable the Pages feature. -- cgit v1.2.1 From 6e7e9e80e0a89c2c295ccaa4b8469b5ed33acd27 Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Tue, 21 Feb 2017 16:32:08 +1100 Subject: prevent filtering Issues by multiple milestones, authors, or assignees --- .../javascripts/filtered_search/dropdown_hint.js | 14 +++++----- .../javascripts/filtered_search/dropdown_utils.js | 18 ++++++++----- app/views/shared/issuable/_search_bar.html.haml | 2 +- changelogs/unreleased/27174-filter-filters.yml | 4 +++ .../filtered_search/dropdown_utils_spec.js | 31 ++++++++++++++++++++-- 5 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 changelogs/unreleased/27174-filter-filters.yml diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index 38ff3fb7158..28e5e3232cb 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -57,13 +57,15 @@ require('./filtered_search_dropdown'); const dropdownData = []; [].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { - const { icon, hint, tag } = dropdownMenu.dataset; + const { icon, hint, tag, type } = dropdownMenu.dataset; if (icon && hint && tag) { - dropdownData.push({ - icon: `fa-${icon}`, - hint, - tag: `<${tag}>`, - }); + dropdownData.push( + Object.assign({ + icon: `fa-${icon}`, + hint, + tag: `<${tag}>`, + }, type && { type }), + ); } }); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index a5a6b56a0d3..77bf191f343 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -51,14 +51,18 @@ static filterHint(input, item) { const updatedItem = item; - const searchInput = gl.DropdownUtils.getSearchInput(input); - let { lastToken } = gl.FilteredSearchTokenizer.processTokens(searchInput); - lastToken = lastToken.key || lastToken || ''; - - if (!lastToken || searchInput.split('').last() === ' ') { + const searchInput = gl.DropdownUtils.getSearchQuery(input); + const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput); + const lastKey = lastToken.key || lastToken || ''; + const allowMultiple = item.type === 'array'; + const itemInExistingTokens = tokens.some(t => t.key === item.hint); + + if (!allowMultiple && itemInExistingTokens) { + updatedItem.droplab_hidden = true; + } else if (!lastKey || searchInput.split('').last() === ' ') { updatedItem.droplab_hidden = false; - } else if (lastToken) { - const split = lastToken.split(':'); + } else if (lastKey) { + const split = lastKey.split(':'); const tokenName = split[0].split(' ').last(); const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index f8123846596..46e8c259a84 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -73,7 +73,7 @@ %li.filter-dropdown-item %button.btn.btn-link.js-data-value {{title}} - #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } } + #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label', type: 'array' } } %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'none' } } %button.btn.btn-link diff --git a/changelogs/unreleased/27174-filter-filters.yml b/changelogs/unreleased/27174-filter-filters.yml new file mode 100644 index 00000000000..0da1e4d5d3b --- /dev/null +++ b/changelogs/unreleased/27174-filter-filters.yml @@ -0,0 +1,4 @@ +--- +title: Prevent filtering issues by multiple Milestones or Authors +merge_request: +author: diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 5c65903701b..e6538020896 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -126,7 +126,11 @@ require('~/filtered_search/filtered_search_dropdown_manager'); beforeEach(() => { setFixtures(` - +
    +
  • + +
  • +
`); input = document.getElementById('test'); @@ -142,7 +146,7 @@ require('~/filtered_search/filtered_search_dropdown_manager'); input.value = 'o'; updatedItem = gl.DropdownUtils.filterHint(input, { hint: 'label', - }, 'o'); + }); expect(updatedItem.droplab_hidden).toBe(true); }); @@ -150,6 +154,29 @@ require('~/filtered_search/filtered_search_dropdown_manager'); const updatedItem = gl.DropdownUtils.filterHint(input, {}, ''); expect(updatedItem.droplab_hidden).toBe(false); }); + + it('should allow multiple if item.type is array', () => { + input.value = 'label:~first la'; + const updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', + type: 'array', + }); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should prevent multiple if item.type is not array', () => { + input.value = 'milestone:~first mile'; + let updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', + }); + expect(updatedItem.droplab_hidden).toBe(true); + + updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', + type: 'string', + }); + expect(updatedItem.droplab_hidden).toBe(true); + }); }); describe('setDataValueIfSelected', () => { -- cgit v1.2.1 From 167e4a3072d42d5e741d9d52feb47ef71cb9b750 Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Sat, 11 Mar 2017 10:37:21 +1100 Subject: don't show scrollbar on search field unless necessary --- app/assets/stylesheets/framework/filters.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 8f2150066c7..ebfa1abbf84 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -144,7 +144,7 @@ .scroll-container { display: -webkit-flex; display: flex; - overflow-x: scroll; + overflow-x: auto; white-space: nowrap; width: 100%; } -- cgit v1.2.1 From 245020a127b8e3b95d16d0683f79d48895c8944c Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sat, 11 Mar 2017 19:02:21 +1100 Subject: Fix visibility level on new project page --- app/views/projects/new.html.haml | 3 ++- changelogs/unreleased/fix_visibility_level.yml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/fix_visibility_level.yml diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 2a98bba05ee..1a51f777bd9 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,5 +1,6 @@ - page_title 'New Project' - header_title "Projects", dashboard_projects_path +- visibility_level = params.try(:[], :project).try(:[], :visibility_level).to_i || default_project_visibility .project-edit-container .project-edit-errors @@ -95,7 +96,7 @@ = f.label :visibility_level, class: 'label-light' do Visibility Level = link_to icon('question-circle'), help_page_path("public_access/public_access") - = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project, with_label: false + = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @project, with_label: false = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel' diff --git a/changelogs/unreleased/fix_visibility_level.yml b/changelogs/unreleased/fix_visibility_level.yml new file mode 100644 index 00000000000..4cf649124ca --- /dev/null +++ b/changelogs/unreleased/fix_visibility_level.yml @@ -0,0 +1,4 @@ +--- +title: Fix visibility level on new project page +merge_request: 9885 +author: blackst0ne -- cgit v1.2.1 From 5c209d91c2748935c70b7906015a430d1b85b4b0 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sat, 11 Mar 2017 20:09:39 +1100 Subject: Added specs --- spec/features/projects/new_project_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 45185f2dd1f..651a46f62b1 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -53,6 +53,7 @@ feature "New project", feature: true do click_button('Create project') expect(page).to have_css '.project-edit-errors .alert.alert-danger' + expect(find("[name='project[visibility_level]'][checked].option-title").value).to eq('Internal') end it "selects the group namespace" do -- cgit v1.2.1 From 93e204ea524830baa47be8d6bbeb6f5aa39d7991 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sat, 11 Mar 2017 21:47:48 +1100 Subject: Update specs --- spec/features/projects/new_project_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 651a46f62b1..1dc1179c795 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -53,7 +53,6 @@ feature "New project", feature: true do click_button('Create project') expect(page).to have_css '.project-edit-errors .alert.alert-danger' - expect(find("[name='project[visibility_level]'][checked].option-title").value).to eq('Internal') end it "selects the group namespace" do @@ -61,6 +60,12 @@ feature "New project", feature: true do expect(namespace.text).to eq group.name end + + it 'selects the visibility level' do + level = Gitlab::VisibilityLevel.options['Internal'] + + expect(find_field("project_visibility_level_#{level}")).to be_checked + end end end end -- cgit v1.2.1 From 6625d605ec8405b1d687b13e7624b10ced832fd2 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sun, 12 Mar 2017 00:59:09 +1100 Subject: Refactor specs --- spec/features/projects/new_project_spec.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 1dc1179c795..e153eb2bc42 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -16,6 +16,15 @@ feature "New project", feature: true do expect(find_field("project_visibility_level_#{level}")).to be_checked end + + it 'saves visibility level on validation error' do + visit new_project_path + + choose(key) + click_button('Create project') + + expect(find_field("project_visibility_level_#{level}")).to be_checked + end end end @@ -60,12 +69,6 @@ feature "New project", feature: true do expect(namespace.text).to eq group.name end - - it 'selects the visibility level' do - level = Gitlab::VisibilityLevel.options['Internal'] - - expect(find_field("project_visibility_level_#{level}")).to be_checked - end end end end -- cgit v1.2.1 From d274bbb9b93183cb74f7ba4c3de286ebd5b0cb5a Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sun, 12 Mar 2017 01:20:22 +1100 Subject: Fix specs --- app/views/projects/new.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 1a51f777bd9..560e3439fc2 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,6 +1,6 @@ - page_title 'New Project' - header_title "Projects", dashboard_projects_path -- visibility_level = params.try(:[], :project).try(:[], :visibility_level).to_i || default_project_visibility +- visibility_level = params.try(:[], :project).try(:[], :visibility_level) || default_project_visibility .project-edit-container .project-edit-errors @@ -96,7 +96,7 @@ = f.label :visibility_level, class: 'label-light' do Visibility Level = link_to icon('question-circle'), help_page_path("public_access/public_access") - = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @project, with_label: false + = render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel' -- cgit v1.2.1 From 42151a15df1b920f7092e3f7c6f31cb24136cff9 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sun, 12 Mar 2017 08:01:43 +1100 Subject: Fix rubocop offenses --- spec/features/projects/new_project_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index e153eb2bc42..52196ce49bd 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -18,12 +18,12 @@ feature "New project", feature: true do end it 'saves visibility level on validation error' do - visit new_project_path + visit new_project_path - choose(key) - click_button('Create project') + choose(key) + click_button('Create project') - expect(find_field("project_visibility_level_#{level}")).to be_checked + expect(find_field("project_visibility_level_#{level}")).to be_checked end end end -- cgit v1.2.1 From 67729cecc12a56591160d04ea5d79614b1102dc6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 13 Mar 2017 20:36:17 +0800 Subject: Add a test which would rollback db and migrate again Closes #29106 --- .gitlab-ci.yml | 7 +++++++ db/migrate/20160919145149_add_group_id_to_labels.rb | 2 +- .../20161020083353_add_pipeline_id_to_merge_request_metrics.rb | 2 +- db/migrate/20161031171301_add_project_id_to_subscriptions.rb | 1 + .../20161207231621_create_environment_name_unique_index.rb | 4 ++-- .../20161209153400_add_unique_index_for_environment_slug.rb | 2 +- db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb | 10 ++++++++++ .../20170216141440_drop_index_for_builds_project_status.rb | 2 +- .../20170206101007_remove_trackable_columns_from_timelogs.rb | 3 ++- 9 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index deeb01f9a3c..b1ca61604d5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -222,6 +222,13 @@ rake db:migrate:reset: script: - bundle exec rake db:migrate:reset +rake db:rollback: + stage: test + <<: *use-db + <<: *dedicated-runner + script: + - bundle exec rake db:rollback db:migrate STEP=120 + rake db:seed_fu: stage: test <<: *use-db diff --git a/db/migrate/20160919145149_add_group_id_to_labels.rb b/db/migrate/20160919145149_add_group_id_to_labels.rb index 828b6afddb1..e20e693f3aa 100644 --- a/db/migrate/20160919145149_add_group_id_to_labels.rb +++ b/db/migrate/20160919145149_add_group_id_to_labels.rb @@ -12,8 +12,8 @@ class AddGroupIdToLabels < ActiveRecord::Migration end def down + remove_foreign_key :labels, column: :group_id remove_index :labels, :group_id if index_exists? :labels, :group_id - remove_foreign_key :labels, :namespaces, column: :group_id remove_column :labels, :group_id end end diff --git a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb index ad3eb4a26f9..35ad22b6c01 100644 --- a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb +++ b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb @@ -32,8 +32,8 @@ class AddPipelineIdToMergeRequestMetrics < ActiveRecord::Migration end def down + remove_foreign_key :merge_request_metrics, column: :pipeline_id remove_index :merge_request_metrics, :pipeline_id if index_exists? :merge_request_metrics, :pipeline_id - remove_foreign_key :merge_request_metrics, :ci_commits, column: :pipeline_id remove_column :merge_request_metrics, :pipeline_id end end diff --git a/db/migrate/20161031171301_add_project_id_to_subscriptions.rb b/db/migrate/20161031171301_add_project_id_to_subscriptions.rb index d5c343dc527..8b1c10a124f 100644 --- a/db/migrate/20161031171301_add_project_id_to_subscriptions.rb +++ b/db/migrate/20161031171301_add_project_id_to_subscriptions.rb @@ -9,6 +9,7 @@ class AddProjectIdToSubscriptions < ActiveRecord::Migration end def down + remove_foreign_key :subscriptions, column: :project_id remove_column :subscriptions, :project_id end end diff --git a/db/migrate/20161207231621_create_environment_name_unique_index.rb b/db/migrate/20161207231621_create_environment_name_unique_index.rb index ac680c8d10f..5ff0f5bae4d 100644 --- a/db/migrate/20161207231621_create_environment_name_unique_index.rb +++ b/db/migrate/20161207231621_create_environment_name_unique_index.rb @@ -12,7 +12,7 @@ class CreateEnvironmentNameUniqueIndex < ActiveRecord::Migration end def down - remove_index :environments, [:project_id, :name], unique: true - add_concurrent_index :environments, [:project_id, :name] + remove_index :environments, [:project_id, :name] + add_concurrent_index :environments, [:project_id, :name], unique: true end end diff --git a/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb b/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb index d7ef1aa83d9..ede0316e860 100644 --- a/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb +++ b/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb @@ -14,6 +14,6 @@ class AddUniqueIndexForEnvironmentSlug < ActiveRecord::Migration end def down - remove_index :environments, [:project_id, :slug], unique: true if index_exists? :environments, [:project_id, :slug] + remove_index :environments, [:project_id, :slug] if index_exists? :environments, [:project_id, :slug] end end diff --git a/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb b/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb index 69bfa2d3fc4..676e18cddd3 100644 --- a/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb +++ b/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb @@ -49,6 +49,16 @@ class AddForeignKeysToTimelogs < ActiveRecord::Migration Timelog.where('issue_id IS NOT NULL').update_all("trackable_id = issue_id, trackable_type = 'Issue'") Timelog.where('merge_request_id IS NOT NULL').update_all("trackable_id = merge_request_id, trackable_type = 'MergeRequest'") + constraint = + if Gitlab::Database.postgresql? + 'CONSTRAINT' + else + 'FOREIGN KEY' + end + + execute "ALTER TABLE timelogs DROP #{constraint} fk_timelogs_issues_issue_id" + execute "ALTER TABLE timelogs DROP #{constraint} fk_timelogs_merge_requests_merge_request_id" + remove_columns :timelogs, :issue_id, :merge_request_id end end diff --git a/db/migrate/20170216141440_drop_index_for_builds_project_status.rb b/db/migrate/20170216141440_drop_index_for_builds_project_status.rb index 906711b9f3f..a2839f52d89 100644 --- a/db/migrate/20170216141440_drop_index_for_builds_project_status.rb +++ b/db/migrate/20170216141440_drop_index_for_builds_project_status.rb @@ -3,6 +3,6 @@ class DropIndexForBuildsProjectStatus < ActiveRecord::Migration DOWNTIME = false def change - remove_index(:ci_commits, [:gl_project_id, :status]) + remove_index(:ci_commits, column: [:gl_project_id, :status]) end end diff --git a/db/post_migrate/20170206101007_remove_trackable_columns_from_timelogs.rb b/db/post_migrate/20170206101007_remove_trackable_columns_from_timelogs.rb index 89aa753646c..aee0c1b6245 100644 --- a/db/post_migrate/20170206101007_remove_trackable_columns_from_timelogs.rb +++ b/db/post_migrate/20170206101007_remove_trackable_columns_from_timelogs.rb @@ -18,6 +18,7 @@ class RemoveTrackableColumnsFromTimelogs < ActiveRecord::Migration # disable_ddl_transaction! def change - remove_columns :timelogs, :trackable_id, :trackable_type + remove_column :timelogs, :trackable_id, :integer + remove_column :timelogs, :trackable_type, :string end end -- cgit v1.2.1 From 37ce638ccd476144fe9235a4d091be4135a3e00a Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Tue, 14 Mar 2017 07:24:30 +1100 Subject: Use dig --- app/views/projects/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 560e3439fc2..d129da943f8 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,6 +1,6 @@ - page_title 'New Project' - header_title "Projects", dashboard_projects_path -- visibility_level = params.try(:[], :project).try(:[], :visibility_level) || default_project_visibility +- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility .project-edit-container .project-edit-errors -- cgit v1.2.1 From 0f9e3e2b58f172590d7e8664ebc16b6d143b588c Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Tue, 14 Mar 2017 09:13:03 +1100 Subject: Add quick submit for snippet forms --- app/views/shared/snippets/_form.html.haml | 2 +- changelogs/unreleased/add_quick_submit_for_snippets_form.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/add_quick_submit_for_snippets_form.yml diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index e7f7db73223..0296597b294 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -3,7 +3,7 @@ = page_specific_javascript_bundle_tag('snippet') .snippet-form-holder - = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f| + = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit" } do |f| = form_errors(@snippet) .form-group diff --git a/changelogs/unreleased/add_quick_submit_for_snippets_form.yml b/changelogs/unreleased/add_quick_submit_for_snippets_form.yml new file mode 100644 index 00000000000..088f1335796 --- /dev/null +++ b/changelogs/unreleased/add_quick_submit_for_snippets_form.yml @@ -0,0 +1,4 @@ +--- +title: Add quick submit for snippet forms +merge_request: 9911 +author: blackst0ne -- cgit v1.2.1 From db59e735ae9c30bfa1e9d0800b6edfaaf6981f2a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 28 Feb 2017 21:59:55 -0500 Subject: Toggle project name if too long --- app/assets/javascripts/dispatcher.js | 5 +++ app/assets/javascripts/group_name.js | 40 ++++++++++++++++++++ app/assets/stylesheets/framework/header.scss | 22 +++++++++++ app/helpers/groups_helper.rb | 11 +++--- app/views/layouts/header/_default.html.haml | 2 +- ...187-project-name-cut-off-with-nested-groups.yml | 4 ++ spec/features/groups/group_name_toggle.rb | 44 ++++++++++++++++++++++ 7 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/group_name.js create mode 100644 changelogs/unreleased/28187-project-name-cut-off-with-nested-groups.yml create mode 100644 spec/features/groups/group_name_toggle.rb diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 7b9b9123c31..fcf3cb05e63 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -37,6 +37,7 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make import Issue from './issue'; import BindInOut from './behaviors/bind_in_out'; +import GroupName from './group_name'; import GroupsList from './groups_list'; import ProjectsList from './projects_list'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; @@ -342,6 +343,9 @@ const UserCallout = require('./user_callout'); shortcut_handler = new ShortcutsDashboardNavigation(); new UserCallout(); break; + case 'groups': + new GroupName(); + break; case 'profiles': new NotificationsForm(); new NotificationsDropdown(); @@ -349,6 +353,7 @@ const UserCallout = require('./user_callout'); case 'projects': new Project(); new ProjectAvatar(); + new GroupName(); switch (path[1]) { case 'compare': new CompareAutocomplete(); diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js new file mode 100644 index 00000000000..6a028f299b1 --- /dev/null +++ b/app/assets/javascripts/group_name.js @@ -0,0 +1,40 @@ +const GROUP_LIMIT = 2; + +export default class GroupName { + constructor() { + this.titleContainer = document.querySelector('.title'); + this.groups = document.querySelectorAll('.group-path'); + this.groupTitle = document.querySelector('.group-title'); + this.toggle = null; + this.isHidden = false; + this.init(); + } + + init() { + if (this.groups.length > GROUP_LIMIT) { + this.groups[this.groups.length - 1].classList.remove('hidable'); + this.addToggle(); + } + this.render(); + } + + addToggle() { + const header = document.querySelector('.header-content'); + this.toggle = document.createElement('button'); + this.toggle.className = 'text-expander group-name-toggle'; + this.toggle.setAttribute('aria-label', 'Toggle full path'); + this.toggle.innerHTML = '...'; + this.toggle.addEventListener('click', this.toggleGroups.bind(this)); + header.insertBefore(this.toggle, this.titleContainer); + this.toggleGroups(); + } + + toggleGroups() { + this.isHidden = !this.isHidden; + this.groupTitle.classList.toggle('is-hidden'); + } + + render() { + this.titleContainer.classList.remove('initializing'); + } +} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 5d1aba4e529..6660a022260 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -164,11 +164,25 @@ header { } } + .group-name-toggle { + margin: 0 5px; + vertical-align: sub; + } + + .group-title { + &.is-hidden { + .hidable:not(:last-of-type) { + display: none; + } + } + } + .title { position: relative; padding-right: 20px; margin: 0; font-size: 18px; + max-width: 385px; display: inline-block; line-height: $header-height; font-weight: normal; @@ -178,6 +192,14 @@ header { vertical-align: top; white-space: nowrap; + &.initializing { + display: none; + } + + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + max-width: 300px; + } + @media (max-width: $screen-xs-max) { max-width: 190px; } diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 926c9703628..a6014088e92 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -12,17 +12,18 @@ module GroupsHelper end def group_title(group, name = nil, url = nil) + @has_group_title = true full_title = '' group.ancestors.each do |parent| - full_title += link_to(simple_sanitize(parent.name), group_path(parent)) - full_title += ' / '.html_safe + full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable') + full_title += ' / '.html_safe end - full_title += link_to(simple_sanitize(group.name), group_path(group)) - full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name + full_title += link_to(simple_sanitize(group.name), group_path(group), class: 'group-path') + full_title += ' · '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name - content_tag :span do + content_tag :span, class: 'group-title' do full_title.html_safe end end diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 6f4f2dbea3a..5fde5c2613e 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -67,7 +67,7 @@ = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do = brand_header_logo - %h1.title= title + %h1.title{ class: ('initializing' if @has_group_title) }= title = yield :header_content diff --git a/changelogs/unreleased/28187-project-name-cut-off-with-nested-groups.yml b/changelogs/unreleased/28187-project-name-cut-off-with-nested-groups.yml new file mode 100644 index 00000000000..feca38ff083 --- /dev/null +++ b/changelogs/unreleased/28187-project-name-cut-off-with-nested-groups.yml @@ -0,0 +1,4 @@ +--- +title: Use toggle button to expand / collapse mulit-nested groups +merge_request: 9501 +author: diff --git a/spec/features/groups/group_name_toggle.rb b/spec/features/groups/group_name_toggle.rb new file mode 100644 index 00000000000..ada4ac66e04 --- /dev/null +++ b/spec/features/groups/group_name_toggle.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +feature 'Group name toggle', js: true do + let(:group) { create(:group) } + let(:nested_group_1) { create(:group, parent: group) } + let(:nested_group_2) { create(:group, parent: nested_group_1) } + let(:nested_group_3) { create(:group, parent: nested_group_2) } + + before do + login_as :user + end + + it 'is not present for less than 3 groups' do + visit group_path(group) + expect(page).not_to have_css('.group-name-toggle') + + visit group_path(nested_group_1) + expect(page).not_to have_css('.group-name-toggle') + end + + it 'is present for nested group of 3 or more in the namespace' do + visit group_path(nested_group_2) + expect(page).to have_css('.group-name-toggle') + + visit group_path(nested_group_3) + expect(page).to have_css('.group-name-toggle') + end + + context 'for group with at least 3 groups' do + before do + visit group_path(nested_group_2) + end + + it 'should show the full group namespace when toggled' do + expect(page).not_to have_content(group.name) + expect(page).to have_css('.group-path.hidable', visible: false) + + click_button '...' + + expect(page).to have_content(group.name) + expect(page).to have_css('.group-path.hidable', visible: true) + end + end +end -- cgit v1.2.1 From 0b20d850cb964da89f8b0df3c9e78a1cc2a86e1b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 14 Mar 2017 14:36:33 +0800 Subject: Fix for postgresql --- db/migrate/20161201160452_migrate_project_statistics.rb | 4 ++-- db/migrate/20161212142807_add_lower_path_index_to_routes.rb | 2 +- db/migrate/20170130204620_add_index_to_project_authorizations.rb | 4 ++++ db/migrate/20170305203726_add_owner_id_foreign_key.rb | 6 +++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/db/migrate/20161201160452_migrate_project_statistics.rb b/db/migrate/20161201160452_migrate_project_statistics.rb index 3ae3f2c159b..8386f7f9d4f 100644 --- a/db/migrate/20161201160452_migrate_project_statistics.rb +++ b/db/migrate/20161201160452_migrate_project_statistics.rb @@ -17,7 +17,7 @@ class MigrateProjectStatistics < ActiveRecord::Migration end def down - add_column_with_default :projects, :repository_size, :float, default: 0.0 - add_column_with_default :projects, :commit_count, :integer, default: 0 + add_column :projects, :repository_size, :float, default: 0.0 + add_column :projects, :commit_count, :integer, default: 0 end end diff --git a/db/migrate/20161212142807_add_lower_path_index_to_routes.rb b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb index 6958500306f..53f4c6bbb18 100644 --- a/db/migrate/20161212142807_add_lower_path_index_to_routes.rb +++ b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb @@ -17,6 +17,6 @@ class AddLowerPathIndexToRoutes < ActiveRecord::Migration def down return unless Gitlab::Database.postgresql? - remove_index :routes, name: :index_on_routes_lower_path + remove_index :routes, name: :index_on_routes_lower_path if index_exists?(:routes, name: :index_on_routes_lower_path) end end diff --git a/db/migrate/20170130204620_add_index_to_project_authorizations.rb b/db/migrate/20170130204620_add_index_to_project_authorizations.rb index e9a0aee4d6a..a8c504f265a 100644 --- a/db/migrate/20170130204620_add_index_to_project_authorizations.rb +++ b/db/migrate/20170130204620_add_index_to_project_authorizations.rb @@ -8,4 +8,8 @@ class AddIndexToProjectAuthorizations < ActiveRecord::Migration def up add_concurrent_index(:project_authorizations, :project_id) end + + def down + remove_index(:project_authorizations, :project_id) + end end diff --git a/db/migrate/20170305203726_add_owner_id_foreign_key.rb b/db/migrate/20170305203726_add_owner_id_foreign_key.rb index 3eece0e2eb5..5fbdc45f1a7 100644 --- a/db/migrate/20170305203726_add_owner_id_foreign_key.rb +++ b/db/migrate/20170305203726_add_owner_id_foreign_key.rb @@ -5,7 +5,11 @@ class AddOwnerIdForeignKey < ActiveRecord::Migration disable_ddl_transaction! - def change + def up add_concurrent_foreign_key :ci_triggers, :users, column: :owner_id, on_delete: :cascade end + + def down + remove_foreign_key :ci_triggers, column: :owner_id + end end -- cgit v1.2.1 From 77d93d33820b9771b906c2d2bd708b4b910aa9e9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 14 Mar 2017 09:53:28 +0100 Subject: Add missing steps of Pages source installation [ci skip] --- doc/administration/pages/source.md | 82 ++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md index f6f50e2c571..b4588f8b43c 100644 --- a/doc/administration/pages/source.md +++ b/doc/administration/pages/source.md @@ -1,5 +1,9 @@ # GitLab Pages administration for source installations +>**Note:** +Before attempting to enable GitLab Pages, first make sure you have +[installed GitLab](../../install/installation.md) successfully. + This is the documentation for configuring a GitLab Pages when you have installed GitLab from source and not using the Omnibus packages. @@ -13,7 +17,33 @@ Pages to the latest supported version. ## Overview -[Read the Omnibus overview section.](index.md#overview) +GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server +written in Go that can listen on an external IP address and provide support for +custom domains and custom certificates. It supports dynamic certificates through +SNI and exposes pages using HTTP2 by default. +You are encouraged to read its [README][pages-readme] to fully understand how +it works. + +--- + +In the case of [custom domains](#custom-domains) (but not +[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on +ports `80` and/or `443`. For that reason, there is some flexibility in the way +which you can set it up: + +1. Run the Pages daemon in the same server as GitLab, listening on a secondary IP. +1. Run the Pages daemon in a separate server. In that case, the + [Pages path](#change-storage-path) must also be present in the server that + the Pages daemon is installed, so you will have to share it via network. +1. Run the Pages daemon in the same server as GitLab, listening on the same IP + but on different ports. In that case, you will have to proxy the traffic with + a loadbalancer. If you choose that route note that you should use TCP load + balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the + pages will not be able to be served with user provided certificates. For + HTTP it's OK to use HTTP or TCP load balancing. + +In this document, we will proceed assuming the first option. If you are not +supporting custom domains a secondary IP is not needed. ## Prerequisites @@ -75,7 +105,7 @@ The Pages daemon doesn't listen to the outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v0.3.2 sudo -u git -H make ``` @@ -100,14 +130,21 @@ The Pages daemon doesn't listen to the outside world. https: false ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` must match the `host` setting that you set above. - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 + ``` + +1. Copy the `gitlab-pages` Nginx configuration file: - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + ```bash + sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` 1. Restart NGINX 1. [Restart GitLab][restart] @@ -131,7 +168,7 @@ outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v0.3.2 sudo -u git -H make ``` @@ -149,6 +186,17 @@ outside world. https: true ``` +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` must match the `host` setting that you set above. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash @@ -156,12 +204,9 @@ outside world. sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - 1. Restart NGINX 1. [Restart GitLab][restart] - ## Advanced configuration In addition to the wildcard domains, you can also have the option to configure @@ -189,7 +234,7 @@ world. Custom domains are supported, but no TLS. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v0.3.2 sudo -u git -H make ``` @@ -224,12 +269,10 @@ world. Custom domains are supported, but no TLS. 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab listens to. @@ -257,7 +300,7 @@ world. Custom domains and TLS are supported. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v0.3.2 sudo -u git -H make ``` @@ -300,8 +343,6 @@ world. Custom domains and TLS are supported. sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab listens to. @@ -392,5 +433,6 @@ than GitLab to prevent XSS attacks. [pages-userguide]: ../../user/project/pages/index.md [reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../restart_gitlab.md#installations-from-source -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4 +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.3.2 +[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example [shared runners]: ../../ci/runners/README.md -- cgit v1.2.1 From c9fbbb3ae2c7f0eb44b0f973155d68e678149544 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 14 Mar 2017 19:56:37 +0800 Subject: Disable rubocop for down method --- db/migrate/20161201160452_migrate_project_statistics.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/migrate/20161201160452_migrate_project_statistics.rb b/db/migrate/20161201160452_migrate_project_statistics.rb index 8386f7f9d4f..82fbdf02444 100644 --- a/db/migrate/20161201160452_migrate_project_statistics.rb +++ b/db/migrate/20161201160452_migrate_project_statistics.rb @@ -16,6 +16,7 @@ class MigrateProjectStatistics < ActiveRecord::Migration remove_column :projects, :commit_count end + # rubocop: disable Migration/AddColumn def down add_column :projects, :repository_size, :float, default: 0.0 add_column :projects, :commit_count, :integer, default: 0 -- cgit v1.2.1 From f67d8eb1da269150764224cea1807195cdf2ffb5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 14 Mar 2017 20:03:22 +0800 Subject: Drop the index only for postgresql, because mysql cannot simply drop the index without dropping the corresponding foreign key, and we certainly don't want to drop the foreign key here. --- db/migrate/20170130204620_add_index_to_project_authorizations.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/migrate/20170130204620_add_index_to_project_authorizations.rb b/db/migrate/20170130204620_add_index_to_project_authorizations.rb index a8c504f265a..629b49436e3 100644 --- a/db/migrate/20170130204620_add_index_to_project_authorizations.rb +++ b/db/migrate/20170130204620_add_index_to_project_authorizations.rb @@ -10,6 +10,7 @@ class AddIndexToProjectAuthorizations < ActiveRecord::Migration end def down - remove_index(:project_authorizations, :project_id) + remove_index(:project_authorizations, :project_id) if + Gitlab::Database.postgresql? end end -- cgit v1.2.1 From af8cc2e064bb97a8a1801521735d5403b189bfb5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 14 Mar 2017 20:13:36 +0800 Subject: Use `remove_foreign_key :timelogs, name: '...'` Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9908#note_25324225 --- db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb b/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb index 676e18cddd3..a7d4e141a1a 100644 --- a/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb +++ b/db/migrate/20170124174637_add_foreign_keys_to_timelogs.rb @@ -49,15 +49,8 @@ class AddForeignKeysToTimelogs < ActiveRecord::Migration Timelog.where('issue_id IS NOT NULL').update_all("trackable_id = issue_id, trackable_type = 'Issue'") Timelog.where('merge_request_id IS NOT NULL').update_all("trackable_id = merge_request_id, trackable_type = 'MergeRequest'") - constraint = - if Gitlab::Database.postgresql? - 'CONSTRAINT' - else - 'FOREIGN KEY' - end - - execute "ALTER TABLE timelogs DROP #{constraint} fk_timelogs_issues_issue_id" - execute "ALTER TABLE timelogs DROP #{constraint} fk_timelogs_merge_requests_merge_request_id" + remove_foreign_key :timelogs, name: 'fk_timelogs_issues_issue_id' + remove_foreign_key :timelogs, name: 'fk_timelogs_merge_requests_merge_request_id' remove_columns :timelogs, :issue_id, :merge_request_id end -- cgit v1.2.1 From 83e36064998f77f40c534bad531b6cea19ec198b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 14 Mar 2017 21:40:58 +0800 Subject: Split to two commands, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9908#note_25331127 --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b1ca61604d5..406a0f3dcad 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -227,7 +227,8 @@ rake db:rollback: <<: *use-db <<: *dedicated-runner script: - - bundle exec rake db:rollback db:migrate STEP=120 + - bundle exec rake db:rollback STEP=120 + - bundle exec rake db:migrate rake db:seed_fu: stage: test -- cgit v1.2.1 From 8daa641e4d6d5dd2fab78abd851b0e8899d4ea11 Mon Sep 17 00:00:00 2001 From: Raveesh Date: Tue, 14 Mar 2017 12:50:32 -0400 Subject: Switch to using milestone.to_reference when displaying milestone Fix #29214 --- app/views/projects/milestones/edit.html.haml | 2 +- app/views/projects/milestones/show.html.haml | 2 +- changelogs/unreleased/fix-milestone-name-on-show.yml | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/fix-milestone-name-on-show.yml diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml index 11f41e75e63..55b0b837c6d 100644 --- a/app/views/projects/milestones/edit.html.haml +++ b/app/views/projects/milestones/edit.html.haml @@ -5,7 +5,7 @@ %div{ class: container_class } %h3.page-title - Edit Milestone ##{@milestone.iid} + Edit Milestone #{@milestone.to_reference} %hr diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index b4dde2c86c9..d16f49bd33a 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -20,7 +20,7 @@ .header-text-content %span.identifier %strong - Milestone %#{@milestone.iid} + Milestone #{@milestone.to_reference} - if @milestone.due_date || @milestone.start_date = milestone_date_range(@milestone) .milestone-buttons diff --git a/changelogs/unreleased/fix-milestone-name-on-show.yml b/changelogs/unreleased/fix-milestone-name-on-show.yml new file mode 100644 index 00000000000..bf17a758c80 --- /dev/null +++ b/changelogs/unreleased/fix-milestone-name-on-show.yml @@ -0,0 +1,4 @@ +--- +title: Fix Milestone name on show page +merge_request: +author: Raveesh -- cgit v1.2.1 From f97c1d1001a1c16ab51fb62723f30f6ffa467d4f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 14 Mar 2017 11:47:05 -0500 Subject: Fix link togglers jumping to top Fix #29414 --- app/assets/javascripts/behaviors/toggler_behavior.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 0726c6c9636..92f3bb3ff52 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -21,8 +21,13 @@ // %a.js-toggle-button // %div.js-toggle-content // - $('body').on('click', '.js-toggle-button', function() { + $('body').on('click', '.js-toggle-button', function(e) { toggleContainer($(this).closest('.js-toggle-container')); + + const targetTag = e.target.tagName.toLowerCase(); + if (targetTag === 'a' || targetTag === 'button') { + e.preventDefault(); + } }); // If we're accessing a permalink, ensure it is not inside a -- cgit v1.2.1 From 74ec81a4f3ba3a98946e00fd08bd72567e338271 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 14 Mar 2017 11:25:24 +0100 Subject: Bump pages daemon to 0.4.0 [ci skip] --- doc/administration/pages/source.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md index b4588f8b43c..a45c3306457 100644 --- a/doc/administration/pages/source.md +++ b/doc/administration/pages/source.md @@ -105,7 +105,7 @@ The Pages daemon doesn't listen to the outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.3.2 + sudo -u git -H git checkout v$( Date: Tue, 14 Mar 2017 11:56:15 -0500 Subject: Include time tracking attributes in webhooks payload --- app/models/issue.rb | 8 +++++++- app/models/merge_request.rb | 5 ++++- .../unreleased/27271-missing-time-spent-in-issue-webhook.yml | 4 ++++ spec/models/issue_spec.rb | 11 +++++++++++ spec/models/merge_request_spec.rb | 6 +++++- 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/27271-missing-time-spent-in-issue-webhook.yml diff --git a/app/models/issue.rb b/app/models/issue.rb index 0f7a26ee3e1..2cc237635f9 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -58,7 +58,13 @@ class Issue < ActiveRecord::Base end def hook_attrs - attributes + attrs = { + total_time_spent: total_time_spent, + human_total_time_spent: human_total_time_spent, + human_time_estimate: human_time_estimate + } + + attributes.merge!(attrs) end def self.reference_prefix diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 0f7b8311588..4759829a15c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -523,7 +523,10 @@ class MergeRequest < ActiveRecord::Base source: source_project.try(:hook_attrs), target: target_project.hook_attrs, last_commit: nil, - work_in_progress: work_in_progress? + work_in_progress: work_in_progress?, + total_time_spent: total_time_spent, + human_total_time_spent: human_total_time_spent, + human_time_estimate: human_time_estimate } if diff_head_commit diff --git a/changelogs/unreleased/27271-missing-time-spent-in-issue-webhook.yml b/changelogs/unreleased/27271-missing-time-spent-in-issue-webhook.yml new file mode 100644 index 00000000000..4ea52a70e89 --- /dev/null +++ b/changelogs/unreleased/27271-missing-time-spent-in-issue-webhook.yml @@ -0,0 +1,4 @@ +--- +title: Include time tracking attributes in webhooks payload +merge_request: 9942 +author: diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index bba9058f394..898a9c8da35 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -620,4 +620,15 @@ describe Issue, models: true do end end end + + describe '#hook_attrs' do + let(:attrs_hash) { subject.hook_attrs } + + it 'includes time tracking attrs' do + expect(attrs_hash).to include(:total_time_spent) + expect(attrs_hash).to include(:human_time_estimate) + expect(attrs_hash).to include(:human_total_time_spent) + expect(attrs_hash).to include('time_estimate') + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index fcaf4c71182..24e7c1b17d9 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -542,7 +542,7 @@ describe MergeRequest, models: true do end describe "#hook_attrs" do - let(:attrs_hash) { subject.hook_attrs.to_h } + let(:attrs_hash) { subject.hook_attrs } [:source, :target].each do |key| describe "#{key} key" do @@ -558,6 +558,10 @@ describe MergeRequest, models: true do expect(attrs_hash).to include(:target) expect(attrs_hash).to include(:last_commit) expect(attrs_hash).to include(:work_in_progress) + expect(attrs_hash).to include(:total_time_spent) + expect(attrs_hash).to include(:human_time_estimate) + expect(attrs_hash).to include(:human_total_time_spent) + expect(attrs_hash).to include('time_estimate') end end -- cgit v1.2.1 From c9abdadd7a08f972d5a12472f9f5ac443e37a6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 14 Mar 2017 18:08:50 +0100 Subject: Ensure dots in project path is allowed in the commits API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/commits.rb | 2 +- lib/api/v3/commits.rb | 2 +- spec/requests/api/commits_spec.rb | 17 +++++++++-------- spec/requests/api/v3/commits_spec.rb | 15 ++++++++------- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 42401abfe0f..48939798900 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -10,7 +10,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects do + resource :projects, requirements: { id: /.+/ } do desc 'Get a project repository commits' do success Entities::RepoCommit end diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb index d254d247042..6f36b2bc1c4 100644 --- a/lib/api/v3/commits.rb +++ b/lib/api/v3/commits.rb @@ -11,7 +11,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects do + resource :projects, requirements: { id: /.+/ } do desc 'Get a project repository commits' do success ::API::Entities::RepoCommit end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 585449e62b6..7c0f2fb9fe9 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -178,7 +178,7 @@ describe API::Commits, api: true do end end - describe "Create a commit with multiple files and actions" do + describe "POST /projects/:id/repository/commits" do let!(:url) { "/projects/#{project.id}/repository/commits" } it 'returns a 403 unauthorized for user without permissions' do @@ -193,7 +193,7 @@ describe API::Commits, api: true do expect(response).to have_http_status(400) end - context :create do + describe 'create' do let(:message) { 'Created file' } let!(:invalid_c_params) do { @@ -237,8 +237,9 @@ describe API::Commits, api: true do expect(response).to have_http_status(400) end - context 'with project path in URL' do - let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" } + context 'with project path containing a dot in URL' do + let!(:user) { create(:user, username: 'foo.bar') } + let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" } it 'a new file in project repo' do post api(url, user), valid_c_params @@ -248,7 +249,7 @@ describe API::Commits, api: true do end end - context :delete do + describe 'delete' do let(:message) { 'Deleted file' } let!(:invalid_d_params) do { @@ -289,7 +290,7 @@ describe API::Commits, api: true do end end - context :move do + describe 'move' do let(:message) { 'Moved file' } let!(:invalid_m_params) do { @@ -334,7 +335,7 @@ describe API::Commits, api: true do end end - context :update do + describe 'update' do let(:message) { 'Updated file' } let!(:invalid_u_params) do { @@ -377,7 +378,7 @@ describe API::Commits, api: true do end end - context "multiple operations" do + describe 'multiple operations' do let(:message) { 'Multiple actions' } let!(:invalid_mo_params) do { diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb index e298ef055e1..adba3a787aa 100644 --- a/spec/requests/api/v3/commits_spec.rb +++ b/spec/requests/api/v3/commits_spec.rb @@ -88,7 +88,7 @@ describe API::V3::Commits, api: true do end end - describe "Create a commit with multiple files and actions" do + describe "POST /projects/:id/repository/commits" do let!(:url) { "/projects/#{project.id}/repository/commits" } it 'returns a 403 unauthorized for user without permissions' do @@ -103,7 +103,7 @@ describe API::V3::Commits, api: true do expect(response).to have_http_status(400) end - context :create do + describe 'create' do let(:message) { 'Created file' } let!(:invalid_c_params) do { @@ -147,8 +147,9 @@ describe API::V3::Commits, api: true do expect(response).to have_http_status(400) end - context 'with project path in URL' do - let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" } + context 'with project path containing a dot in URL' do + let!(:user) { create(:user, username: 'foo.bar') } + let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" } it 'a new file in project repo' do post v3_api(url, user), valid_c_params @@ -158,7 +159,7 @@ describe API::V3::Commits, api: true do end end - context :delete do + describe 'delete' do let(:message) { 'Deleted file' } let!(:invalid_d_params) do { @@ -199,7 +200,7 @@ describe API::V3::Commits, api: true do end end - context :move do + describe 'move' do let(:message) { 'Moved file' } let!(:invalid_m_params) do { @@ -244,7 +245,7 @@ describe API::V3::Commits, api: true do end end - context :update do + describe 'update' do let(:message) { 'Updated file' } let!(:invalid_u_params) do { -- cgit v1.2.1 From 464ca33747ec3f2a4063795e18ab5888e429b334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 14 Mar 2017 18:30:53 +0100 Subject: Allow to override GITLAB_GIT_TEST_REPO_URL to specify a different gitlab-git-test repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will set this to the dev mirror in GitLab CE and EE on dev. Signed-off-by: Rémy Coutable --- spec/lib/gitlab/git/repository_spec.rb | 2 +- spec/support/seed_helper.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index bc139d5ef28..9c3a4571ce4 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -507,7 +507,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "#remote_add" do before(:all) do @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) - @repo.remote_add("new_remote", SeedHelper::GITLAB_URL) + @repo.remote_add("new_remote", SeedHelper::GITLAB_GIT_TEST_REPO_URL) end it "should add the remote" do diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb index 07f81e9c4f3..f55fee28ff9 100644 --- a/spec/support/seed_helper.rb +++ b/spec/support/seed_helper.rb @@ -7,7 +7,7 @@ TEST_MUTABLE_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "mutable-repo.git") TEST_BROKEN_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "broken-repo.git") module SeedHelper - GITLAB_URL = "https://gitlab.com/gitlab-org/gitlab-git-test.git".freeze + GITLAB_GIT_TEST_REPO_URL = ENV.fetch('GITLAB_GIT_TEST_REPO_URL', 'https://gitlab.com/gitlab-org/gitlab-git-test.git').freeze def ensure_seeds if File.exist?(SEED_REPOSITORY_PATH) @@ -25,7 +25,7 @@ module SeedHelper end def create_bare_seeds - system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_URL}), + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_GIT_TEST_REPO_URL}), chdir: SEED_REPOSITORY_PATH, out: '/dev/null', err: '/dev/null') @@ -45,7 +45,7 @@ module SeedHelper system(git_env, *%w(git branch -t feature origin/feature), chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null') - system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_URL}), + system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_GIT_TEST_REPO_URL}), chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null') end -- cgit v1.2.1 From ee2ddd059520f2c9a875c888a2c4eb44af3643a5 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Fri, 10 Mar 2017 18:02:35 -0600 Subject: Moved the gear settings dropdown in the group view to a tab --- app/views/groups/_settings_head.html.haml | 14 ++++++++++++++ app/views/groups/edit.html.haml | 1 + app/views/groups/projects.html.haml | 1 + app/views/layouts/nav/_group.html.haml | 9 ++++++++- app/views/layouts/nav/_group_settings.html.haml | 18 ------------------ .../unreleased/group-gear-setting-dropdown-to-tab.yml | 4 ++++ 6 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 app/views/groups/_settings_head.html.haml delete mode 100644 app/views/layouts/nav/_group_settings.html.haml create mode 100644 changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml new file mode 100644 index 00000000000..dc11efeb0c4 --- /dev/null +++ b/app/views/groups/_settings_head.html.haml @@ -0,0 +1,14 @@ += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: container_class } + = nav_link(path: 'groups#projects') do + = link_to projects_group_path(@group), title: 'Projects' do + %span + Projects + + = nav_link(path: 'groups#edit') do + = link_to edit_group_path(@group), title: 'Edit Group' do + %span + Edit Group \ No newline at end of file diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 2706e8692d1..80a77dab97f 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,3 +1,4 @@ += render "groups/settings_head" .panel.panel-default.prepend-top-default .panel-heading Group settings diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 2e7e5e5c309..1f4a3e2a829 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,4 +1,5 @@ - page_title "Projects" += render "groups/settings_head" .panel.panel-default.prepend-top-default .panel-heading diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index a6e96942021..9de0e344196 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,4 +1,5 @@ -= render 'layouts/nav/group_settings' +- can_admin_group = can?(current_user, :admin_group, @group) +- can_edit = can?(current_user, :admin_group, @group) .scrolling-tabs-container{ class: nav_control_class } .fade-left = icon('angle-left') @@ -25,3 +26,9 @@ = link_to group_group_members_path(@group), title: 'Members' do %span Members + - if current_user + - if can_admin_group || can_edit + = nav_link(path: %w[groups#projects groups#edit]) do + = link_to projects_group_path(@group), title: 'Settings' do + %span + Settings diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml deleted file mode 100644 index 30feb6813b4..00000000000 --- a/app/views/layouts/nav/_group_settings.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -- if current_user - - can_admin_group = can?(current_user, :admin_group, @group) - - can_edit = can?(current_user, :admin_group, @group) - - - if can_admin_group || can_edit - .controls - .dropdown.group-settings-dropdown - %a.dropdown-new.btn.btn-default#group-settings-button{ href: '#', 'data-toggle' => 'dropdown' } - = icon('cog') - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - - if can_admin_group - = nav_link(path: 'groups#projects') do - = link_to 'Projects', projects_group_path(@group), title: 'Projects' - - if can_edit && can_admin_group - %li.divider - %li - = link_to 'Edit Group', edit_group_path(@group) diff --git a/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml b/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml new file mode 100644 index 00000000000..aff1bdd957c --- /dev/null +++ b/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml @@ -0,0 +1,4 @@ +--- +title: Moved the gear settings dropdown to a tab in the groups view +merge_request: +author: -- cgit v1.2.1 From f47946591a52536c7dd7d02d11ffb7390549470b Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Fri, 10 Mar 2017 18:40:33 -0600 Subject: Fixed haml_lint warning for the settings_head partial --- app/views/groups/_settings_head.html.haml | 2 +- app/views/groups/projects.html.haml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml index dc11efeb0c4..d225f7ed3c0 100644 --- a/app/views/groups/_settings_head.html.haml +++ b/app/views/groups/_settings_head.html.haml @@ -11,4 +11,4 @@ = nav_link(path: 'groups#edit') do = link_to edit_group_path(@group), title: 'Edit Group' do %span - Edit Group \ No newline at end of file + Edit Group diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 1f4a3e2a829..83bdd654f27 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,4 +1,3 @@ -- page_title "Projects" = render "groups/settings_head" .panel.panel-default.prepend-top-default -- cgit v1.2.1 From 30f99608ffa5a4ce3d403276df5d68a23ec9b338 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Tue, 14 Mar 2017 12:00:00 -0600 Subject: Fixed some missing permission conditions --- app/views/groups/_settings_head.html.haml | 11 +++++++---- app/views/layouts/nav/_group.html.haml | 12 +++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml index d225f7ed3c0..d99426bc2c1 100644 --- a/app/views/groups/_settings_head.html.haml +++ b/app/views/groups/_settings_head.html.haml @@ -1,3 +1,5 @@ +- can_admin_group = can?(current_user, :admin_group, @group) +- can_edit = can?(current_user, :admin_group, @group) = content_for :sub_nav do .scrolling-tabs-container.sub-nav-scroll = render 'shared/nav_scroll' @@ -8,7 +10,8 @@ %span Projects - = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group), title: 'Edit Group' do - %span - Edit Group + - if can_edit && can_admin_group + = nav_link(path: 'groups#edit') do + = link_to edit_group_path(@group), title: 'Edit Group' do + %span + Edit Group diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 9de0e344196..b2ecf6504e0 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,5 +1,4 @@ - can_admin_group = can?(current_user, :admin_group, @group) -- can_edit = can?(current_user, :admin_group, @group) .scrolling-tabs-container{ class: nav_control_class } .fade-left = icon('angle-left') @@ -26,9 +25,8 @@ = link_to group_group_members_path(@group), title: 'Members' do %span Members - - if current_user - - if can_admin_group || can_edit - = nav_link(path: %w[groups#projects groups#edit]) do - = link_to projects_group_path(@group), title: 'Settings' do - %span - Settings + - if current_user && can_admin_group + = nav_link(path: %w[groups#projects groups#edit]) do + = link_to projects_group_path(@group), title: 'Settings' do + %span + Settings -- cgit v1.2.1 From bb99fc2572a796b938bd67128f6482c180e3942b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 7 Mar 2017 21:32:36 +0100 Subject: Add nested groups documentation [ci skip] --- doc/user/group/subgroups/img/create_new_group.png | Bin 0 -> 18503 bytes .../group/subgroups/img/create_subgroup_button.png | Bin 0 -> 8402 bytes doc/user/group/subgroups/img/group_members.png | Bin 0 -> 48240 bytes doc/user/group/subgroups/img/mention_subgroups.png | Bin 0 -> 39666 bytes doc/user/group/subgroups/index.md | 153 +++++++++++++++++++++ doc/user/permissions.md | 1 + doc/workflow/README.md | 1 + 7 files changed, 155 insertions(+) create mode 100644 doc/user/group/subgroups/img/create_new_group.png create mode 100644 doc/user/group/subgroups/img/create_subgroup_button.png create mode 100644 doc/user/group/subgroups/img/group_members.png create mode 100644 doc/user/group/subgroups/img/mention_subgroups.png create mode 100644 doc/user/group/subgroups/index.md diff --git a/doc/user/group/subgroups/img/create_new_group.png b/doc/user/group/subgroups/img/create_new_group.png new file mode 100644 index 00000000000..9d011ec709a Binary files /dev/null and b/doc/user/group/subgroups/img/create_new_group.png differ diff --git a/doc/user/group/subgroups/img/create_subgroup_button.png b/doc/user/group/subgroups/img/create_subgroup_button.png new file mode 100644 index 00000000000..000b54c2855 Binary files /dev/null and b/doc/user/group/subgroups/img/create_subgroup_button.png differ diff --git a/doc/user/group/subgroups/img/group_members.png b/doc/user/group/subgroups/img/group_members.png new file mode 100644 index 00000000000..b95fe6263bf Binary files /dev/null and b/doc/user/group/subgroups/img/group_members.png differ diff --git a/doc/user/group/subgroups/img/mention_subgroups.png b/doc/user/group/subgroups/img/mention_subgroups.png new file mode 100644 index 00000000000..8e6bed0111b Binary files /dev/null and b/doc/user/group/subgroups/img/mention_subgroups.png differ diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md new file mode 100644 index 00000000000..9522e6fc4ba --- /dev/null +++ b/doc/user/group/subgroups/index.md @@ -0,0 +1,153 @@ +# Subgroups + +> [Introduced][ce-2772] in GitLab 9.0. + +With subgroups (also called nested groups or hierarchical groups) you can have +up to 20 levels of nested groups, which among other things can help you to: + +- **Separate internal / external organizations.** Since every group + can have its own visibility level, you are able to host groups for different + purposes under the same umbrella. +- **Organize large projects.** For large projects, subgroups makes it + potentially easier to separate permissions on parts of the source code. +- **Make it easier to manage people and control visibility.** Give people + different [permissions][] depending on their group [membership](#membership). + +## Overview + +A group can have many subgroups inside it, and at the same time a group can have +only 1 parent group. It resembles a directory behavior, like the one below: + +``` +group0 +└── subgroup01a +└── subgroup01b + └── subgroup02 + └── subgroup03 +``` + +In a real world example, imagine maintaining a GNU/Linux distribution with the +first group being the name of the distro and subsequent groups split like: + +``` +Organization Group - GNU/Linux distro + └── Category Subgroup - Packages + └── project - Package01 + └── project - Package02 + └── Category Subgroup - Software + └── project - Core + └── project - CLI + └── project - Android app + └── project - iOS app + └── Category Subgroup - Infra tools + └── project - Ansible playbooks +``` + +Another example of GitLab as a company would be the following: + +``` +Organization Group - GitLab + └── Category Subroup - Marketing + └── project - Design + └── project - General + └── Category Subgroup - Software + └── project - GitLab CE + └── project - GitLab EE + └── project - Omnibus GitLab + └── project - GitLab Runner + └── project - GitLab Pages daemon + └── Category Subgroup - Infra tools + └── project - Chef cookbooks + └── Category Subgroup - Executive team +``` + +--- + +The maximum nested groups a group can have, including the first one in the +hierarchy, is 21. + +Things like transferring or importing a project inside nested groups, work like +when performing these actions the traditional way with the `group/project` +structure. + +## Creating a subgroup + +>**Notes:** +- You need to be an Owner of a group in order to be able to create + a subgroup. For more information check the [permissions table][permissions]. +- For a list of words that are not allowed to be used as group names see the + [`namespace_validator.rb` file][reserved] under the `RESERVED` and + `WILDCARD_ROUTES` lists. + +To create a subgroup: + +1. In the group's dashboard go to the **Subgroups** page and click **Create subgroup**. + + ![Subgroups page](img/create_subgroup_button.png) + +1. Create a new group like you would normally do. Notice that the parent group + namespace is fixed under **Group path**. The visibility level can differ from + the parent group. + + ![Subgroups page](img/create_new_group.png) + +1. Click the **Create group** button and you will be taken to the new group's + dashboard page. + +--- + +You can follow the same process to create any subsequent groups. + +## Membership + +When you add a member to a subgroup, they inherit the membership and permission +level from the parent group. This model allows access to nested groups if you +have membership in one of its parents. + +You can tell if a member has inherited the permissions from a parent group by +looking at the group's **Members** page. + +![Group members page](img/group_members.png) + +From the image above, we can deduct the following things: + +- There are 5 members that have access to the group **four** +- Administrator is the Owner and member of all subgroups +- User0 is a Reporter and has inherited their permissions from group **one** + which is above the hierarchy of group **four** +- User1 is a Developer and has inherited their permissions from group + **one/two** which is above the hierarchy of group **four** +- User2 is a Developer and has inherited their permissions from group + **one/two/three** which is above the hierarchy of group **four** +- User3 is a Master of group **four**, there is no indication of a parent + group therefore they belong to group **four** + +The group permissions for a member can be changed only by Owners and only on +the **Members** page of the group the member was added. + +## Mentioning subgroups + +Mentioning groups (`@group`) in issues, commits and merge requests, would +mention all members of that group. Now with subgroups, there is a more granular +support if you want to split your group's structure. Mentioning works as before +and you can choose the group of people to be summoned. + +![Mentioning subgroups](img/mention_subgroups.png) + +## Limitations + +Here's a list of what you can't do with subgroups: + +- [GitLab Pages](../../project/pages/index.md) are not currently working for + projects hosted under a subgroup. That means that only projects hosted under + the first parent group will work. +- Group level labels don't work in subgroups / sub projects +- It is not possible to share a project with a group that's an ancestor of + the group the project is in. That means you can only share as you walk down + the hierarchy. For example, `group/subgroup01/project` **cannot** be shared + with `group`, but can be shared with `group/subgroup02` or + `group/subgroup01/subgroup03`. + +[ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772 +[permissions]: ../../permissions.md#group +[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/validators/namespace_validator.rb diff --git a/doc/user/permissions.md b/doc/user/permissions.md index b49a244160a..0ea6d01411f 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -81,6 +81,7 @@ group. |-------------------------|-------|----------|-----------|--------|-------| | Browse group | ✓ | ✓ | ✓ | ✓ | ✓ | | Edit group | | | | | ✓ | +| Create subgroup | | | | | ✓ | | Create project in group | | | | ✓ | ✓ | | Manage group members | | | | | ✓ | | Remove group | | | | | ✓ | diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 9e7ee47387c..a286a23765d 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -40,3 +40,4 @@ - [Importing from SVN, GitHub, Bitbucket, etc](importing/README.md) - [Todos](todos.md) - [Snippets](../user/snippets.md) +- [Nested groups](../user/group/subgroups/index.md) -- cgit v1.2.1 From 1913f1ed9efced37cc597515ea4c7219eb17b4be Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 10 Mar 2017 14:15:28 +0100 Subject: Add info on group membership [ci skip] --- doc/user/group/subgroups/index.md | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index 9522e6fc4ba..2338d8e9b42 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -104,6 +104,9 @@ When you add a member to a subgroup, they inherit the membership and permission level from the parent group. This model allows access to nested groups if you have membership in one of its parents. +The group permissions for a member can be changed only by Owners and only on +the **Members** page of the group the member was added. + You can tell if a member has inherited the permissions from a parent group by looking at the group's **Members** page. @@ -111,19 +114,35 @@ looking at the group's **Members** page. From the image above, we can deduct the following things: -- There are 5 members that have access to the group **four** -- Administrator is the Owner and member of all subgroups -- User0 is a Reporter and has inherited their permissions from group **one** - which is above the hierarchy of group **four** +- There are 5 members that have access to the group `four` +- User0 is a Reporter and has inherited their permissions from group `one` + which is above the hierarchy of group `four` - User1 is a Developer and has inherited their permissions from group - **one/two** which is above the hierarchy of group **four** + `one/two` which is above the hierarchy of group `four` - User2 is a Developer and has inherited their permissions from group - **one/two/three** which is above the hierarchy of group **four** -- User3 is a Master of group **four**, there is no indication of a parent - group therefore they belong to group **four** + `one/two/three` which is above the hierarchy of group `four` +- For User3 there is no indication of a parent group, therefore they belong to + group `four`, the one we're inspecting +- Administrator is the Owner and member of **all** subgroups and for that reason, + same as User3, there is no indication of an ancestor group -The group permissions for a member can be changed only by Owners and only on -the **Members** page of the group the member was added. +### Overriding the ancestor group membership + +>**Note:** +You need to be an Owner of a group in order to be able to add members to it. + +To override the membership of an ancestor group, simply add the user in the new +subgroup again, but with different permissions. + +For example, if User0 was first added to group `one/two` with Developer +permissions, then they will inherit those permissions in every other subgroup +of `one/two`. To give them Master access to `one/two/three`, you would add them +again in that group as Master. Removing them from that group, the permissions +will fallback to those of the ancestor group. + +Note that the higher permission wins, so if in the above example the permissions +where reversed, User0 would have Master access to all groups, even to the one +that was explicitly given Developer access. ## Mentioning subgroups -- cgit v1.2.1 From f35d7a16595c199add9a582b8a80f8da75e5544d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 10 Mar 2017 14:18:48 +0100 Subject: Fix wording [ci skip] --- doc/user/group/subgroups/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index 2338d8e9b42..ff28f458f99 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -147,9 +147,9 @@ that was explicitly given Developer access. ## Mentioning subgroups Mentioning groups (`@group`) in issues, commits and merge requests, would -mention all members of that group. Now with subgroups, there is a more granular +notify all members of that group. Now with subgroups, there is a more granular support if you want to split your group's structure. Mentioning works as before -and you can choose the group of people to be summoned. +and you can choose the group of people to be notified. ![Mentioning subgroups](img/mention_subgroups.png) -- cgit v1.2.1 From b5142f92a03c1b2a58ee583e2d0632150e74d45a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 14 Mar 2017 21:41:46 +0100 Subject: Address subgroups docs review [ci skip] --- doc/user/group/subgroups/index.md | 86 ++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index ff28f458f99..ce5da07c61a 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -2,7 +2,7 @@ > [Introduced][ce-2772] in GitLab 9.0. -With subgroups (also called nested groups or hierarchical groups) you can have +With subgroups (aka nested groups or hierarchical groups) you can have up to 20 levels of nested groups, which among other things can help you to: - **Separate internal / external organizations.** Since every group @@ -16,50 +16,45 @@ up to 20 levels of nested groups, which among other things can help you to: ## Overview A group can have many subgroups inside it, and at the same time a group can have -only 1 parent group. It resembles a directory behavior, like the one below: +only 1 parent group. It resembles a directory behavior or a nested items list: -``` -group0 -└── subgroup01a -└── subgroup01b - └── subgroup02 - └── subgroup03 -``` +- Group 1 + - Group 1.1 + - Group 1.2 + - Group 1.2.1 + - Group 1.2.2 + - Group 1.2.2.1 In a real world example, imagine maintaining a GNU/Linux distribution with the first group being the name of the distro and subsequent groups split like: -``` -Organization Group - GNU/Linux distro - └── Category Subgroup - Packages - └── project - Package01 - └── project - Package02 - └── Category Subgroup - Software - └── project - Core - └── project - CLI - └── project - Android app - └── project - iOS app - └── Category Subgroup - Infra tools - └── project - Ansible playbooks -``` +- Organization Group - GNU/Linux distro + - Category Subgroup - Packages + - (project) Package01 + - (project) Package02 + - Category Subgroup - Software + - (project) Core + - (project) CLI + - (project) Android app + - (project) iOS app + - Category Subgroup - Infra tools + - (project) Ansible playbooks Another example of GitLab as a company would be the following: -``` -Organization Group - GitLab - └── Category Subroup - Marketing - └── project - Design - └── project - General - └── Category Subgroup - Software - └── project - GitLab CE - └── project - GitLab EE - └── project - Omnibus GitLab - └── project - GitLab Runner - └── project - GitLab Pages daemon - └── Category Subgroup - Infra tools - └── project - Chef cookbooks - └── Category Subgroup - Executive team -``` +- Organization Group - GitLab + - Category Subroup - Marketing + - (project) Design + - (project) General + - Category Subgroup - Software + - (project) GitLab CE + - (project) GitLab EE + - (project) Omnibus GitLab + - (project) GitLab Runner + - (project) GitLab Pages daemon + - Category Subgroup - Infra tools + - (project) Chef cookbooks + - Category Subgroup - Executive team --- @@ -131,18 +126,15 @@ From the image above, we can deduct the following things: >**Note:** You need to be an Owner of a group in order to be able to add members to it. -To override the membership of an ancestor group, simply add the user in the new -subgroup again, but with different permissions. +To override a user's membership of an ancestor group (the first group they were +added to), simply add the user in the new subgroup again, but with different +permissions. -For example, if User0 was first added to group `one/two` with Developer +For example, if User0 was first added to group `group-1/group-1-1` with Developer permissions, then they will inherit those permissions in every other subgroup -of `one/two`. To give them Master access to `one/two/three`, you would add them -again in that group as Master. Removing them from that group, the permissions -will fallback to those of the ancestor group. - -Note that the higher permission wins, so if in the above example the permissions -where reversed, User0 would have Master access to all groups, even to the one -that was explicitly given Developer access. +of `group-1/group-1-1`. To give them Master access to `group-1/group-1-1/group1-1-1`, +you would add them again in that group as Master. Removing them from that group, +the permissions will fallback to those of the ancestor group. ## Mentioning subgroups -- cgit v1.2.1 From 6890327762eaeca572ada783804a9c7af01e6144 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 10 Mar 2017 15:34:29 -0600 Subject: Copy code as GFM from diffs, blobs and GFM code blocks --- app/assets/javascripts/copy_as_gfm.js | 72 ++- changelogs/unreleased/dm-copy-code-as-gfm.yml | 4 + lib/banzai/filter/syntax_highlight_filter.rb | 13 +- lib/gitlab/highlight.rb | 4 +- lib/rouge/formatters/html_gitlab.rb | 5 +- spec/features/copy_as_gfm_spec.rb | 782 +++++++++++++++----------- 6 files changed, 537 insertions(+), 343 deletions(-) create mode 100644 changelogs/unreleased/dm-copy-code-as-gfm.yml diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js index 0fb7bde1fd6..67f7226fe82 100644 --- a/app/assets/javascripts/copy_as_gfm.js +++ b/app/assets/javascripts/copy_as_gfm.js @@ -118,10 +118,10 @@ const gfmRules = { }, SyntaxHighlightFilter: { 'pre.code.highlight'(el, t) { - const text = t.trim(); + const text = t.trimRight(); let lang = el.getAttribute('lang'); - if (lang === 'plaintext') { + if (!lang || lang === 'plaintext') { lang = ''; } @@ -157,7 +157,7 @@ const gfmRules = { const backticks = Array(backtickCount + 1).join('`'); const spaceOrNoSpace = backtickCount > 1 ? ' ' : ''; - return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks; + return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks; }, 'blockquote'(el, text) { return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); @@ -273,28 +273,29 @@ const gfmRules = { class CopyAsGFM { constructor() { - $(document).on('copy', '.md, .wiki', this.handleCopy); - $(document).on('paste', '.js-gfm-input', this.handlePaste); + $(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); + $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); + $(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this)); } - handleCopy(e) { + copyAsGFM(e, transformer) { const clipboardData = e.originalEvent.clipboardData; if (!clipboardData) return; const documentFragment = window.gl.utils.getSelectedFragment(); if (!documentFragment) return; - // If the documentFragment contains more than just Markdown, don't copy as GFM. - if (documentFragment.querySelector('.md, .wiki')) return; + const el = transformer(documentFragment.cloneNode(true)); + if (!el) return; e.preventDefault(); - clipboardData.setData('text/plain', documentFragment.textContent); + e.stopPropagation(); - const gfm = CopyAsGFM.nodeToGFM(documentFragment); - clipboardData.setData('text/x-gfm', gfm); + clipboardData.setData('text/plain', el.textContent); + clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el)); } - handlePaste(e) { + pasteGFM(e) { const clipboardData = e.originalEvent.clipboardData; if (!clipboardData) return; @@ -306,7 +307,54 @@ class CopyAsGFM { window.gl.utils.insertText(e.target, gfm); } + static transformGFMSelection(documentFragment) { + // If the documentFragment contains more than just Markdown, don't copy as GFM. + if (documentFragment.querySelector('.md, .wiki')) return null; + + return documentFragment; + } + + static transformCodeSelection(documentFragment) { + const lineEls = documentFragment.querySelectorAll('.line'); + + let codeEl; + if (lineEls.length > 1) { + codeEl = document.createElement('pre'); + codeEl.className = 'code highlight'; + + const lang = lineEls[0].getAttribute('lang'); + if (lang) { + codeEl.setAttribute('lang', lang); + } + } else { + codeEl = document.createElement('code'); + } + + if (lineEls.length > 0) { + for (let i = 0; i < lineEls.length; i += 1) { + const lineEl = lineEls[i]; + codeEl.appendChild(lineEl); + codeEl.appendChild(document.createTextNode('\n')); + } + } else { + codeEl.appendChild(documentFragment); + } + + return codeEl; + } + + static selectionToGFM(documentFragment, transformer) { + const el = transformer(documentFragment.cloneNode(true)); + if (!el) return null; + + return CopyAsGFM.nodeToGFM(el); + } + static nodeToGFM(node) { + if (node.nodeType === Node.COMMENT_NODE) { + return ''; + } + if (node.nodeType === Node.TEXT_NODE) { return node.textContent; } diff --git a/changelogs/unreleased/dm-copy-code-as-gfm.yml b/changelogs/unreleased/dm-copy-code-as-gfm.yml new file mode 100644 index 00000000000..15ae2da44a3 --- /dev/null +++ b/changelogs/unreleased/dm-copy-code-as-gfm.yml @@ -0,0 +1,4 @@ +--- +title: Copy code as GFM from diffs, blobs and GFM code blocks +merge_request: +author: diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index a447e2b8bff..9f09ca90697 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -5,8 +5,6 @@ module Banzai # HTML Filter to highlight fenced code blocks # class SyntaxHighlightFilter < HTML::Pipeline::Filter - include Rouge::Plugins::Redcarpet - def call doc.search('pre > code').each do |node| highlight_node(node) @@ -23,7 +21,7 @@ module Banzai lang = lexer.tag begin - code = format(lex(lexer, code)) + code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: lang) css_classes << " js-syntax-highlight #{lang}" rescue @@ -45,10 +43,6 @@ module Banzai lexer.lex(code) end - def format(tokens) - rouge_formatter.format(tokens) - end - def lexer_for(language) (Rouge::Lexer.find(language) || Rouge::Lexers::PlainText).new end @@ -57,11 +51,6 @@ module Banzai # Replace the parent `pre` element with the entire highlighted block node.parent.replace(highlighted) end - - # Override Rouge::Plugins::Redcarpet#rouge_formatter - def rouge_formatter(lexer = nil) - @rouge_formatter ||= Rouge::Formatters::HTML.new - end end end end diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 9360afedfcb..d787d5db4a0 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -14,7 +14,7 @@ module Gitlab end def initialize(blob_name, blob_content, repository: nil) - @formatter = Rouge::Formatters::HTMLGitlab.new + @formatter = Rouge::Formatters::HTMLGitlab @repository = repository @blob_name = blob_name @blob_content = blob_content @@ -28,7 +28,7 @@ module Gitlab hl_lexer = self.lexer end - @formatter.format(hl_lexer.lex(text, continue: continue)).html_safe + @formatter.format(hl_lexer.lex(text, continue: continue), tag: hl_lexer.tag).html_safe rescue @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe end diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index 4edfd015074..ec95ddf03ea 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -6,9 +6,10 @@ module Rouge # Creates a new Rouge::Formatter::HTMLGitlab instance. # # [+linenostart+] The line number for the first line (default: 1). - def initialize(linenostart: 1) + def initialize(linenostart: 1, tag: nil) @linenostart = linenostart @line_number = linenostart + @tag = tag end def stream(tokens, &b) @@ -17,7 +18,7 @@ module Rouge yield "\n" unless is_first is_first = false - yield %() + yield %() line.each { |token, value| yield span(token, value.chomp) } yield %() diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index 4638812b2d9..f134d4be154 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -2,437 +2,589 @@ require 'spec_helper' describe 'Copy as GFM', feature: true, js: true do include GitlabMarkdownHelper + include RepoHelpers include ActionView::Helpers::JavaScriptHelper before do - @feat = MarkdownFeature.new + login_as :admin + end - # `markdown` helper expects a `@project` variable - @project = @feat.project + describe 'Copying rendered GFM' do + before do + @feat = MarkdownFeature.new - visit namespace_project_issue_path(@project.namespace, @project, @feat.issue) - end + # `markdown` helper expects a `@project` variable + @project = @feat.project - # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML. - # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM. - # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle - # by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper. + visit namespace_project_issue_path(@project.namespace, @project, @feat.issue) + end - # These are all in a single `it` for performance reasons. - it 'works', :aggregate_failures do - verify( - 'nesting', + # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML. + # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM. + # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle + # by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper. - '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**' - ) + # These are all in a single `it` for performance reasons. + it 'works', :aggregate_failures do + verify( + 'nesting', - verify( - 'a real world example from the gitlab-ce README', + '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**' + ) - <<-GFM.strip_heredoc - # GitLab + verify( + 'a real world example from the gitlab-ce README', - [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) - [![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) - [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) - [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) + <<-GFM.strip_heredoc + # GitLab - ## Canonical source + [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) + [![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) + [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) + [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) - The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). + ## Canonical source - ## Open source software to collaborate on code + The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). - To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). + ## Open source software to collaborate on code + To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). - - Manage Git repositories with fine grained access controls that keep your code secure - - Perform code reviews and enhance collaboration with merge requests + - Manage Git repositories with fine grained access controls that keep your code secure - - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications + - Perform code reviews and enhance collaboration with merge requests - - Each project can also have an issue tracker, issue board, and a wiki + - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications - - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises + - Each project can also have an issue tracker, issue board, and a wiki - - Completely free and open source (MIT Expat license) - GFM - ) + - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises - verify( - 'InlineDiffFilter', + - Completely free and open source (MIT Expat license) + GFM + ) - '{-Deleted text-}', - '{+Added text+}' - ) + verify( + 'InlineDiffFilter', - verify( - 'TaskListFilter', + '{-Deleted text-}', + '{+Added text+}' + ) - '- [ ] Unchecked task', - '- [x] Checked task', - '1. [ ] Unchecked numbered task', - '1. [x] Checked numbered task' - ) + verify( + 'TaskListFilter', - verify( - 'ReferenceFilter', + '- [ ] Unchecked task', + '- [x] Checked task', + '1. [ ] Unchecked numbered task', + '1. [x] Checked numbered task' + ) - # issue reference - @feat.issue.to_reference, - # full issue reference - @feat.issue.to_reference(full: true), - # issue URL - namespace_project_issue_url(@project.namespace, @project, @feat.issue), - # issue URL with note anchor - namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'), - # issue link - "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})", - # issue link with note anchor - "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})", - ) + verify( + 'ReferenceFilter', - verify( - 'AutolinkFilter', + # issue reference + @feat.issue.to_reference, + # full issue reference + @feat.issue.to_reference(full: true), + # issue URL + namespace_project_issue_url(@project.namespace, @project, @feat.issue), + # issue URL with note anchor + namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'), + # issue link + "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})", + # issue link with note anchor + "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})", + ) - 'https://example.com' - ) + verify( + 'AutolinkFilter', - verify( - 'TableOfContentsFilter', + 'https://example.com' + ) - '[[_TOC_]]' - ) + verify( + 'TableOfContentsFilter', - verify( - 'EmojiFilter', + '[[_TOC_]]' + ) - ':thumbsup:' - ) + verify( + 'EmojiFilter', - verify( - 'ImageLinkFilter', - - '![Image](https://example.com/image.png)' - ) + ':thumbsup:' + ) - verify( - 'VideoLinkFilter', + verify( + 'ImageLinkFilter', + + '![Image](https://example.com/image.png)' + ) - '![Video](https://example.com/video.mp4)' - ) + verify( + 'VideoLinkFilter', - verify( - 'MathFilter: math as converted from GFM to HTML', + '![Video](https://example.com/video.mp4)' + ) - '$`c = \pm\sqrt{a^2 + b^2}`$', + verify( + 'MathFilter: math as converted from GFM to HTML', - # math block - <<-GFM.strip_heredoc - ```math - c = \pm\sqrt{a^2 + b^2} - ``` - GFM - ) + '$`c = \pm\sqrt{a^2 + b^2}`$', - aggregate_failures('MathFilter: math as transformed from HTML to KaTeX') do - gfm = '$`c = \pm\sqrt{a^2 + b^2}`$' + # math block + <<-GFM.strip_heredoc + ```math + c = \pm\sqrt{a^2 + b^2} + ``` + GFM + ) - html = <<-HTML.strip_heredoc - - - - - - c - = - ± - - - - a - 2 - - + - - b - 2 - - - - - c = \\pm\\sqrt{a^2 + b^2} - - - -