Merge branch 'master' into filter-branch-by-name
This commit is contained in:
commit
850813d298
|
@ -1,9 +1,5 @@
|
|||
image: "ruby:2.1"
|
||||
|
||||
services:
|
||||
- mysql:latest
|
||||
- redis:alpine
|
||||
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
|
@ -34,7 +30,6 @@ stages:
|
|||
- post-test
|
||||
|
||||
# Prepare and merge knapsack tests
|
||||
|
||||
.knapsack-state: &knapsack-state
|
||||
services: []
|
||||
variables:
|
||||
|
@ -68,8 +63,14 @@ update-knapsack:
|
|||
|
||||
# Execute all testing suites
|
||||
|
||||
.use-db: &use-db
|
||||
services:
|
||||
- mysql:latest
|
||||
- redis:alpine
|
||||
|
||||
.rspec-knapsack: &rspec-knapsack
|
||||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- JOB_NAME=( $CI_BUILD_NAME )
|
||||
|
@ -85,6 +86,7 @@ update-knapsack:
|
|||
|
||||
.spinach-knapsack: &spinach-knapsack
|
||||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- JOB_NAME=( $CI_BUILD_NAME )
|
||||
|
@ -133,6 +135,7 @@ spinach 9 10: *spinach-knapsack
|
|||
# Execute all testing suites against Ruby 2.3
|
||||
.ruby-23: &ruby-23
|
||||
image: "ruby:2.3"
|
||||
<<: *use-db
|
||||
only:
|
||||
- master
|
||||
cache:
|
||||
|
@ -148,7 +151,7 @@ spinach 9 10: *spinach-knapsack
|
|||
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23
|
||||
<<: *spinach-knapsack
|
||||
<<: *ruby-23
|
||||
|
||||
|
||||
rspec 0 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 1 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 2 20 ruby23: *rspec-knapsack-ruby23
|
||||
|
@ -183,22 +186,41 @@ spinach 9 10 ruby23: *spinach-knapsack-ruby23
|
|||
|
||||
# Other generic tests
|
||||
|
||||
.static-analyses-variables: &static-analyses-variables
|
||||
variables:
|
||||
SIMPLECOV: "false"
|
||||
USE_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
|
||||
.exec: &exec
|
||||
<<: *static-analyses-variables
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec $CI_BUILD_NAME
|
||||
|
||||
teaspoon: *exec
|
||||
rubocop: *exec
|
||||
rake scss_lint: *exec
|
||||
rake brakeman: *exec
|
||||
rake flog: *exec
|
||||
rake flay: *exec
|
||||
rake db:migrate:reset: *exec
|
||||
license_finder: *exec
|
||||
rake downtime_check: *exec
|
||||
|
||||
rake db:migrate:reset:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- rake db:migrate:reset
|
||||
|
||||
teaspoon:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- teaspoon
|
||||
|
||||
bundler:audit:
|
||||
stage: test
|
||||
<<: *static-analyses-variables
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
|
|
|
@ -291,6 +291,10 @@ Style/MultilineMethodDefinitionBraceLayout:
|
|||
Style/MultilineOperationIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
|
||||
Style/MultilineTernaryOperator:
|
||||
Enabled: true
|
||||
|
||||
# Favor unless over if for negative conditions (or control flow or).
|
||||
Style/NegatedIf:
|
||||
Enabled: true
|
||||
|
|
|
@ -226,10 +226,6 @@ Style/LineEndConcatenation:
|
|||
Style/MethodCallParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3
|
||||
Style/MultilineTernaryOperator:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 62
|
||||
# Cop supports --auto-correct.
|
||||
Style/MutableConstant:
|
||||
|
|
91
CHANGELOG
91
CHANGELOG
|
@ -1,59 +1,80 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.11.0 (unreleased)
|
||||
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
|
||||
- Limit git rev-list output count to one in forced push check
|
||||
- Retrieve rendered HTML from cache in one request
|
||||
- Load project invited groups and members eagerly in ProjectTeam#fetch_members
|
||||
|
||||
v 8.10.0 (unreleased)
|
||||
- User can now search branches by name. !5144 (tiagonbotelho)
|
||||
v 8.10.0
|
||||
- Fix profile activity heatmap to show correct day name (eanplatter)
|
||||
- Speed up ExternalWikiHelper#get_project_wiki_path
|
||||
- Expose {should,force}_remove_source_branch (Ben Boeckel)
|
||||
- Add the functionality to be able to rename a file. !5049 (tiagonbotelho)
|
||||
- Add the functionality to be able to rename a file. !5049
|
||||
- Disable PostgreSQL statement timeout during migrations
|
||||
- Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho)
|
||||
- Fix projects dropdown loading performance with a simplified api cal. !5113
|
||||
- Fix commit builds API, return all builds for all pipelines for given commit. !4849
|
||||
- Replace Haml with Hamlit to make view rendering faster. !3666
|
||||
- Refresh the branch cache after `git gc` runs
|
||||
- Allow to disable request access button on projects/groups
|
||||
- Refactor repository paths handling to allow multiple git mount points
|
||||
- Optimize system note visibility checking by memoizing the visible reference count !5070
|
||||
- Optimize system note visibility checking by memoizing the visible reference count. !5070
|
||||
- Add Application Setting to configure default Repository Path for new projects
|
||||
- Delete award emoji when deleting a user
|
||||
- Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell)
|
||||
- Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell)
|
||||
- Add an API for downloading latest successful build from a particular branch or tag. !5347
|
||||
- Avoid data-integrity issue when cleaning up repository archive cache.
|
||||
- Add link to profile to commit avatar. !5163 (winniehell)
|
||||
- Wrap code blocks on Activies and Todos page. !4783 (winniehell)
|
||||
- Align flash messages with left side of page content !4959 (winniehell)
|
||||
- Display tooltip for "Copy to Clipboard" button !5164 (winniehell)
|
||||
- Use default cursor for table header of project files !5165 (winniehell)
|
||||
- Align flash messages with left side of page content. !4959 (winniehell)
|
||||
- Display tooltip for "Copy to Clipboard" button. !5164 (winniehell)
|
||||
- Use default cursor for table header of project files. !5165 (winniehell)
|
||||
- Store when and yaml variables in builds table
|
||||
- Display last commit of deleted branch in push events !4699 (winniehell)
|
||||
- Escape file extension when parsing search results !5141 (winniehell)
|
||||
- Display last commit of deleted branch in push events. !4699 (winniehell)
|
||||
- Escape file extension when parsing search results. !5141 (winniehell)
|
||||
- Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004
|
||||
- Add image border in Markdown preview. !5162 (winniehell)
|
||||
- Apply the trusted_proxies config to the rack request object for use with rack_attack
|
||||
- Added the ability to block sign ups using a domain blacklist. !5259
|
||||
- Upgrade to Rails 4.2.7. !5236
|
||||
- Extend exposed environment variables for CI builds
|
||||
- Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead
|
||||
- Add API "deploy_keys" for admins to get all deploy keys
|
||||
- Allow to pull code with deploy key from public projects
|
||||
- Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts)
|
||||
- Add Sidekiq queue duration to transaction metrics.
|
||||
- Add a new column `artifacts_size` to table `ci_builds` !4964
|
||||
- Add a new column `artifacts_size` to table `ci_builds`. !4964
|
||||
- Let Workhorse serve format-patch diffs
|
||||
- Display tooltip for mentioned users and groups !5261 (winniehell)
|
||||
- Display tooltip for mentioned users and groups. !5261 (winniehell)
|
||||
- Allow build email service to be tested
|
||||
- Added day name to contribution calendar tooltips
|
||||
- Make images fit to the size of the viewport !4810
|
||||
- Fix check for New Branch button on Issue page !4630 (winniehell)
|
||||
- Refactor user authorization check for a single project to avoid querying all user projects
|
||||
- Make images fit to the size of the viewport. !4810
|
||||
- Fix check for New Branch button on Issue page. !4630 (winniehell)
|
||||
- Fix GFM autocomplete not working on wiki pages
|
||||
- Fixed enter key not triggering click on first row when searching in a dropdown
|
||||
- Fix MR-auto-close text added to description. !4836
|
||||
- Support U2F devices in Firefox. !5177
|
||||
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
|
||||
- Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
|
||||
- Add Spring EmojiOne updates.
|
||||
- Added Rake task for tracking deployments !5320
|
||||
- Added Rake task for tracking deployments. !5320
|
||||
- Fix fetching LFS objects for private CI projects
|
||||
- Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237
|
||||
- Add syntax for multiline blockquote using `>>>` fence !3954
|
||||
- Add syntax for multiline blockquote using `>>>` fence. !3954
|
||||
- Fix viewing notification settings when a project is pending deletion
|
||||
- Updated compare dropdown menus to use GL dropdown
|
||||
- Redirects back to issue after clicking login link
|
||||
- Eager load award emoji on notes
|
||||
- Allow to define manual actions/builds on Pipelines and Environments
|
||||
- Fix pagination when sorting by columns with lots of ties (like priority)
|
||||
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
|
||||
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020
|
||||
- Updated project header design
|
||||
- Issuable collapsed assignee tooltip is now the users name
|
||||
- Fix compare view not changing code view rendering style
|
||||
- Exclude email check from the standard health check
|
||||
- Updated layout for Projects, Groups, Users on Admin area !4424
|
||||
- Updated layout for Projects, Groups, Users on Admin area. !4424
|
||||
- Fix changing issue state columns in milestone view
|
||||
- Update health_check gem to version 2.1.0
|
||||
- Add notification settings dropdown for groups
|
||||
|
@ -61,22 +82,24 @@ v 8.10.0 (unreleased)
|
|||
- Wildcards for protected branches. !4665
|
||||
- Allow importing from Github using Personal Access Tokens. (Eric K Idema)
|
||||
- API: Expose `due_date` for issues (Robert Schilling)
|
||||
- API: Todos !3188 (Robert Schilling)
|
||||
- API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
|
||||
- API: Expose `developers_can_push` and `developers_can_merge` for branches !5208 (Robert Schilling)
|
||||
- API: Todos. !3188 (Robert Schilling)
|
||||
- API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling)
|
||||
- API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling)
|
||||
- Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects
|
||||
- Add "Enabled Git access protocols" to Application Settings
|
||||
- Diffs will create button/diff form on demand no on server side
|
||||
- Reduce size of HTML used by diff comment forms
|
||||
- Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard)
|
||||
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
|
||||
- Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt)
|
||||
- Only show New Snippet button to users that can create snippets.
|
||||
- PipelinesFinder uses git cache data
|
||||
- Track a user who created a pipeline
|
||||
- Actually render old and new sections of parallel diff next to each other
|
||||
- Throttle the update of `project.pushes_since_gc` to 1 minute.
|
||||
- Allow expanding and collapsing files in diff view (!4990)
|
||||
- Allow expanding and collapsing files in diff view. !4990
|
||||
- Collapse large diffs by default (!4990)
|
||||
- Fix mentioned users list on diff notes
|
||||
- Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes)
|
||||
- Fix creation of deployment on build that is retried, redeployed or rollback
|
||||
- Don't parse Rinku returned value to DocFragment when it didn't change the original html string.
|
||||
- Check for conflicts with existing Project's wiki path when creating a new project.
|
||||
|
@ -89,8 +112,10 @@ v 8.10.0 (unreleased)
|
|||
- ObjectRenderer retrieve renderer content using Rails.cache.read_multi
|
||||
- Better caching of git calls on ProjectsController#show.
|
||||
- Avoid to retrieve MR closes_issues as much as possible.
|
||||
- Add API endpoint for a group issues !4520 (mahcsig)
|
||||
- Add Bugzilla integration !4930 (iamtjg)
|
||||
- Hide project name in project activities. !5068 (winniehell)
|
||||
- Add API endpoint for a group issues. !4520 (mahcsig)
|
||||
- Add Bugzilla integration. !4930 (iamtjg)
|
||||
- Fix new snippet style bug (elliotec)
|
||||
- Instrument Rinku usage
|
||||
- Be explicit to define merge request discussion variables
|
||||
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
|
||||
|
@ -101,6 +126,7 @@ v 8.10.0 (unreleased)
|
|||
- Don't render discussion notes when requesting diff tab through AJAX
|
||||
- Add basic system information like memory and disk usage to the admin panel
|
||||
- Don't garbage collect commits that have related DB records like comments
|
||||
- Allow to setup event by channel on slack service
|
||||
- More descriptive message for git hooks and file locks
|
||||
- Aliases of award emoji should be stored as original name. !5060 (dixpac)
|
||||
- Handle custom Git hook result in GitLab UI
|
||||
|
@ -110,10 +136,10 @@ v 8.10.0 (unreleased)
|
|||
- Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
|
||||
- Add date when user joined the team on the member page
|
||||
- Fix 404 redirect after validation fails importing a GitLab project
|
||||
- Added setting to set new users by default as external !4545 (Dravere)
|
||||
- Add min value for project limit field on user's form !3622 (jastkand)
|
||||
- Added setting to set new users by default as external. !4545 (Dravere)
|
||||
- Add min value for project limit field on user's form. !3622 (jastkand)
|
||||
- Reset project pushes_since_gc when we enqueue the git gc call
|
||||
- Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt)
|
||||
- Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt)
|
||||
- Collapsed diffs lines/size don't acumulate to overflow diffs.
|
||||
- Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel)
|
||||
- Style of import project buttons were fixed in the new project page. !5183 (rdemirbay)
|
||||
|
@ -121,19 +147,25 @@ v 8.10.0 (unreleased)
|
|||
- Optimistic locking for Issues and Merge Requests (Title and description overriding prevention)
|
||||
- Redesign Builds and Pipelines pages
|
||||
- Change status color and icon for running builds
|
||||
- Fix commenting issue in side by side diff view for unchanged lines
|
||||
- Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.`
|
||||
- Project export filename now includes the project and namespace path
|
||||
- Fix last update timestamp on issues not preserved on gitlab.com and project imports
|
||||
- Fix issues importing projects from EE to CE
|
||||
- Fix creating group with space in group path
|
||||
- Improve cron_jobs loading error messages !5318
|
||||
- Improve cron_jobs loading error messages. !5318 / !5360
|
||||
- Prevent toggling sidebar when clipboard icon clicked
|
||||
- Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska)
|
||||
- Limit the number of retries on error to 3 for exporting projects
|
||||
- Allow empty repositories on project import/export
|
||||
- Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Allow bulk (un)subscription from issues in issue index
|
||||
- Fix MR diff encoding issues exporting GitLab projects
|
||||
- Move builds settings out of project settings and rename Pipelines
|
||||
- Add builds badge to Pipelines settings page
|
||||
- Export and import avatar as part of project import/export
|
||||
- Fix migration corrupting import data for old version upgrades
|
||||
- Show tooltip on GitLab export link in new project page
|
||||
|
||||
v 8.9.6
|
||||
- Fix importing of events under notes for GitLab projects. !5154
|
||||
|
@ -236,6 +268,7 @@ v 8.9.1
|
|||
- Remove width restriction for logo on sign-in page. !4888
|
||||
- Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
|
||||
- Apply selected value as label. !4886
|
||||
- Change Retry to Re-deploy on Deployments page
|
||||
- Fix temp file being deleted after the request while importing a GitLab project. !4894
|
||||
- Fix pagination when sorting by columns with lots of ties (like priority)
|
||||
- Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -52,7 +52,7 @@ gem 'browser', '~> 2.2'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem 'gitlab_git', '~> 10.3.2'
|
||||
gem 'gitlab_git', '~> 10.4.1'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
|
@ -347,8 +347,5 @@ gem 'paranoia', '~> 2.0'
|
|||
gem 'health_check', '~> 2.1.0'
|
||||
|
||||
# System information
|
||||
gem 'vmstat', '~> 2.1.0'
|
||||
gem 'vmstat', '~> 2.1.1'
|
||||
gem 'sys-filesystem', '~> 1.1.6'
|
||||
|
||||
# Secure headers for Content Security Policy
|
||||
gem 'secure_headers', '~> 3.3'
|
||||
|
|
14
Gemfile.lock
14
Gemfile.lock
|
@ -274,7 +274,7 @@ GEM
|
|||
diff-lcs (~> 1.1)
|
||||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_git (10.3.2)
|
||||
gitlab_git (10.4.1)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
|
@ -578,7 +578,7 @@ GEM
|
|||
railties (>= 4.2.0, < 5.1)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
rouge (2.0.3)
|
||||
rouge (2.0.5)
|
||||
rqrcode (0.7.0)
|
||||
chunky_png
|
||||
rqrcode-rails3 (0.1.7)
|
||||
|
@ -645,8 +645,6 @@ GEM
|
|||
sdoc (0.3.20)
|
||||
json (>= 1.1.3)
|
||||
rdoc (~> 3.10)
|
||||
secure_headers (3.3.2)
|
||||
useragent
|
||||
seed-fu (2.3.6)
|
||||
activerecord (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
|
@ -769,7 +767,6 @@ GEM
|
|||
get_process_mem (~> 0)
|
||||
unicorn (>= 4, < 6)
|
||||
uniform_notifier (1.9.0)
|
||||
useragent (0.16.7)
|
||||
uuid (2.3.8)
|
||||
macaddr (~> 1.0)
|
||||
version_sorter (2.0.0)
|
||||
|
@ -778,7 +775,7 @@ GEM
|
|||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
vmstat (2.1.0)
|
||||
vmstat (2.1.1)
|
||||
warden (1.2.6)
|
||||
rack (>= 1.0)
|
||||
web-console (2.3.0)
|
||||
|
@ -864,7 +861,7 @@ DEPENDENCIES
|
|||
github-linguist (~> 4.7.0)
|
||||
github-markup (~> 1.4)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_git (~> 10.3.2)
|
||||
gitlab_git (~> 10.4.1)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.2)
|
||||
|
@ -947,7 +944,6 @@ DEPENDENCIES
|
|||
sass-rails (~> 5.0.0)
|
||||
scss_lint (~> 0.47.0)
|
||||
sdoc (~> 0.3.20)
|
||||
secure_headers (~> 3.3)
|
||||
seed-fu (~> 2.3.5)
|
||||
select2-rails (~> 3.5.9)
|
||||
sentry-raven (~> 1.1.0)
|
||||
|
@ -984,7 +980,7 @@ DEPENDENCIES
|
|||
unicorn-worker-killer (~> 0.4.2)
|
||||
version_sorter (~> 2.0.0)
|
||||
virtus (~> 1.0.1)
|
||||
vmstat (~> 2.1.0)
|
||||
vmstat (~> 2.1.1)
|
||||
web-console (~> 2.0)
|
||||
webmock (~> 1.21.0)
|
||||
wikicloth (= 0.8.1)
|
||||
|
|
|
@ -38,3 +38,14 @@ class @Admin
|
|||
|
||||
$('li.group_member').bind 'ajax:success', ->
|
||||
Turbolinks.visit(location.href)
|
||||
|
||||
showBlacklistType = ->
|
||||
if $("input[name='blacklist_type']:checked").val() == 'file'
|
||||
$('.blacklist-file').show()
|
||||
$('.blacklist-raw').hide()
|
||||
else
|
||||
$('.blacklist-file').hide()
|
||||
$('.blacklist-raw').show()
|
||||
|
||||
$("input[name='blacklist_type']").click showBlacklistType
|
||||
showBlacklistType()
|
||||
|
|
|
@ -7,7 +7,6 @@ class @FilesCommentButton
|
|||
UNFOLDABLE_LINE_CLASS = 'js-unfold'
|
||||
EMPTY_CELL_CLASS = 'empty-cell'
|
||||
OLD_LINE_CLASS = 'old_line'
|
||||
NEW_CLASS = 'new'
|
||||
LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content"
|
||||
TEXT_FILE_SELECTOR = '.text-file'
|
||||
DEBOUNCE_TIMEOUT_DURATION = 100
|
||||
|
@ -18,6 +17,8 @@ class @FilesCommentButton
|
|||
debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION
|
||||
|
||||
$(document)
|
||||
.off 'mouseover', LINE_COLUMN_CLASSES
|
||||
.off 'mouseleave', LINE_COLUMN_CLASSES
|
||||
.on 'mouseover', LINE_COLUMN_CLASSES, debounce
|
||||
.on 'mouseleave', LINE_COLUMN_CLASSES, @destroy
|
||||
|
||||
|
@ -64,20 +65,20 @@ class @FilesCommentButton
|
|||
getLineContent: (hoveredElement) ->
|
||||
return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS
|
||||
|
||||
$(".#{LINE_CONTENT_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent())
|
||||
if @VIEW_TYPE is 'inline'
|
||||
return $(hoveredElement).closest(LINE_HOLDER_CLASS).find ".#{LINE_CONTENT_CLASS}"
|
||||
else
|
||||
return $(hoveredElement).next ".#{LINE_CONTENT_CLASS}"
|
||||
|
||||
getButtonParent: (hoveredElement) ->
|
||||
if @VIEW_TYPE is 'inline'
|
||||
return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS
|
||||
|
||||
$(".#{OLD_LINE_CLASS}", hoveredElement.parent())
|
||||
hoveredElement.parent().find ".#{OLD_LINE_CLASS}"
|
||||
else
|
||||
return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS
|
||||
|
||||
$(".#{LINE_NUMBER_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent())
|
||||
|
||||
diffTypeClass: (hoveredElement) ->
|
||||
if hoveredElement.hasClass(NEW_CLASS) then '.new' else '.old'
|
||||
$(hoveredElement).prev ".#{LINE_NUMBER_CLASS}"
|
||||
|
||||
isMovingToSameType: (e) ->
|
||||
newButtonParent = @getButtonParent $(e.toElement)
|
||||
|
|
|
@ -210,9 +210,22 @@ class GitLabDropdown
|
|||
data: =>
|
||||
return @fullData
|
||||
callback: (data) =>
|
||||
currentIndex = -1
|
||||
@parseData data
|
||||
|
||||
unless @filterInput.val() is ''
|
||||
selector = '.dropdown-content li:not(.divider):visible'
|
||||
|
||||
if @dropdown.find('.dropdown-toggle-page').length
|
||||
selector = ".dropdown-page-one #{selector}"
|
||||
|
||||
$(selector, @dropdown)
|
||||
.first()
|
||||
.find('a')
|
||||
.addClass('is-focused')
|
||||
|
||||
currentIndex = 0
|
||||
|
||||
|
||||
# Event listeners
|
||||
|
||||
@dropdown.on "shown.bs.dropdown", @opened
|
||||
|
|
|
@ -55,10 +55,13 @@ class @MergeRequestWidget
|
|||
$('.mr-state-widget').replaceWith(data)
|
||||
|
||||
ciLabelForStatus: (status) ->
|
||||
if status is 'success'
|
||||
'passed'
|
||||
else
|
||||
status
|
||||
switch status
|
||||
when 'success'
|
||||
'passed'
|
||||
when 'success_with_warnings'
|
||||
'passed with warnings'
|
||||
else
|
||||
status
|
||||
|
||||
pollCIStatus: ->
|
||||
@fetchBuildStatusInterval = setInterval ( =>
|
||||
|
@ -116,7 +119,7 @@ class @MergeRequestWidget
|
|||
showCIStatus: (state) ->
|
||||
return if not state?
|
||||
$('.ci_widget').hide()
|
||||
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
|
||||
allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]
|
||||
if state in allowed_states
|
||||
$('.ci_widget.ci-' + state).show()
|
||||
switch state
|
||||
|
@ -124,7 +127,7 @@ class @MergeRequestWidget
|
|||
@setMergeButtonClass('btn-danger')
|
||||
when "running"
|
||||
@setMergeButtonClass('btn-warning')
|
||||
when "success"
|
||||
when "success", "success_with_warnings"
|
||||
@setMergeButtonClass('btn-create')
|
||||
else
|
||||
$('.ci_widget.ci-error').show()
|
||||
|
|
|
@ -162,7 +162,7 @@ class @Notes
|
|||
@last_fetched_at = data.last_fetched_at
|
||||
@setPollingInterval(data.notes.length)
|
||||
$.each notes, (i, note) =>
|
||||
if note.discussion_with_diff_html?
|
||||
if note.discussion_html?
|
||||
@renderDiscussionNote(note)
|
||||
else
|
||||
@renderNote(note)
|
||||
|
@ -251,7 +251,7 @@ class @Notes
|
|||
discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']")
|
||||
if discussionContainer.length is 0
|
||||
# insert the note and the reply button after the temp row
|
||||
row.after note.discussion_html
|
||||
row.after note.diff_discussion_html
|
||||
|
||||
# remove the note (will be added again below)
|
||||
row.next().find(".note").remove()
|
||||
|
@ -265,7 +265,7 @@ class @Notes
|
|||
# Init discussion on 'Discussion' page if it is merge request page
|
||||
if $('body').attr('data-page').indexOf('projects:merge_request') is 0
|
||||
$('ul.main-notes-list')
|
||||
.append(note.discussion_with_diff_html)
|
||||
.append(note.discussion_html)
|
||||
.syntaxHighlight()
|
||||
else
|
||||
# append new note to all matching discussions
|
||||
|
|
|
@ -120,6 +120,9 @@ class @Sidebar
|
|||
i.show()
|
||||
|
||||
sidebarCollapseClicked: (e) ->
|
||||
|
||||
return if $(e.currentTarget).hasClass('dont-change-state')
|
||||
|
||||
sidebar = e.data
|
||||
e.preventDefault()
|
||||
$block = $(@).closest('.block')
|
||||
|
|
|
@ -232,7 +232,9 @@
|
|||
.nav-block {
|
||||
.controls {
|
||||
float: right;
|
||||
margin-top: 11px;
|
||||
margin-top: 8px;
|
||||
padding-bottom: 7px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,17 @@
|
|||
border-color: $border-dark;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
svg {
|
||||
|
||||
path {
|
||||
fill: $color;
|
||||
}
|
||||
|
||||
use {
|
||||
stroke: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin btn-green {
|
||||
|
@ -173,6 +184,13 @@
|
|||
.caret {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 15px;
|
||||
width: auto;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
|
|
|
@ -98,13 +98,30 @@
|
|||
|
||||
.md {
|
||||
&.md-preview-holder {
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
// Reset ul style types since we're nested inside a ul already
|
||||
@include bulleted-list;
|
||||
}
|
||||
|
||||
// On diffs code should wrap nicely and not overflow
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
hr {
|
||||
// Darken 'whitesmoke' a bit to make it more visible in note bodies
|
||||
border-color: darken(#f5f5f5, 8%);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
// Border around images in issue and MR comments.
|
||||
img:not(.emoji) {
|
||||
border: 1px solid $table-border-gray;
|
||||
padding: 5px;
|
||||
margin: 5px 0;
|
||||
// Ensure that image does not exceed viewport
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-group {
|
||||
|
|
|
@ -198,6 +198,10 @@ header.header-pinned-nav {
|
|||
|
||||
.sidebar-collapsed-icon {
|
||||
cursor: pointer;
|
||||
|
||||
.btn {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,14 @@
|
|||
left: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
svg {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.build-header {
|
||||
|
|
|
@ -68,6 +68,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ci-status-link {
|
||||
svg {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.commit-box {
|
||||
border-top: 1px solid $border-color;
|
||||
|
||||
|
|
|
@ -176,3 +176,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hide event scope (namespace + project) where it is not necessary
|
||||
.project-activity {
|
||||
.event-scope {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,8 @@
|
|||
|
||||
button {
|
||||
float: right;
|
||||
padding: 3px 5px;
|
||||
padding: 1px 5px;
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,7 +269,7 @@
|
|||
.issuable-header-btn {
|
||||
background: $gray-normal;
|
||||
border: 1px solid $border-gray-normal;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: $gray-dark;
|
||||
border: 1px solid $border-gray-dark;
|
||||
|
|
|
@ -78,6 +78,14 @@ form.edit-issue {
|
|||
}
|
||||
}
|
||||
|
||||
.merge-request-ci-status {
|
||||
svg {
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.issue-btn-group {
|
||||
width: 100%;
|
||||
|
|
|
@ -60,14 +60,25 @@
|
|||
.ci_widget {
|
||||
border-bottom: 1px solid #eef0f2;
|
||||
|
||||
i {
|
||||
svg {
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&.ci-success {
|
||||
color: $gl-success;
|
||||
}
|
||||
|
||||
&.ci-success_with_warnings {
|
||||
color: $gl-success;
|
||||
|
||||
i {
|
||||
color: $gl-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&.ci-skipped {
|
||||
background-color: #eee;
|
||||
color: #888;
|
||||
|
@ -196,6 +207,16 @@
|
|||
|
||||
.merge-request-title {
|
||||
margin-bottom: 2px;
|
||||
|
||||
.ci-status-link {
|
||||
|
||||
svg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,34 +91,11 @@ ul.notes {
|
|||
// Reset ul style types since we're nested inside a ul already
|
||||
@include bulleted-list;
|
||||
|
||||
// On diffs code should wrap nicely and not overflow
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
ul.task-list {
|
||||
ul:not(.task-list) {
|
||||
padding-left: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
// Darken 'whitesmoke' a bit to make it more visible in note bodies
|
||||
border-color: darken(#f5f5f5, 8%);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
code {
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
// Border around images in issue and MR comments.
|
||||
img:not(.emoji) {
|
||||
border: 1px solid $table-border-gray;
|
||||
padding: 5px;
|
||||
margin: 5px 0;
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.pipeline-holder {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.table.builds {
|
||||
min-width: 1200px;
|
||||
|
||||
&.pipeline {
|
||||
min-width: 650px;
|
||||
}
|
||||
|
||||
tr {
|
||||
th {
|
||||
padding: 16px 8px;
|
||||
|
@ -49,6 +58,14 @@
|
|||
|
||||
.commit-link {
|
||||
|
||||
.ci-status {
|
||||
|
||||
svg {
|
||||
top: 1px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -68,7 +85,7 @@
|
|||
|
||||
svg {
|
||||
height: 14px;
|
||||
width: auto;
|
||||
width: 14px;
|
||||
vertical-align: middle;
|
||||
fill: $table-text-gray;
|
||||
}
|
||||
|
@ -85,7 +102,7 @@
|
|||
|
||||
.commit-title {
|
||||
margin-top: 4px;
|
||||
max-width: 320px;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -124,6 +141,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.stage-cell {
|
||||
|
||||
svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
vertical-align: middle;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.light {
|
||||
width: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.duration,
|
||||
.finished-at {
|
||||
color: $table-text-gray;
|
||||
|
@ -136,7 +167,7 @@
|
|||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: auto;
|
||||
height: 12px;
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
|
|
@ -129,6 +129,17 @@
|
|||
color: $layout-link-gray;
|
||||
}
|
||||
|
||||
svg {
|
||||
|
||||
path {
|
||||
fill: $layout-link-gray;
|
||||
}
|
||||
|
||||
use {
|
||||
stroke: $layout-link-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-caret-down {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
@ -322,18 +333,53 @@ a.deploy-project-label {
|
|||
}
|
||||
|
||||
.fork-namespaces {
|
||||
.fork-thumbnail {
|
||||
text-align: center;
|
||||
margin-bottom: $gl-padding;
|
||||
.row {
|
||||
-webkit-flex-wrap: wrap;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
|
||||
.caption {
|
||||
padding: $gl-padding 0;
|
||||
min-height: 30px;
|
||||
}
|
||||
.fork-thumbnail {
|
||||
@include border-radius($border-radius-base);
|
||||
background-color: $white-light;
|
||||
border: 1px solid $border-white-light;
|
||||
height: 202px;
|
||||
margin: $gl-padding;
|
||||
text-align: center;
|
||||
width: 169px;
|
||||
&:hover, &.forked {
|
||||
background-color: $row-hover;
|
||||
border-color: $row-hover-border;
|
||||
}
|
||||
.no-avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: $gray-light;
|
||||
border: 1px solid $gray-dark;
|
||||
margin: 0 auto;
|
||||
@include border-radius(50%);
|
||||
i {
|
||||
font-size: 100px;
|
||||
color: $gray-dark;
|
||||
}
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: $gl-padding;
|
||||
color: $gl-gray;
|
||||
.caption {
|
||||
min-height: 30px;
|
||||
padding: $gl-padding 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
@include border-radius(50%);
|
||||
max-width: 100px;
|
||||
img {
|
||||
@include border-radius(50%);
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -486,6 +532,11 @@ pre.light-well {
|
|||
> span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
border-color: $gl-danger;
|
||||
}
|
||||
|
||||
&.ci-success {
|
||||
&.ci-success,
|
||||
&.ci-success_with_warnings {
|
||||
color: $gl-success;
|
||||
border-color: $gl-success;
|
||||
}
|
||||
|
@ -41,6 +42,15 @@
|
|||
color: $blue-normal;
|
||||
border-color: $blue-normal;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 13px;
|
||||
width: 13px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin: 0 3px;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.ci-status-icon-success {
|
||||
|
@ -49,9 +59,12 @@
|
|||
.ci-status-icon-failed {
|
||||
color: $gl-danger;
|
||||
}
|
||||
.ci-status-icon-pending {
|
||||
|
||||
.ci-status-icon-pending,
|
||||
.ci-status-icon-success_with_warning {
|
||||
color: $gl-warning;
|
||||
}
|
||||
|
||||
.ci-status-icon-running {
|
||||
color: $blue-normal;
|
||||
}
|
||||
|
@ -62,3 +75,11 @@
|
|||
color: $gl-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.visible-xs-inline {
|
||||
.ci-status-link {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
left: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.tag-buttons {
|
||||
line-height: 40px;
|
||||
|
||||
.btn:not(.dropdown-toggle) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
|
@ -64,6 +64,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
params[:application_setting][:disabled_oauth_sign_in_sources] =
|
||||
AuthHelper.button_based_providers.map(&:to_s) -
|
||||
Array(enabled_oauth_sign_in_sources)
|
||||
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
|
||||
|
||||
params.require(:application_setting).permit(
|
||||
:default_projects_limit,
|
||||
|
@ -83,7 +84,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
:default_project_visibility,
|
||||
:default_snippet_visibility,
|
||||
:default_group_visibility,
|
||||
:restricted_signup_domains_raw,
|
||||
:domain_whitelist_raw,
|
||||
:domain_blacklist_enabled,
|
||||
:domain_blacklist_raw,
|
||||
:domain_blacklist_file,
|
||||
:version_check_enabled,
|
||||
:admin_notification_email,
|
||||
:user_oauth_applications,
|
||||
|
|
|
@ -60,6 +60,6 @@ class Admin::GroupsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def group_params
|
||||
params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level)
|
||||
params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Admin::ServicesController < Admin::ApplicationController
|
||||
include ServiceParams
|
||||
|
||||
before_action :service, only: [:edit, :update]
|
||||
|
||||
def index
|
||||
|
@ -13,7 +15,7 @@ class Admin::ServicesController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
if service.update_attributes(application_services_params[:service])
|
||||
if service.update_attributes(service_params[:service])
|
||||
redirect_to admin_application_settings_services_path,
|
||||
notice: 'Application settings saved successfully'
|
||||
else
|
||||
|
@ -37,15 +39,4 @@ class Admin::ServicesController < Admin::ApplicationController
|
|||
def service
|
||||
@service ||= Service.where(id: params[:id], template: true).first
|
||||
end
|
||||
|
||||
def application_services_params
|
||||
application_services_params = params.permit(:id,
|
||||
service: Projects::ServicesController::ALLOWED_PARAMS)
|
||||
if application_services_params[:service].is_a?(Hash)
|
||||
Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
|
||||
application_services_params[:service].delete(param) if application_services_params[:service][param].blank?
|
||||
end
|
||||
end
|
||||
application_services_params
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
module ServiceParams
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
|
||||
:room, :recipients, :project_url, :webhook,
|
||||
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
|
||||
:build_key, :server, :teamcity_url, :drone_url, :build_type,
|
||||
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
|
||||
:colorize_messages, :channels,
|
||||
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
|
||||
:note_events, :build_events, :wiki_page_events,
|
||||
:notify_only_broken_builds, :add_pusher,
|
||||
:send_from_committer_email, :disable_diffs, :external_wiki_url,
|
||||
:notify, :color,
|
||||
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
|
||||
:jira_issue_transition_id]
|
||||
|
||||
# Parameters to ignore if no value is specified
|
||||
FILTER_BLANK_PARAMS = [:password]
|
||||
|
||||
def service_params
|
||||
dynamic_params = []
|
||||
dynamic_params.concat(@service.event_channel_names)
|
||||
|
||||
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
|
||||
|
||||
if service_params[:service].is_a?(Hash)
|
||||
FILTER_BLANK_PARAMS.each do |param|
|
||||
service_params[:service].delete(param) if service_params[:service][param].blank?
|
||||
end
|
||||
end
|
||||
|
||||
service_params
|
||||
end
|
||||
end
|
|
@ -121,7 +121,7 @@ class GroupsController < Groups::ApplicationController
|
|||
end
|
||||
|
||||
def group_params
|
||||
params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock)
|
||||
params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled)
|
||||
end
|
||||
|
||||
def load_events
|
||||
|
|
|
@ -30,7 +30,7 @@ class HelpController < ApplicationController
|
|||
end
|
||||
|
||||
# Allow access to images in the doc folder
|
||||
format.any(:png, :gif, :jpeg) do
|
||||
format.any(:png, :gif, :jpeg, :mp4) do
|
||||
# Note: We are purposefully NOT using `Rails.root.join`
|
||||
path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}")
|
||||
|
||||
|
|
|
@ -3,11 +3,6 @@ class Projects::BadgesController < Projects::ApplicationController
|
|||
before_action :authorize_admin_project!, only: [:index]
|
||||
before_action :no_cache_headers, except: [:index]
|
||||
|
||||
def index
|
||||
@ref = params[:ref] || @project.default_branch || 'master'
|
||||
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
|
||||
end
|
||||
|
||||
def build
|
||||
badge = Gitlab::Badge::Build.new(project, params[:ref])
|
||||
|
||||
|
|
|
@ -115,11 +115,11 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def define_note_vars
|
||||
@grouped_diff_notes = commit.notes.grouped_diff_notes
|
||||
@grouped_diff_discussions = commit.notes.grouped_diff_discussions
|
||||
@notes = commit.notes.non_diff_notes.fresh
|
||||
|
||||
Banzai::NoteRenderer.render(
|
||||
@grouped_diff_notes.values.flatten + @notes,
|
||||
@grouped_diff_discussions.values.flat_map(&:notes) + @notes,
|
||||
@project,
|
||||
current_user,
|
||||
)
|
||||
|
|
|
@ -15,6 +15,7 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
apply_diff_view_cookie!
|
||||
end
|
||||
|
||||
def diff_for_path
|
||||
|
@ -53,7 +54,7 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
)
|
||||
|
||||
@diff_notes_disabled = true
|
||||
@grouped_diff_notes = {}
|
||||
@grouped_diff_discussions = {}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
else
|
||||
build_merge_request
|
||||
@diff_notes_disabled = true
|
||||
@grouped_diff_notes = {}
|
||||
@grouped_diff_discussions = {}
|
||||
end
|
||||
|
||||
define_commit_vars
|
||||
|
@ -286,6 +286,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
status = pipeline.status
|
||||
coverage = pipeline.try(:coverage)
|
||||
|
||||
status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings?
|
||||
|
||||
status ||= "preparing"
|
||||
else
|
||||
ci_service = @merge_request.source_project.ci_service
|
||||
|
@ -376,7 +378,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
|
||||
# This is not executed lazily
|
||||
@notes = Banzai::NoteRenderer.render(
|
||||
@discussions.flatten,
|
||||
@discussions.flat_map(&:notes),
|
||||
@project,
|
||||
current_user,
|
||||
@path,
|
||||
|
@ -402,10 +404,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
}
|
||||
|
||||
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
|
||||
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
|
||||
@grouped_diff_discussions = @merge_request.notes.grouped_diff_discussions
|
||||
|
||||
Banzai::NoteRenderer.render(
|
||||
@grouped_diff_notes.values.flatten,
|
||||
@grouped_diff_discussions.values.flat_map(&:notes),
|
||||
@project,
|
||||
current_user,
|
||||
@path,
|
||||
|
|
|
@ -73,7 +73,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
end
|
||||
alias_method :awardable, :note
|
||||
|
||||
def note_to_html(note)
|
||||
def note_html(note)
|
||||
render_to_string(
|
||||
"projects/notes/_note",
|
||||
layout: false,
|
||||
|
@ -82,20 +82,20 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def note_to_discussion_html(note)
|
||||
return unless note.diff_note?
|
||||
def diff_discussion_html(discussion)
|
||||
return unless discussion.diff_discussion?
|
||||
|
||||
if params[:view] == 'parallel'
|
||||
template = "projects/notes/_diff_notes_with_reply_parallel"
|
||||
template = "discussions/_parallel_diff_discussion"
|
||||
locals =
|
||||
if params[:line_type] == 'old'
|
||||
{ notes_left: [note], notes_right: [] }
|
||||
{ discussion_left: discussion, discussion_right: nil }
|
||||
else
|
||||
{ notes_left: [], notes_right: [note] }
|
||||
{ discussion_left: nil, discussion_right: discussion }
|
||||
end
|
||||
else
|
||||
template = "projects/notes/_diff_notes_with_reply"
|
||||
locals = { notes: [note] }
|
||||
template = "discussions/_diff_discussion"
|
||||
locals = { discussion: discussion }
|
||||
end
|
||||
|
||||
render_to_string(
|
||||
|
@ -106,14 +106,14 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def note_to_discussion_with_diff_html(note)
|
||||
return unless note.diff_note?
|
||||
def discussion_html(discussion)
|
||||
return unless discussion.diff_discussion?
|
||||
|
||||
render_to_string(
|
||||
"projects/notes/_discussion",
|
||||
"discussions/_discussion",
|
||||
layout: false,
|
||||
formats: [:html],
|
||||
locals: { discussion_notes: [note] }
|
||||
locals: { discussion: discussion }
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -132,26 +132,33 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
valid: true,
|
||||
id: note.id,
|
||||
discussion_id: note.discussion_id,
|
||||
html: note_to_html(note),
|
||||
html: note_html(note),
|
||||
award: false,
|
||||
note: note.note,
|
||||
discussion_html: note_to_discussion_html(note),
|
||||
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
|
||||
note: note.note
|
||||
}
|
||||
|
||||
# The discussion_id is used to add the comment to the correct discussion
|
||||
# element on the merge request page. Among other things, the discussion_id
|
||||
# contains the sha of head commit of the merge request.
|
||||
# When new commits are pushed into the merge request after the initial
|
||||
# load of the merge request page, the discussion elements will still have
|
||||
# the old discussion_ids, with the old head commit sha. The new comment,
|
||||
# however, will have the new discussion_id with the new commit sha.
|
||||
# To ensure that these new comments will still end up in the correct
|
||||
# discussion element, we also send the original discussion_id, with the
|
||||
# old commit sha, along, and fall back on this value when no discussion
|
||||
# element with the new discussion_id could be found.
|
||||
if note.new_diff_note? && note.position != note.original_position
|
||||
attrs[:original_discussion_id] = note.original_discussion_id
|
||||
if note.diff_note?
|
||||
discussion = Discussion.new([note])
|
||||
|
||||
attrs.merge!(
|
||||
diff_discussion_html: diff_discussion_html(discussion),
|
||||
discussion_html: discussion_html(discussion)
|
||||
)
|
||||
|
||||
# The discussion_id is used to add the comment to the correct discussion
|
||||
# element on the merge request page. Among other things, the discussion_id
|
||||
# contains the sha of head commit of the merge request.
|
||||
# When new commits are pushed into the merge request after the initial
|
||||
# load of the merge request page, the discussion elements will still have
|
||||
# the old discussion_ids, with the old head commit sha. The new comment,
|
||||
# however, will have the new discussion_id with the new commit sha.
|
||||
# To ensure that these new comments will still end up in the correct
|
||||
# discussion element, we also send the original discussion_id, with the
|
||||
# old commit sha, along, and fall back on this value when no discussion
|
||||
# element with the new discussion_id could be found.
|
||||
if note.new_diff_note? && note.position != note.original_position
|
||||
attrs[:original_discussion_id] = note.original_discussion_id
|
||||
end
|
||||
end
|
||||
|
||||
attrs
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
class Projects::PipelinesSettingsController < Projects::ApplicationController
|
||||
before_action :authorize_admin_pipeline!
|
||||
|
||||
def show
|
||||
@ref = params[:ref] || @project.default_branch || 'master'
|
||||
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
|
||||
end
|
||||
|
||||
def update
|
||||
if @project.update_attributes(update_params)
|
||||
flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated."
|
||||
redirect_to namespace_project_pipelines_settings_path(@project.namespace, @project)
|
||||
else
|
||||
render 'index'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_params
|
||||
params.require(:pipeline).permit(:ref)
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:project).permit(
|
||||
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
|
||||
:public_builds
|
||||
)
|
||||
end
|
||||
end
|
|
@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController
|
|||
when "graphs_commits"
|
||||
commits_namespace_project_graph_path(@project.namespace, @project, @id)
|
||||
when "badges"
|
||||
namespace_project_badges_path(@project.namespace, @project, ref: @id)
|
||||
namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id)
|
||||
else
|
||||
namespace_project_commits_path(@project.namespace, @project, @id)
|
||||
end
|
||||
|
|
|
@ -1,20 +1,5 @@
|
|||
class Projects::ServicesController < Projects::ApplicationController
|
||||
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
|
||||
:room, :recipients, :project_url, :webhook,
|
||||
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
|
||||
:build_key, :server, :teamcity_url, :drone_url, :build_type,
|
||||
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
|
||||
:colorize_messages, :channels,
|
||||
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
|
||||
:note_events, :build_events, :wiki_page_events,
|
||||
:notify_only_broken_builds, :add_pusher,
|
||||
:send_from_committer_email, :disable_diffs, :external_wiki_url,
|
||||
:notify, :color,
|
||||
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
|
||||
:jira_issue_transition_id]
|
||||
|
||||
# Parameters to ignore if no value is specified
|
||||
FILTER_BLANK_PARAMS = [:password]
|
||||
include ServiceParams
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_project!
|
||||
|
@ -33,7 +18,7 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
if @service.update_attributes(service_params)
|
||||
if @service.update_attributes(service_params[:service])
|
||||
redirect_to(
|
||||
edit_namespace_project_service_path(@project.namespace, @project,
|
||||
@service.to_param, notice:
|
||||
|
@ -64,12 +49,4 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
def service
|
||||
@service ||= @project.services.find { |service| service.to_param == params[:id] }
|
||||
end
|
||||
|
||||
def service_params
|
||||
service_params = params.require(:service).permit(ALLOWED_PARAMS)
|
||||
FILTER_BLANK_PARAMS.each do |param|
|
||||
service_params.delete(param) if service_params[param].blank?
|
||||
end
|
||||
service_params
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Projects::UploadsController < Projects::ApplicationController
|
||||
skip_before_action :reject_blocked!, :project,
|
||||
:repository, if: -> { action_name == 'show' && image? }
|
||||
:repository, if: -> { action_name == 'show' && image_or_video? }
|
||||
|
||||
before_action :authorize_upload_file!, only: [:create]
|
||||
|
||||
|
@ -24,7 +24,7 @@ class Projects::UploadsController < Projects::ApplicationController
|
|||
def show
|
||||
return render_404 if uploader.nil? || !uploader.file.exists?
|
||||
|
||||
disposition = uploader.image? ? 'inline' : 'attachment'
|
||||
disposition = uploader.image_or_video? ? 'inline' : 'attachment'
|
||||
send_file uploader.file.path, disposition: disposition
|
||||
end
|
||||
|
||||
|
@ -49,7 +49,7 @@ class Projects::UploadsController < Projects::ApplicationController
|
|||
@uploader
|
||||
end
|
||||
|
||||
def image?
|
||||
uploader && uploader.file.exists? && uploader.image?
|
||||
def image_or_video?
|
||||
uploader && uploader.file.exists? && uploader.image_or_video?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -296,7 +296,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
:issues_tracker_id, :default_branch,
|
||||
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
|
||||
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
|
||||
:public_builds, :only_allow_merge_if_build_succeeds
|
||||
:public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
module AvatarsHelper
|
||||
|
||||
def author_avatar(commit_or_event, options = {})
|
||||
user_avatar(options.merge({
|
||||
user: commit_or_event.author,
|
||||
user_name: commit_or_event.author_name,
|
||||
user_email: commit_or_event.author_email,
|
||||
}))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_avatar(options = {})
|
||||
avatar_size = options[:size] || 16
|
||||
user_name = options[:user].try(:name) || options[:user_name]
|
||||
avatar = image_tag(
|
||||
avatar_icon(options[:user] || options[:user_email], avatar_size),
|
||||
class: "avatar has-tooltip hidden-xs s#{avatar_size}",
|
||||
alt: "#{user_name}'s avatar",
|
||||
title: user_name
|
||||
)
|
||||
|
||||
if options[:user]
|
||||
link_to(avatar, user_path(options[:user]))
|
||||
elsif options[:user_email]
|
||||
mail_to(options[:user_email], avatar)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -15,8 +15,11 @@ module CiStatusHelper
|
|||
end
|
||||
|
||||
def ci_label_for_status(status)
|
||||
if status == 'success'
|
||||
case status
|
||||
when 'success'
|
||||
'passed'
|
||||
when 'success_with_warnings'
|
||||
'passed with warnings'
|
||||
else
|
||||
status
|
||||
end
|
||||
|
@ -26,24 +29,26 @@ module CiStatusHelper
|
|||
icon_name =
|
||||
case status
|
||||
when 'success'
|
||||
'check'
|
||||
'icon_status_success'
|
||||
when 'success_with_warnings'
|
||||
'icon_status_warning'
|
||||
when 'failed'
|
||||
'close'
|
||||
'icon_status_failed'
|
||||
when 'pending'
|
||||
'clock-o'
|
||||
'icon_status_pending'
|
||||
when 'running'
|
||||
'spinner'
|
||||
'icon_status_running'
|
||||
else
|
||||
'circle'
|
||||
'icon_status_cancel'
|
||||
end
|
||||
|
||||
icon(icon_name + ' fw')
|
||||
custom_icon(icon_name)
|
||||
end
|
||||
|
||||
def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
|
||||
def render_commit_status(commit, tooltip_placement: 'auto left')
|
||||
project = commit.project
|
||||
path = builds_namespace_project_commit_path(project.namespace, project, commit)
|
||||
render_status_with_link('commit', commit.status, path, tooltip_placement, cssclass: cssclass)
|
||||
render_status_with_link('commit', commit.status, path, tooltip_placement)
|
||||
end
|
||||
|
||||
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
|
||||
|
|
|
@ -16,16 +16,6 @@ module CommitsHelper
|
|||
commit_person_link(commit, options.merge(source: :committer))
|
||||
end
|
||||
|
||||
def commit_author_avatar(commit, options = {})
|
||||
options = options.merge(source: :author)
|
||||
user = commit.send(options[:source])
|
||||
|
||||
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
|
||||
person_email = user.try(:email) || source_email
|
||||
|
||||
image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]} hidden-xs", width: options[:size], alt: "")
|
||||
end
|
||||
|
||||
def image_diff_class(diff)
|
||||
if diff.deleted_file
|
||||
"deleted"
|
||||
|
|
|
@ -54,18 +54,20 @@ module DiffHelper
|
|||
end
|
||||
end
|
||||
|
||||
def organize_comments(left, right)
|
||||
notes_left = notes_right = nil
|
||||
def parallel_diff_discussions(left, right, diff_file)
|
||||
discussion_left = discussion_right = nil
|
||||
|
||||
unless left[:type].nil? && right[:type] == 'new'
|
||||
notes_left = @grouped_diff_notes[left[:line_code]]
|
||||
if left && (left.unchanged? || left.removed?)
|
||||
line_code = diff_file.line_code(left)
|
||||
discussion_left = @grouped_diff_discussions[line_code]
|
||||
end
|
||||
|
||||
unless left[:type].nil? && right[:type].nil?
|
||||
notes_right = @grouped_diff_notes[right[:line_code]]
|
||||
if right && right.added?
|
||||
line_code = diff_file.line_code(right)
|
||||
discussion_right = @grouped_diff_discussions[line_code]
|
||||
end
|
||||
|
||||
[notes_left, notes_right]
|
||||
[discussion_left, discussion_right]
|
||||
end
|
||||
|
||||
def inline_diff_btn
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
module ExternalWikiHelper
|
||||
def get_project_wiki_path(project)
|
||||
external_wiki_service = project.services.
|
||||
find { |service| service.to_param == 'external_wiki' }
|
||||
if external_wiki_service.present? && external_wiki_service.active?
|
||||
external_wiki_service = project.external_wiki
|
||||
if external_wiki_service
|
||||
external_wiki_service.properties['external_wiki_url']
|
||||
else
|
||||
namespace_project_wiki_path(project.namespace, project, :home)
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
module NotesHelper
|
||||
# Helps to distinguish e.g. commit notes in mr notes list
|
||||
def note_for_main_target?(note)
|
||||
@noteable.class.name == note.noteable_type && !note.diff_note?
|
||||
end
|
||||
|
||||
def note_target_fields(note)
|
||||
if note.noteable
|
||||
hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
|
||||
|
@ -44,8 +39,8 @@ module NotesHelper
|
|||
# If we didn't, diff notes that would show for the same line on the changes
|
||||
# tab, would show in different discussions on the discussion tab.
|
||||
use_legacy_diff_note ||= begin
|
||||
line_diff_notes = @grouped_diff_notes[line_code]
|
||||
line_diff_notes && line_diff_notes.any?(&:legacy_diff_note?)
|
||||
discussion = @grouped_diff_discussions[line_code]
|
||||
discussion && discussion.legacy_diff_discussion?
|
||||
end
|
||||
|
||||
data = {
|
||||
|
@ -81,22 +76,10 @@ module NotesHelper
|
|||
data
|
||||
end
|
||||
|
||||
def link_to_reply_discussion(note, line_type = nil)
|
||||
def link_to_reply_discussion(discussion, line_type = nil)
|
||||
return unless current_user
|
||||
|
||||
data = {
|
||||
noteable_type: note.noteable_type,
|
||||
noteable_id: note.noteable_id,
|
||||
commit_id: note.commit_id,
|
||||
discussion_id: note.discussion_id,
|
||||
line_type: line_type
|
||||
}
|
||||
|
||||
if note.diff_note?
|
||||
data[:note_type] = note.type
|
||||
|
||||
data.merge!(note.diff_attributes)
|
||||
end
|
||||
data = discussion.reply_attributes.merge(line_type: line_type)
|
||||
|
||||
content_tag(:div, class: "discussion-reply-holder") do
|
||||
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
|
||||
|
@ -114,13 +97,13 @@ module NotesHelper
|
|||
@max_access_by_user_id[full_key]
|
||||
end
|
||||
|
||||
def diff_note_path(note)
|
||||
return unless note.diff_note?
|
||||
def discussion_diff_path(discussion)
|
||||
return unless discussion.diff_discussion?
|
||||
|
||||
if note.for_merge_request? && note.active?
|
||||
diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
|
||||
elsif note.for_commit?
|
||||
namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
|
||||
if discussion.for_merge_request? && discussion.active?
|
||||
diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
|
||||
elsif discussion.for_commit?
|
||||
namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -112,7 +112,8 @@ module SearchHelper
|
|||
search: params[:search],
|
||||
project_id: params[:project_id],
|
||||
group_id: params[:group_id],
|
||||
scope: params[:scope]
|
||||
scope: params[:scope],
|
||||
repository_ref: params[:repository_ref]
|
||||
}
|
||||
|
||||
options = exist_opts.merge(options)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
module ServicesHelper
|
||||
def service_event_description(event)
|
||||
case event
|
||||
when "push"
|
||||
"Event will be triggered by a push to the repository"
|
||||
when "tag_push"
|
||||
"Event will be triggered when a new tag is pushed to the repository"
|
||||
when "note"
|
||||
"Event will be triggered when someone adds a comment"
|
||||
when "issue"
|
||||
"Event will be triggered when an issue is created/updated/merged"
|
||||
when "merge_request"
|
||||
"Event will be triggered when a merge request is created/updated/merged"
|
||||
when "build"
|
||||
"Event will be triggered when a build status changes"
|
||||
when "wiki_page"
|
||||
"Event will be triggered when a wiki page is created/updated"
|
||||
end
|
||||
end
|
||||
|
||||
def service_event_field_name(event)
|
||||
event = event.pluralize if %w[merge_request issue].include?(event)
|
||||
"#{event}_events"
|
||||
end
|
||||
end
|
|
@ -1,15 +1,6 @@
|
|||
module TimeHelper
|
||||
def duration_in_words(finished_at, started_at)
|
||||
if finished_at && started_at
|
||||
interval_in_seconds = finished_at.to_i - started_at.to_i
|
||||
elsif started_at
|
||||
interval_in_seconds = Time.now.to_i - started_at.to_i
|
||||
end
|
||||
|
||||
time_interval_in_words(interval_in_seconds)
|
||||
end
|
||||
|
||||
def time_interval_in_words(interval_in_seconds)
|
||||
interval_in_seconds = interval_in_seconds.to_i
|
||||
minutes = interval_in_seconds / 60
|
||||
seconds = interval_in_seconds - minutes * 60
|
||||
|
||||
|
@ -25,9 +16,19 @@ module TimeHelper
|
|||
end
|
||||
|
||||
def duration_in_numbers(finished_at, started_at)
|
||||
diff_in_seconds = finished_at.to_i - started_at.to_i
|
||||
time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S"
|
||||
interval = interval_in_seconds(started_at, finished_at)
|
||||
time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
|
||||
|
||||
Time.at(diff_in_seconds).utc.strftime(time_format)
|
||||
Time.at(interval).utc.strftime(time_format)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def interval_in_seconds(started_at, finished_at = nil)
|
||||
if started_at && finished_at
|
||||
finished_at.to_i - started_at.to_i
|
||||
elsif started_at
|
||||
Time.now.to_i - started_at.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -172,7 +172,7 @@ class Ability
|
|||
rules << :read_build if project.public_builds?
|
||||
|
||||
unless owner || project.team.member?(user) || project_group_member?(project, user)
|
||||
rules << :request_access
|
||||
rules << :request_access if project.request_access_enabled
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -373,7 +373,7 @@ class Ability
|
|||
end
|
||||
|
||||
if group.public? || (group.internal? && !user.external?)
|
||||
rules << :request_access unless group.users.include?(user)
|
||||
rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
|
||||
end
|
||||
|
||||
rules.flatten
|
||||
|
|
|
@ -4,12 +4,20 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
add_authentication_token_field :health_check_access_token
|
||||
|
||||
CACHE_KEY = 'application_setting.last'
|
||||
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
|
||||
| # or
|
||||
\s # any whitespace character
|
||||
| # or
|
||||
[\r\n] # any number of newline characters
|
||||
}x
|
||||
|
||||
serialize :restricted_visibility_levels
|
||||
serialize :import_sources
|
||||
serialize :disabled_oauth_sign_in_sources, Array
|
||||
serialize :restricted_signup_domains, Array
|
||||
attr_accessor :restricted_signup_domains_raw
|
||||
serialize :domain_whitelist, Array
|
||||
serialize :domain_blacklist, Array
|
||||
|
||||
attr_accessor :domain_whitelist_raw, :domain_blacklist_raw
|
||||
|
||||
validates :session_expire_delay,
|
||||
presence: true,
|
||||
|
@ -62,6 +70,10 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
validates :enabled_git_access_protocol,
|
||||
inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
|
||||
|
||||
validates :domain_blacklist,
|
||||
presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' },
|
||||
if: :domain_blacklist_enabled?
|
||||
|
||||
validates_each :restricted_visibility_levels do |record, attr, value|
|
||||
unless value.nil?
|
||||
value.each do |level|
|
||||
|
@ -129,7 +141,7 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
session_expire_delay: Settings.gitlab['session_expire_delay'],
|
||||
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
|
||||
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
|
||||
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
|
||||
domain_whitelist: Settings.gitlab['domain_whitelist'],
|
||||
import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project],
|
||||
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
|
||||
max_artifacts_size: Settings.artifacts['max_size'],
|
||||
|
@ -150,20 +162,30 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
|
||||
end
|
||||
|
||||
def restricted_signup_domains_raw
|
||||
self.restricted_signup_domains.join("\n") unless self.restricted_signup_domains.nil?
|
||||
def domain_whitelist_raw
|
||||
self.domain_whitelist.join("\n") unless self.domain_whitelist.nil?
|
||||
end
|
||||
|
||||
def restricted_signup_domains_raw=(values)
|
||||
self.restricted_signup_domains = []
|
||||
self.restricted_signup_domains = values.split(
|
||||
/\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
|
||||
| # or
|
||||
\s # any whitespace character
|
||||
| # or
|
||||
[\r\n] # any number of newline characters
|
||||
/x)
|
||||
self.restricted_signup_domains.reject! { |d| d.empty? }
|
||||
def domain_blacklist_raw
|
||||
self.domain_blacklist.join("\n") unless self.domain_blacklist.nil?
|
||||
end
|
||||
|
||||
def domain_whitelist_raw=(values)
|
||||
self.domain_whitelist = []
|
||||
self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
|
||||
self.domain_whitelist.reject! { |d| d.empty? }
|
||||
self.domain_whitelist
|
||||
end
|
||||
|
||||
def domain_blacklist_raw=(values)
|
||||
self.domain_blacklist = []
|
||||
self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
|
||||
self.domain_blacklist.reject! { |d| d.empty? }
|
||||
self.domain_blacklist
|
||||
end
|
||||
|
||||
def domain_blacklist_file=(file)
|
||||
self.domain_blacklist_raw = file.read
|
||||
end
|
||||
|
||||
def runners_registration_token
|
||||
|
|
|
@ -12,7 +12,7 @@ module Ci
|
|||
|
||||
scope :unstarted, ->() { where(runner_id: nil) }
|
||||
scope :ignore_failures, ->() { where(allow_failure: false) }
|
||||
scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
|
||||
scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) }
|
||||
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
|
||||
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
|
||||
scope :manual_actions, ->() { where(when: :manual) }
|
||||
|
@ -97,7 +97,7 @@ module Ci
|
|||
end
|
||||
|
||||
def other_actions
|
||||
pipeline.manual_actions.where.not(id: self)
|
||||
pipeline.manual_actions.where.not(name: name)
|
||||
end
|
||||
|
||||
def playable?
|
||||
|
@ -145,7 +145,15 @@ module Ci
|
|||
end
|
||||
|
||||
def variables
|
||||
predefined_variables + yaml_variables + project_variables + trigger_variables
|
||||
variables = predefined_variables
|
||||
variables += project.predefined_variables
|
||||
variables += pipeline.predefined_variables
|
||||
variables += runner.predefined_variables if runner
|
||||
variables += project.container_registry_variables
|
||||
variables += yaml_variables
|
||||
variables += project.secret_variables
|
||||
variables += trigger_request.user_variables if trigger_request
|
||||
variables
|
||||
end
|
||||
|
||||
def merge_request
|
||||
|
@ -430,28 +438,23 @@ module Ci
|
|||
self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil)
|
||||
end
|
||||
|
||||
def project_variables
|
||||
project.variables.map do |variable|
|
||||
{ key: variable.key, value: variable.value, public: false }
|
||||
end
|
||||
end
|
||||
|
||||
def trigger_variables
|
||||
if trigger_request && trigger_request.variables
|
||||
trigger_request.variables.map do |key, value|
|
||||
{ key: key, value: value, public: false }
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
variables = []
|
||||
variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
|
||||
variables << { key: :CI_BUILD_NAME, value: name, public: true }
|
||||
variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
|
||||
variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
|
||||
variables = [
|
||||
{ key: 'CI', value: 'true', public: true },
|
||||
{ key: 'GITLAB_CI', value: 'true', public: true },
|
||||
{ key: 'CI_BUILD_ID', value: id.to_s, public: true },
|
||||
{ key: 'CI_BUILD_TOKEN', value: token, public: false },
|
||||
{ key: 'CI_BUILD_REF', value: sha, public: true },
|
||||
{ key: 'CI_BUILD_BEFORE_SHA', value: before_sha, public: true },
|
||||
{ key: 'CI_BUILD_REF_NAME', value: ref, public: true },
|
||||
{ key: 'CI_BUILD_NAME', value: name, public: true },
|
||||
{ key: 'CI_BUILD_STAGE', value: stage, public: true },
|
||||
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
|
||||
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
|
||||
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }
|
||||
]
|
||||
variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
|
||||
variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
|
||||
variables
|
||||
end
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ module Ci
|
|||
after_touch :update_state
|
||||
after_save :keep_around_commits
|
||||
|
||||
# ref can't be HEAD or SHA, can only be branch/tag name
|
||||
scope :latest_successful_for, ->(ref = default_branch) do
|
||||
where(ref: ref).success.order(id: :desc).limit(1)
|
||||
end
|
||||
|
||||
def self.truncate_sha(sha)
|
||||
sha[0...8]
|
||||
end
|
||||
|
@ -146,6 +151,10 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def has_warnings?
|
||||
builds.latest.ignored.any?
|
||||
end
|
||||
|
||||
def config_processor
|
||||
return nil unless ci_yaml_file
|
||||
return @config_processor if defined?(@config_processor)
|
||||
|
@ -198,6 +207,12 @@ module Ci
|
|||
Note.for_commit_id(sha)
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
[
|
||||
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_builds_for_stages(stages, user, status, trigger_request)
|
||||
|
@ -206,8 +221,9 @@ module Ci
|
|||
# build builds only for the first stage that has builds available.
|
||||
#
|
||||
stages.any? do |stage|
|
||||
CreateBuildsService.new(self)
|
||||
.execute(stage, user, status, trigger_request).present?
|
||||
CreateBuildsService.new(self).
|
||||
execute(stage, user, status, trigger_request).
|
||||
any?(&:active?)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -226,7 +242,7 @@ module Ci
|
|||
|
||||
def keep_around_commits
|
||||
return unless project
|
||||
|
||||
|
||||
project.repository.keep_around(self.sha)
|
||||
project.repository.keep_around(self.before_sha)
|
||||
end
|
||||
|
|
|
@ -114,6 +114,14 @@ module Ci
|
|||
tag_list.any?
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
[
|
||||
{ key: 'CI_RUNNER_ID', value: id.to_s, public: true },
|
||||
{ key: 'CI_RUNNER_DESCRIPTION', value: description, public: true },
|
||||
{ key: 'CI_RUNNER_TAGS', value: tag_list.to_s, public: true }
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tag_constraints
|
||||
|
|
|
@ -7,5 +7,13 @@ module Ci
|
|||
has_many :builds, class_name: 'Ci::Build'
|
||||
|
||||
serialize :variables
|
||||
|
||||
def user_variables
|
||||
return [] unless variables
|
||||
|
||||
variables.map do |key, value|
|
||||
{ key: key, value: value, public: false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,11 @@ class CommitStatus < ActiveRecord::Base
|
|||
|
||||
alias_attribute :author, :user
|
||||
|
||||
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
|
||||
scope :latest, -> do
|
||||
max_id = unscope(:select).select("max(#{quoted_table_name}.id)")
|
||||
|
||||
where(id: max_id.group(:name, :commit_id))
|
||||
end
|
||||
scope :retried, -> { where.not(id: latest) }
|
||||
scope :ordered, -> { order(:name) }
|
||||
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
module NoteOnDiff
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
NUMBER_OF_TRUNCATED_DIFF_LINES = 16
|
||||
|
||||
included do
|
||||
delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
|
||||
end
|
||||
|
||||
def diff_note?
|
||||
true
|
||||
end
|
||||
|
@ -30,23 +24,4 @@ module NoteOnDiff
|
|||
def can_be_award_emoji?
|
||||
false
|
||||
end
|
||||
|
||||
# Returns an array of at most 16 highlighted lines above a diff note
|
||||
def truncated_diff_lines
|
||||
prev_lines = []
|
||||
|
||||
highlighted_diff_lines.each do |line|
|
||||
if line.meta?
|
||||
prev_lines.clear
|
||||
else
|
||||
prev_lines << line
|
||||
|
||||
break if for_line?(line)
|
||||
|
||||
prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
|
||||
end
|
||||
end
|
||||
|
||||
prev_lines
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
class Discussion
|
||||
NUMBER_OF_TRUNCATED_DIFF_LINES = 16
|
||||
|
||||
attr_reader :first_note, :notes
|
||||
|
||||
delegate :created_at,
|
||||
:project,
|
||||
:author,
|
||||
|
||||
:noteable,
|
||||
:for_commit?,
|
||||
:for_merge_request?,
|
||||
|
||||
:line_code,
|
||||
:diff_file,
|
||||
:for_line?,
|
||||
:active?,
|
||||
|
||||
to: :first_note
|
||||
|
||||
delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
|
||||
|
||||
def self.for_notes(notes)
|
||||
notes.group_by(&:discussion_id).values.map { |notes| new(notes) }
|
||||
end
|
||||
|
||||
def self.for_diff_notes(notes)
|
||||
notes.group_by(&:line_code).values.map { |notes| new(notes) }
|
||||
end
|
||||
|
||||
def initialize(notes)
|
||||
@first_note = notes.first
|
||||
@notes = notes
|
||||
end
|
||||
|
||||
def id
|
||||
first_note.discussion_id
|
||||
end
|
||||
|
||||
def diff_discussion?
|
||||
first_note.diff_note?
|
||||
end
|
||||
|
||||
def legacy_diff_discussion?
|
||||
notes.any?(&:legacy_diff_note?)
|
||||
end
|
||||
|
||||
def for_target?(target)
|
||||
self.noteable == target && !diff_discussion?
|
||||
end
|
||||
|
||||
def expanded?
|
||||
!diff_discussion? || active?
|
||||
end
|
||||
|
||||
def reply_attributes
|
||||
data = {
|
||||
noteable_type: first_note.noteable_type,
|
||||
noteable_id: first_note.noteable_id,
|
||||
commit_id: first_note.commit_id,
|
||||
discussion_id: self.id,
|
||||
}
|
||||
|
||||
if diff_discussion?
|
||||
data[:note_type] = first_note.type
|
||||
|
||||
data.merge!(first_note.diff_attributes)
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
# Returns an array of at most 16 highlighted lines above a diff note
|
||||
def truncated_diff_lines
|
||||
prev_lines = []
|
||||
|
||||
highlighted_diff_lines.each do |line|
|
||||
if line.meta?
|
||||
prev_lines.clear
|
||||
else
|
||||
prev_lines << line
|
||||
|
||||
break if for_line?(line)
|
||||
|
||||
prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
|
||||
end
|
||||
end
|
||||
|
||||
prev_lines
|
||||
end
|
||||
end
|
|
@ -52,10 +52,50 @@ class Issue < ActiveRecord::Base
|
|||
attributes
|
||||
end
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
# Returns the project that the current scope belongs to if any, nil otherwise.
|
||||
#
|
||||
# Examples:
|
||||
# - my_project.issues.without_due_date.owner_project => my_project
|
||||
# - Issue.all.owner_project => nil
|
||||
def owner_project
|
||||
# No owner if we're not being called from an association
|
||||
return unless all.respond_to?(:proxy_association)
|
||||
|
||||
owner = all.proxy_association.owner
|
||||
|
||||
# Check if the association is or belongs to a project
|
||||
if owner.is_a?(Project)
|
||||
owner
|
||||
else
|
||||
begin
|
||||
owner.association(:project).target
|
||||
rescue ActiveRecord::AssociationNotFoundError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.visible_to_user(user)
|
||||
return where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank?
|
||||
return all if user.admin?
|
||||
|
||||
# Check if we are scoped to a specific project's issues
|
||||
if owner_project
|
||||
if owner_project.authorized_for_user?(user, Gitlab::Access::REPORTER)
|
||||
# If the project is authorized for the user, they can see all issues in the project
|
||||
return all
|
||||
else
|
||||
# else only non confidential and authored/assigned to them
|
||||
return where('issues.confidential IS NULL OR issues.confidential IS FALSE
|
||||
OR issues.author_id = :user_id OR issues.assignee_id = :user_id',
|
||||
user_id: user.id)
|
||||
end
|
||||
end
|
||||
|
||||
where('
|
||||
issues.confidential IS NULL
|
||||
OR issues.confidential IS FALSE
|
||||
|
|
|
@ -82,11 +82,12 @@ class Note < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def discussions
|
||||
all.group_by(&:discussion_id).values
|
||||
Discussion.for_notes(all)
|
||||
end
|
||||
|
||||
def grouped_diff_notes
|
||||
diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
|
||||
def grouped_diff_discussions
|
||||
notes = diff_notes.fresh.select(&:active?)
|
||||
Discussion.for_diff_notes(notes).map { |d| [d.line_code, d] }.to_h
|
||||
end
|
||||
|
||||
# Searches for notes matching the given query.
|
||||
|
|
|
@ -429,6 +429,17 @@ class Project < ActiveRecord::Base
|
|||
repository.commit(ref)
|
||||
end
|
||||
|
||||
# ref can't be HEAD, can only be branch/tag name or SHA
|
||||
def latest_successful_builds_for(ref = default_branch)
|
||||
latest_pipeline = pipelines.latest_successful_for(ref).first
|
||||
|
||||
if latest_pipeline
|
||||
latest_pipeline.builds.latest.with_artifacts
|
||||
else
|
||||
builds.none
|
||||
end
|
||||
end
|
||||
|
||||
def merge_base_commit(first_commit_id, second_commit_id)
|
||||
sha = repository.merge_base(first_commit_id, second_commit_id)
|
||||
repository.commit(sha) if sha
|
||||
|
@ -650,6 +661,22 @@ class Project < ActiveRecord::Base
|
|||
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
|
||||
end
|
||||
|
||||
def external_wiki
|
||||
if has_external_wiki.nil?
|
||||
cache_has_external_wiki # Populate
|
||||
end
|
||||
|
||||
if has_external_wiki
|
||||
@external_wiki ||= services.external_wikis.first
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cache_has_external_wiki
|
||||
update_column(:has_external_wiki, services.external_wikis.any?)
|
||||
end
|
||||
|
||||
def build_missing_services
|
||||
services_templates = Service.where(template: true)
|
||||
|
||||
|
@ -1164,4 +1191,74 @@ class Project < ActiveRecord::Base
|
|||
def ensure_dir_exist
|
||||
gitlab_shell.add_namespace(repository_storage_path, namespace.path)
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
[
|
||||
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true },
|
||||
{ key: 'CI_PROJECT_NAME', value: path, public: true },
|
||||
{ key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true },
|
||||
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.path, public: true },
|
||||
{ key: 'CI_PROJECT_URL', value: web_url, public: true }
|
||||
]
|
||||
end
|
||||
|
||||
def container_registry_variables
|
||||
return [] unless Gitlab.config.registry.enabled
|
||||
|
||||
variables = [
|
||||
{ key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port, public: true }
|
||||
]
|
||||
|
||||
if container_registry_enabled?
|
||||
variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_repository_url, public: true }
|
||||
end
|
||||
|
||||
variables
|
||||
end
|
||||
|
||||
def secret_variables
|
||||
variables.map do |variable|
|
||||
{ key: variable.key, value: variable.value, public: false }
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if `user` is authorized for this project, with at least the
|
||||
# `min_access_level` (if given).
|
||||
#
|
||||
# If you change the logic of this method, please also update `User#authorized_projects`
|
||||
def authorized_for_user?(user, min_access_level = nil)
|
||||
return false unless user
|
||||
|
||||
return true if personal? && namespace_id == user.namespace_id
|
||||
|
||||
authorized_for_user_by_group?(user, min_access_level) ||
|
||||
authorized_for_user_by_members?(user, min_access_level) ||
|
||||
authorized_for_user_by_shared_projects?(user, min_access_level)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorized_for_user_by_group?(user, min_access_level)
|
||||
member = user.group_members.find_by(source_id: group)
|
||||
|
||||
member && (!min_access_level || member.access_level >= min_access_level)
|
||||
end
|
||||
|
||||
def authorized_for_user_by_members?(user, min_access_level)
|
||||
member = members.find_by(user_id: user)
|
||||
|
||||
member && (!min_access_level || member.access_level >= min_access_level)
|
||||
end
|
||||
|
||||
def authorized_for_user_by_shared_projects?(user, min_access_level)
|
||||
shared_projects = user.group_members.joins(group: :shared_projects).
|
||||
where(project_group_links: { project_id: self })
|
||||
|
||||
if min_access_level
|
||||
members_scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
|
||||
shared_projects = shared_projects.where(members: members_scope)
|
||||
end
|
||||
|
||||
shared_projects.any?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,9 @@ class SlackService < Service
|
|||
validates :webhook, presence: true, url: true, if: :activated?
|
||||
|
||||
def initialize_properties
|
||||
# Custom serialized properties initialization
|
||||
self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
|
||||
|
||||
if properties.nil?
|
||||
self.properties = {}
|
||||
self.notify_only_broken_builds = true
|
||||
|
@ -29,13 +32,15 @@ class SlackService < Service
|
|||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{ type: 'text', name: 'webhook',
|
||||
placeholder: 'https://hooks.slack.com/services/...' },
|
||||
{ type: 'text', name: 'username', placeholder: 'username' },
|
||||
{ type: 'text', name: 'channel', placeholder: '#channel' },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_builds' },
|
||||
]
|
||||
default_fields =
|
||||
[
|
||||
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
|
||||
{ type: 'text', name: 'username', placeholder: 'username' },
|
||||
{ type: 'text', name: 'channel', placeholder: "#general" },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_builds' },
|
||||
]
|
||||
|
||||
default_fields + build_event_channels
|
||||
end
|
||||
|
||||
def supported_events
|
||||
|
@ -74,7 +79,10 @@ class SlackService < Service
|
|||
end
|
||||
|
||||
opt = {}
|
||||
opt[:channel] = channel if channel
|
||||
|
||||
event_channel = get_channel_field(object_kind) || channel
|
||||
|
||||
opt[:channel] = event_channel if event_channel
|
||||
opt[:username] = username if username
|
||||
|
||||
if message
|
||||
|
@ -83,8 +91,35 @@ class SlackService < Service
|
|||
end
|
||||
end
|
||||
|
||||
def event_channel_names
|
||||
supported_events.map { |event| event_channel_name(event) }
|
||||
end
|
||||
|
||||
def event_field(event)
|
||||
fields.find { |field| field[:name] == event_channel_name(event) }
|
||||
end
|
||||
|
||||
def global_fields
|
||||
fields.reject { |field| field[:name].end_with?('channel') }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_channel_field(event)
|
||||
field_name = event_channel_name(event)
|
||||
self.public_send(field_name)
|
||||
end
|
||||
|
||||
def build_event_channels
|
||||
supported_events.reduce([]) do |channels, event|
|
||||
channels << { type: 'text', name: event_channel_name(event), placeholder: "#general" }
|
||||
end
|
||||
end
|
||||
|
||||
def event_channel_name(event)
|
||||
"#{event}_channel"
|
||||
end
|
||||
|
||||
def project_name
|
||||
project.name_with_namespace.gsub(/\s/, '')
|
||||
end
|
||||
|
|
|
@ -173,7 +173,7 @@ class ProjectTeam
|
|||
invited_members = []
|
||||
|
||||
if project.invited_groups.any? && project.allowed_to_share_with_group?
|
||||
project.project_group_links.each do |group_link|
|
||||
project.project_group_links.includes(group: [:group_members]).each do |group_link|
|
||||
invited_group = group_link.group
|
||||
im = invited_group.members
|
||||
|
||||
|
|
|
@ -11,16 +11,6 @@ class Repository
|
|||
|
||||
attr_accessor :path_with_namespace, :project
|
||||
|
||||
def self.clean_old_archives
|
||||
Gitlab::Metrics.measure(:clean_old_archives) do
|
||||
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
|
||||
|
||||
return unless File.directory?(repository_downloads_path)
|
||||
|
||||
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(path_with_namespace, project)
|
||||
@path_with_namespace = path_with_namespace
|
||||
@project = project
|
||||
|
@ -80,7 +70,12 @@ class Repository
|
|||
|
||||
def commit(ref = 'HEAD')
|
||||
return nil unless exists?
|
||||
commit = Gitlab::Git::Commit.find(raw_repository, ref)
|
||||
commit =
|
||||
if ref.is_a?(Gitlab::Git::Commit)
|
||||
ref
|
||||
else
|
||||
Gitlab::Git::Commit.find(raw_repository, ref)
|
||||
end
|
||||
commit = ::Commit.new(commit, @project) if commit
|
||||
commit
|
||||
rescue Rugged::OdbError
|
||||
|
@ -216,11 +211,20 @@ class Repository
|
|||
|
||||
return if kept_around?(sha)
|
||||
|
||||
rugged.references.create(keep_around_ref_name(sha), sha)
|
||||
# This will still fail if the file is corrupted (e.g. 0 bytes)
|
||||
begin
|
||||
rugged.references.create(keep_around_ref_name(sha), sha, force: true)
|
||||
rescue Rugged::ReferenceError => ex
|
||||
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
|
||||
end
|
||||
end
|
||||
|
||||
def kept_around?(sha)
|
||||
ref_exists?(keep_around_ref_name(sha))
|
||||
begin
|
||||
ref_exists?(keep_around_ref_name(sha))
|
||||
rescue Rugged::ReferenceError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def tag_names
|
||||
|
@ -257,10 +261,10 @@ class Repository
|
|||
# Rugged seems to throw a `ReferenceError` when given branch_names rather
|
||||
# than SHA-1 hashes
|
||||
number_commits_behind = raw_repository.
|
||||
count_commits_between(branch.target, root_ref_hash)
|
||||
count_commits_between(branch.target.sha, root_ref_hash)
|
||||
|
||||
number_commits_ahead = raw_repository.
|
||||
count_commits_between(root_ref_hash, branch.target)
|
||||
count_commits_between(root_ref_hash, branch.target.sha)
|
||||
|
||||
{ behind: number_commits_behind, ahead: number_commits_ahead }
|
||||
end
|
||||
|
@ -392,6 +396,11 @@ class Repository
|
|||
|
||||
expire_cache if exists?
|
||||
|
||||
# expire cache that don't depend on repository data (when expiring)
|
||||
expire_tags_cache
|
||||
expire_tag_count_cache
|
||||
expire_branches_cache
|
||||
expire_branch_count_cache
|
||||
expire_root_ref_cache
|
||||
expire_emptiness_caches
|
||||
expire_exists_cache
|
||||
|
@ -681,9 +690,7 @@ class Repository
|
|||
end
|
||||
|
||||
def local_branches
|
||||
@local_branches ||= rugged.branches.each(:local).map do |branch|
|
||||
Gitlab::Git::Branch.new(branch.name, branch.target)
|
||||
end
|
||||
@local_branches ||= raw_repository.local_branches
|
||||
end
|
||||
|
||||
alias_method :branches, :local_branches
|
||||
|
@ -824,7 +831,7 @@ class Repository
|
|||
end
|
||||
|
||||
def revert(user, commit, base_branch, revert_tree_id = nil)
|
||||
source_sha = find_branch(base_branch).target
|
||||
source_sha = find_branch(base_branch).target.sha
|
||||
revert_tree_id ||= check_revert_content(commit, base_branch)
|
||||
|
||||
return false unless revert_tree_id
|
||||
|
@ -841,7 +848,7 @@ class Repository
|
|||
end
|
||||
|
||||
def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
|
||||
source_sha = find_branch(base_branch).target
|
||||
source_sha = find_branch(base_branch).target.sha
|
||||
cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
|
||||
|
||||
return false unless cherry_pick_tree_id
|
||||
|
@ -862,7 +869,7 @@ class Repository
|
|||
end
|
||||
|
||||
def check_revert_content(commit, base_branch)
|
||||
source_sha = find_branch(base_branch).target
|
||||
source_sha = find_branch(base_branch).target.sha
|
||||
args = [commit.id, source_sha]
|
||||
args << { mainline: 1 } if commit.merge_commit?
|
||||
|
||||
|
@ -876,7 +883,7 @@ class Repository
|
|||
end
|
||||
|
||||
def check_cherry_pick_content(commit, base_branch)
|
||||
source_sha = find_branch(base_branch).target
|
||||
source_sha = find_branch(base_branch).target.sha
|
||||
args = [commit.id, source_sha]
|
||||
args << 1 if commit.merge_commit?
|
||||
|
||||
|
@ -1041,7 +1048,7 @@ class Repository
|
|||
end
|
||||
|
||||
def tags_sorted_by_committed_date
|
||||
tags.sort_by { |tag| commit(tag.target).committed_date }
|
||||
tags.sort_by { |tag| tag.target.committed_date }
|
||||
end
|
||||
|
||||
def keep_around_ref_name(sha)
|
||||
|
|
|
@ -17,6 +17,7 @@ class Service < ActiveRecord::Base
|
|||
|
||||
after_commit :reset_updated_properties
|
||||
after_commit :cache_project_has_external_issue_tracker
|
||||
after_commit :cache_project_has_external_wiki
|
||||
|
||||
belongs_to :project, inverse_of: :services
|
||||
has_one :service_hook
|
||||
|
@ -25,6 +26,7 @@ class Service < ActiveRecord::Base
|
|||
|
||||
scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
|
||||
scope :issue_trackers, -> { where(category: 'issue_tracker') }
|
||||
scope :external_wikis, -> { where(type: 'ExternalWikiService').active }
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :without_defaults, -> { where(default: false) }
|
||||
|
||||
|
@ -80,6 +82,18 @@ class Service < ActiveRecord::Base
|
|||
Gitlab::PushDataBuilder.build_sample(project, user)
|
||||
end
|
||||
|
||||
def event_channel_names
|
||||
[]
|
||||
end
|
||||
|
||||
def event_field(event)
|
||||
nil
|
||||
end
|
||||
|
||||
def global_fields
|
||||
fields
|
||||
end
|
||||
|
||||
def supported_events
|
||||
%w(push tag_push issue merge_request wiki_page)
|
||||
end
|
||||
|
@ -212,4 +226,10 @@ class Service < ActiveRecord::Base
|
|||
project.cache_has_external_issue_tracker
|
||||
end
|
||||
end
|
||||
|
||||
def cache_project_has_external_wiki
|
||||
if project && !project.destroyed?
|
||||
project.cache_has_external_wiki
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -111,7 +111,7 @@ class User < ActiveRecord::Base
|
|||
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
|
||||
|
||||
before_validation :generate_password, on: :create
|
||||
before_validation :restricted_signup_domains, on: :create
|
||||
before_validation :signup_domain_valid?, on: :create
|
||||
before_validation :sanitize_attrs
|
||||
before_validation :set_notification_email, if: ->(user) { user.email_changed? }
|
||||
before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
|
||||
|
@ -412,6 +412,8 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
# Returns projects user is authorized to access.
|
||||
#
|
||||
# If you change the logic of this method, please also update `Project#authorized_for_user`
|
||||
def authorized_projects(min_access_level = nil)
|
||||
Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})")
|
||||
end
|
||||
|
@ -760,29 +762,6 @@ class User < ActiveRecord::Base
|
|||
Project.where(id: events)
|
||||
end
|
||||
|
||||
def restricted_signup_domains
|
||||
email_domains = current_application_settings.restricted_signup_domains
|
||||
|
||||
unless email_domains.blank?
|
||||
match_found = email_domains.any? do |domain|
|
||||
escaped = Regexp.escape(domain).gsub('\*', '.*?')
|
||||
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
|
||||
email_domain = Mail::Address.new(self.email).domain
|
||||
email_domain =~ regexp
|
||||
end
|
||||
|
||||
unless match_found
|
||||
self.errors.add :email,
|
||||
'is not whitelisted. ' +
|
||||
'Email domains valid for registration are: ' +
|
||||
email_domains.join(', ')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def can_be_removed?
|
||||
!solo_owned_groups.present?
|
||||
end
|
||||
|
@ -854,7 +833,7 @@ class User < ActiveRecord::Base
|
|||
groups.joins(:shared_projects).select(:project_id)]
|
||||
|
||||
if min_access_level
|
||||
scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
|
||||
scope = { access_level: Gitlab::Access.all_values.select { |access| access >= min_access_level } }
|
||||
relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
|
||||
end
|
||||
|
||||
|
@ -881,4 +860,40 @@ class User < ActiveRecord::Base
|
|||
self.can_create_group = false
|
||||
self.projects_limit = 0
|
||||
end
|
||||
|
||||
def signup_domain_valid?
|
||||
valid = true
|
||||
error = nil
|
||||
|
||||
if current_application_settings.domain_blacklist_enabled?
|
||||
blocked_domains = current_application_settings.domain_blacklist
|
||||
if domain_matches?(blocked_domains, self.email)
|
||||
error = 'is not from an allowed domain.'
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
allowed_domains = current_application_settings.domain_whitelist
|
||||
unless allowed_domains.blank?
|
||||
if domain_matches?(allowed_domains, self.email)
|
||||
valid = true
|
||||
else
|
||||
error = "is not whitelisted. Email domains valid for registration are: #{allowed_domains.join(', ')}"
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
self.errors.add(:email, error) unless valid
|
||||
|
||||
valid
|
||||
end
|
||||
|
||||
def domain_matches?(email_domains, email)
|
||||
signup_domain = Mail::Address.new(email).domain
|
||||
email_domains.any? do |domain|
|
||||
escaped = Regexp.escape(domain).gsub('\*', '.*?')
|
||||
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
|
||||
signup_domain =~ regexp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,6 +40,6 @@ class DeleteBranchService < BaseService
|
|||
|
||||
def build_push_data(branch)
|
||||
Gitlab::PushDataBuilder
|
||||
.build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
|
||||
.build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,6 +34,6 @@ class DeleteTagService < BaseService
|
|||
|
||||
def build_push_data(tag)
|
||||
Gitlab::PushDataBuilder
|
||||
.build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
|
||||
.build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,8 +26,8 @@ class GitTagPushService < BaseService
|
|||
unless Gitlab::Git.blank_ref?(params[:newrev])
|
||||
tag_name = Gitlab::Git.ref_name(params[:ref])
|
||||
tag = project.repository.find_tag(tag_name)
|
||||
|
||||
if tag && tag.target == params[:newrev]
|
||||
|
||||
if tag && tag.object_sha == params[:newrev]
|
||||
commit = project.commit(tag.target)
|
||||
commits = [commit].compact
|
||||
message = tag.message
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
class RepositoryArchiveCleanUpService
|
||||
LAST_MODIFIED_TIME_IN_MINUTES = 120
|
||||
|
||||
def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES)
|
||||
@mmin = mmin
|
||||
@path = Gitlab.config.gitlab.repository_downloads_path
|
||||
end
|
||||
|
||||
def execute
|
||||
Gitlab::Metrics.measure(:repository_archive_clean_up) do
|
||||
return unless File.directory?(path)
|
||||
|
||||
clean_up_old_archives
|
||||
clean_up_empty_directories
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :mmin, :path
|
||||
|
||||
def clean_up_old_archives
|
||||
run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete))
|
||||
end
|
||||
|
||||
def clean_up_empty_directories
|
||||
run(%W(find #{path} -not -path #{path} -type d -empty -name \*.git -maxdepth 1 -delete))
|
||||
end
|
||||
|
||||
def run(cmd)
|
||||
Gitlab::Popen.popen(cmd)
|
||||
end
|
||||
end
|
|
@ -33,16 +33,15 @@ class FileUploader < CarrierWave::Uploader::Base
|
|||
end
|
||||
|
||||
def to_h
|
||||
filename = image? ? self.file.basename : self.file.filename
|
||||
filename = image_or_video? ? self.file.basename : self.file.filename
|
||||
escaped_filename = filename.gsub("]", "\\]")
|
||||
|
||||
markdown = "[#{escaped_filename}](#{self.secure_url})"
|
||||
markdown.prepend("!") if image?
|
||||
markdown.prepend("!") if image_or_video?
|
||||
|
||||
{
|
||||
alt: filename,
|
||||
url: self.secure_url,
|
||||
is_image: image?,
|
||||
markdown: markdown
|
||||
}
|
||||
end
|
||||
|
|
|
@ -1,16 +1,37 @@
|
|||
# Extra methods for uploader
|
||||
module UploaderHelper
|
||||
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff]
|
||||
# We recommend using the .mp4 format over .mov. Videos in .mov format can
|
||||
# still be used but you really need to make sure they are served with the
|
||||
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
|
||||
# on IE >= 9.
|
||||
# http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
|
||||
VIDEO_EXT = %w[mp4 m4v mov webm ogv]
|
||||
|
||||
def image?
|
||||
img_ext = %w(png jpg jpeg gif bmp tiff)
|
||||
if file.respond_to?(:extension)
|
||||
img_ext.include?(file.extension.downcase)
|
||||
else
|
||||
# Not all CarrierWave storages respond to :extension
|
||||
ext = file.path.split('.').last.downcase
|
||||
img_ext.include?(ext)
|
||||
end
|
||||
rescue
|
||||
false
|
||||
extension_match?(IMAGE_EXT)
|
||||
end
|
||||
|
||||
def video?
|
||||
extension_match?(VIDEO_EXT)
|
||||
end
|
||||
|
||||
def image_or_video?
|
||||
image? || video?
|
||||
end
|
||||
|
||||
def extension_match?(extensions)
|
||||
return false unless file
|
||||
|
||||
extension =
|
||||
if file.respond_to?(:extension)
|
||||
file.extension
|
||||
else
|
||||
# Not all CarrierWave storages respond to :extension
|
||||
File.extname(file.path).delete('.')
|
||||
end
|
||||
|
||||
extensions.include?(extension.downcase)
|
||||
end
|
||||
|
||||
def file_storage?
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
Newly registered users will by default be external
|
||||
|
||||
%fieldset
|
||||
%legend Sign-in Restrictions
|
||||
%legend Sign-up Restrictions
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
|
@ -122,6 +122,49 @@
|
|||
= f.label :send_user_confirmation_email do
|
||||
= f.check_box :send_user_confirmation_email
|
||||
Send confirmation email on sign-up
|
||||
.form-group
|
||||
= f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.text_area :domain_whitelist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
|
||||
.help-block ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
|
||||
.form-group
|
||||
= f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
.checkbox
|
||||
= f.label :domain_blacklist_enabled do
|
||||
= f.check_box :domain_blacklist_enabled
|
||||
Enable domain blacklist for sign ups
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.radio
|
||||
= label_tag :blacklist_type_file do
|
||||
= radio_button_tag :blacklist_type, :file
|
||||
.option-title
|
||||
Upload blacklist file
|
||||
.radio
|
||||
= label_tag :blacklist_type_raw do
|
||||
= radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank?
|
||||
.option-title
|
||||
Enter blacklist manually
|
||||
.form-group.blacklist-file
|
||||
= f.label :domain_blacklist_file, 'Blacklist file', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.file_field :domain_blacklist_file, class: 'form-control', accept: '.txt,.conf'
|
||||
.help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.
|
||||
.form-group.blacklist-raw
|
||||
= f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
|
||||
.help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
|
||||
|
||||
.form-group
|
||||
= f.label :after_sign_up_text, class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.text_area :after_sign_up_text, class: 'form-control', rows: 4
|
||||
.help-block Markdown enabled
|
||||
|
||||
%fieldset
|
||||
%legend Sign-in Restrictions
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
|
@ -147,11 +190,6 @@
|
|||
.col-sm-10
|
||||
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
|
||||
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
|
||||
.form-group
|
||||
= f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
|
||||
.help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
|
||||
.form-group
|
||||
= f.label :home_page_url, 'Home page URL', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
|
@ -167,11 +205,6 @@
|
|||
.col-sm-10
|
||||
= f.text_area :sign_in_text, class: 'form-control', rows: 4
|
||||
.help-block Markdown enabled
|
||||
.form-group
|
||||
= f.label :after_sign_up_text, class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.text_area :after_sign_up_text, class: 'form-control', rows: 4
|
||||
.help-block Markdown enabled
|
||||
.form-group
|
||||
= f.label :help_page_text, class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
|
@ -352,4 +385,4 @@
|
|||
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Save', class: 'btn btn-save'
|
||||
= f.submit 'Save', class: 'btn btn-save'
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
|
||||
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
= render 'shared/allow_request_access', form: f
|
||||
|
||||
- if @group.new_record?
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
%tr.notes_holder
|
||||
%td.notes_line{ colspan: 2 }
|
||||
%td.notes_content
|
||||
%ul.notes{ data: { discussion_id: discussion.id } }
|
||||
= render partial: "projects/notes/note", collection: discussion.notes, as: :note
|
||||
= link_to_reply_discussion(discussion)
|
|
@ -0,0 +1,14 @@
|
|||
- diff_file = discussion.diff_file
|
||||
- blob = discussion.blob
|
||||
|
||||
.diff-file.file-holder
|
||||
.file-title
|
||||
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_diff_path(discussion)
|
||||
|
||||
.diff-content.code.js-syntax-highlight
|
||||
%table
|
||||
- discussion.truncated_diff_lines.each do |line|
|
||||
= render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
|
||||
|
||||
- if discussion.for_line?(line)
|
||||
= render "discussions/diff_discussion", discussion: discussion
|
|
@ -0,0 +1,45 @@
|
|||
- expanded = discussion.expanded?
|
||||
%li.note.note-discussion.timeline-entry
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
= link_to user_path(discussion.author) do
|
||||
= image_tag avatar_icon(discussion.author), class: "avatar s40"
|
||||
.timeline-content
|
||||
.discussion.js-toggle-container{ class: discussion.id }
|
||||
.discussion-header
|
||||
= link_to_member(@project, discussion.author, avatar: false)
|
||||
|
||||
.inline.discussion-headline-light
|
||||
= discussion.author.to_reference
|
||||
started a discussion on
|
||||
|
||||
- if discussion.for_commit?
|
||||
- commit = discussion.noteable
|
||||
- if commit
|
||||
commit
|
||||
= link_to commit.short_id, namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code), class: 'monospace'
|
||||
- else
|
||||
a deleted commit
|
||||
- else
|
||||
- if discussion.active?
|
||||
= link_to diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code) do
|
||||
the diff
|
||||
- else
|
||||
an outdated diff
|
||||
|
||||
= time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
|
||||
|
||||
.discussion-actions
|
||||
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
|
||||
- if expanded
|
||||
= icon("chevron-up")
|
||||
- else
|
||||
= icon("chevron-down")
|
||||
|
||||
Toggle discussion
|
||||
|
||||
.discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
|
||||
- if discussion.diff_discussion? && discussion.diff_file
|
||||
= render "discussions/diff_with_notes", discussion: discussion
|
||||
- else
|
||||
= render "discussions/notes", discussion: discussion
|
|
@ -0,0 +1,5 @@
|
|||
.panel.panel-default
|
||||
.notes{ data: { discussion_id: discussion.id } }
|
||||
%ul.notes.timeline
|
||||
= render partial: "projects/notes/note", collection: discussion.notes, as: :note
|
||||
= link_to_reply_discussion(discussion)
|
|
@ -0,0 +1,22 @@
|
|||
%tr.notes_holder
|
||||
- if discussion_left
|
||||
%td.notes_line.old
|
||||
%td.notes_content.parallel.old
|
||||
%ul.notes{ data: { discussion_id: discussion_left.id } }
|
||||
= render partial: "projects/notes/note", collection: discussion_left.notes, as: :note
|
||||
|
||||
= link_to_reply_discussion(discussion_left, 'old')
|
||||
- else
|
||||
%td.notes_line.old= ""
|
||||
%td.notes_content.parallel.old= ""
|
||||
|
||||
- if discussion_right
|
||||
%td.notes_line.new
|
||||
%td.notes_content.parallel.new
|
||||
%ul.notes{ data: { discussion_id: discussion_right.id } }
|
||||
= render partial: "projects/notes/note", collection: discussion_right.notes, as: :note
|
||||
|
||||
= link_to_reply_discussion(discussion_right, 'new')
|
||||
- else
|
||||
%td.notes_line.new= ""
|
||||
%td.notes_content.parallel.new= ""
|
|
@ -4,11 +4,7 @@
|
|||
#{time_ago_with_tooltip(event.created_at)}
|
||||
|
||||
= cache [event, current_application_settings, "v2.2"] do
|
||||
- if event.author
|
||||
= link_to user_path(event.author) do
|
||||
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
|
||||
- else
|
||||
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
|
||||
= author_avatar(event, size: 40)
|
||||
|
||||
- if event.created_project?
|
||||
= render "events/event/created_project", event: event
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
%span.event-scope
|
||||
= event_preposition(event)
|
||||
- if event.project
|
||||
= link_to_project event.project
|
||||
- else
|
||||
= event.project_name
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
.event-title
|
||||
%span.author_name= link_to_author event
|
||||
%span.event_label{class: event.action_name}
|
||||
%span{class: event.action_name}
|
||||
- if event.target
|
||||
= event.action_name
|
||||
%strong
|
||||
|
@ -10,12 +10,7 @@
|
|||
- else
|
||||
= event_action_name(event)
|
||||
|
||||
= event_preposition(event)
|
||||
|
||||
- if event.project
|
||||
= link_to_project event.project
|
||||
- else
|
||||
= event.project_name
|
||||
= render "events/event_scope", event: event
|
||||
|
||||
- if event.target.respond_to?(:title)
|
||||
.event-body
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.event-title
|
||||
%span.author_name= link_to_author event
|
||||
%span.event_label{class: event.action_name}
|
||||
%span{class: event.action_name}
|
||||
= event_action_name(event)
|
||||
|
||||
- if event.project
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
.event-title
|
||||
%span.author_name= link_to_author event
|
||||
%span.event_label
|
||||
= event.action_name
|
||||
= event_note_title_html(event)
|
||||
at
|
||||
= event.action_name
|
||||
= event_note_title_html(event)
|
||||
|
||||
- if event.project
|
||||
= link_to_project event.project
|
||||
- else
|
||||
= event.project_name
|
||||
= render "events/event_scope", event: event
|
||||
|
||||
.event-body
|
||||
.event-note
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
.event-title
|
||||
%span.author_name= link_to_author event
|
||||
%span.event_label.pushed #{event.action_name} #{event.ref_type}
|
||||
%span.pushed #{event.action_name} #{event.ref_type}
|
||||
- if event.rm_ref?
|
||||
%strong= event.ref_name
|
||||
- else
|
||||
%strong
|
||||
= link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title)
|
||||
at
|
||||
= link_to_project project
|
||||
|
||||
= render "events/event_scope", event: event
|
||||
|
||||
- if event.push_with_commits?
|
||||
.event-body
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
|
||||
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
|
||||
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
= render 'shared/allow_request_access', form: f
|
||||
|
||||
.form-group
|
||||
%hr
|
||||
= f.label :share_with_group_lock, class: 'control-label' do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- project = @target_project || @project
|
||||
- noteable_class = @noteable.class if @noteable.present?
|
||||
|
||||
- if @noteable
|
||||
:javascript
|
||||
GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}"
|
||||
GitLab.GfmAutoComplete.cachedData = undefined;
|
||||
GitLab.GfmAutoComplete.setup();
|
||||
:javascript
|
||||
GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_class, type_id: params[:id])}"
|
||||
GitLab.GfmAutoComplete.cachedData = undefined;
|
||||
GitLab.GfmAutoComplete.setup();
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
%span
|
||||
Merge Requests
|
||||
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
|
||||
= nav_link(controller: :snippets) do
|
||||
= nav_link(controller: 'dashboard/snippets') do
|
||||
= link_to dashboard_snippets_path, title: 'Snippets' do
|
||||
%span
|
||||
Snippets
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
%ul.nav.nav-sidebar
|
||||
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
|
||||
= link_to explore_root_path, title: 'Projects' do
|
||||
= icon('bookmark fw')
|
||||
%span
|
||||
Projects
|
||||
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
|
||||
= link_to explore_groups_path, title: 'Groups' do
|
||||
= icon('group fw')
|
||||
%span
|
||||
Groups
|
||||
= nav_link(controller: :snippets) do
|
||||
= link_to explore_snippets_path, title: 'Snippets' do
|
||||
= icon('clipboard fw')
|
||||
%span
|
||||
Snippets
|
||||
= nav_link(controller: :help) do
|
||||
= link_to help_path, title: 'Help' do
|
||||
= icon('question-circle fw')
|
||||
%span
|
||||
Help
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
|
||||
%span
|
||||
Triggers
|
||||
= nav_link(controller: :badges) do
|
||||
= link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
|
||||
= nav_link(controller: :pipelines_settings) do
|
||||
= link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
|
||||
%span
|
||||
Badges
|
||||
CI/CD Pipelines
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
- content_for :scripts_body_top do
|
||||
- project = @target_project || @project
|
||||
- if @project_wiki && @page
|
||||
- markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, params[:id])
|
||||
- markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, @page.title)
|
||||
- else
|
||||
- markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
|
||||
- if current_user
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_tag('lib/cropper.js')
|
||||
= page_specific_javascript_tag('profile/application.js')
|
||||
= page_specific_javascript_tag('profile/profile_bundle.js')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue