Commit 2e11cdd0 authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge branch 'master' into '21961-issues-filtering-issue-with-labels-that-contain-spaces'

# Conflicts:
#   app/assets/javascripts/gl_dropdown.js
parents 32979803 b94de5fd
*.erb *.erb
lib/gitlab/sanitizers/svg/whitelist.rb
...@@ -209,8 +209,12 @@ rubocop: *exec ...@@ -209,8 +209,12 @@ rubocop: *exec
rake haml_lint: *exec rake haml_lint: *exec
rake scss_lint: *exec rake scss_lint: *exec
rake brakeman: *exec rake brakeman: *exec
rake flog: *exec rake flog:
rake flay: *exec <<: *exec
allow_failure: yes
rake flay:
<<: *exec
allow_failure: yes
license_finder: *exec license_finder: *exec
rake downtime_check: *exec rake downtime_check: *exec
......
...@@ -767,26 +767,33 @@ Rails/ScopeArgs: ...@@ -767,26 +767,33 @@ Rails/ScopeArgs:
RSpec/AnyInstance: RSpec/AnyInstance:
Enabled: false Enabled: false
# Check that the first argument to the top level describe is the tested class or # Check for expectations where `be(...)` can replace `eql(...)`.
# module. RSpec/BeEql:
Enabled: false
# Check that the first argument to the top level describe is a constant.
RSpec/DescribeClass: RSpec/DescribeClass:
Enabled: false Enabled: false
# Use `described_class` for tested class / module. # Checks that tests use `described_class`.
RSpec/DescribedClass:
Enabled: false
# Checks that the second argument to `describe` specifies a method.
RSpec/DescribeMethod: RSpec/DescribeMethod:
Enabled: false Enabled: false
# Checks that the second argument to top level describe is the tested method # Checks if an example group does not include any tests.
# name. RSpec/EmptyExampleGroup:
RSpec/DescribedClass:
Enabled: false Enabled: false
CustomIncludeMethods: []
# Checks for long example. # Checks for long examples.
RSpec/ExampleLength: RSpec/ExampleLength:
Enabled: false Enabled: false
Max: 5 Max: 5
# Do not use should when describing your tests. # Checks that example descriptions do not start with "should".
RSpec/ExampleWording: RSpec/ExampleWording:
Enabled: false Enabled: false
CustomTransform: CustomTransform:
...@@ -795,6 +802,10 @@ RSpec/ExampleWording: ...@@ -795,6 +802,10 @@ RSpec/ExampleWording:
not: does not not: does not
IgnoredWords: [] IgnoredWords: []
# Checks for `expect(...)` calls containing literal values.
RSpec/ExpectActual:
Enabled: false
# Checks the file and folder naming of the spec file. # Checks the file and folder naming of the spec file.
RSpec/FilePath: RSpec/FilePath:
Enabled: false Enabled: false
...@@ -806,19 +817,65 @@ RSpec/FilePath: ...@@ -806,19 +817,65 @@ RSpec/FilePath:
RSpec/Focus: RSpec/Focus:
Enabled: true Enabled: true
# Checks the arguments passed to `before`, `around`, and `after`.
RSpec/HookArgument:
Enabled: false
EnforcedStyle: implicit
# Check that a consistent implict expectation style is used.
# TODO (rspeicher): Available in rubocop-rspec 1.8.0
# RSpec/ImplicitExpect:
# Enabled: true
# EnforcedStyle: is_expected
# Checks for the usage of instance variables. # Checks for the usage of instance variables.
RSpec/InstanceVariable: RSpec/InstanceVariable:
Enabled: false Enabled: false
# Checks for multiple top-level describes. # Checks for `subject` definitions that come after `let` definitions.
RSpec/LeadingSubject:
Enabled: false
# Checks unreferenced `let!` calls being used for test setup.
RSpec/LetSetup:
Enabled: false
# Check that chains of messages are not being stubbed.
RSpec/MessageChain:
Enabled: false
# Checks for consistent message expectation style.
RSpec/MessageExpectation:
Enabled: false
EnforcedStyle: allow
# Checks for multiple top level describes.
RSpec/MultipleDescribes: RSpec/MultipleDescribes:
Enabled: false Enabled: false
# Enforces the usage of the same method on all negative message expectations. # Checks if examples contain too many `expect` calls.
RSpec/MultipleExpectations:
Enabled: false
Max: 1
# Checks for explicitly referenced test subjects.
RSpec/NamedSubject:
Enabled: false
# Checks for nested example groups.
RSpec/NestedGroups:
Enabled: false
MaxNesting: 2
# Checks for consistent method usage for negating expectations.
RSpec/NotToNot: RSpec/NotToNot:
EnforcedStyle: not_to EnforcedStyle: not_to
Enabled: true Enabled: true
# Checks for stubbed test subjects.
RSpec/SubjectStub:
Enabled: false
# Prefer using verifying doubles over normal doubles. # Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles: RSpec/VerifiedDoubles:
Enabled: false Enabled: false
This diff is collapsed.
...@@ -3,16 +3,21 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,16 +3,21 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased) v 8.12.0 (unreleased)
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
- Only check :can_resolve permission if the note is resolvable - Only check :can_resolve permission if the note is resolvable
- Bump fog-aws to v0.11.0 to support ap-south-1 region
- Add ability to fork to a specific namespace using API. (ritave) - Add ability to fork to a specific namespace using API. (ritave)
- Allow to set request_access_enabled for groups and projects
- Cleanup misalignments in Issue list view !6206 - Cleanup misalignments in Issue list view !6206
- Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist
- Prune events older than 12 months. (ritave) - Prune events older than 12 months. (ritave)
- Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell)
- Fix issues/merge-request templates dropdown for forked projects
- Filter tags by name !6121 - Filter tags by name !6121
- Update gitlab shell secret file also when it is empty. !3774 (glensc) - Update gitlab shell secret file also when it is empty. !3774 (glensc)
- Give project selection dropdowns responsive width, make non-wrapping. - Give project selection dropdowns responsive width, make non-wrapping.
- Make push events have equal vertical spacing. - Make push events have equal vertical spacing.
- Add two-factor recovery endpoint to internal API !5510 - Add two-factor recovery endpoint to internal API !5510
- Pass the "Remember me" value to the U2F authentication form - Pass the "Remember me" value to the U2F authentication form
- Only update projects.last_activity_at once per hour when creating a new event
- Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
- Move pushes_since_gc from the database to Redis - Move pushes_since_gc from the database to Redis
- Add font color contrast to external label in admin area (ClemMakesApps) - Add font color contrast to external label in admin area (ClemMakesApps)
...@@ -20,27 +25,36 @@ v 8.12.0 (unreleased) ...@@ -20,27 +25,36 @@ v 8.12.0 (unreleased)
- Instructions for enabling Git packfile bitmaps !6104 - Instructions for enabling Git packfile bitmaps !6104
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
- Fix pagination on user snippets page - Fix pagination on user snippets page
- Run CI builds with the permissions of users !5735
- Fix sorting of issues in API - Fix sorting of issues in API
- Sort project variables by key. !6275 (Diego Souza)
- Ensure specs on sorting of issues in API are deterministic on MySQL - Ensure specs on sorting of issues in API are deterministic on MySQL
- Added ability to use predefined CI variables for environment name
- Added ability to specify URL in environment configuration in gitlab-ci.yml
- Escape search term before passing it to Regexp.new !6241 (winniehell) - Escape search term before passing it to Regexp.new !6241 (winniehell)
- Fix pinned sidebar behavior in smaller viewports !6169 - Fix pinned sidebar behavior in smaller viewports !6169
- Fix file permissions change when updating a file on the Gitlab UI !5979 - Fix file permissions change when updating a file on the Gitlab UI !5979
- Change merge_error column from string to text type - Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps) - Reduce contributions calendar data payload (ClemMakesApps)
- Replace contributions calendar timezone payload with dates (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Enable pipeline events by default !6278 - Enable pipeline events by default !6278
- Move parsing of sidekiq ps into helper !6245 (pascalbetz) - Move parsing of sidekiq ps into helper !6245 (pascalbetz)
- Added go to issue boards keyboard shortcut
- Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel)
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
- Fix blame table layout width - Fix blame table layout width
- Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps)
- Request only the LDAP attributes we need !6187 - Request only the LDAP attributes we need !6187
- Center build stage columns in pipeline overview (ClemMakesApps) - Center build stage columns in pipeline overview (ClemMakesApps)
- Fix bug with tooltip not hiding on discussion toggle button
- Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps)
- Fix bug stopping issue description being scrollable after selecting issue template
- Remove suggested colors hover underline (ClemMakesApps) - Remove suggested colors hover underline (ClemMakesApps)
- Shorten task status phrase (ClemMakesApps) - Shorten task status phrase (ClemMakesApps)
- Fix project visibility level fields on settings - Fix project visibility level fields on settings
- Add hover color to emoji icon (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps)
- Increase ci_builds artifacts_size column to 8-byte integer to allow larger files
- Add textarea autoresize after comment (ClemMakesApps) - Add textarea autoresize after comment (ClemMakesApps)
- Refresh todos count cache when an Issue/MR is deleted - Refresh todos count cache when an Issue/MR is deleted
- Fix branches page dropdown sort alignment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps)
...@@ -60,10 +74,12 @@ v 8.12.0 (unreleased) ...@@ -60,10 +74,12 @@ v 8.12.0 (unreleased)
- Use 'git update-ref' for safer web commits !6130 - Use 'git update-ref' for safer web commits !6130
- Sort pipelines requested through the API - Sort pipelines requested through the API
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Fix issue boards loading on large screens
- Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084
- Show queued time when showing a pipeline !6084 - Show queued time when showing a pipeline !6084
- Remove unused mixins (ClemMakesApps) - Remove unused mixins (ClemMakesApps)
- Add search to all issue board lists - Add search to all issue board lists
- Scroll active tab into view on mobile
- Fix groups sort dropdown alignment (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Use JavaScript tooltips for mentions !5301 (winniehell) - Use JavaScript tooltips for mentions !5301 (winniehell)
...@@ -74,7 +90,8 @@ v 8.12.0 (unreleased) ...@@ -74,7 +90,8 @@ v 8.12.0 (unreleased)
- Add last commit time to repo view (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps)
- Fix accessibility and visibility of project list dropdown button !6140 - Fix accessibility and visibility of project list dropdown button !6140
- Fix missing flash messages on service edit page (airatshigapov) - Fix missing flash messages on service edit page (airatshigapov)
- Added project specific enable/disable setting for LFS !5997 - Added project-specific enable/disable setting for LFS !5997
- Added group-specific enable/disable setting for LFS !6164
- Don't expose a user's token in the `/api/v3/user` API (!6047) - Don't expose a user's token in the `/api/v3/user` API (!6047)
- Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
- Ability to manage project issues, snippets, wiki, merge requests and builds access level - Ability to manage project issues, snippets, wiki, merge requests and builds access level
...@@ -128,15 +145,27 @@ v 8.12.0 (unreleased) ...@@ -128,15 +145,27 @@ v 8.12.0 (unreleased)
- Use default clone protocol on "check out, review, and merge locally" help page URL - Use default clone protocol on "check out, review, and merge locally" help page URL
- API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska)
- Allow bulk update merge requests from merge requests index page - Allow bulk update merge requests from merge requests index page
- Ensure validation messages are shown within the milestone form
- Add notification_settings API calls !5632 (mahcsig) - Add notification_settings API calls !5632 (mahcsig)
- Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska)
- Fix URLs with anchors in wiki !6300 (houqp)
- Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska)
- Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225
- Fix Gitlab::Popen.popen thread-safety issue - Fix Gitlab::Popen.popen thread-safety issue
- Add specs to removing project (Katarzyna Kobierska Ula Budziszewska)
v 8.11.6 (unreleased) - Clean environment variables when running git hooks
- Fix an error where we were unable to create a CommitStatus for running state - Fix Import/Export issues importing protected branches and some specific models
- Fix non-master branch readme display in tree view
v 8.11.6
- Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
- Make merge conflict file size limit 200 KB, to match the docs. !6052
- Fix an error where we were unable to create a CommitStatus for running state. !6107
- Optimize discussion notes resolving and unresolving. !6141
- Fix GitLab import button. !6167
- Restore SSH Key title auto-population behavior. !6186 - Restore SSH Key title auto-population behavior. !6186
- Fix DB schema to match latest migration. !6256
- Exclude some pending or inactivated rows in Member scopes.
v 8.11.5 v 8.11.5
- Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087
...@@ -149,6 +178,7 @@ v 8.11.5 ...@@ -149,6 +178,7 @@ v 8.11.5
- Scope webhooks/services that will run for confidential issues - Scope webhooks/services that will run for confidential issues
- Remove gitorious from import_sources - Remove gitorious from import_sources
- Fix confidential issues being exposed as public using gitlab.com export - Fix confidential issues being exposed as public using gitlab.com export
- Use oj gem for faster JSON processing
v 8.11.4 v 8.11.4
- Fix resolving conflicts on forks. !6082 - Fix resolving conflicts on forks. !6082
...@@ -341,6 +371,13 @@ v 8.11.0 ...@@ -341,6 +371,13 @@ v 8.11.0
- Update gitlab_git gem to 10.4.7 - Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done - Simplify SQL queries of marking a todo as done
v 8.10.9
- Exclude some pending or inactivated rows in Member scopes
v 8.10.8
- Fix information disclosure in issue boards.
- Fix privilege escalation in project import.
v 8.10.7 v 8.10.7
- Upgrade Hamlit to 2.6.1. !5873 - Upgrade Hamlit to 2.6.1. !5873
- Upgrade Doorkeeper to 4.2.0. !5881 - Upgrade Doorkeeper to 4.2.0. !5881
...@@ -566,6 +603,9 @@ v 8.10.0 ...@@ -566,6 +603,9 @@ v 8.10.0
- Fix migration corrupting import data for old version upgrades - Fix migration corrupting import data for old version upgrades
- Show tooltip on GitLab export link in new project page - Show tooltip on GitLab export link in new project page
v 8.9.9
- Exclude some pending or inactivated rows in Member scopes
v 8.9.8 v 8.9.8
- Upgrade Doorkeeper to 4.2.0. !5881 - Upgrade Doorkeeper to 4.2.0. !5881
......
...@@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs]. ...@@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
## Implement design & UI elements ## Implement design & UI elements
### Design reference Please see the [UI Guide for building GitLab].
The GitLab design reference can be found in the [gitlab-design] project.
The designs are made using Antetype (`.atype` files). You can use the
[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
(the PNG is 1:1).
The current designs can be found in the [`gitlab8.atype` file].
### UI development kit
Implemented UI elements can also be found at https://gitlab.com/help/ui. Please
note that this page isn't comprehensive at this time.
## Issue tracker ## Issue tracker
...@@ -289,6 +277,8 @@ request is as follows: ...@@ -289,6 +277,8 @@ request is as follows:
1. For more complex migrations, write tests. 1. For more complex migrations, write tests.
1. Merge requests **must** adhere to the [merge request performance 1. Merge requests **must** adhere to the [merge request performance
guidelines](doc/development/merge_request_performance_guidelines.md). guidelines](doc/development/merge_request_performance_guidelines.md).
1. For tests that use Capybara or PhantomJS, see this [article on how
to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
The **official merge window** is in the beginning of the month from the 1st to The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get the 7th day of the month. This is the best time to submit an MR and get
...@@ -489,7 +479,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -489,7 +479,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide" [scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide" [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design [UI Guide for building GitLab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/ui_guide.md
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
[license-finder-doc]: doc/development/licensing.md [license-finder-doc]: doc/development/licensing.md
...@@ -26,7 +26,7 @@ gem 'omniauth-auth0', '~> 1.4.1' ...@@ -26,7 +26,7 @@ gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 3.0.0' gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-google-oauth2', '~> 0.4.1'
...@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' ...@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem 'gitlab_git', '~> 10.6.3' gem 'gitlab_git', '~> 10.6.6'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -206,6 +206,9 @@ gem 'mousetrap-rails', '~> 1.4.6' ...@@ -206,6 +206,9 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.3' gem 'charlock_holmes', '~> 0.7.3'
# Faster JSON
gem 'oj', '~> 2.17.4'
# Parse time & duration # Parse time & duration
gem 'chronic', '~> 0.10.2' gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
...@@ -295,8 +298,8 @@ group :development, :test do ...@@ -295,8 +298,8 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.41.2', require: false gem 'rubocop', '~> 0.42.0', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'rubocop-rspec', '~> 1.7.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.18.2', require: false gem 'haml_lint', '~> 0.18.2', require: false
gem 'simplecov', '0.12.0', require: false gem 'simplecov', '0.12.0', require: false
......
...@@ -189,7 +189,7 @@ GEM ...@@ -189,7 +189,7 @@ GEM
erubis (2.7.0) erubis (2.7.0)
escape_utils (1.1.1) escape_utils (1.1.1)
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.49.0) excon (0.52.0)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.5.0) factory_girl (4.5.0)
...@@ -215,8 +215,8 @@ GEM ...@@ -215,8 +215,8 @@ GEM
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
fog-aws (0.9.2) fog-aws (0.11.0)
fog-core (~> 1.27) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
...@@ -225,7 +225,7 @@ GEM ...@@ -225,7 +225,7 @@ GEM
fog-core (~> 1.27) fog-core (~> 1.27)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
fog-core (1.40.0) fog-core (1.42.0)
builder builder
excon (~> 0.49) excon (~> 0.49)
formatador (~> 0.2) formatador (~> 0.2)
...@@ -279,7 +279,7 @@ GEM ...@@ -279,7 +279,7 @@ GEM
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_git (10.6.3) gitlab_git (10.6.6)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -333,7 +333,7 @@ GEM ...@@ -333,7 +333,7 @@ GEM
temple (~> 0.7.6) temple (~> 0.7.6)
thor thor
tilt tilt
hashie (3.4.3) hashie (3.4.4)
health_check (2.1.0) health_check (2.1.0)
rails (>= 4.0) rails (>= 4.0)
hipchat (1.5.2) hipchat (1.5.2)
...@@ -401,7 +401,7 @@ GEM ...@@ -401,7 +401,7 @@ GEM
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mail_room (0.8.0) mail_room (0.8.0)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.2) mime-types (2.99.3)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_portile2 (2.1.0) mini_portile2 (2.1.0)
minitest (5.7.0) minitest (5.7.0)
...@@ -427,6 +427,7 @@ GEM ...@@ -427,6 +427,7 @@ GEM
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.3.0) octokit (4.3.0)
sawyer (~> 0.7.0, >= 0.5.3) sawyer (~> 0.7.0, >= 0.5.3)
oj (2.17.4)
omniauth (1.3.1) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
...@@ -444,7 +445,7 @@ GEM ...@@ -444,7 +445,7 @@ GEM
addressable (~> 2.3) addressable (~> 2.3)
nokogiri (~> 1.6.6) nokogiri (~> 1.6.6)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-facebook (3.0.0) omniauth-facebook (4.0.0)
omniauth-oauth2 (~> 1.2) omniauth-oauth2 (~> 1.2)
omniauth-github (1.1.2) omniauth-github (1.1.2)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -619,14 +620,14 @@ GEM ...@@ -619,14 +620,14 @@ GEM
rspec-retry (0.4.5) rspec-retry (0.4.5)
rspec-core rspec-core
rspec-support (3.5.0) rspec-support (3.5.0)
rubocop (0.41.2) rubocop (0.42.0)
parser (>= 2.3.1.1, < 3.0) parser (>= 2.3.1.1, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.5.0) rubocop-rspec (1.7.0)
rubocop (>= 0.40.0) rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-prof (0.15.9) ruby-prof (0.15.9)
...@@ -762,7 +763,7 @@ GEM ...@@ -762,7 +763,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.1.0) unicode-display_width (1.1.1)
unicorn (4.9.0) unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
...@@ -866,7 +867,7 @@ DEPENDENCIES ...@@ -866,7 +867,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
github-markup (~> 1.4) github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_git (~> 10.6.3) gitlab_git (~> 10.6.6)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
...@@ -904,12 +905,13 @@ DEPENDENCIES ...@@ -904,12 +905,13 @@ DEPENDENCIES
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0) oauth2 (~> 1.2.0)
octokit (~> 4.3.0) octokit (~> 4.3.0)
oj (~> 2.17.4)
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2) omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2) omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 3.0.0) omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.4.1) omniauth-google-oauth2 (~> 0.4.1)
...@@ -944,8 +946,8 @@ DEPENDENCIES ...@@ -944,8 +946,8 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0) rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rubocop (~> 0.41.2) rubocop (~> 0.42.0)
rubocop-rspec (~> 1.5.0) rubocop-rspec (~> 1.7.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.15.9) ruby-prof (~> 0.15.9)
sanitize (~> 2.0) sanitize (~> 2.0)
......
...@@ -3,10 +3,11 @@ ...@@ -3,10 +3,11 @@
[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![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)
## Canonical source ## Canonical source
The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
## Open source software to collaborate on code ## Open source software to collaborate on code
......
...@@ -251,6 +251,7 @@ ...@@ -251,6 +251,7 @@
} else { } else {
notesHolders.hide(); notesHolders.hide();
} }
$this.trigger('blur');
return e.preventDefault(); return e.preventDefault();
}); });
$document.off("click", '.js-confirm-danger'); $document.off("click", '.js-confirm-danger');
......
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
this.buildDropdown(); this.buildDropdown();
this.bindEvents(); this.bindEvents();
this.onFilenameUpdate(); this.onFilenameUpdate();
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
} }
TemplateSelector.prototype.buildDropdown = function() { TemplateSelector.prototype.buildDropdown = function() {
...@@ -72,6 +75,10 @@ ...@@ -72,6 +75,10 @@
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) { TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
this.editor.setValue(file.content, 1); this.editor.setValue(file.content, 1);
if (!skipFocus) this.editor.focus(); if (!skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
}; };
TemplateSelector.prototype.startLoadingSpinner = function() { TemplateSelector.prototype.startLoadingSpinner = function() {
......
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
}, },
issues () { issues () {
this.$nextTick(() => { this.$nextTick(() => {
if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) {
this.list.page++;
this.list.getIssues(false);
}
if (this.scrollHeight() > this.listHeight()) { if (this.scrollHeight() > this.listHeight()) {
this.showCount = true; this.showCount = true;
} else { } else {
......
...@@ -352,7 +352,13 @@ ...@@ -352,7 +352,13 @@
if (self.options.clicked) { if (self.options.clicked) {
self.options.clicked(selected, $el, e); self.options.clicked(selected, $el, e);
} }
return $el.trigger('blur');
// Update label right after all modifications in dropdown has been done
if (self.options.toggleLabel) {
self.updateLabel(selected, $el, self);
}
$el.trigger('blur');
}); });
} }
} }
...@@ -529,7 +535,7 @@ ...@@ -529,7 +535,7 @@
} else { } else {
if (!selected) { if (!selected) {
value = this.options.id ? this.options.id(data) : data.id; value = this.options.id ? this.options.id(data) : data.id;
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName; fieldName = this.options.fieldName;
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
if (field.length) { if (field.length) {
...@@ -589,6 +595,7 @@ ...@@ -589,6 +595,7 @@
GitLabDropdown.prototype.rowClicked = function(el) { GitLabDropdown.prototype.rowClicked = function(el) {
var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
fieldName = this.options.fieldName;
isInput = $(this.el).is('input'); isInput = $(this.el).is('input');
if (this.renderedData) { if (this.renderedData) {
groupName = el.data('group'); groupName = el.data('group');
...@@ -645,11 +652,6 @@ ...@@ -645,11 +652,6 @@
} }
} }
// Update label right after input has been added
if (this.options.toggleLabel) {
this.updateLabel(selectedObject, el, this);
}
return selectedObject; return selectedObject;
}; };
...@@ -660,9 +662,6 @@ ...@@ -660,9 +662,6 @@
if (this.options.inputId != null) { if (this.options.inputId != null) {
$input.attr('id', this.options.inputId); $input.attr('id', this.options.inputId);
} }
if (selectedObject && selectedObject.type) {
$input.attr('data-type', selectedObject.type);
}
return this.dropdown.before($input); return this.dropdown.before($input);
}; };
......
...@@ -10,11 +10,13 @@ ...@@ -10,11 +10,13 @@
}; };
$(function() { $(function() {
hideEndFade($('.scrolling-tabs')); var $scrollingTabs = $('.scrolling-tabs');
hideEndFade($scrollingTabs);
$(window).off('resize.nav').on('resize.nav', function() { $(window).off('resize.nav').on('resize.nav', function() {
return hideEndFade($('.scrolling-tabs')); return hideEndFade($scrollingTabs);
}); });
return $('.scrolling-tabs').on('scroll', function(event) { $scrollingTabs.off('scroll').on('scroll', function(event) {
var $this, currentPosition, maxPosition; var $this, currentPosition, maxPosition;
$this = $(this); $this = $(this);
currentPosition = $this.scrollLeft(); currentPosition = $this.scrollLeft();
...@@ -22,6 +24,23 @@ ...@@ -22,6 +24,23 @@
$this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0); $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
}); });
$scrollingTabs.each(function () {
var $this = $(this),
scrollingTabWidth = $this.width(),
$active = $this.find('.active'),
activeWidth = $active.width();
if ($active.length) {
var offset = $active.offset().left + activeWidth;
if (offset > scrollingTabWidth - 30) {
var scrollLeft = scrollingTabWidth / 2;
scrollLeft = (offset - scrollLeft) - (activeWidth / 2);
$this.scrollLeft(scrollLeft);
}
}
});
}); });
}).call(this); }).call(this);
...@@ -232,10 +232,10 @@ ...@@ -232,10 +232,10 @@
$('.hll').removeClass('hll'); $('.hll').removeClass('hll');
locationHash = window.location.hash; locationHash = window.location.hash;
if (locationHash !== '') { if (locationHash !== '') {
hashClassString = "." + (locationHash.replace('#', '')); dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]';
$diffLine = $(locationHash + ":not(.match)", $('#diffs')); $diffLine = $(locationHash + ":not(.match)", $('#diffs'));
if (!$diffLine.is('tr')) { if (!$diffLine.is('tr')) {
$diffLine = $('#diffs').find("td" + locationHash + ", td" + hashClassString); $diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString);
} else { } else {
$diffLine = $diffLine.find('td'); $diffLine = $diffLine.find('td');
} }
......
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
Mousetrap.bind('g i', function() { Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
}); });
Mousetrap.bind('g l', function() {
ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
});
Mousetrap.bind('g m', function() { Mousetrap.bind('g m', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
}); });
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
date.setDate(date.getDate() + i); date.setDate(date.getDate() + i);
var day = date.getDay(); var day = date.getDay();
var count = timestamps[date.getTime() * 0.001]; var count = timestamps[dateFormat(date, 'yyyy-mm-dd')];
// Create a new group array if this is the first day of the week // Create a new group array if this is the first day of the week
// or if is first object // or if is first object
......
...@@ -162,6 +162,10 @@ ul.content-list { ...@@ -162,6 +162,10 @@ ul.content-list {
margin-right: 0; margin-right: 0;
} }
} }
.no-comments {
opacity: 0.5;
}
} }
// When dragging a list item // When dragging a list item
......
...@@ -164,7 +164,7 @@ ...@@ -164,7 +164,7 @@
text-decoration: none; text-decoration: none;
&:after { &:after {
content: url('icon_anchor.svg'); content: image-url('icon_anchor.svg');
visibility: hidden; visibility: hidden;
} }
} }
......
lex
[v-cloak] { [v-cloak] {
display: none; display: none;
} }
...@@ -18,6 +19,10 @@ ...@@ -18,6 +19,10 @@
} }
} }
.is-ghost {
opacity: 0.3;
}
.dropdown-menu-issues-board-new { .dropdown-menu-issues-board-new {
width: 320px; width: 320px;
...@@ -34,47 +39,13 @@ ...@@ -34,47 +39,13 @@
> p { > p {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
color: #9c9c9c;
} }
} }
.issue-boards-page { .issue-boards-page {
.content-wrapper {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
.sub-nav,
.issues-filters {
-webkit-flex: none;
flex: none;
}
.page-with-sidebar { .page-with-sidebar {
display: -webkit-flex;
display: flex;
min-height: 100vh;
max-height: 100vh;
padding-bottom: 0; padding-bottom: 0;
} }
.issue-boards-content {
display: -webkit-flex;
display: flex;
-webkit-flex: 1;
flex: 1;
width: 100%;
.content {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
width: 100%;
}
}
} }
.boards-app-loading { .boards-app-loading {
...@@ -83,46 +54,38 @@ ...@@ -83,46 +54,38 @@
} }
.boards-list { .boards-list {
display: -webkit-flex; height: calc(100vh - 152px);
display: flex; width: 100%;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0;
flex-basis: 0;
min-height: calc(100vh - 152px);
max-height: calc(100vh - 152px);
padding-top: 25px; padding-top: 25px;
padding-bottom: 25px;
padding-right: ($gl-padding / 2); padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2); padding-left: ($gl-padding / 2);
overflow-x: scroll; overflow-x: scroll;
white-space: nowrap;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS
height: calc(100vh - 220px);
min-height: 475px; min-height: 475px;
max-height: none;
} }
} }
.board { .board {
display: -webkit-flex; display: inline-block;
display: flex; width: calc(85vw - 15px);
min-width: calc(85vw - 15px); height: 100%;
max-width: calc(85vw - 15px);
margin-bottom: 25px;
padding-right: ($gl-padding / 2); padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2); padding-left: ($gl-padding / 2);
white-space: normal;
vertical-align: top;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
min-width: 400px; width: 400px;
max-width: 400px;
} }
} }
.board-inner { .board-inner {
display: -webkit-flex; height: 100%;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
width: 100%;
font-size: $issue-boards-font-size; font-size: $issue-boards-font-size;
background: $background-color; background: $background-color;
border: 1px solid $border-color; border: 1px solid $border-color;
...@@ -193,45 +156,31 @@ ...@@ -193,45 +156,31 @@
} }
.board-list { .board-list {
-webkit-flex: 1; height: calc(100% - 49px);
flex: 1;
height: 400px;
margin-bottom: 0; margin-bottom: 0;
padding: 5px; padding: 5px;
list-style: none;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
} }
.board-list-loading { .board-list-loading {
margin-top: 10px; margin-top: 10px;
font-size: 26px; font-size: (26px / $issue-boards-font-size) * 1em;
}
.is-ghost {
opacity: 0.3;
} }
.card { .card {
position: relative; position: relative;
width: 100%;
padding: 10px $gl-padding; padding: 10px $gl-padding;
background: #fff; background: #fff;
border-radius: $border-radius-default; border-radius: $border-radius-default;
box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5); box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5);
list-style: none; list-style: none;
&.user-can-drag {
padding-left: $gl-padding;
}
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 5px; margin-bottom: 5px;
} }
a {
cursor: pointer;
}
.label { .label {
border: 0; border: 0;
outline: 0; outline: 0;
...@@ -256,14 +205,13 @@ ...@@ -256,14 +205,13 @@
line-height: 25px; line-height: 25px;
.label { .label {
margin-right: 4px; margin-right: 5px;
font-size: (14px / $issue-boards-font-size) * 1em; font-size: (14px / $issue-boards-font-size) * 1em;
} }
} }
.card-number { .card-number {
margin-right: 8px; margin-right: 5px;
font-weight: 500;
} }
.issue-boards-search { .issue-boards-search {
......
...@@ -10,10 +10,6 @@ ...@@ -10,10 +10,6 @@
.issue-labels { .issue-labels {
display: inline-block; display: inline-block;
} }
.issue-no-comments {
opacity: 0.5;
}
} }
} }
......
...@@ -231,10 +231,6 @@ ...@@ -231,10 +231,6 @@
.merge-request-labels { .merge-request-labels {
display: inline-block; display: inline-block;
} }
.merge-request-no-comments {
opacity: 0.5;
}
} }
.merge-request-angle { .merge-request-angle {
......
...@@ -147,14 +147,37 @@ ...@@ -147,14 +147,37 @@
} }
.stage-cell { .stage-cell {
text-align: center; font-size: 0;
svg { svg {
height: 18px; height: 18px;
width: 18px; width: 18px;
position: relative;
z-index: 2;
vertical-align: middle; vertical-align: middle;
overflow: visible; overflow: visible;
} }
.stage-container {
display: inline-block;
position: relative;
margin-right: 6px;
.tooltip {
white-space: nowrap;
}
&:not(:last-child) {
&::after {
content: '';
width: 8px;
position: absolute;;
right: -7px;
bottom: 8px;
border-bottom: 2px solid $border-color;
}
}
}
} }
.duration, .duration,
...@@ -318,9 +341,17 @@ ...@@ -318,9 +341,17 @@
.build-content { .build-content {
width: 130px; width: 130px;
.ci-status-text {
width: 110px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: middle;
display: inline-block;
position: relative;
top: -1px;
}
a { a {
color: $layout-link-gray; color: $layout-link-gray;
...@@ -331,13 +362,74 @@ ...@@ -331,13 +362,74 @@
text-decoration: underline; text-decoration: underline;
} }
} }
}
.dropdown-menu-toggle {
border: none;
width: auto;
padding: 0;
color: $layout-link-gray;
.ci-status-text {
width: 80px;
}
}
.grouped-pipeline-dropdown {
padding: 8px 0;
width: 200px;
left: auto;
right: -214px;
top: -9px;
a:hover {
.ci-status-text {
text-decoration: none;
}
}
.ci-status-text {
width: 145px;
}
.arrow {
&:before,
&:after {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 18px;
}
&:before {
left: -5px;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: $border-color;
}
&:after {
left: -4px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: $white-light;
}
}
}
.badge {
background-color: $gray-dark;
color: $layout-link-gray;
font-weight: normal;
} }
} }
svg { svg {
position: relative; vertical-align: middle;
top: 2px;
margin-right: 5px; margin-right: 5px;
} }
...@@ -442,7 +534,7 @@ ...@@ -442,7 +534,7 @@
width: 21px; width: 21px;
height: 25px; height: 25px;
position: absolute; position: absolute;
top: -28.5px; top: -29px;
border-top: 2px solid $border-color; border-top: 2px solid $border-color;
} }
......
...@@ -334,6 +334,10 @@ a.deploy-project-label { ...@@ -334,6 +334,10 @@ a.deploy-project-label {
a { a {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
.dropdown-menu {
width: 240px;
}
} }
.last-push-widget { .last-push-widget {
......
...@@ -2,20 +2,6 @@ ...@@ -2,20 +2,6 @@
padding: 2px; padding: 2px;
} }
.snippet-holder {
margin-bottom: -$gl-padding;
.file-holder {
border-top: 0;
}
.file-actions {
.btn-clipboard {
@extend .btn;
}
}
}
.markdown-snippet-copy { .markdown-snippet-copy {
position: fixed; position: fixed;
top: -10px; top: -10px;
...@@ -24,29 +10,18 @@ ...@@ -24,29 +10,18 @@
max-width: 0; max-width: 0;
} }
.file-holder.snippet-file-content { .snippet-file-content {
padding-bottom: $gl-padding; border-radius: 3px;
border-bottom: 1px solid $border-color; .btn-clipboard {
@extend .btn;
.file-title {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
}
.file-actions {
top: 12px;
}
.file-content {
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
border-bottom: 1px solid $border-color;
} }
} }
.snippet-title { .snippet-title {
font-size: 24px; font-size: 24px;
font-weight: normal; font-weight: 600;
padding: $gl-padding;
padding-left: 0;
} }
.snippet-actions { .snippet-actions {
......
...@@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled) params.require(:group).permit(
:avatar,
:description,
:lfs_enabled,
:name,
:path,
:request_access_enabled,
:visibility_level
)
end end
end end
...@@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController ...@@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled) params.require(:group).permit(
:avatar,
:description,
:lfs_enabled,
:name,
:path,
:public,
:request_access_enabled,
:share_with_group_lock,
:visibility_level
)
end end
def load_events def load_events
......
...@@ -11,7 +11,10 @@ class JwtController < ApplicationController ...@@ -11,7 +11,10 @@ class JwtController < ApplicationController
service = SERVICES[params[:service]] service = SERVICES[params[:service]]
return head :not_found unless service return head :not_found unless service
result = service.new(@project, @user, auth_params).execute @authentication_result ||= Gitlab::Auth::Result.new
result = service.new(@authentication_result.project, @authentication_result.actor, auth_params).
execute(authentication_abilities: @authentication_result.authentication_abilities)
render json: result, status: result[:http_status] render json: result, status: result[:http_status]
end end
...@@ -20,30 +23,23 @@ class JwtController < ApplicationController ...@@ -20,30 +23,23 @@ class JwtController < ApplicationController
def authenticate_project_or_user def authenticate_project_or_user
authenticate_with_http_basic do |login, password| authenticate_with_http_basic do |login, password|
# if it's possible we first try to authenticate project with login and password @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
@project = authenticate_project(login, password)
return if @project
@user = authenticate_user(login, password)
return if @user
render_403 render_403 unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
end end
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end end
def auth_params def render_missing_personal_token
params.permit(:service, :scope, :account, :client_id) render plain: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
status: 401
end end
def authenticate_project(login, password) def auth_params
if login == 'gitlab-ci-token' params.permit(:service, :scope, :account, :client_id)
Project.with_builds_enabled.find_by(runners_token: password)
end
end
def authenticate_user(login, password)
user = Gitlab::Auth.find_with_user_password(login, password)
Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
user
end end
end end
...@@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
render json: @build.to_json(methods: :trace_html) render json: {
id: @build.id,
status: @build.status,
trace_html: @build.trace_html
}
end end
end end
end end
...@@ -74,7 +78,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -74,7 +78,7 @@ class Projects::BuildsController < Projects::ApplicationController
def erase def erase
@build.erase(erased_by: current_user) @build.erase(erased_by: current_user)
redirect_to namespace_project_build_path(project.namespace, project, @build), redirect_to namespace_project_build_path(project.namespace, project, @build),
notice: "Build has been sucessfully erased!" notice: "Build has been successfully erased!"
end end
def raw def raw
......
...@@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper include KerberosSpnegoHelper
attr_reader :user attr_reader :authentication_result
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
alias_method :user, :actor
# Git clients will not know what authenticity token to send along # Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
...@@ -15,32 +19,25 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -15,32 +19,25 @@ class Projects::GitHttpClientController < Projects::ApplicationController
private private
def authenticate_user def authenticate_user
@authentication_result = Gitlab::Auth::Result.new
if project && project.public? && download_request? if project && project.public? && download_request?
return # Allow access return # Allow access
end end
if allow_basic_auth? && basic_auth_provided? if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request) login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && download_request?
@ci = true
elsif auth_result.type == :oauth && !download_request?
# Not allowed
elsif auth_result.type == :missing_personal_token
render_missing_personal_token
return # Render above denied access, nothing left to do
else
@user = auth_result.user
end
if ci? || user if handle_basic_authentication(login, password)
return # Allow access return # Allow access
end end
elsif allow_kerberos_spnego_auth? && spnego_provided? elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user user = find_kerberos_user
if user if user
@authentication_result = Gitlab::Auth::Result.new(
user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
send_final_spnego_response send_final_spnego_response
return # Allow access return # Allow access
end end
...@@ -48,6 +45,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -48,6 +45,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401 render plain: "HTTP Basic: Access denied\n", status: 401
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end end
def basic_auth_provided? def basic_auth_provided?
...@@ -114,8 +113,39 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -114,8 +113,39 @@ class Projects::GitHttpClientController < Projects::ApplicationController
render plain: 'Not Found', status: :not_found render plain: 'Not Found', status: :not_found
end end
def handle_basic_authentication(login, password)
@authentication_result = Gitlab::Auth.find_for_git_client(
login, password, project: project, ip: request.ip)
return false unless @authentication_result.success?
if download_request?
authentication_has_download_access?
else
authentication_has_upload_access?
end
end
def ci? def ci?
@ci.present? authentication_result.ci? &&
authentication_project &&
authentication_project == project
end
def authentication_has_download_access?
has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
end
def authentication_has_upload_access?
has_authentication_ability?(:push_code)
end
def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability)
end
def authentication_project
authentication_result.project
end end
def verify_workhorse_api! def verify_workhorse_api!
......
...@@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end end
def access def access
@access ||= Gitlab::GitAccess.new(user, project, 'http') @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities)
end end
def access_check def access_check
......
...@@ -428,6 +428,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -428,6 +428,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def validates_merge_request def validates_merge_request
# If source project was removed and merge request for some reason
# wasn't close (Ex. mr from fork to origin)
return invalid_mr if !@merge_request.source_project && @merge_request.open?
# Show git not found page # Show git not found page
# if there is no saved commits between source & target branch # if there is no saved commits between source & target branch
if @merge_request.commits.blank? if @merge_request.commits.blank?
......
...@@ -73,7 +73,7 @@ class UsersController < ApplicationController ...@@ -73,7 +73,7 @@ class UsersController < ApplicationController
def calendar def calendar
calendar = contributions_calendar calendar = contributions_calendar
@timestamps = calendar.timestamps @activity_dates = calendar.activity_dates
render 'calendar', layout: false render 'calendar', layout: false
end end
......
...@@ -23,4 +23,29 @@ module GroupsHelper ...@@ -23,4 +23,29 @@ module GroupsHelper
full_title full_title
end end
end end
def projects_lfs_status(group)
lfs_status =
if group.lfs_enabled?
group.projects.select(&:lfs_enabled?).size
else
group.projects.reject(&:lfs_enabled?).size
end
size = group.projects.size
if lfs_status == size
'for all projects'
else
"for #{lfs_status} out of #{pluralize(size, 'project')}"
end
end
def group_lfs_status(group)
status = group.lfs_enabled? ? 'enabled' : 'disabled'
content_tag(:span, class: "lfs-#{status}") do
"#{status.humanize} #{projects_lfs_status(group)}"
end
end
end end
...@@ -25,13 +25,21 @@ module LfsHelper ...@@ -25,13 +25,21 @@ module LfsHelper
def lfs_download_access? def lfs_download_access?
return false unless project.lfs_enabled? return false unless project.lfs_enabled?
project.public? || ci? || (user && user.can?(:download_code, project)) project.public? || ci? || user_can_download_code? || build_can_download_code?
end
def user_can_download_code?
has_authentication_ability?(:download_code) && can?(user, :download_code, project)
end
def build_can_download_code?
has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
end end
def lfs_upload_access? def lfs_upload_access?
return false unless project.lfs_enabled? return false unless project.lfs_enabled?
user && user.can?(:push_code, project) has_authentication_ability?(:push_code) && can?(user, :push_code, project)
end end
def render_lfs_forbidden def render_lfs_forbidden
......
...@@ -27,7 +27,7 @@ module ProjectsHelper ...@@ -27,7 +27,7 @@ module ProjectsHelper
author_html = "" author_html = ""
# Build avatar image tag # Build avatar image tag
author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar] author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar]
# Build name span tag # Build name span tag
if opts[:by_username] if opts[:by_username]
......
...@@ -30,6 +30,37 @@ module SearchHelper ...@@ -30,6 +30,37 @@ module SearchHelper
"Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
end end
def parse_search_result(result)
ref = nil
filename = nil
basename = nil
startline = 0
result.each_line.each_with_index do |line, index|
if line =~ /^.*:.*:\d+:/
ref, filename, startline = line.split(':')
startline = startline.to_i - index
extname = Regexp.escape(File.extname(filename))
basename = filename.sub(/#{extname}$/, '')
break
end
end
data = ""
result.each_line do |line|
data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
end
OpenStruct.new(
filename: filename,
basename: basename,
ref: ref,
startline: startline,
data: data
)
end
private private
# Autocomplete results for various settings pages # Autocomplete results for various settings pages
......
module SnippetsHelper module SnippetsHelper
def reliable_snippet_path(snippet) def reliable_snippet_path(snippet, opts = nil)
if snippet.project_id? if snippet.project_id?
namespace_project_snippet_path(snippet.project.namespace, namespace_project_snippet_path(snippet.project.namespace,
snippet.project, snippet) snippet.project, snippet, opts)
else else
snippet_path(snippet) snippet_path(snippet, opts)
end end
end end
......
...@@ -22,6 +22,18 @@ class Blob < SimpleDelegator ...@@ -22,6 +22,18 @@ class Blob < SimpleDelegator
new(blob) new(blob)
end end
# Returns the data of the blob.
#
# If the blob is a text based blob the content is converted to UTF-8 and any
# invalid byte sequences are replaced.
def data
if binary?
super
else
@data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
end
end
def no_highlighting? def no_highlighting?
size && size > 1.megabyte size && size > 1.megabyte
end end
......
module Ci module Ci
class Build < CommitStatus class Build < CommitStatus
include TokenAuthenticatable
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :erased_by, class_name: 'User' belongs_to :erased_by, class_name: 'User'
...@@ -23,7 +25,10 @@ module Ci ...@@ -23,7 +25,10 @@ module Ci
acts_as_taggable acts_as_taggable
add_authentication_token_field :token
before_save :update_artifacts_size, if: :artifacts_file_changed? before_save :update_artifacts_size, if: :artifacts_file_changed?
before_save :ensure_token
before_destroy { project } before_destroy { project }
after_create :execute_hooks after_create :execute_hooks
...@@ -38,6 +43,7 @@ module Ci ...@@ -38,6 +43,7 @@ module Ci
new_build.status = 'pending' new_build.status = 'pending'
new_build.runner_id = nil new_build.runner_id = nil
new_build.trigger_request_id = nil new_build.trigger_request_id = nil
new_build.token = nil
new_build.save new_build.save
end end
...@@ -79,11 +85,14 @@ module Ci ...@@ -79,11 +85,14 @@ module Ci
after_transition any => [:success] do |build| after_transition any => [:success] do |build|
if build.environment.present? if build.environment.present?
service = CreateDeploymentService.new(build.project, build.user, service = CreateDeploymentService.new(
build.project, build.user,
environment: build.environment, environment: build.environment,
sha: build.sha, sha: build.sha,
ref: build.ref, ref: build.ref,
tag: build.tag) tag: build.tag,
options: build.options[:environment],
variables: build.variables)
service.execute(build) service.execute(build)
end end
end end
...@@ -173,7 +182,7 @@ module Ci ...@@ -173,7 +182,7 @@ module Ci
end end
def repo_url def repo_url
auth = "gitlab-ci-token:#{token}@" auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix| project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
prefix + auth prefix + auth
end end
...@@ -235,12 +244,7 @@ module Ci ...@@ -235,12 +244,7 @@ module Ci
end end
def trace def trace
trace = raw_trace hide_secrets(raw_trace)
if project && trace.present? && project.runners_token.present?
trace.gsub(project.runners_token, 'xxxxxx')
else
trace
end
end end
def trace_length def trace_length
...@@ -253,6 +257,7 @@ module Ci ...@@ -253,6 +257,7 @@ module Ci
def trace=(trace) def trace=(trace)
recreate_trace_dir recreate_trace_dir
trace = hide_secrets(trace)
File.write(path_to_trace, trace) File.write(path_to_trace, trace)
end end
...@@ -266,6 +271,8 @@ module Ci ...@@ -266,6 +271,8 @@ module Ci
def append_trace(trace_part, offset) def append_trace(trace_part, offset)
recreate_trace_dir recreate_trace_dir
trace_part = hide_secrets(trace_part)
File.truncate(path_to_trace, offset) if File.exist?(path_to_trace) File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
File.open(path_to_trace, 'ab') do |f| File.open(path_to_trace, 'ab') do |f|
f.write(trace_part) f.write(trace_part)
...@@ -341,12 +348,8 @@ module Ci ...@@ -341,12 +348,8 @@ module Ci
) )
end end
def token
project.runners_token
end
def valid_token?(token) def valid_token?(token)
project.valid_runners_token?(token) self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end end
def has_tags? def has_tags?
...@@ -488,5 +491,11 @@ module Ci ...@@ -488,5 +491,11 @@ module Ci
pipeline.config_processor.build_attributes(name) pipeline.config_processor.build_attributes(name)
end end
def hide_secrets(trace)
trace = Ci::MaskSecret.mask(trace, project.runners_token) if project
trace = Ci::MaskSecret.mask(trace, token)
trace
end
end end
end end
...@@ -2,6 +2,7 @@ module Ci ...@@ -2,6 +2,7 @@ module Ci
class Pipeline < ActiveRecord::Base class Pipeline < ActiveRecord::Base
extend Ci::Model extend Ci::Model
include HasStatus include HasStatus
include Importable
self.table_name = 'ci_commits' self.table_name = 'ci_commits'
...@@ -12,12 +13,12 @@ module Ci ...@@ -12,12 +13,12 @@ module Ci
has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
validates_presence_of :sha validates_presence_of :sha, unless: :importing?
validates_presence_of :ref validates_presence_of :ref, unless: :importing?
validates_presence_of :status validates_presence_of :status, unless: :importing?
validate :valid_commit_sha validate :valid_commit_sha, unless: :importing?
after_save :keep_around_commits after_save :keep_around_commits, unless: :importing?
delegate :stages, to: :statuses delegate :stages, to: :statuses
......
...@@ -11,6 +11,8 @@ module Ci ...@@ -11,6 +11,8 @@ module Ci
format: { with: /\A[a-zA-Z0-9_]+\z/, format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." } message: "can contain only letters, digits and '_'." }
scope :order_key_asc, -> { reorder(key: :asc) }
attr_encrypted :value, attr_encrypted :value,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true, insecure_mode: true,
......
...@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base ...@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now commit_status.update_attributes finished_at: Time.now
end end
# We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
around_transition any => [:success, :failed, :canceled] do |commit_status, block|
block.call
commit_status.pipeline.try(:process!)
end
after_transition do |commit_status, transition| after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback? commit_status.pipeline.try(:build_updated) unless transition.loopback?
end end
after_transition any => [:success, :failed, :canceled] do |commit_status|
commit_status.pipeline.try(:process!)
true
end
after_transition [:created, :pending, :running] => :success do |commit_status| after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end end
...@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base
pipeline.before_sha || Gitlab::Git::BLANK_SHA pipeline.before_sha || Gitlab::Git::BLANK_SHA
end end
def group_name
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end
def self.stages def self.stages
# We group by stage name, but order stages by theirs' index # We group by stage name, but order stages by theirs' index
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
...@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base
allow_failure? && (failed? || canceled?) allow_failure? && (failed? || canceled?)
end end
def playable?
false
end
def duration def duration
calculate_duration calculate_duration
end end
......
...@@ -8,8 +8,9 @@ module HasStatus ...@@ -8,8 +8,9 @@ module HasStatus
class_methods do class_methods do
def status_sql def status_sql
scope = all.relevant scope = all
builds = scope.select('count(*)').to_sql builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql
ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
ignored ||= '0' ignored ||= '0'
...@@ -19,12 +20,12 @@ module HasStatus ...@@ -19,12 +20,12 @@ module HasStatus
skipped = scope.skipped.select('count(*)').to_sql skipped = scope.skipped.select('count(*)').to_sql
deduce_status = "(CASE deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{created}) THEN 'created'
WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{running})+(#{pending})>0 THEN 'running' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
ELSE 'failed' ELSE 'failed'
END)" END)"
......
...@@ -4,6 +4,7 @@ class Environment < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class Environment < ActiveRecord::Base
has_many :deployments has_many :deployments
before_validation :nullify_external_url before_validation :nullify_external_url
before_save :set_environment_type
validates :name, validates :name,
presence: true, presence: true,
...@@ -26,6 +27,17 @@ class Environment < ActiveRecord::Base ...@@ -26,6 +27,17 @@ class Environment < ActiveRecord::Base
self.external_url = nil if self.external_url.blank? self.external_url = nil if self.external_url.blank?
end end
def set_environment_type
names = name.split('/')
self.environment_type =
if names.many?
names.first
else
nil
end
end
def includes_commit?(commit) def includes_commit?(commit)
return false unless last_deployment return false unless last_deployment
......
...@@ -13,6 +13,8 @@ class Event < ActiveRecord::Base ...@@ -13,6 +13,8 @@ class Event < ActiveRecord::Base
LEFT = 9 # User left project LEFT = 9 # User left project
DESTROYED = 10 DESTROYED = 10
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
delegate :name, :email, to: :author, prefix: true, allow_nil: true delegate :name, :email, to: :author, prefix: true, allow_nil: true
delegate :title, to: :issue, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true
delegate :title, to: :merge_request, prefix: true, allow_nil: true delegate :title, to: :merge_request, prefix: true, allow_nil: true
...@@ -324,8 +326,17 @@ class Event < ActiveRecord::Base ...@@ -324,8 +326,17 @@ class Event < ActiveRecord::Base
end end
def reset_project_activity def reset_project_activity
if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain return unless project
project.update_column(:last_activity_at, self.created_at)
end # Don't even bother obtaining a lock if the last update happened less than
# 60 minutes ago.
return if project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
return unless Gitlab::ExclusiveLease.
new("project:update_last_activity_at:#{project.id}",
timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
try_obtain
project.update_column(:last_activity_at, created_at)
end end
end end
...@@ -95,6 +95,13 @@ class Group < Namespace ...@@ -95,6 +95,13 @@ class Group < Namespace
end end
end end
def lfs_enabled?
return false unless Gitlab.config.lfs.enabled
return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
self[:lfs_enabled]
end
def add_users(user_ids, access_level, current_user: nil, expires_at: nil) def add_users(user_ids, access_level, current_user: nil, expires_at: nil)
user_ids.each do |user_id| user_ids.each do |user_id|
Member.add_user( Member.add_user(
......
...@@ -28,17 +28,34 @@ class Member < ActiveRecord::Base ...@@ -28,17 +28,34 @@ class Member < ActiveRecord::Base
allow_nil: true allow_nil: true
} }
# This scope encapsulates (most of) the conditions a row in the member table
# must satisfy if it is a valid permission. Of particular note:
#
# * Access requests must be excluded
# * Blocked users must be excluded
# * Invitations take effect immediately
# * expires_at is not implemented. A background worker purges expired rows
scope :active, -> do
is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil))
user_is_active = User.arel_table[:state].eq(:active)
includes(:user).references(:users)
.where(is_external_invite.or(user_is_active))
.where(requested_at: nil)
end
scope :invite, -> { where.not(invite_token: nil) } scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) } scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) } scope :request, -> { where.not(requested_at: nil) }
scope :has_access, -> { where('access_level > 0') }
scope :has_access, -> { active.where('access_level > 0') }
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) } scope :guests, -> { active.where(access_level: GUEST) }
scope :developers, -> { where(access_level: DEVELOPER) } scope :reporters, -> { active.where(access_level: REPORTER) }
scope :masters, -> { where(access_level: MASTER) } scope :developers, -> { active.where(access_level: DEVELOPER) }
scope :owners, -> { where(access_level: OWNER) } scope :masters, -> { active.where(access_level: MASTER) }
scope :owners_and_masters, -> { where(access_level: [OWNER, MASTER]) } scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) }
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
......
...@@ -652,7 +652,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -652,7 +652,7 @@ class MergeRequest < ActiveRecord::Base
end end
def environments def environments
return unless diff_head_commit return [] unless diff_head_commit
target_project.environments.select do |environment| target_project.environments.select do |environment|
environment.includes_commit?(diff_head_commit) environment.includes_commit?(diff_head_commit)
......
...@@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base ...@@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base
projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
end end
def lfs_enabled?
# User namespace will always default to the global setting
Gitlab.config.lfs.enabled
end
private private
def repository_storage_paths def repository_storage_paths
......
...@@ -393,10 +393,9 @@ class Project < ActiveRecord::Base ...@@ -393,10 +393,9 @@ class Project < ActiveRecord::Base
end end
def lfs_enabled? def lfs_enabled?
return false unless Gitlab.config.lfs.enabled return namespace.lfs_enabled? if self[:lfs_enabled].nil?
return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
self[:lfs_enabled] self[:lfs_enabled] && Gitlab.config.lfs.enabled
end end
def repository_storage_path def repository_storage_path
...@@ -1138,12 +1137,6 @@ class Project < ActiveRecord::Base ...@@ -1138,12 +1137,6 @@ class Project < ActiveRecord::Base
self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end end
# TODO (ayufan): For now we use runners_token (backward compatibility)
# In 8.4 every build will have its own individual token valid for time of build
def valid_build_token?(token)
self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end
def build_coverage_enabled? def build_coverage_enabled?
build_coverage_regex.present? build_coverage_regex.present?
end end
......
...@@ -990,37 +990,6 @@ class Repository ...@@ -990,37 +990,6 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end end
def parse_search_result(result)
ref = nil
filename = nil
basename = nil
startline = 0
result.each_line.each_with_index do |line, index|
if line =~ /^.*:.*:\d+:/
ref, filename, startline = line.split(':')
startline = startline.to_i - index
extname = Regexp.escape(File.extname(filename))
basename = filename.sub(/#{extname}$/, '')
break
end
end
data = ""
result.each_line do |line|
data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
end
OpenStruct.new(
filename: filename,
basename: basename,
ref: ref,
startline: startline,
data: data
)
end
def fetch_ref(source_path, source_ref, target_ref) def fetch_ref(source_path, source_ref, target_ref)
args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo) Gitlab::Popen.popen(args, path_to_repo)
......
...@@ -64,6 +64,12 @@ class ProjectPolicy < BasePolicy ...@@ -64,6 +64,12 @@ class ProjectPolicy < BasePolicy
can! :read_deployment can! :read_deployment
end end
# Permissions given when an user is team member of a project
def team_member_reporter_access!
can! :build_download_code
can! :build_read_container_image
end
def developer_access! def developer_access!
can! :admin_merge_request can! :admin_merge_request
can! :update_merge_request can! :update_merge_request
...@@ -109,6 +115,8 @@ class ProjectPolicy < BasePolicy ...@@ -109,6 +115,8 @@ class ProjectPolicy < BasePolicy
can! :read_commit_status can! :read_commit_status
can! :read_pipeline can! :read_pipeline
can! :read_container_image can! :read_container_image
can! :build_download_code
can! :build_read_container_image
end end
def owner_access! def owner_access!
...@@ -132,6 +140,7 @@ class ProjectPolicy < BasePolicy ...@@ -132,6 +140,7 @@ class ProjectPolicy < BasePolicy
guest_access! if access >= Gitlab::Access::GUEST guest_access! if access >= Gitlab::Access::GUEST
reporter_access! if access >= Gitlab::Access::REPORTER reporter_access! if access >= Gitlab::Access::REPORTER
team_member_reporter_access! if access >= Gitlab::Access::REPORTER
developer_access! if access >= Gitlab::Access::DEVELOPER developer_access! if access >= Gitlab::Access::DEVELOPER
master_access! if access >= Gitlab::Access::MASTER master_access! if access >= Gitlab::Access::MASTER
end end
......
...@@ -4,7 +4,9 @@ module Auth ...@@ -4,7 +4,9 @@ module Auth
AUDIENCE = 'container_registry' AUDIENCE = 'container_registry'
def execute def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities || []
return error('not found', 404) unless registry.enabled return error('not found', 404) unless registry.enabled
unless current_user || project unless current_user || project
...@@ -74,9 +76,9 @@ module Auth ...@@ -74,9 +76,9 @@ module Auth
case requested_action case requested_action
when 'pull' when 'pull'
requested_project == project || can?(current_user, :read_container_image, requested_project) requested_project.public? || build_can_pull?(requested_project) || user_can_pull?(requested_project)
when 'push' when 'push'
requested_project == project || can?(current_user, :create_container_image, requested_project) build_can_push?(requested_project) || user_can_push?(requested_project)
else else
false false
end end
...@@ -85,5 +87,29 @@ module Auth ...@@ -85,5 +87,29 @@ module Auth
def registry def registry
Gitlab.config.registry Gitlab.config.registry
end end
def build_can_pull?(requested_project)
# Build can:
# 1. pull from its own project (for ex. a build)
# 2. read images from dependent projects if creator of build is a team member
@authentication_abilities.include?(:build_read_container_image) &&
(requested_project == project || can?(current_user, :build_read_container_image, requested_project))
end
def user_can_pull?(requested_project)
@authentication_abilities.include?(:read_container_image) &&
can?(current_user, :read_container_image, requested_project)
end
def build_can_push?(requested_project)
# Build can push only to the project from which it originates
@authentication_abilities.include?(:build_create_container_image) &&
requested_project == project
end
def user_can_push?(requested_project)
@authentication_abilities.include?(:create_container_image) &&
can?(current_user, :create_container_image, requested_project)
end
end end
end end
...@@ -16,11 +16,29 @@ module Commits ...@@ -16,11 +16,29 @@ module Commits
error(ex.message) error(ex.message)
end end
private
def commit def commit
raise NotImplementedError raise NotImplementedError
end end
private def commit_change(action)
raise NotImplementedError unless repository.respond_to?(action)
into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch
tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch)
if tree_id
create_target_branch(into) if @create_merge_request
repository.public_send(action, current_user, @commit, into, tree_id)
success
else
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically.
It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
end
def check_push_permissions def check_push_permissions
allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch) allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
......
module Commits module Commits
class CherryPickService < ChangeService class CherryPickService < ChangeService
def commit def commit
cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch commit_change(:cherry_pick)
cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch)
if cherry_pick_tree_id
create_target_branch(cherry_pick_into) if @create_merge_request
repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id)
success
else
error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically.
It may have already been cherry-picked, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
end end
end end
end end
module Commits module Commits
class RevertService < ChangeService class RevertService < ChangeService
def commit def commit
revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch commit_change(:revert)
revert_tree_id = repository.check_revert_content(@commit, @target_branch)
if revert_tree_id
create_target_branch(revert_into) if @create_merge_request
repository.revert(current_user, @commit, revert_into, revert_tree_id)
success
else
error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically.
It may have already been reverted, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
end end
end end
end end
...@@ -2,9 +2,7 @@ require_relative 'base_service' ...@@ -2,9 +2,7 @@ require_relative 'base_service'
class CreateDeploymentService < BaseService class CreateDeploymentService < BaseService
def execute(deployable = nil) def execute(deployable = nil)
environment = project.environments.find_or_create_by( environment = find_or_create_environment
name: params[:environment]
)
project.deployments.create( project.deployments.create(
environment: environment, environment: environment,
...@@ -15,4 +13,38 @@ class CreateDeploymentService < BaseService ...@@ -15,4 +13,38 @@ class CreateDeploymentService < BaseService
deployable: deployable deployable: deployable
) )
end end
private
def find_or_create_environment
project.environments.find_or_create_by(name: expanded_name) do |environment|
environment.external_url = expanded_url
end
end
def expanded_name
ExpandVariables.expand(name, variables)
end
def expanded_url
return unless url
@expanded_url ||= ExpandVariables.expand(url, variables)
end
def name
params[:environment]
end
def url
options[:url]
end
def options
params[:options] || {}
end
def variables
params[:variables] || []
end
end end
...@@ -87,7 +87,7 @@ class GitPushService < BaseService ...@@ -87,7 +87,7 @@ class GitPushService < BaseService
project.change_head(branch_name) project.change_head(branch_name)
# Set protection on the default branch if configured # Set protection on the default branch if configured
if current_application_settings.default_branch_protection != PROTECTION_NONE if current_application_settings.default_branch_protection != PROTECTION_NONE && !@project.protected_branch?(@project.default_branch)
params = { params = {
name: @project.default_branch, name: @project.default_branch,
......
...@@ -3,7 +3,7 @@ module Milestones ...@@ -3,7 +3,7 @@ module Milestones
def execute def execute
milestone = project.milestones.new(params) milestone = project.milestones.new(params)
if milestone.save! if milestone.save
event_service.open_milestone(milestone, current_user) event_service.open_milestone(milestone, current_user)
end end
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= render 'shared/allow_request_access', form: f = render 'shared/allow_request_access', form: f
= render 'groups/group_lfs_settings', f: f
- if @group.new_record? - if @group.new_record?
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
...@@ -37,6 +37,12 @@ ...@@ -37,6 +37,12 @@
%strong %strong
= @group.created_at.to_s(:medium) = @group.created_at.to_s(:medium)
%li
%span.light Group Git LFS status:
%strong
= group_lfs_status(@group)
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
- if current_user.admin?
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :lfs_enabled do
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?
%strong
Allow projects within this group to use Git LFS
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/
%span.descr This setting can be overridden in each project.
\ No newline at end of file
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= render 'shared/allow_request_access', form: f = render 'shared/allow_request_access', form: f
= render 'group_lfs_settings', f: f
.form-group .form-group
%hr %hr
= f.label :share_with_group_lock, class: 'control-label' do = f.label :share_with_group_lock, class: 'control-label' do
......
...@@ -162,6 +162,12 @@ ...@@ -162,6 +162,12 @@
.key i .key i
%td %td
Go to issues Go to issues
%tr
%td.shortcut
.key g
.key l
%td
Go to issue boards
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
......
...@@ -113,3 +113,7 @@ ...@@ -113,3 +113,7 @@
%li.hidden %li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits Commits
-# Shortcut to issue boards
%li.hidden
= link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
- if @user.valid? - if @user.valid?
:plain :plain
new Flash("Username sucessfully changed", "notice") new Flash("Username successfully changed", "notice")
- else - else
:plain :plain
new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert")
- is_playable = subject.playable? && can?(current_user, :update_build, @project) - is_playable = subject.playable? && can?(current_user, :update_build, @project)
%li.build{class: ("playable" if is_playable)} - if is_playable
.curve
.build-content
- if is_playable
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
= render_status_with_link('build', 'play') = render_status_with_link('build', 'play')
%span.ci-status-text= subject.name .ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project) - elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
%span.ci-status-text= subject.name .ci-status-text= subject.name
- else - else
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status) = ci_icon_for_status(subject.status)
...@@ -36,16 +36,14 @@ ...@@ -36,16 +36,14 @@
- stages_status = pipeline.statuses.relevant.latest.stages_status - stages_status = pipeline.statuses.relevant.latest.stages_status
- stages.each do |stage|
%td.stage-cell %td.stage-cell
- stages.each do |stage|
- status = stages_status[stage] - status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}" - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status - if status
.stage-container
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
= ci_icon_for_status(status) = ci_icon_for_status(status)
- else
.light.has-tooltip{ title: tooltip }
\-
%td %td
- if pipeline.duration - if pipeline.duration
......
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
= stage.titleize = stage.titleize
.builds-container .builds-container
%ul %ul
- statuses.each do |status| = render "projects/commit/pipeline_stage", statuses: statuses
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- if pipeline.yaml_errors.present? - if pipeline.yaml_errors.present?
......
- status_groups = statuses.group_by(&:group_name)
- status_groups.each do |group_name, grouped_statuses|
- if grouped_statuses.one?
- status = grouped_statuses.first
- is_playable = status.playable? && can?(current_user, :update_build, @project)
%li.build{ class: ("playable" if is_playable) }
.curve
.build-content
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- else
%li.build
.curve
.build-content
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
- group_status = CommitStatus.where(id: subject).status
= render_status_with_link('build', group_status)
.dropdown.inline
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
%span.ci-status-text
= name
%span.badge= subject.size
%ul.dropdown-menu.grouped-pipeline-dropdown
.arrow
- subject.each do |status|
= render "projects/#{status.to_partial_path}_pipeline", subject: status
...@@ -84,15 +84,14 @@ ...@@ -84,15 +84,14 @@
= project_feature_access_select(:snippets_access_level) = project_feature_access_select(:snippets_access_level)
- if Gitlab.config.lfs.enabled && current_user.admin? - if Gitlab.config.lfs.enabled && current_user.admin?
.form-group .row
.checkbox .col-md-9
= f.label :lfs_enabled do = f.label :lfs_enabled, 'LFS', class: 'label-light'
= f.check_box :lfs_enabled, checked: @project.lfs_enabled? %span.help-block
%strong LFS
%br
%span.descr
Git Large File Storage Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control'
- if Gitlab.config.registry.enabled - if Gitlab.config.registry.enabled
.form-group .form-group
......
%li.build - if subject.target_url
.curve = link_to subject.target_url do
.build-content
- if subject.target_url
- link_to subject.target_url do
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name %span.ci-status-text= subject.name
- else - else
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name %span.ci-status-text= subject.name
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
- note_count = issue.notes.user.count - note_count = issue.notes.user.count
%li %li
= link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
= icon('comments') = icon('comments')
= note_count = note_count
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
- note_count = merge_request.mr_and_commit_notes.user.count - note_count = merge_request.mr_and_commit_notes.user.count
%li %li
= link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
= icon('comments') = icon('comments')
= note_count = note_count
......
...@@ -47,13 +47,7 @@ ...@@ -47,13 +47,7 @@
%tbody %tbody
%th Status %th Status
%th Commit %th Commit
- stages.each do |stage| %th Stages
%th.stage
- if stage.titleize.length > 12
%span.has-tooltip{ title: "#{stage.titleize}" }
= stage.titleize
- else
= stage.titleize
%th %th
%th %th
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown .visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
...@@ -19,11 +19,11 @@ ...@@ -19,11 +19,11 @@
%li %li
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
%li %li
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
- page_title @snippet.title, "Snippets" - page_title @snippet.title, "Snippets"
.snippet-holder = render 'shared/snippets/header'
= render 'shared/snippets/header'
%article.file-holder.file-holder-no-border.snippet-file-content %article.file-holder.snippet-file-content
.file-title.file-title-clear .file-title
= blob_icon 0, @snippet.file_name = blob_icon 0, @snippet.file_name
= @snippet.file_name = @snippet.file_name
.file-actions.hidden-xs .file-actions
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob' = render 'shared/snippets/blob'
%div#notes= render "projects/notes/notes_with_form" %div#notes= render "projects/notes/notes_with_form"
%article.file-holder.readme-holder %article.file-holder.readme-holder
.file-title .file-title
= blob_icon readme.mode, readme.name = blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, @path, readme.name)) do = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do
%strong %strong
= readme.name = readme.name
.file-content.wiki .file-content.wiki
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%th Value %th Value
%th %th
%tbody %tbody
- @project.variables.each do |variable| - @project.variables.order_key_asc.each do |variable|
- if variable.id? - if variable.id?
%tr %tr
%td= variable.key %td= variable.key
......
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
in group #{link_to @group.name, @group} in group #{link_to @group.name, @group}
.results.prepend-top-10 .results.prepend-top-10
- if @scope == 'commits'
%ul.list-unstyled
= render partial: "search/results/commit", collection: @search_objects
- else
.search-results .search-results
- if @scope == 'projects' - if @scope == 'projects'
.term .term
......
- blob = @project.repository.parse_search_result(blob) - blob = parse_search_result(blob)
.blob-result .blob-result
.file-holder .file-holder
.file-title .file-title
......
.search-result-row = render 'projects/commits/commit', project: @project, commit: commit
= render 'projects/commits/commit', project: @project, commit: commit
- wiki_blob = @project.repository.parse_search_result(wiki_blob) - wiki_blob = parse_search_result(wiki_blob)
.blob-result .blob-result
.file-holder .file-holder
.file-title .file-title
......
.form-group.project-visibility-level-holder .form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'control-label' do = f.label :visibility_level, class: 'control-label' do
Visibility Level Visibility Level
= link_to "(?)", help_page_path("public_access/public_access") = link_to icon('question-circle'), help_page_path("public_access/public_access")
.col-sm-10 .col-sm-10
- if can_change_visibility_level - if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= dropdown_tag(title, options: { toggle_class: 'js-issuable-selector', = dropdown_tag(title, options: { toggle_class: 'js-issuable-selector',
title: title, filter: true, placeholder: 'Filter', footer_content: true, title: title, filter: true, placeholder: 'Filter', footer_content: true,
data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: @project.path, namespace_path: @project.namespace.path } } ) do data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do
%ul.dropdown-footer-list %ul.dropdown-footer-list
%li %li
%a.reset-template %a.reset-template
......
...@@ -6,12 +6,13 @@ ...@@ -6,12 +6,13 @@
%strong.item-title %strong.item-title
Snippet #{@snippet.to_reference} Snippet #{@snippet.to_reference}
%span.creator %span.creator
created by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title")} authored
= time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
- if @snippet.updated_at != @snippet.created_at - if @snippet.updated_at != @snippet.created_at
%span %span
= icon('edit', title: 'edited') = icon('edit', title: 'edited')
= time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago') = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")}
.snippet-actions .snippet-actions
- if @snippet.project_id? - if @snippet.project_id?
...@@ -19,6 +20,5 @@ ...@@ -19,6 +20,5 @@
- else - else
= render "snippets/actions" = render "snippets/actions"
.content-block.second-block %h2.snippet-title.prepend-top-0.append-bottom-0
%h2.snippet-title.prepend-top-0.append-bottom-0
= markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
...@@ -3,19 +3,30 @@ ...@@ -3,19 +3,30 @@
.title .title
= link_to reliable_snippet_path(snippet) do = link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60) = snippet.title
- if snippet.private? - if snippet.private?
%span.label.label-gray %span.label.label-gray.hidden-xs
= icon('lock') = icon('lock')
private private
%span.monospace.pull-right %span.monospace.pull-right.hidden-xs
= snippet.file_name = snippet.file_name
%small.pull-right.cgray %ul.controls.visible-xs
%li
- note_count = snippet.notes.user.count
= link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
= icon('comments')
= note_count
%li
%span.sr-only
= visibility_level_label(snippet.visibility_level)
= visibility_level_icon(snippet.visibility_level, fw: false)
%small.pull-right.cgray.hidden-xs
- if snippet.project_id? - if snippet.project_id?
= link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
.snippet-info .snippet-info.hidden-xs
= link_to user_snippets_path(snippet.author) do = link_to user_snippets_path(snippet.author) do
= snippet.author_name = snippet.author_name
authored #{time_ago_with_tooltip(snippet.created_at)} authored #{time_ago_with_tooltip(snippet.created_at)}
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
- if current_user - if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if current_user - if current_user
.visible-xs-block.dropdown .visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
...@@ -18,11 +18,11 @@ ...@@ -18,11 +18,11 @@
%li %li
= link_to new_snippet_path, title: "New Snippet" do = link_to new_snippet_path, title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
%li %li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
- page_title @snippet.title, "Snippets" - page_title @snippet.title, "Snippets"
.snippet-holder = render 'shared/snippets/header'
= render 'shared/snippets/header'
%article.file-holder.file-holder-no-border.snippet-file-content %article.file-holder.snippet-file-content
.file-title.file-title-clear .file-title
= blob_icon 0, @snippet.file_name = blob_icon 0, @snippet.file_name
= @snippet.file_name = @snippet.file_name
.file-actions.hidden-xs .file-actions
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob' = render 'shared/snippets/blob'
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
Summary of issues, merge requests, and push events Summary of issues, merge requests, and push events
:javascript :javascript
new Calendar( new Calendar(
#{@timestamps.to_json}, #{@activity_dates.to_json},
'#{user_calendar_activities_path}' '#{user_calendar_activities_path}'
); );
\ No newline at end of file
...@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines ...@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines
BUILDS = [ BUILDS = [
{ name: 'build:linux', stage: 'build', status: :success }, { name: 'build:linux', stage: 'build', status: :success },
{ name: 'build:osx', stage: 'build', status: :success }, { name: 'build:osx', stage: 'build', status: :success },
{ name: 'rspec:linux', stage: 'test', status: :success }, { name: 'rspec:linux 0 3', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:linux 1 3', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:linux 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 0 3', stage: 'test', status: :success },
{ name: 'rspec:windows 1 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:osx', stage: 'test', status_event: :success }, { name: 'rspec:osx', stage: 'test', status_event: :success },
{ name: 'spinach:linux', stage: 'test', status: :success }, { name: 'spinach:linux', stage: 'test', status: :success },
{ name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true},
......
# rubocop:disable all # rubocop:disable all
class MigrateRepoSize < ActiveRecord::Migration class MigrateRepoSize < ActiveRecord::Migration
DOWNTIME = false
def up def up
project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id') project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
project_data.each do |project| project_data.each do |project|
id = project['id'] id = project['id']
namespace_path = project['namespace_path'] || '' namespace_path = project['namespace_path'] || ''
path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git') repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
begin begin
repo = Gitlab::Git::Repository.new(path) repo = Gitlab::Git::Repository.new(path)
......
...@@ -8,14 +8,28 @@ class MergeRequestDiffRemoveUniq < ActiveRecord::Migration ...@@ -8,14 +8,28 @@ class MergeRequestDiffRemoveUniq < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
def up def up
constraint_name = 'merge_request_diffs_merge_request_id_key'
transaction do
if index_exists?(:merge_request_diffs, :merge_request_id) if index_exists?(:merge_request_diffs, :merge_request_id)
remove_index :merge_request_diffs, :merge_request_id remove_index(:merge_request_diffs, :merge_request_id)
end
# In some bizarre cases PostgreSQL might have a separate unique constraint
# that we'll need to drop.
if constraint_exists?(constraint_name) && Gitlab::Database.postgresql?
execute("ALTER TABLE merge_request_diffs DROP CONSTRAINT IF EXISTS #{constraint_name};")
end
end end
end end
def down def down
unless index_exists?(:merge_request_diffs, :merge_request_id) unless index_exists?(:merge_request_diffs, :merge_request_id)
add_concurrent_index :merge_request_diffs, :merge_request_id, unique: true add_concurrent_index(:merge_request_diffs, :merge_request_id, unique: true)
end
end end
def constraint_exists?(name)
indexes(:merge_request_diffs).map(&:name).include?(name)
end end
end end
class AddTokenToBuild < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :ci_builds, :token, :string
end
end
class AddIndexForBuildToken < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_index :ci_builds, :token, unique: true
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddLfsEnabledToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :namespaces, :lfs_enabled, :boolean
end
end
class AddEnvironmentTypeToEnvironments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :environments, :environment_type, :string
end
end
...@@ -14,6 +14,6 @@ class RemoveProjectsPushesSinceGc < ActiveRecord::Migration ...@@ -14,6 +14,6 @@ class RemoveProjectsPushesSinceGc < ActiveRecord::Migration
end end
def down def down
add_column_with_default! :projects, :pushes_since_gc, :integer, default: 0 add_column_with_default :projects, :pushes_since_gc, :integer, default: 0
end end
end end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment