Merge branch 'master' into filter-branch-by-name

This commit is contained in:
tiagonbotelho 2016-07-22 21:57:46 +01:00
commit 850813d298
283 changed files with 4364 additions and 2561 deletions

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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.

View File

@ -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'

View File

@ -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)

View File

@ -1 +1 @@
8.10.0-pre
8.11.0-pre

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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')

View File

@ -232,7 +232,9 @@
.nav-block {
.controls {
float: right;
margin-top: 11px;
margin-top: 8px;
padding-bottom: 7px;
border-bottom: 1px solid $border-color;
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -198,6 +198,10 @@ header.header-pinned-nav {
.sidebar-collapsed-icon {
cursor: pointer;
.btn {
background-color: $gray-light;
}
}
}

View File

@ -53,6 +53,14 @@
left: 70px;
}
}
.nav-links {
svg {
position: relative;
top: 2px;
margin-right: 3px;
}
}
}
.build-header {

View File

@ -68,6 +68,12 @@
}
}
.ci-status-link {
svg {
overflow: visible;
}
}
.commit-box {
border-top: 1px solid $border-color;

View File

@ -176,3 +176,11 @@
}
}
}
// hide event scope (namespace + project) where it is not necessary
.project-activity {
.event-scope {
display: none;
}
}

View File

@ -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;

View File

@ -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%;

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
.tag-buttons {
line-height: 40px;
.btn:not(.dropdown-toggle) {
margin-left: 10px;
}
}

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]}")

View File

@ -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])

View File

@ -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,
)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]) }

View File

@ -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

91
app/models/discussion.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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= ""

View File

@ -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

View File

@ -0,0 +1,7 @@
%span.event-scope
= event_preposition(event)
- if event.project
= link_to_project event.project
- else
= event.project_name

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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