Commit 7ae53600 authored by ZJ van de Weg's avatar ZJ van de Weg

Merge branch 'master' into awardables

parents 20e6e0db 318b2245
This diff is collapsed.
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased) v 8.9.0 (unreleased)
- Redesign navigation for project pages
- Use gitlab-shell v3.0.0
- Changed the Slack build message to use the singular duration if necessary (Aran Koning)
v 8.8.2 (unreleased)
- Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources
- Fix Error 500 in CI charts by gracefully handling commits with no durations
- Fix concurrent request when updating build log in browser
v 8.8.1
- Add documentation for the "Health Check" feature
- Allow anonymous users to access a public project's pipelines
- Fix MySQL compatibility in zero downtime migrations helpers
- Fix the CI login to Container Registry (the gitlab-ci-token user)
v 8.8.0
- Implement GFM references for milestones (Alejandro Rodríguez)
- Snippets tab under user profile. !4001 (Long Nguyen) - Snippets tab under user profile. !4001 (Long Nguyen)
- Fix error when using link to uploads in global snippets - Fix error when using link to uploads in global snippets
- Fix Error 500 when attempting to retrieve project license when HEAD points to non-existent ref
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
- Use a case-insensitive comparison in sanitizing URI schemes - Use a case-insensitive comparison in sanitizing URI schemes
- Toggle sign-up confirmation emails in application settings - Toggle sign-up confirmation emails in application settings
- Make it possible to prevent tagged runner from picking untagged jobs
- Added `InlineDiffFilter` to the markdown parser. (Adam Butler)
- Added inline diff styling for `change_title` system notes. (Adam Butler)
- Project#open_branches has been cleaned up and no longer loads entire records into memory. - Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Escape HTML in commit titles in system note messages - Escape HTML in commit titles in system note messages
- Fix scope used when accessing container registry
- Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios
- Improve multiple branch push performance by memoizing permission checking - Improve multiple branch push performance by memoizing permission checking
- Log to application.log when an admin starts and stops impersonating a user - Log to application.log when an admin starts and stops impersonating a user
- Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi)
- Updated gitlab_git to 10.1.0 - Updated gitlab_git to 10.1.0
- GitAccess#protected_tag? no longer loads all tags just to check if a single one exists - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists
- Reduce delay in destroying a project from 1-minute to immediately - Reduce delay in destroying a project from 1-minute to immediately
...@@ -37,6 +61,7 @@ v 8.8.0 (unreleased) ...@@ -37,6 +61,7 @@ v 8.8.0 (unreleased)
- Added button to toggle whitespaces changes on diff view - Added button to toggle whitespaces changes on diff view
- Backport GitHub Enterprise import support from EE - Backport GitHub Enterprise import support from EE
- Create tags using Rugged for performance reasons. !3745 - Create tags using Rugged for performance reasons. !3745
- Allow guests to set notification level in projects
- API: Expose Issue#user_notes_count. !3126 (Anton Popov) - API: Expose Issue#user_notes_count. !3126 (Anton Popov)
- Don't show forks button when user can't view forks - Don't show forks button when user can't view forks
- Fix atom feed links and rendering - Fix atom feed links and rendering
...@@ -44,7 +69,7 @@ v 8.8.0 (unreleased) ...@@ -44,7 +69,7 @@ v 8.8.0 (unreleased)
- Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
- Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724 - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724
- Added multiple colors for labels in dropdowns when dups happen. - Added multiple colors for labels in dropdowns when dups happen.
- Always group commits by server timezone, not commit timestamp - Show commits in the same order as `git log`
- Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
- API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
- Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
...@@ -61,6 +86,9 @@ v 8.8.0 (unreleased) ...@@ -61,6 +86,9 @@ v 8.8.0 (unreleased)
- Import pull requests from GitHub where the source or target branches were removed - Import pull requests from GitHub where the source or target branches were removed
- All Grape API helpers are now instrumented - All Grape API helpers are now instrumented
- Improve Issue formatting for the Slack Service (Jeroen van Baarsen) - Improve Issue formatting for the Slack Service (Jeroen van Baarsen)
- Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine)
- Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs)
- When creating a .gitignore file a dropdown with templates will be provided
v 8.7.6 v 8.7.6
- Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko) - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
...@@ -87,6 +115,7 @@ v 8.7.3 ...@@ -87,6 +115,7 @@ v 8.7.3
- Merge request widget displays TeamCity build state and code coverage correctly again. - Merge request widget displays TeamCity build state and code coverage correctly again.
- Fix the line code when importing PR review comments from GitHub. !4010 - Fix the line code when importing PR review comments from GitHub. !4010
- Wikis are now initialized on legacy projects when checking repositories - Wikis are now initialized on legacy projects when checking repositories
- Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea)
v 8.7.2 v 8.7.2
- The "New Branch" button is now loaded asynchronously - The "New Branch" button is now loaded asynchronously
...@@ -1520,20 +1549,17 @@ v 7.10.0 ...@@ -1520,20 +1549,17 @@ v 7.10.0
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller) - Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
- Fix merge request comments on files with multiple commits - Fix merge request comments on files with multiple commits
- Fix Resource Owner Password Authentication Flow - Fix Resource Owner Password Authentication Flow
v 7.9.4
- Security: Fix project import URL regex to prevent arbitary local repos from being imported
- Fixed issue where only 25 commits would load in file listings
- Fix LDAP identities after config update
v 7.9.3
- Contains no changes
- Add icons to Add dropdown items. - Add icons to Add dropdown items.
- Allow admin to create public deploy keys that are accessible to any project. - Allow admin to create public deploy keys that are accessible to any project.
- Warn when gitlab-shell version doesn't match requirement. - Warn when gitlab-shell version doesn't match requirement.
- Skip email confirmation when set by admin or via LDAP. - Skip email confirmation when set by admin or via LDAP.
- Only allow users to reference groups, projects, issues, MRs, commits they have access to. - Only allow users to reference groups, projects, issues, MRs, commits they have access to.
v 7.9.4
- Security: Fix project import URL regex to prevent arbitary local repos from being imported
- Fixed issue where only 25 commits would load in file listings
- Fix LDAP identities after config update
v 7.9.3 v 7.9.3
- Contains no changes - Contains no changes
......
...@@ -293,7 +293,8 @@ group :development, :test do ...@@ -293,7 +293,8 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.38.0', require: false gem 'rubocop', '~> 0.40.0', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.11.0', require: false gem 'simplecov', '~> 0.11.0', require: false
...@@ -325,7 +326,7 @@ gem "mail_room", "~> 0.7" ...@@ -325,7 +326,7 @@ gem "mail_room", "~> 0.7"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
## CI ## CI
gem 'activerecord-session_store', '~> 0.1.0' gem 'activerecord-session_store', '~> 1.0.0'
gem "nested_form", '~> 0.3.2' gem "nested_form", '~> 0.3.2'
# OAuth # OAuth
......
...@@ -33,10 +33,12 @@ GEM ...@@ -33,10 +33,12 @@ GEM
activemodel (= 4.2.6) activemodel (= 4.2.6)
activesupport (= 4.2.6) activesupport (= 4.2.6)
arel (~> 6.0) arel (~> 6.0)
activerecord-session_store (0.1.2) activerecord-session_store (1.0.0)
actionpack (>= 4.0.0, < 5) actionpack (>= 4.0, < 5.1)
activerecord (>= 4.0.0, < 5) activerecord (>= 4.0, < 5.1)
railties (>= 4.0.0, < 5) multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3)
railties (>= 4.0, < 5.1)
activesupport (4.2.6) activesupport (4.2.6)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
...@@ -549,7 +551,7 @@ GEM ...@@ -549,7 +551,7 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
paranoia (2.1.4) paranoia (2.1.4)
activerecord (~> 4.0) activerecord (~> 4.0)
parser (2.3.0.6) parser (2.3.1.0)
ast (~> 2.2) ast (~> 2.2)
pg (0.18.4) pg (0.18.4)
poltergeist (1.9.0) poltergeist (1.9.0)
...@@ -684,15 +686,17 @@ GEM ...@@ -684,15 +686,17 @@ GEM
rspec-retry (0.4.5) rspec-retry (0.4.5)
rspec-core rspec-core
rspec-support (3.4.1) rspec-support (3.4.1)
rubocop (0.38.0) rubocop (0.40.0)
parser (>= 2.3.0.6, < 3.0) parser (>= 2.3.1.0, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.5.0)
rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-progressbar (1.7.5) ruby-progressbar (1.8.1)
ruby-saml (1.1.2) ruby-saml (1.1.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3) uuid (~> 2.3)
...@@ -839,7 +843,7 @@ GEM ...@@ -839,7 +843,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.0.2) unicode-display_width (1.0.5)
unicorn (4.9.0) unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
...@@ -883,7 +887,7 @@ PLATFORMS ...@@ -883,7 +887,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.2.9) RedCloth (~> 4.2.9)
ace-rails-ap (~> 4.0.2) ace-rails-ap (~> 4.0.2)
activerecord-session_store (~> 0.1.0) activerecord-session_store (~> 1.0.0)
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8) addressable (~> 2.3.8)
after_commit_queue after_commit_queue
...@@ -1013,7 +1017,8 @@ DEPENDENCIES ...@@ -1013,7 +1017,8 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.4.0) rspec-rails (~> 3.4.0)
rspec-retry rspec-retry
rubocop (~> 0.38.0) rubocop (~> 0.40.0)
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.0) sass-rails (~> 5.0.0)
...@@ -1058,4 +1063,4 @@ DEPENDENCIES ...@@ -1058,4 +1063,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.12.3 1.12.4
8.8.0-pre 8.9.0-pre
@Api = @Api =
groups_path: "/api/:version/groups.json" groupsPath: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json" groupPath: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.json" namespacesPath: "/api/:version/namespaces.json"
group_projects_path: "/api/:version/groups/:id/projects.json" groupProjectsPath: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json" projectsPath: "/api/:version/projects.json"
labels_path: "/api/:version/projects/:id/labels" labelsPath: "/api/:version/projects/:id/labels"
license_path: "/api/:version/licenses/:key" licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key"
group: (group_id, callback) -> group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path) url = Api.buildUrl(Api.groupPath)
url = url.replace(':id', group_id) url = url.replace(':id', group_id)
$.ajax( $.ajax(
...@@ -22,7 +23,7 @@ ...@@ -22,7 +23,7 @@
# Return groups list. Filtered by query # Return groups list. Filtered by query
# Only active groups retrieved # Only active groups retrieved
groups: (query, skip_ldap, callback) -> groups: (query, skip_ldap, callback) ->
url = Api.buildUrl(Api.groups_path) url = Api.buildUrl(Api.groupsPath)
$.ajax( $.ajax(
url: url url: url
...@@ -36,7 +37,7 @@ ...@@ -36,7 +37,7 @@
# Return namespaces list. Filtered by query # Return namespaces list. Filtered by query
namespaces: (query, callback) -> namespaces: (query, callback) ->
url = Api.buildUrl(Api.namespaces_path) url = Api.buildUrl(Api.namespacesPath)
$.ajax( $.ajax(
url: url url: url
...@@ -50,7 +51,7 @@ ...@@ -50,7 +51,7 @@
# Return projects list. Filtered by query # Return projects list. Filtered by query
projects: (query, order, callback) -> projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path) url = Api.buildUrl(Api.projectsPath)
$.ajax( $.ajax(
url: url url: url
...@@ -64,7 +65,7 @@ ...@@ -64,7 +65,7 @@
callback(projects) callback(projects)
newLabel: (project_id, data, callback) -> newLabel: (project_id, data, callback) ->
url = Api.buildUrl(Api.labels_path) url = Api.buildUrl(Api.labelsPath)
url = url.replace(':id', project_id) url = url.replace(':id', project_id)
data.private_token = gon.api_token data.private_token = gon.api_token
...@@ -80,7 +81,7 @@ ...@@ -80,7 +81,7 @@
# Return group projects list. Filtered by query # Return group projects list. Filtered by query
groupProjects: (group_id, query, callback) -> groupProjects: (group_id, query, callback) ->
url = Api.buildUrl(Api.group_projects_path) url = Api.buildUrl(Api.groupProjectsPath)
url = url.replace(':id', group_id) url = url.replace(':id', group_id)
$.ajax( $.ajax(
...@@ -95,7 +96,7 @@ ...@@ -95,7 +96,7 @@
# Return text for a specific license # Return text for a specific license
licenseText: (key, data, callback) -> licenseText: (key, data, callback) ->
url = Api.buildUrl(Api.license_path).replace(':key', key) url = Api.buildUrl(Api.licensePath).replace(':key', key)
$.ajax( $.ajax(
url: url url: url
...@@ -103,6 +104,12 @@ ...@@ -103,6 +104,12 @@
).done (license) -> ).done (license) ->
callback(license) callback(license)
gitignoreText: (key, callback) ->
url = Api.buildUrl(Api.gitignorePath).replace(':key', key)
$.get url, (gitignore) ->
callback(gitignore)
buildUrl: (url) -> buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root? url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version) return url.replace(':version', gon.api_version)
class @BlobGitignoreSelector
constructor: (opts) ->
{
@dropdown
@editor
@$wrapper = @dropdown.closest('.gitignore-selector')
@$filenameInput = $('#file_name')
@data = @dropdown.data('filenames')
} = opts
@dropdown.glDropdown(
data: @data,
filterable: true,
selectable: true,
search:
fields: ['name']
clicked: @onClick
text: (gitignore) ->
gitignore.name
)
@toggleGitignoreSelector()
@bindEvents()
bindEvents: ->
@$filenameInput
.on 'keyup blur', (e) =>
@toggleGitignoreSelector()
toggleGitignoreSelector: ->
filename = @$filenameInput.val() or $('.editor-file-name').text().trim()
@$wrapper.toggleClass 'hidden', filename isnt '.gitignore'
onClick: (item, el, e) =>
e.preventDefault()
@requestIgnoreFile(item.name)
requestIgnoreFile: (name) ->
Api.gitignoreText name, @requestIgnoreFileSuccess.bind(@)
requestIgnoreFileSuccess: (gitignore) ->
@editor.setValue(gitignore.content, 1)
@editor.focus()
class @BlobGitignoreSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitignore-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobGitignoreSelector(
dropdown: $dropdown,
editor: @editor
)
...@@ -13,6 +13,7 @@ class @EditBlob ...@@ -13,6 +13,7 @@ class @EditBlob
@initModePanesAndLinks() @initModePanesAndLinks()
new BlobLicenseSelector(@editor) new BlobLicenseSelector(@editor)
new BlobGitignoreSelectors(editor: @editor)
initModePanesAndLinks: -> initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane") @$editModePanes = $(".js-edit-mode-pane")
......
...@@ -28,12 +28,15 @@ class CiBuild ...@@ -28,12 +28,15 @@ class CiBuild
# #
CiBuild.interval = setInterval => CiBuild.interval = setInterval =>
if window.location.href.split("#").first() is build_url if window.location.href.split("#").first() is build_url
last_state = @state
$.ajax $.ajax
url: build_url + "/trace.json?state=" + encodeURIComponent(@state) url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
dataType: "json" dataType: "json"
success: (log) => success: (log) =>
return unless last_state is @state
if log.state and log.status is "running"
@state = log.state @state = log.state
if log.status is "running"
if log.append if log.append
$('.fa-refresh').before log.html $('.fa-refresh').before log.html
else else
......
...@@ -121,7 +121,7 @@ class Dispatcher ...@@ -121,7 +121,7 @@ class Dispatcher
new UsersSelect() new UsersSelect()
when 'projects' when 'projects'
new NamespaceSelect() new NamespaceSelect()
when 'dashboard' when 'dashboard', 'root'
shortcut_handler = new ShortcutsDashboardNavigation() shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles' when 'profiles'
new Profile() new Profile()
......
...@@ -11,6 +11,7 @@ class @DueDateSelect ...@@ -11,6 +11,7 @@ class @DueDateSelect
$block = $dropdown.closest('.block') $block = $dropdown.closest('.block')
$selectbox = $dropdown.closest('.selectbox') $selectbox = $dropdown.closest('.selectbox')
$value = $block.find('.value') $value = $block.find('.value')
$valueContent = $block.find('.value-content')
$sidebarValue = $('.js-due-date-sidebar-value', $block) $sidebarValue = $('.js-due-date-sidebar-value', $block)
fieldName = $dropdown.data('field-name') fieldName = $dropdown.data('field-name')
...@@ -23,11 +24,15 @@ class @DueDateSelect ...@@ -23,11 +24,15 @@ class @DueDateSelect
$value.removeAttr('style') $value.removeAttr('style')
) )
addDueDate = -> addDueDate = (isDropdown) ->
# Create the post date # Create the post date
value = $("input[name='#{fieldName}']").val() value = $("input[name='#{fieldName}']").val()
if value isnt ''
date = new Date value.replace(new RegExp('-', 'g'), ',') date = new Date value.replace(new RegExp('-', 'g'), ',')
mediumDate = $.datepicker.formatDate 'M d, yy', date mediumDate = $.datepicker.formatDate 'M d, yy', date
else
mediumDate = 'None'
data = {} data = {}
data[abilityName] = {} data[abilityName] = {}
...@@ -39,23 +44,35 @@ class @DueDateSelect ...@@ -39,23 +44,35 @@ class @DueDateSelect
data: data data: data
beforeSend: -> beforeSend: ->
$loading.fadeIn() $loading.fadeIn()
if isDropdown
$dropdown.trigger('loading.gl.dropdown') $dropdown.trigger('loading.gl.dropdown')
$selectbox.hide() $selectbox.hide()
$value.removeAttr('style') $value.removeAttr('style')
$value.html(mediumDate) $valueContent.html(mediumDate)
$sidebarValue.html(mediumDate) $sidebarValue.html(mediumDate)
if value isnt ''
$('.js-remove-due-date-holder').removeClass 'hidden'
else
$('.js-remove-due-date-holder').addClass 'hidden'
).done (data) -> ).done (data) ->
if isDropdown
$dropdown.trigger('loaded.gl.dropdown') $dropdown.trigger('loaded.gl.dropdown')
$dropdown.dropdown('toggle') $dropdown.dropdown('toggle')
$loading.fadeOut() $loading.fadeOut()
$block.on 'click', '.js-remove-due-date', (e) ->
e.preventDefault()
$("input[name='#{fieldName}']").val ''
addDueDate(false)
$datePicker.datepicker( $datePicker.datepicker(
dateFormat: 'yy-mm-dd', dateFormat: 'yy-mm-dd',
defaultDate: $("input[name='#{fieldName}']").val() defaultDate: $("input[name='#{fieldName}']").val()
altField: "input[name='#{fieldName}']" altField: "input[name='#{fieldName}']"
onSelect: -> onSelect: ->
addDueDate() addDueDate(true)
) )
$(document) $(document)
......
...@@ -18,6 +18,10 @@ GitLab.GfmAutoComplete = ...@@ -18,6 +18,10 @@ GitLab.GfmAutoComplete =
Issues: Issues:
template: '<li><small>${id}</small> ${title}</li>' template: '<li><small>${id}</small> ${title}</li>'
# Milestones
Milestones:
template: '<li>${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input. # Add GFM auto-completion to all input fields, that accept GFM input.
setup: (wrap) -> setup: (wrap) ->
@input = $('.js-gfm-input') @input = $('.js-gfm-input')
...@@ -81,6 +85,19 @@ GitLab.GfmAutoComplete = ...@@ -81,6 +85,19 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title) title: sanitize(i.title)
search: "#{i.iid} #{i.title}" search: "#{i.iid} #{i.title}"
@input.atwho
at: '%'
alias: 'milestones'
searchKey: 'search'
displayTpl: @Milestones.template
insertTpl: '${atwho-at}"${title}"'
callbacks:
beforeSave: (milestones) ->
$.map milestones, (m) ->
id: m.iid
title: sanitize(m.title)
search: "#{m.title}"
@input.atwho @input.atwho
at: '!' at: '!'
alias: 'mergerequests' alias: 'mergerequests'
...@@ -105,6 +122,8 @@ GitLab.GfmAutoComplete = ...@@ -105,6 +122,8 @@ GitLab.GfmAutoComplete =
@input.atwho 'load', '@', data.members @input.atwho 'load', '@', data.members
# load issues # load issues
@input.atwho 'load', 'issues', data.issues @input.atwho 'load', 'issues', data.issues
# load milestones
@input.atwho 'load', 'milestones', data.milestones
# load merge requests # load merge requests
@input.atwho 'load', 'mergerequests', data.mergerequests @input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis # load emojis
......
...@@ -60,9 +60,36 @@ class GitLabDropdownFilter ...@@ -60,9 +60,36 @@ class GitLabDropdownFilter
results = data results = data
if search_text isnt '' if search_text isnt ''
# When data is an array of objects therefore [object Array] e.g.
# [
# { prop: 'foo' },
# { prop: 'baz' }
# ]
if _.isArray(data)
results = fuzzaldrinPlus.filter(data, search_text, results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys key: @options.keys
) )
else
# If data is grouped therefore an [object Object]. e.g.
# {
# groupName1: [
# { prop: 'foo' },
# { prop: 'baz' }
# ],
# groupName2: [
# { prop: 'abc' },
# { prop: 'def' }
# ]
# }
if gl.utils.isObject data
results = {}
for key, group of data
tmp = fuzzaldrinPlus.filter(group, search_text,
key: @options.keys
)
if tmp.length
results[key] = tmp.map (item) -> item
@options.callback results @options.callback results
else else
...@@ -141,8 +168,9 @@ class GitLabDropdown ...@@ -141,8 +168,9 @@ class GitLabDropdown
searchFields = if @options.search then @options.search.fields else []; searchFields = if @options.search then @options.search.fields else [];
if @options.data if @options.data
# If data is an array # If we provided data
if _.isArray @options.data # data could be an array of objects or a group of arrays
if _.isObject(@options.data) and not _.isFunction(@options.data)
@fullData = @options.data @fullData = @options.data
@parseData @options.data @parseData @options.data
else else
...@@ -230,19 +258,33 @@ class GitLabDropdown ...@@ -230,19 +258,33 @@ class GitLabDropdown
parseData: (data) -> parseData: (data) ->
@renderedData = data @renderedData = data
# Render each row
html = $.map data, (obj) =>
return @renderItem(obj)
if @options.filterable and data.length is 0 if @options.filterable and data.length is 0
# render no matching results # render no matching results
html = [@noResults()] html = [@noResults()]
else
# Handle array groups
if gl.utils.isObject data
html = []
for name, groupData of data
# Add header for each group
html.push(@renderItem(header: name, name))
@renderData(groupData, name)
.map (item) ->
html.push item
else
# Render each row
html = @renderData(data)
# Render the full menu # Render the full menu
full_html = @renderMenu(html.join("")) full_html = @renderMenu(html.join(""))
@appendMenu(full_html) @appendMenu(full_html)
renderData: (data, group = false) ->
data.map (obj, index) =>
return @renderItem(obj, group, index)
shouldPropagate: (e) => shouldPropagate: (e) =>
if @options.multiSelect if @options.multiSelect
$target = $(e.target) $target = $(e.target)
...@@ -299,11 +341,10 @@ class GitLabDropdown ...@@ -299,11 +341,10 @@ class GitLabDropdown
selector = '.dropdown-content' selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content" selector = ".dropdown-page-one .dropdown-content"
$(selector, @dropdown).html html $(selector, @dropdown).html html
# Render the row # Render the row
renderItem: (data) -> renderItem: (data, group = false, index = false) ->
html = "" html = ""
# Divider # Divider
...@@ -346,8 +387,13 @@ class GitLabDropdown ...@@ -346,8 +387,13 @@ class GitLabDropdown
if @highlight if @highlight
text = @highlightTextMatches(text, @filterInput.val()) text = @highlightTextMatches(text, @filterInput.val())
if group
groupAttrs = "data-group='#{group}' data-index='#{index}'"
else
groupAttrs = ''
html = "<li> html = "<li>
<a href='#{url}' class='#{cssClass}'> <a href='#{url}' #{groupAttrs} class='#{cssClass}'>
#{text} #{text}
</a> </a>
</li>" </li>"
...@@ -377,9 +423,15 @@ class GitLabDropdown ...@@ -377,9 +423,15 @@ class GitLabDropdown
rowClicked: (el) -> rowClicked: (el) ->
fieldName = @options.fieldName fieldName = @options.fieldName
selectedIndex = el.parent().index()
if @renderedData if @renderedData
groupName = el.data('group')
if groupName
selectedIndex = el.data('index')
selectedObject = @renderedData[groupName][selectedIndex]
else
selectedIndex = el.closest('li').index()
selectedObject = @renderedData[selectedIndex] selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS) if el.hasClass(ACTIVE_CLASS)
...@@ -460,7 +512,7 @@ class GitLabDropdown ...@@ -460,7 +512,7 @@ class GitLabDropdown
return false return false
if currentKeyCode is 13 if currentKeyCode is 13
@selectRowAtIndex currentIndex @selectRowAtIndex if currentIndex < 0 then 0 else currentIndex
removeArrayKeyEvent: -> removeArrayKeyEvent: ->
$('body').off 'keydown' $('body').off 'keydown'
......
...@@ -20,6 +20,15 @@ class @IssuableForm ...@@ -20,6 +20,15 @@ class @IssuableForm
@initWip() @initWip()
$issuableDueDate = $('#issuable-due-date')
if $issuableDueDate.length
$('.datepicker').datepicker(
dateFormat: 'yy-mm-dd',
onSelect: (dateText, inst) ->
$issuableDueDate.val dateText
).datepicker 'setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val())
initAutosave: -> initAutosave: ->
new Autosave @titleField, [ new Autosave @titleField, [
document.location.pathname, document.location.pathname,
......
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.isObject = (obj) ->
obj? and (obj.constructor is Object)
) window
...@@ -75,6 +75,9 @@ class @MergeRequestTabs ...@@ -75,6 +75,9 @@ class @MergeRequestTabs
@loadDiff($target.attr('href')) @loadDiff($target.attr('href'))
if bp? and bp.getBreakpointSize() isnt 'lg' if bp? and bp.getBreakpointSize() isnt 'lg'
@shrinkView() @shrinkView()
navBarHeight = $('.navbar-gitlab').outerHeight()
$.scrollTo(".merge-request-details .merge-request-tabs", offset: -navBarHeight)
else if action == 'builds' else if action == 'builds'
@loadBuilds($target.attr('href')) @loadBuilds($target.attr('href'))
@expandView() @expandView()
......
...@@ -113,7 +113,7 @@ class @MergeRequestWidget ...@@ -113,7 +113,7 @@ class @MergeRequestWidget
switch state switch state
when "failed", "canceled", "not_found" when "failed", "canceled", "not_found"
@setMergeButtonClass('btn-danger') @setMergeButtonClass('btn-danger')
when "running", "pending" when "running"
@setMergeButtonClass('btn-warning') @setMergeButtonClass('btn-warning')
when "success" when "success"
@setMergeButtonClass('btn-create') @setMergeButtonClass('btn-create')
...@@ -126,6 +126,6 @@ class @MergeRequestWidget ...@@ -126,6 +126,6 @@ class @MergeRequestWidget
$('.ci_widget:visible .ci-coverage').text(text) $('.ci_widget:visible .ci-coverage').text(text)
setMergeButtonClass: (css_class) -> setMergeButtonClass: (css_class) ->
$('.accept_merge_request') $('.js-merge-button')
.removeClass('btn-danger btn-warning btn-create') .removeClass('btn-danger btn-warning btn-create')
.addClass(css_class) .addClass(css_class)
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
class @ShortcutsDashboardNavigation extends Shortcuts class @ShortcutsDashboardNavigation extends Shortcuts
constructor: -> constructor: ->
super() super()
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-activity')) Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-issues')) Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-merge_requests')) Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-projects')) Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'))
@findAndFollowLink: (selector) -> @findAndFollowLink: (selector) ->
link = $(selector).attr('href') link = $(selector).attr('href')
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
*= require dropzone/basic *= require dropzone/basic
*= require cal-heatmap *= require cal-heatmap
*= require cropper.css *= require cropper.css
*= require animate
*/ */
/* /*
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
@import 'framework/tw_bootstrap'; @import 'framework/tw_bootstrap';
@import "framework/layout"; @import "framework/layout";
@import "framework/animations.scss";
@import "framework/avatar.scss"; @import "framework/avatar.scss";
@import "framework/blocks.scss"; @import "framework/blocks.scss";
@import "framework/buttons.scss"; @import "framework/buttons.scss";
......
// This file is based off animate.css 3.5.1, available here:
// https://github.com/daneden/animate.css/blob/3.5.1/animate.css
//
// animate.css - http://daneden.me/animate
// Version - 3.5.1
// Licensed under the MIT license - http://opensource.org/licenses/MIT
//
// Copyright (c) 2016 Daniel Eden
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.animated.flipOutX,
.animated.flipOutY,
.animated.bounceIn,
.animated.bounceOut {
-webkit-animation-duration: .75s;
animation-duration: .75s;
}
@-webkit-keyframes pulse {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
50% {
-webkit-transform: scale3d(1.05, 1.05, 1.05);
transform: scale3d(1.05, 1.05, 1.05);
}
to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes pulse {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
50% {
-webkit-transform: scale3d(1.05, 1.05, 1.05);
transform: scale3d(1.05, 1.05, 1.05);
}
to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
.pulse {
-webkit-animation-name: pulse;
animation-name: pulse;
}
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
&.s32 { font-size: 20px; line-height: 32px; } &.s32 { font-size: 20px; line-height: 32px; }
&.s40 { font-size: 16px; line-height: 40px; } &.s40 { font-size: 16px; line-height: 40px; }
&.s60 { font-size: 32px; line-height: 60px; } &.s60 { font-size: 32px; line-height: 60px; }
&.s70 { font-size: 34px; line-height: 70px; }
&.s90 { font-size: 36px; line-height: 90px; } &.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; } &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
&.s140 { font-size: 72px; line-height: 140px; } &.s140 { font-size: 72px; line-height: 140px; }
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
background-color: $background-color; background-color: $background-color;
padding: $gl-padding; padding: $gl-padding;
margin-bottom: 0; margin-bottom: 0;
border-top: 1px solid $border-color; border-top: 1px solid $white-dark;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $white-dark;
color: $gl-gray; color: $gl-gray;
&.oneline-block { &.oneline-block {
...@@ -110,9 +110,9 @@ ...@@ -110,9 +110,9 @@
.cover-title { .cover-title {
color: $gl-header-color; color: $gl-header-color;
margin: 0; margin: 0;
font-size: 23px; font-size: 24px;
font-weight: normal; font-weight: normal;
margin: 16px 0 5px; margin-bottom: 5px;
color: #4c4e54; color: #4c4e54;
font-size: 23px; font-size: 23px;
line-height: 1.1; line-height: 1.1;
...@@ -137,7 +137,6 @@ ...@@ -137,7 +137,6 @@
} }
.cover-desc { .cover-desc {
padding: 0 $gl-padding 3px;
color: $gl-text-color; color: $gl-text-color;
&.username:last-child { &.username:last-child {
...@@ -205,7 +204,7 @@ ...@@ -205,7 +204,7 @@
.content-block { .content-block {
padding: $gl-padding 0; padding: $gl-padding 0;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $white-dark;
&.oneline-block { &.oneline-block {
line-height: 36px; line-height: 36px;
......
...@@ -162,6 +162,10 @@ ...@@ -162,6 +162,10 @@
} }
} }
.dropdown-menu-full-width {
width: 100%;
}
.dropdown-menu-paging { .dropdown-menu-paging {
.dropdown-page-two, .dropdown-page-two,
.dropdown-menu-back { .dropdown-menu-back {
......
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
.file-holder { .file-holder {
border: 1px solid $border-color; border: 1px solid $border-color;
&.file-holder-no-border {
border: 0;
}
&.readme-holder { &.readme-holder {
margin: $gl-padding-top 0; margin: $gl-padding-top 0;
} }
...@@ -23,8 +27,17 @@ ...@@ -23,8 +27,17 @@
word-wrap: break-word; word-wrap: break-word;
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
&.file-title-clear {
padding-left: 0;
padding-right: 0;
background-color: transparent;
.file-actions {
right: 0;
}
}
.file-actions { .file-actions {
float: right;
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 15px; right: 15px;
...@@ -36,22 +49,6 @@ ...@@ -36,22 +49,6 @@
} }
} }
.filename {
&.old {
display: inline-block;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
display: inline-block;
span.idiff {
background-color: #a6f3a6;
}
}
}
a:not(.btn) { a:not(.btn) {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
......
...@@ -28,10 +28,6 @@ input[type='text'].danger { ...@@ -28,10 +28,6 @@ input[type='text'].danger {
} }
label { label {
&.control-label {
@extend .col-sm-2;
}
&.inline-label { &.inline-label {
margin: 0; margin: 0;
} }
...@@ -41,6 +37,10 @@ label { ...@@ -41,6 +37,10 @@ label {
} }
} }
.control-label {
@extend .col-sm-2;
}
.inline-input-group { .inline-input-group {
width: 250px; width: 250px;
} }
......
...@@ -48,10 +48,6 @@ ...@@ -48,10 +48,6 @@
display: block; display: block;
} }
.project-home-desc {
font-size: 21px;
}
.project-repo-buttons, .project-repo-buttons,
.git-clone-holder { .git-clone-holder {
display: none; display: none;
......
...@@ -196,7 +196,7 @@ ...@@ -196,7 +196,7 @@
position: fixed; position: fixed;
top: $header-height; top: $header-height;
width: 100%; width: 100%;
z-index: 1; z-index: 3;
background: $background-color; background: $background-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition-duration: .3s; transition-duration: .3s;
...@@ -238,6 +238,10 @@ ...@@ -238,6 +238,10 @@
@media (max-width: $screen-xs-min) { @media (max-width: $screen-xs-min) {
margin-left: 0; margin-left: 0;
} }
li.active {
font-weight: bold;
}
} }
} }
...@@ -246,6 +250,11 @@ ...@@ -246,6 +250,11 @@
height: 51px; height: 51px;
white-space: nowrap; white-space: nowrap;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
}
li { li {
...@@ -279,4 +288,8 @@ ...@@ -279,4 +288,8 @@
margin-top: 96px; margin-top: 96px;
} }
} }
.right-sidebar {
top: ($header-height * 2) + 2;
}
} }
...@@ -269,3 +269,11 @@ h1, h2, h3, h4 { ...@@ -269,3 +269,11 @@ h1, h2, h3, h4 {
text-align: right; text-align: right;
} }
} }
.idiff.deletion {
background: $line-removed-dark;
}
.idiff.addition {
background: $line-added-dark;
}
...@@ -12,7 +12,7 @@ $gutter_inner_width: 258px; ...@@ -12,7 +12,7 @@ $gutter_inner_width: 258px;
*/ */
$border-color: #e5e5e5; $border-color: #e5e5e5;
$focus-border-color: #3aabf0; $focus-border-color: #3aabf0;
$table-border-color: #ececec; $table-border-color: #f0f0f0;
$background-color: #fafafa; $background-color: #fafafa;
/* /*
...@@ -119,8 +119,8 @@ $border-white-light: #f1f2f4; ...@@ -119,8 +119,8 @@ $border-white-light: #f1f2f4;
$border-white-normal: #d6dae2; $border-white-normal: #d6dae2;
$border-white-dark: #c6cacf; $border-white-dark: #c6cacf;
$border-gray-light: rgba(0, 0, 0, 0.06); $border-gray-light: #dcdcdc;
$border-gray-normal: rgba(0, 0, 0, 0.10);; $border-gray-normal: rgba(0, 0, 0, 0.10);
$border-gray-dark: #c6cacf; $border-gray-dark: #c6cacf;
$border-green-light: #2faa60; $border-green-light: #2faa60;
...@@ -178,6 +178,7 @@ $table-border-gray: #f0f0f0; ...@@ -178,6 +178,7 @@ $table-border-gray: #f0f0f0;
$line-target-blue: #eaf3fc; $line-target-blue: #eaf3fc;
$line-select-yellow: #fcf8e7; $line-select-yellow: #fcf8e7;
$line-select-yellow-dark: #f0e2bd; $line-select-yellow-dark: #f0e2bd;
/* /*
* Fonts * Fonts
*/ */
......
@import "framework/variables";
table.code {
width: 100%;
font-family: monospace;
border: none;
border-collapse: separate;
margin: 0;
padding: 0;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
td {
line-height: $code_line_height;
font-family: monospace;
font-size: $code_font_size;
}
td.diff-line-num {
margin: 0;
padding: 0;
border: none;
background: $background-color;
color: rgba(0, 0, 0, 0.3);
padding: 0 5px;
border-right: 1px solid $border-color;
text-align: right;
min-width: 35px;
max-width: 50px;
width: 35px;
}
td.line_content {
display: block;
margin: 0;
padding: 0 0.5em;
border: none;
white-space: pre;
}
}
@import "highlight/white";
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.file-title { .file-title {
@extend .monospace; @extend .monospace;
line-height: 42px; line-height: 35px;
padding-top: 7px; padding-top: 7px;
padding-bottom: 7px; padding-bottom: 7px;
...@@ -59,7 +59,22 @@ ...@@ -59,7 +59,22 @@
} }
.encoding-selector, .encoding-selector,
.license-selector { .license-selector,
.gitignore-selector {
display: inline-block; display: inline-block;
vertical-align: top;
font-family: $regular_font;
}
.gitignore-selector {
.dropdown {
line-height: 21px;
}
.dropdown-menu-toggle {
vertical-align: top;
width: 220px;
}
} }
} }
...@@ -150,6 +150,10 @@ ...@@ -150,6 +150,10 @@
font-weight: 600; font-weight: 600;
} }
.light {
font-weight: normal;
}
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
display: none; display: none;
} }
......
.pipeline-stage {
overflow: hidden;
text-overflow: ellipsis;
}
...@@ -134,14 +134,6 @@ ...@@ -134,14 +134,6 @@
} }
} }
.change-username-title {
color: $gl-warning;
}
.remove-account-title {
color: $gl-danger;
}
.provider-btn-group { .provider-btn-group {
display: inline-block; display: inline-block;
margin-right: 10px; margin-right: 10px;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
margin-bottom: 0; margin-bottom: 0;
} }
.new_project, .new_project,
.edit_project { .edit-project {
fieldset.features { fieldset.features {
.control-label { .control-label {
font-weight: normal; font-weight: normal;
...@@ -26,8 +26,13 @@ ...@@ -26,8 +26,13 @@
} }
.project-home-panel { .project-home-panel {
padding-bottom: 40px; background: $white-light;
border-bottom: 1px solid $border-color; text-align: left;
padding: 24px 0;
.container-fluid {
position: relative;
}
.cover-controls { .cover-controls {
.project-settings-dropdown { .project-settings-dropdown {
...@@ -43,21 +48,55 @@ ...@@ -43,21 +48,55 @@
} }
} }
.project-identicon-holder { .cover-title {
margin-bottom: 16px; margin-bottom: 0;
}
.project-image-container {
@include make-sm-column(1);
max-width: 86px;
min-width: 86px;
padding-right: 0;
margin: 11px 0;
.avatar, .identicon { @media (max-width: $screen-md-max) {
margin: 0 auto; padding-left: 0;
float: none; margin: 0 0 10px;
max-width: none;
min-width: none;
.avatar.s70 {
margin: auto;
}
}
}
.project-info {
@include make-sm-column(10);
h1 {
font-size: 24px;
font-weight: normal;
margin: 0;
}
.project-home-desc {
p {
margin: 0;
}
}
} }
.identicon { .identicon {
float: left;
@include border-radius(50%); @include border-radius(50%);
} }
.avatar {
float: none;
} }
.notifications-btn { .notifications-btn {
margin-top: -28px;
.fa-bell { .fa-bell {
margin-right: 6px; margin-right: 6px;
...@@ -69,28 +108,45 @@ ...@@ -69,28 +108,45 @@
} }
.project-repo-buttons { .project-repo-buttons {
margin-top: 20px; font-size: 0;
margin-bottom: 0;
.count-buttons { .btn {
display: block; @include btn-gray;
margin-bottom: 20px; padding: 3px 10px;
text-transform: none;
background-color: $background-color;
.fa {
color: $layout-link-gray;
} }
.clone-row { .fa-caret-down {
.split-repo-buttons, margin-left: 3px;
.project-clone-holder { }
display: inline-block;
} }
.split-repo-buttons { .btn-group:not(:first-child):not(:last-child) > .btn {
margin: 0 12px; border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
} }
form {
margin-left: 10px;
} }
.btn { .count-buttons {
@include btn-gray; display: inline-block;
text-transform: none; vertical-align: top;
margin-top: 16px;
}
.project-clone-holder {
display: inline-block;
margin-top: 16px;
input {
height: 29px;
}
} }
.count-with-arrow { .count-with-arrow {
...@@ -140,14 +196,18 @@ ...@@ -140,14 +196,18 @@
line-height: 13px; line-height: 13px;
padding: $gl-vert-padding $gl-padding; padding: $gl-vert-padding $gl-padding;
letter-spacing: .4px; letter-spacing: .4px;
padding: 10px 14px; padding: 7px 14px;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
touch-action: manipulation; touch-action: manipulation;
cursor: pointer; cursor: pointer;
background-image: none; background-image: none;
white-space: nowrap; white-space: nowrap;
margin: 0 11px 0 4px; margin: 0 10px 0 4px;
a {
color: inherit;
}
&:hover { &:hover {
background: #fff; background: #fff;
...@@ -155,13 +215,37 @@ ...@@ -155,13 +215,37 @@
} }
} }
} }
.project-right-buttons {
position: absolute;
right: 16px;
bottom: 0;
.btn {
padding: 3px 10px;
background-color: $background-color;
}
@media (max-width: 1304px) {
top: 0;
}
}
@media (max-width: $screen-md-max) {
text-align: center;
.project-info,
.project-image-container {
width: 100%;
}
}
} }
.split-one { .split-one {
display: inline-table; display: inline-table;
margin-right: 12px; margin-right: 12px;
a { > a {
margin: -1px; margin: -1px;
} }
} }
...@@ -285,11 +369,11 @@ a.deploy-project-label { ...@@ -285,11 +369,11 @@ a.deploy-project-label {
} }
.project-stats { .project-stats {
text-align: center;
margin-top: $gl-padding; margin-top: $gl-padding;
margin-bottom: 0; margin-bottom: 0;
padding-top: 10px; padding: 16px 0;
padding-bottom: 4px; background-color: $white-light;
font-size: 0;
ul.nav { ul.nav {
display: inline-block; display: inline-block;
...@@ -300,12 +384,11 @@ a.deploy-project-label { ...@@ -300,12 +384,11 @@ a.deploy-project-label {
} }
.nav > li > a { .nav > li > a {
@include btn-default;
@include btn-gray;
background-color: transparent; background-color: transparent;
border: 1px solid #f7f8fa; margin-right: 12px;
margin-left: 12px; padding: 0 10px;
font-size: 15px;
color: $notes-light-color;
} }
li { li {
...@@ -325,6 +408,10 @@ a.deploy-project-label { ...@@ -325,6 +408,10 @@ a.deploy-project-label {
background-color: #f0f2f5; background-color: #f0f2f5;
} }
} }
&.row-content-block.second-block {
margin-top: 0;
}
} }
pre.light-well { pre.light-well {
...@@ -442,9 +529,14 @@ pre.light-well { ...@@ -442,9 +529,14 @@ pre.light-well {
border-top: 0; border-top: 0;
.edit-project-readme { .edit-project-readme {
z-index: 100; z-index: 2;
position: relative; position: relative;
} }
.wiki h1 {
border-bottom: none;
padding: 0;
}
} }
.git-clone-holder { .git-clone-holder {
......
...@@ -12,3 +12,11 @@ ...@@ -12,3 +12,11 @@
border: 1px solid $warning-message-border; border: 1px solid $warning-message-border;
border-radius: $border-radius-base; border-radius: $border-radius-base;
} }
.warning-title {
color: $gl-warning;
}
.danger-title {
color: $gl-danger;
}
...@@ -16,19 +16,6 @@ ...@@ -16,19 +16,6 @@
} }
} }
.snippet-box {
@include border-radius(2px);
display: block;
float: left;
padding: 0 $gl-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
border: 1px solid;
line-height: 32px;
}
.markdown-snippet-copy { .markdown-snippet-copy {
position: fixed; position: fixed;
top: -10px; top: -10px;
...@@ -36,3 +23,34 @@ ...@@ -36,3 +23,34 @@
max-height: 0; max-height: 0;
max-width: 0; max-width: 0;
} }
.file-holder.snippet-file-content {
padding-bottom: $gl-padding;
border-bottom: 1px solid $border-color;
.file-title {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
}
.file-actions {
top: 12px;
}
.file-content {
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
border-bottom: 1px solid $border-color;
}
}
.snippet-title {
font-size: 24px;
font-weight: normal;
}
.snippet-actions {
@media (min-width: $screen-sm-min) {
float: right;
}
}
...@@ -9,24 +9,19 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -9,24 +9,19 @@ class Admin::RunnersController < Admin::ApplicationController
end end
def show def show
@builds = @runner.builds.order('id DESC').first(30) assign_builds_and_projects
@projects =
if params[:search].present?
::Project.search(params[:search])
else
Project.all
end
@projects = @projects.where.not(id: @runner.projects.select(:id)) if @runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end end
def update def update
@runner.update_attributes(runner_params) if @runner.update_attributes(runner_params)
respond_to do |format| respond_to do |format|
format.js format.js
format.html { redirect_to admin_runner_path(@runner) } format.html { redirect_to admin_runner_path(@runner) }
end end
else
assign_builds_and_projects
render 'show'
end
end end
def destroy def destroy
...@@ -60,4 +55,16 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -60,4 +55,16 @@ class Admin::RunnersController < Admin::ApplicationController
def runner_params def runner_params
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE) params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end end
def assign_builds_and_projects
@builds = runner.builds.order('id DESC').first(30)
@projects =
if params[:search].present?
::Project.search(params[:search])
else
Project.all
end
@projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end
end end
...@@ -36,7 +36,7 @@ class JwtController < ApplicationController ...@@ -36,7 +36,7 @@ class JwtController < ApplicationController
end end
def authenticate_project(login, password) def authenticate_project(login, password)
if login == 'gitlab_ci_token' if login == 'gitlab-ci-token'
Project.find_by(builds_enabled: true, runners_token: password) Project.find_by(builds_enabled: true, runners_token: password)
end end
end end
......
...@@ -336,7 +336,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -336,7 +336,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params.require(:merge_request).permit( params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch, :title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id, :target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, label_ids: [] :state_event, :description, :task_num, :force_remove_source_branch,
label_ids: []
) )
end end
......
class Projects::PipelinesController < Projects::ApplicationController
before_action :pipeline, except: [:index, :new, :create]
before_action :commit, only: [:show]
before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
def index
@scope = params[:scope]
all_pipelines = project.ci_commits
@pipelines_count = all_pipelines.count
@running_or_pending_count = all_pipelines.running_or_pending.count
@pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope)
@pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30)
end
def new
@pipeline = project.ci_commits.new(ref: @project.default_branch)
end
def create
@pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute
unless @pipeline.persisted?
render 'new'
return
end
redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
end
def show
end
def retry
pipeline.retry_failed
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
end
def cancel
pipeline.cancel_running
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
end
private
def create_params
params.require(:pipeline).permit(:ref)
end
def pipeline
@pipeline ||= project.ci_commits.find_by!(id: params[:id])
end
def commit
@commit ||= @pipeline.commit_data
end
end
...@@ -20,7 +20,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::RunnersController < Projects::ApplicationController
if @runner.update_attributes(runner_params) if @runner.update_attributes(runner_params)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else else
redirect_to runner_path(@runner), alert: 'Runner was not updated.' render 'edit'
end end
end end
......
...@@ -3,20 +3,44 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -3,20 +3,44 @@ class Projects::VariablesController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index
@variable = Ci::Variable.new
end
def show def show
@variable = @project.variables.find(params[:id])
end end
def update def update
if project.update_attributes(project_params) @variable = @project.variables.find(params[:id])
if @variable.update_attributes(project_params)
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.'
else
render action: "show"
end
end
def create
@variable = Ci::Variable.new(project_params)
if @variable.valid? && @project.variables << @variable
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.' redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
else else
render action: 'show' render action: "index"
end
end end
def destroy
@key = @project.variables.find(params[:id])
@key.destroy
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully removed.'
end end
private private
def project_params def project_params
params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] }) params.require(:variable).permit([:id, :key, :value, :_destroy])
end end
end end
...@@ -101,13 +101,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -101,13 +101,7 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
if current_user @notification_setting = current_user.notification_settings_for(@project) if current_user
@membership = @project.team.find_member(current_user.id)
if @membership
@notification_setting = current_user.notification_settings_for(@project)
end
end
if @project.repository_exists? if @project.repository_exists?
if @project.empty_repo? if @project.empty_repo?
...@@ -147,6 +141,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -147,6 +141,7 @@ class ProjectsController < Projects::ApplicationController
@suggestions = { @suggestions = {
emojis: Gitlab::AwardEmoji.urls, emojis: Gitlab::AwardEmoji.urls,
issues: autocomplete.issues, issues: autocomplete.issues,
milestones: autocomplete.milestones,
mergerequests: autocomplete.merge_requests, mergerequests: autocomplete.merge_requests,
members: participants members: participants
} }
......
...@@ -38,7 +38,7 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -38,7 +38,7 @@ class RegistrationsController < Devise::RegistrationsController
end end
def after_sign_up_path_for(user) def after_sign_up_path_for(user)
user.confirmed_at.present? ? dashboard_projects_path : users_almost_there_path user.confirmed? ? dashboard_projects_path : users_almost_there_path
end end
def after_inactive_sign_up_path_for(_resource) def after_inactive_sign_up_path_for(_resource)
......
class PipelinesFinder
attr_reader :project
def initialize(project)
@project = project
end
def execute(pipelines, scope)
case scope
when 'running'
pipelines.running_or_pending
when 'branches'
from_ids(pipelines, ids_for_ref(pipelines, branches))
when 'tags'
from_ids(pipelines, ids_for_ref(pipelines, tags))
else
pipelines
end
end
private
def ids_for_ref(pipelines, refs)
pipelines.where(ref: refs).group(:ref).select('max(id)')
end
def from_ids(pipelines, ids)
pipelines.unscoped.where(id: ids)
end
def branches
project.repository.branches.map(&:name)
end
def tags
project.repository.tags.map(&:name)
end
end
...@@ -36,7 +36,7 @@ class TodosFinder ...@@ -36,7 +36,7 @@ class TodosFinder
private private
def action_id? def action_id?
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED].include?(action_id.to_i) action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED].include?(action_id.to_i)
end end
def action_id def action_id
......
...@@ -110,8 +110,7 @@ module ApplicationHelper ...@@ -110,8 +110,7 @@ module ApplicationHelper
] ]
# If reference is commit id - we should add it to branch/tag selectbox # If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) && if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
@ref =~ /\A[0-9a-zA-Z]{6,52}\z/)
options << ['Commit', [@ref]] options << ['Commit', [@ref]]
end end
......
...@@ -184,4 +184,14 @@ module BlobHelper ...@@ -184,4 +184,14 @@ module BlobHelper
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] } Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
} }
end end
def gitignore_names
return @gitignore_names if defined?(@gitignore_names)
@gitignore_names = {
Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } },
# Note that the key here doesn't cover it really
Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } }
}
end
end end
...@@ -38,19 +38,30 @@ module CiStatusHelper ...@@ -38,19 +38,30 @@ module CiStatusHelper
icon(icon_name + ' fw') icon(icon_name + ' fw')
end end
def render_ci_status(ci_commit, tooltip_placement: 'auto left') def render_commit_status(commit, tooltip_placement: 'auto left')
# TODO: split this method into project = commit.project
# - render_commit_status path = builds_namespace_project_commit_path(project.namespace, project, commit)
# - render_pipeline_status render_status_with_link('commit', commit.status, path, tooltip_placement)
link_to ci_icon_for_status(ci_commit.status), end
ci_status_path(ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
title: "Build #{ci_label_for_status(ci_commit.status)}", project = pipeline.project
data: { toggle: 'tooltip', placement: tooltip_placement } path = namespace_project_pipeline_path(project.namespace, project, pipeline)
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement)
end end
def no_runners_for_project?(project) def no_runners_for_project?(project)
project.runners.blank? && project.runners.blank? &&
Ci::Runner.shared.blank? Ci::Runner.shared.blank?
end end
private
def render_status_with_link(type, status, path, tooltip_placement)
link_to ci_icon_for_status(status),
path,
class: "ci-status-link ci-status-icon-#{status.dasherize}",
title: "#{type.titleize}: #{ci_label_for_status(status)}",
data: { toggle: 'tooltip', placement: tooltip_placement }
end
end end
...@@ -2,8 +2,8 @@ module DiffHelper ...@@ -2,8 +2,8 @@ module DiffHelper
def mark_inline_diffs(old_line, new_line) def mark_inline_diffs(old_line, new_line)
old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs
marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs) marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs, mode: :deletion)
marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs) marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs, mode: :addition)
[marked_old_line, marked_new_line] [marked_old_line, marked_new_line]
end end
......
...@@ -32,12 +32,6 @@ module EmailsHelper ...@@ -32,12 +32,6 @@ module EmailsHelper
nil nil
end end
def color_email_diff(diffcontent)
formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github')
lexer = Rouge::Lexers::Diff
raw formatter.format(lexer.lex(diffcontent))
end
def password_reset_token_valid_time def password_reset_token_valid_time
valid_hours = Devise.reset_password_within / 60 / 60 valid_hours = Devise.reset_password_within / 60 / 60
if valid_hours >= 24 if valid_hours >= 24
......
...@@ -13,7 +13,7 @@ module GitlabMarkdownHelper ...@@ -13,7 +13,7 @@ module GitlabMarkdownHelper
def link_to_gfm(body, url, html_options = {}) def link_to_gfm(body, url, html_options = {})
return "" if body.blank? return "" if body.blank?
escaped_body = if body =~ /\A\<img/ escaped_body = if body.start_with?('<img')
body body
else else
escape_once(body) escape_once(body)
......
...@@ -124,11 +124,7 @@ module ProjectsHelper ...@@ -124,11 +124,7 @@ module ProjectsHelper
end end
def license_short_name(project) def license_short_name(project)
no_license_key = project.repository.license_key.nil? || return 'LICENSE' if project.repository.license_key.nil?
# Back-compat if cache contains 'no-license', can be removed in a few weeks
project.repository.license_key == 'no-license'
return 'LICENSE' if no_license_key
license = Licensee::License.new(project.repository.license_key) license = Licensee::License.new(project.repository.license_key)
...@@ -148,6 +144,10 @@ module ProjectsHelper ...@@ -148,6 +144,10 @@ module ProjectsHelper
nav_tabs << :merge_requests nav_tabs << :merge_requests
end end
if can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines
end
if can?(current_user, :read_build, project) if can?(current_user, :read_build, project)
nav_tabs << :builds nav_tabs << :builds
end end
......
...@@ -95,7 +95,9 @@ module TabHelper ...@@ -95,7 +95,9 @@ module TabHelper
end end
def project_tab_class def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project) if controller.controller_path.start_with?('projects')
return 'active'
end
if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
"active" "active"
...@@ -112,7 +114,7 @@ module TabHelper ...@@ -112,7 +114,7 @@ module TabHelper
end end
def profile_tab_class def profile_tab_class
if controller.controller_path =~ /\Aprofiles/ if controller.controller_path.start_with?('profiles')
return 'active' return 'active'
end end
......
...@@ -11,6 +11,7 @@ module TodosHelper ...@@ -11,6 +11,7 @@ module TodosHelper
case todo.action case todo.action
when Todo::ASSIGNED then 'assigned you' when Todo::ASSIGNED then 'assigned you'
when Todo::MENTIONED then 'mentioned you on' when Todo::MENTIONED then 'mentioned you on'
when Todo::BUILD_FAILED then 'The build failed for your'
end end
end end
...@@ -28,8 +29,11 @@ module TodosHelper ...@@ -28,8 +29,11 @@ module TodosHelper
namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project, namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
todo.target, anchor: anchor) todo.target, anchor: anchor)
else else
polymorphic_path([todo.project.namespace.becomes(Namespace), path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
todo.project, todo.target], anchor: anchor)
path.unshift(:builds) if todo.build_failed?
polymorphic_path(path, anchor: anchor)
end end
end end
......
...@@ -65,7 +65,8 @@ module Emails ...@@ -65,7 +65,8 @@ module Emails
# used in notify layout # used in notify layout
@target_url = @message.target_url @target_url = @message.target_url
@project = Project.find project_id @project = Project.find(project_id)
@diff_notes_disabled = true
add_project_headers add_project_headers
headers['X-GitLab-Author'] = @message.author_username headers['X-GitLab-Author'] = @message.author_username
......
...@@ -10,6 +10,8 @@ class Notify < BaseMailer ...@@ -10,6 +10,8 @@ class Notify < BaseMailer
include Emails::Builds include Emails::Builds
add_template_helper MergeRequestsHelper add_template_helper MergeRequestsHelper
add_template_helper DiffHelper
add_template_helper BlobHelper
add_template_helper EmailsHelper add_template_helper EmailsHelper
def test_email(recipient_email, subject, body) def test_email(recipient_email, subject, body)
......
...@@ -60,6 +60,7 @@ class Ability ...@@ -60,6 +60,7 @@ class Ability
:read_project_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:read_pipeline,
:read_commit_status, :read_commit_status,
:read_container_image, :read_container_image,
:download_code :download_code
...@@ -205,6 +206,7 @@ class Ability ...@@ -205,6 +206,7 @@ class Ability
:read_commit_status, :read_commit_status,
:read_build, :read_build,
:read_container_image, :read_container_image,
:read_pipeline,
] ]
end end
...@@ -216,6 +218,8 @@ class Ability ...@@ -216,6 +218,8 @@ class Ability
:update_commit_status, :update_commit_status,
:create_build, :create_build,
:update_build, :update_build,
:create_pipeline,
:update_pipeline,
:create_merge_request, :create_merge_request,
:create_wiki, :create_wiki,
:push_code, :push_code,
...@@ -248,6 +252,7 @@ class Ability ...@@ -248,6 +252,7 @@ class Ability
:admin_commit_status, :admin_commit_status,
:admin_build, :admin_build,
:admin_container_image, :admin_container_image,
:admin_pipeline
] ]
end end
...@@ -290,6 +295,7 @@ class Ability ...@@ -290,6 +295,7 @@ class Ability
unless project.builds_enabled unless project.builds_enabled
rules += named_abilities('build') rules += named_abilities('build')
rules += named_abilities('pipeline')
end end
unless project.container_registry_enabled unless project.container_registry_enabled
......
...@@ -7,7 +7,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -7,7 +7,7 @@ class ApplicationSetting < ActiveRecord::Base
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :import_sources serialize :import_sources
serialize :disabled_oauth_sign_in_sources serialize :disabled_oauth_sign_in_sources, Array
serialize :restricted_signup_domains, Array serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw attr_accessor :restricted_signup_domains_raw
......
...@@ -53,6 +53,7 @@ module Ci ...@@ -53,6 +53,7 @@ module Ci
new_build.stage_idx = build.stage_idx new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request new_build.trigger_request = build.trigger_request
new_build.save new_build.save
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build new_build
end end
end end
...@@ -290,9 +291,15 @@ module Ci ...@@ -290,9 +291,15 @@ module Ci
end end
def can_be_served?(runner) def can_be_served?(runner)
return false unless has_tags? || runner.run_untagged?
(tag_list - runner.tag_list).empty? (tag_list - runner.tag_list).empty?
end end
def has_tags?
tag_list.any?
end
def any_runners_online? def any_runners_online?
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
end end
......
...@@ -8,8 +8,6 @@ module Ci ...@@ -8,8 +8,6 @@ module Ci
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
delegate :stages, to: :statuses
validates_presence_of :sha validates_presence_of :sha
validates_presence_of :status validates_presence_of :status
validate :valid_commit_sha validate :valid_commit_sha
...@@ -22,7 +20,8 @@ module Ci ...@@ -22,7 +20,8 @@ module Ci
end end
def self.stages def self.stages
CommitStatus.where(commit: all).stages # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
CommitStatus.where(commit: pluck(:id)).stages
end end
def project_id def project_id
...@@ -67,6 +66,25 @@ module Ci ...@@ -67,6 +66,25 @@ module Ci
end end
end end
def cancel_running
builds.running_or_pending.each(&:cancel)
end
def retry_failed
builds.latest.failed.select(&:retryable?).each(&:retry)
end
def latest?
return false unless ref
commit = project.commit(ref)
return false unless commit
commit.sha == sha
end
def triggered?
trigger_requests.any?
end
def create_builds(user, trigger_request = nil) def create_builds(user, trigger_request = nil)
return unless config_processor return unless config_processor
config_processor.stages.any? do |stage| config_processor.stages.any? do |stage|
......
...@@ -4,7 +4,7 @@ module Ci ...@@ -4,7 +4,7 @@ module Ci
LAST_CONTACT_TIME = 5.minutes.ago LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = %w[specific shared active paused online] AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active] FORM_EDITABLE = %i[description tag_list active run_untagged]
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
...@@ -26,6 +26,8 @@ module Ci ...@@ -26,6 +26,8 @@ module Ci
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end end
validate :tag_constraints
acts_as_taggable acts_as_taggable
# Searches for runners matching the given query. # Searches for runners matching the given query.
...@@ -96,5 +98,18 @@ module Ci ...@@ -96,5 +98,18 @@ module Ci
def short_sha def short_sha
token[0...8] if token token[0...8] if token
end end
def has_tags?
tag_list.any?
end
private
def tag_constraints
unless has_tags? || run_untagged?
errors.add(:tags_list,
'can not be empty when runner is not allowed to pick untagged jobs')
end
end
end end
end end
...@@ -14,7 +14,8 @@ class CommitStatus < ActiveRecord::Base ...@@ -14,7 +14,8 @@ class CommitStatus < ActiveRecord::Base
alias_attribute :author, :user alias_attribute :author, :user
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do state_machine :status, initial: :pending do
...@@ -45,6 +46,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -45,6 +46,10 @@ class CommitStatus < ActiveRecord::Base
after_transition [:pending, :running] => :success do |commit_status| after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end end
after_transition any => :failed do |commit_status|
MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.commit.project, nil).execute(commit_status)
end
end end
delegate :sha, :short_sha, to: :commit delegate :sha, :short_sha, to: :commit
...@@ -54,13 +59,15 @@ class CommitStatus < ActiveRecord::Base ...@@ -54,13 +59,15 @@ class CommitStatus < ActiveRecord::Base
end end
def self.stages def self.stages
order_by = 'max(stage_idx)' # We group by stage name, but order stages by theirs' index
group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
end end
def self.stages_status def self.stages_status
all.stages.inject({}) do |h, stage| # We execute subquery for each stage to calculate a stage status
h[stage] = all.where(stage: stage).status statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql)
statuses.inject({}) do |h, k|
h[k.first] = k.last
h h
end end
end end
......
...@@ -286,6 +286,18 @@ class MergeRequest < ActiveRecord::Base ...@@ -286,6 +286,18 @@ class MergeRequest < ActiveRecord::Base
last_commit == source_project.commit(source_branch) last_commit == source_project.commit(source_branch)
end end
def should_remove_source_branch?
merge_params['should_remove_source_branch'].present?
end
def force_remove_source_branch?
merge_params['force_remove_source_branch'].present?
end
def remove_source_branch?
should_remove_source_branch? || force_remove_source_branch?
end
def mr_and_commit_notes def mr_and_commit_notes
# Fetch comments only from last 100 commits # Fetch comments only from last 100 commits
commits_for_notes_limit = 100 commits_for_notes_limit = 100
...@@ -426,7 +438,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -426,7 +438,10 @@ class MergeRequest < ActiveRecord::Base
self.merge_when_build_succeeds = false self.merge_when_build_succeeds = false
self.merge_user = nil self.merge_user = nil
self.merge_params = nil if merge_params
merge_params.delete('should_remove_source_branch')
merge_params.delete('commit_message')
end
self.save self.save
end end
......
...@@ -98,9 +98,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -98,9 +98,7 @@ class MergeRequestDiff < ActiveRecord::Base
commits = compare.commits commits = compare.commits
if commits.present? if commits.present?
commits = Commit.decorate(commits, merge_request.source_project). commits = Commit.decorate(commits, merge_request.source_project).reverse
sort_by(&:created_at).
reverse
end end
commits commits
......
...@@ -59,8 +59,27 @@ class Milestone < ActiveRecord::Base ...@@ -59,8 +59,27 @@ class Milestone < ActiveRecord::Base
end end
end end
def self.reference_prefix
'%'
end
def self.reference_pattern def self.reference_pattern
nil # NOTE: The iid pattern only matches when all characters on the expression
# are digits, so it will match %2 but not %2.1 because that's probably a
# milestone name and we want it to be matched as such.
@reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?:
(?<milestone_iid>
\d+(?!\S\w)\b # Integer-based milestone iid, or
) |
(?<milestone_name>
[^"\s]+\b | # String-based single-word milestone title, or
"[^"]+" # String-based multi-word milestone surrounded in quotes
)
)
}x
end end
def self.link_reference_pattern def self.link_reference_pattern
...@@ -81,13 +100,26 @@ class Milestone < ActiveRecord::Base ...@@ -81,13 +100,26 @@ class Milestone < ActiveRecord::Base
end end
end end
def to_reference(from_project = nil) ##
escaped_title = self.title.gsub("]", "\\]") # Returns the String necessary to reference this Milestone in Markdown
#
h = Gitlab::Routing.url_helpers # format - Symbol format to use (default: :iid, optional: :name)
url = h.namespace_project_milestone_url(self.project.namespace, self.project, self) #
# Examples:
#
# Milestone.first.to_reference # => "%1"
# Milestone.first.to_reference(format: :name) # => "%\"goal\""
# Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1"
#
def to_reference(from_project = nil, format: :iid)
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
"[#{escaped_title}](#{url})" if cross_project_reference?(from_project)
project.to_reference + reference
else
reference
end
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
...@@ -159,4 +191,16 @@ class Milestone < ActiveRecord::Base ...@@ -159,4 +191,16 @@ class Milestone < ActiveRecord::Base
issues.where(id: ids). issues.where(id: ids).
update_all(["position = CASE #{conditions} ELSE position END", *pairs]) update_all(["position = CASE #{conditions} ELSE position END", *pairs])
end end
private
def milestone_format_reference(format = :iid)
raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
if format == :name && !name.include?('"')
%("#{name}")
else
iid
end
end
end end
...@@ -253,7 +253,7 @@ module Network ...@@ -253,7 +253,7 @@ module Network
leaves = [] leaves = []
leaves.push(commit) if commit.space.zero? leaves.push(commit) if commit.space.zero?
while true loop do
return leaves if commit.parents(@map).count.zero? return leaves if commit.parents(@map).count.zero?
commit = commit.parents(@map).first commit = commit.parents(@map).first
......
...@@ -171,17 +171,17 @@ class Project < ActiveRecord::Base ...@@ -171,17 +171,17 @@ class Project < ActiveRecord::Base
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :in_group_namespace, -> { joins(:group) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
state_machine :import_status, initial: :none do state_machine :import_status, initial: :none do
event :import_start do event :import_start do
...@@ -204,22 +204,10 @@ class Project < ActiveRecord::Base ...@@ -204,22 +204,10 @@ class Project < ActiveRecord::Base
state :finished state :finished
state :failed state :failed
after_transition any => :finished, do: :clear_import_data after_transition any => :finished, do: :reset_cache_and_import_attrs
end end
class << self class << self
def abandoned
where('projects.last_activity_at < ?', 6.months.ago)
end
def with_push
joins(:events).where('events.action = ?', Event::PUSHED)
end
def active
joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
end
# Searches for a list of projects based on the query given in `query`. # Searches for a list of projects based on the query given in `query`.
# #
# On PostgreSQL this method uses "ILIKE" to perform a case-insensitive # On PostgreSQL this method uses "ILIKE" to perform a case-insensitive
...@@ -281,10 +269,6 @@ class Project < ActiveRecord::Base ...@@ -281,10 +269,6 @@ class Project < ActiveRecord::Base
projects.iwhere('projects.path' => project_path).take projects.iwhere('projects.path' => project_path).take
end end
def find_by_ci_id(id)
find_by(ci_id: id.to_i)
end
def visibility_levels def visibility_levels
Gitlab::VisibilityLevel.options Gitlab::VisibilityLevel.options
end end
...@@ -315,10 +299,6 @@ class Project < ActiveRecord::Base ...@@ -315,10 +299,6 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC') joins(join_body).reorder('join_note_counts.amount DESC')
end end
def visible_to_user(user)
where(id: user.authorized_projects.select(:id).reorder(nil))
end
end end
def team def team
...@@ -380,7 +360,7 @@ class Project < ActiveRecord::Base ...@@ -380,7 +360,7 @@ class Project < ActiveRecord::Base
end end
end end
def clear_import_data def reset_cache_and_import_attrs
update(import_error: nil) update(import_error: nil)
ProjectCacheWorker.perform_async(self.id) ProjectCacheWorker.perform_async(self.id)
...@@ -389,14 +369,14 @@ class Project < ActiveRecord::Base ...@@ -389,14 +369,14 @@ class Project < ActiveRecord::Base
end end
def import_url=(value) def import_url=(value)
import_url = Gitlab::ImportUrl.new(value) import_url = Gitlab::UrlSanitizer.new(value)
create_or_update_import_data(credentials: import_url.credentials) create_or_update_import_data(credentials: import_url.credentials)
super(import_url.sanitized_url) super(import_url.sanitized_url)
end end
def import_url def import_url
if import_data && super if import_data && super
import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials) import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
import_url.full_url import_url.full_url
else else
super super
...@@ -446,12 +426,7 @@ class Project < ActiveRecord::Base ...@@ -446,12 +426,7 @@ class Project < ActiveRecord::Base
end end
def safe_import_url def safe_import_url
result = URI.parse(self.import_url) Gitlab::UrlSanitizer.new(import_url).masked_url
result.password = '*****' unless result.password.nil?
result.user = '*****' unless result.user.nil? || result.user == "git" #tokens or other data may be saved as user
result.to_s
rescue
self.import_url
end end
def check_limit def check_limit
......
...@@ -35,7 +35,7 @@ class SlackService ...@@ -35,7 +35,7 @@ class SlackService
private private
def message def message
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} second(s)" "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
end end
def format(string) def format(string)
......
...@@ -245,7 +245,7 @@ class Repository ...@@ -245,7 +245,7 @@ class Repository
def cache_keys def cache_keys
%i(size branch_names tag_names commit_count %i(size branch_names tag_names commit_count
readme version contribution_guide changelog readme version contribution_guide changelog
license_blob license_key) license_blob license_key gitignore)
end end
def build_cache def build_cache
...@@ -256,6 +256,10 @@ class Repository ...@@ -256,6 +256,10 @@ class Repository
end end
end end
def expire_gitignore
cache.expire(:gitignore)
end
def expire_tags_cache def expire_tags_cache
cache.expire(:tag_names) cache.expire(:tag_names)
@tags = nil @tags = nil
...@@ -472,33 +476,37 @@ class Repository ...@@ -472,33 +476,37 @@ class Repository
def changelog def changelog
cache.fetch(:changelog) do cache.fetch(:changelog) do
tree(:head).blobs.find do |file| file_on_head(/\A(changelog|history|changes|news)/i)
file.name =~ /\A(changelog|history|changes|news)/i
end
end end
end end
def license_blob def license_blob
return nil if !exists? || empty? return nil unless head_exists?
cache.fetch(:license_blob) do cache.fetch(:license_blob) do
tree(:head).blobs.find do |file| file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
end
end end
end end
def license_key def license_key
return nil if !exists? || empty? return nil unless head_exists?
cache.fetch(:license_key) do cache.fetch(:license_key) do
Licensee.license(path).try(:key) Licensee.license(path).try(:key)
end end
end end
def gitlab_ci_yml def gitignore
return nil if !exists? || empty? return nil if !exists? || empty?
cache.fetch(:gitignore) do
file_on_head(/\A\.gitignore\z/)
end
end
def gitlab_ci_yml
return nil unless head_exists?
@gitlab_ci_yml ||= tree(:head).blobs.find do |file| @gitlab_ci_yml ||= tree(:head).blobs.find do |file|
file.name == '.gitlab-ci.yml' file.name == '.gitlab-ci.yml'
end end
...@@ -854,7 +862,7 @@ class Repository ...@@ -854,7 +862,7 @@ class Repository
def search_files(query, ref) def search_files(query, ref)
offset = 2 offset = 2
args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref}) args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end end
...@@ -965,7 +973,7 @@ class Repository ...@@ -965,7 +973,7 @@ class Repository
end end
def main_language def main_language
return if empty? || rugged.head_unborn? return unless head_exists?
Linguist::Repository.new(rugged, rugged.head.target_id).language Linguist::Repository.new(rugged, rugged.head.target_id).language
end end
...@@ -985,4 +993,12 @@ class Repository ...@@ -985,4 +993,12 @@ class Repository
def cache def cache
@cache ||= RepositoryCache.new(path_with_namespace) @cache ||= RepositoryCache.new(path_with_namespace)
end end
def head_exists?
exists? && !empty? && !rugged.head_unborn?
end
def file_on_head(regex)
tree(:head).blobs.find { |file| file.name =~ regex }
end
end end
class Todo < ActiveRecord::Base class Todo < ActiveRecord::Base
ASSIGNED = 1 ASSIGNED = 1
MENTIONED = 2 MENTIONED = 2
BUILD_FAILED = 3
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :note belongs_to :note
...@@ -28,6 +29,10 @@ class Todo < ActiveRecord::Base ...@@ -28,6 +29,10 @@ class Todo < ActiveRecord::Base
state :done state :done
end end
def build_failed?
action == BUILD_FAILED
end
def body def body
if note.present? if note.present?
note.note note.note
......
...@@ -398,11 +398,6 @@ class User < ActiveRecord::Base ...@@ -398,11 +398,6 @@ class User < ActiveRecord::Base
owned_groups.select(:id), namespace.id).joins(:namespace) owned_groups.select(:id), namespace.id).joins(:namespace)
end end
# Team membership in authorized projects
def tm_in_authorized_projects
ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
end
def is_admin? def is_admin?
admin admin
end end
...@@ -492,10 +487,6 @@ class User < ActiveRecord::Base ...@@ -492,10 +487,6 @@ class User < ActiveRecord::Base
"#{name} (#{username})" "#{name} (#{username})"
end end
def tm_of(project)
project.project_member_by_id(self.id)
end
def already_forked?(project) def already_forked?(project)
!!fork_of(project) !!fork_of(project)
end end
......
...@@ -6,7 +6,7 @@ module Auth ...@@ -6,7 +6,7 @@ module Auth
return error('not found', 404) unless registry.enabled return error('not found', 404) unless registry.enabled
if params[:offline_token] if params[:offline_token]
return error('unauthorized', 401) unless current_user return error('unauthorized', 401) unless current_user || project
else else
return error('forbidden', 403) unless scope return error('forbidden', 403) unless scope
end end
...@@ -20,7 +20,7 @@ module Auth ...@@ -20,7 +20,7 @@ module Auth
token.issuer = registry.issuer token.issuer = registry.issuer
token.audience = AUDIENCE token.audience = AUDIENCE
token[:access] = names.map do |name| token[:access] = names.map do |name|
{ type: 'repository', name: name, actions: %w(pull push) } { type: 'repository', name: name, actions: %w(*) }
end end
token.encoded token.encoded
end end
......
module Ci
class CreatePipelineService < BaseService
def execute
pipeline = project.ci_commits.new(params)
unless ref_names.include?(params[:ref])
pipeline.errors.add(:base, 'Reference not found')
return pipeline
end
unless commit
pipeline.errors.add(:base, 'Commit not found')
return pipeline
end
unless can?(current_user, :create_pipeline, project)
pipeline.errors.add(:base, 'Insufficient permissions to create a new pipeline')
return pipeline
end
begin
Ci::Commit.transaction do
pipeline.sha = commit.id
unless pipeline.config_processor
pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
raise ActiveRecord::Rollback
end
pipeline.save!
pipeline.create_builds(current_user)
end
rescue
pipeline.errors.add(:base, 'The pipeline could not be created. Please try again.')
end
pipeline
end
private
def ref_names
@ref_names ||= project.repository.ref_names
end
def commit
@commit ||= project.commit(params[:ref])
end
end
end
...@@ -18,9 +18,7 @@ class CreateCommitBuildsService ...@@ -18,9 +18,7 @@ class CreateCommitBuildsService
return false return false
end end
commit = project.ci_commit(sha, ref) commit = Ci::Commit.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
unless commit
commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
# Skip creating ci_commit when no gitlab-ci.yml is found # Skip creating ci_commit when no gitlab-ci.yml is found
unless commit.ci_yaml_file unless commit.ci_yaml_file
...@@ -29,7 +27,6 @@ class CreateCommitBuildsService ...@@ -29,7 +27,6 @@ class CreateCommitBuildsService
# Create a new ci_commit # Create a new ci_commit
commit.save! commit.save!
end
# Skip creating builds for commits that have [ci skip] # Skip creating builds for commits that have [ci skip]
unless commit.skip_ci? unless commit.skip_ci?
......
...@@ -24,6 +24,10 @@ module Issues ...@@ -24,6 +24,10 @@ module Issues
todo_service.reassigned_issue(issue, current_user) todo_service.reassigned_issue(issue, current_user)
end end
if issue.previous_changes.include?('confidential')
create_confidentiality_note(issue)
end
added_labels = issue.labels - old_labels added_labels = issue.labels - old_labels
if added_labels.present? if added_labels.present?
notification_service.relabeled_issue(issue, added_labels, current_user) notification_service.relabeled_issue(issue, added_labels, current_user)
...@@ -37,5 +41,11 @@ module Issues ...@@ -37,5 +41,11 @@ module Issues
def close_service def close_service
Issues::CloseService Issues::CloseService
end end
private
def create_confidentiality_note(issue)
SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
end
end end
end end
module MergeRequests
class AddTodoWhenBuildFailsService < MergeRequests::BaseService
# Adds a todo to the parent merge_request when a CI build fails
def execute(commit_status)
each_merge_request(commit_status) do |merge_request|
todo_service.merge_request_build_failed(merge_request)
end
end
# Closes any pending build failed todos for the parent MRs when a build is retried
def close(commit_status)
each_merge_request(commit_status) do |merge_request|
todo_service.merge_request_build_retried(merge_request)
end
end
end
end
...@@ -38,5 +38,30 @@ module MergeRequests ...@@ -38,5 +38,30 @@ module MergeRequests
def filter_params def filter_params
super(:merge_request) super(:merge_request)
end end
def merge_request_from(commit_status)
branches = commit_status.ref
# This is for ref-less builds
branches ||= @project.repository.branch_names_contains(commit_status.sha)
return [] if branches.blank?
merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
merge_requests.uniq.select(&:source_project)
end
def each_merge_request(commit_status)
merge_request_from(commit_status).each do |merge_request|
ci_commit = merge_request.ci_commit
next unless ci_commit
next unless ci_commit.sha == commit_status.sha
yield merge_request, ci_commit
end
end
end end
end end
...@@ -8,11 +8,14 @@ module MergeRequests ...@@ -8,11 +8,14 @@ module MergeRequests
@project = Project.find(params[:target_project_id]) if params[:target_project_id] @project = Project.find(params[:target_project_id]) if params[:target_project_id]
filter_params filter_params
label_params = params[:label_ids] label_params = params.delete(:label_ids)
merge_request = MergeRequest.new(params.except(:label_ids)) force_remove_source_branch = params.delete(:force_remove_source_branch)
merge_request = MergeRequest.new(params)
merge_request.source_project = source_project merge_request.source_project = source_project
merge_request.target_project ||= source_project merge_request.target_project ||= source_project
merge_request.author = current_user merge_request.author = current_user
merge_request.merge_params['force_remove_source_branch'] = force_remove_source_branch
if merge_request.save if merge_request.save
merge_request.update_attributes(label_ids: label_params) merge_request.update_attributes(label_ids: label_params)
......
...@@ -45,10 +45,14 @@ module MergeRequests ...@@ -45,10 +45,14 @@ module MergeRequests
def after_merge def after_merge
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
DeleteBranchService.new(@merge_request.source_project, current_user). DeleteBranchService.new(@merge_request.source_project, branch_deletion_user).
execute(merge_request.source_branch) execute(merge_request.source_branch)
end end
end end
def branch_deletion_user
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
end
end end
end end
...@@ -20,15 +20,9 @@ module MergeRequests ...@@ -20,15 +20,9 @@ module MergeRequests
# Triggers the automatic merge of merge_request once the build succeeds # Triggers the automatic merge of merge_request once the build succeeds
def trigger(commit_status) def trigger(commit_status)
merge_requests = merge_request_from(commit_status) each_merge_request(commit_status) do |merge_request, ci_commit|
merge_requests.each do |merge_request|
next unless merge_request.merge_when_build_succeeds? next unless merge_request.merge_when_build_succeeds?
next unless merge_request.mergeable? next unless merge_request.mergeable?
ci_commit = merge_request.ci_commit
next unless ci_commit
next unless ci_commit.sha == commit_status.sha
next unless ci_commit.success? next unless ci_commit.success?
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
...@@ -47,20 +41,5 @@ module MergeRequests ...@@ -47,20 +41,5 @@ module MergeRequests
end end
end end
private
def merge_request_from(commit_status)
branches = commit_status.ref
# This is for ref-less builds
branches ||= @project.repository.branch_names_contains(commit_status.sha)
return [] if branches.blank?
merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
merge_requests.uniq.select(&:source_project)
end
end end
end end
...@@ -12,6 +12,7 @@ module MergeRequests ...@@ -12,6 +12,7 @@ module MergeRequests
close_merge_requests close_merge_requests
reload_merge_requests reload_merge_requests
reset_merge_when_build_succeeds reset_merge_when_build_succeeds
mark_pending_todos_done
# Leave a system note if a branch was deleted/added # Leave a system note if a branch was deleted/added
if branch_added? || branch_removed? if branch_added? || branch_removed?
...@@ -80,6 +81,12 @@ module MergeRequests ...@@ -80,6 +81,12 @@ module MergeRequests
merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds) merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds)
end end
def mark_pending_todos_done
merge_requests_for_source_branch.each do |merge_request|
todo_service.merge_request_push(merge_request, @current_user)
end
end
def find_new_commits def find_new_commits
if branch_added? if branch_added?
@commits = [] @commits = []
......
...@@ -11,6 +11,8 @@ module MergeRequests ...@@ -11,6 +11,8 @@ module MergeRequests
params.except!(:target_project_id) params.except!(:target_project_id)
params.except!(:source_branch) params.except!(:source_branch)
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request) update(merge_request)
end end
......
...@@ -4,6 +4,10 @@ module Projects ...@@ -4,6 +4,10 @@ module Projects
@project.issues.visible_to_user(current_user).opened.select([:iid, :title]) @project.issues.visible_to_user(current_user).opened.select([:iid, :title])
end end
def milestones
@project.milestones.active.reorder(due_date: :asc, title: :asc).select([:iid, :title])
end
def merge_requests def merge_requests
@project.merge_requests.opened.select([:iid, :title]) @project.merge_requests.opened.select([:iid, :title])
end end
......
...@@ -169,12 +169,33 @@ class SystemNoteService ...@@ -169,12 +169,33 @@ class SystemNoteService
# #
# Returns the created Note object # Returns the created Note object
def self.change_title(noteable, project, author, old_title) def self.change_title(noteable, project, author, old_title)
return unless noteable.respond_to?(:title) new_title = noteable.title.dup
body = "Title changed from **#{old_title}** to **#{noteable.title}**" old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_title, new_title).inline_diffs
marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true)
marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true)
body = "Changed title: **#{marked_old_title}** → **#{marked_new_title}**"
create_note(noteable: noteable, project: project, author: author, note: body) create_note(noteable: noteable, project: project, author: author, note: body)
end end
# Called when the confidentiality changes
#
# issue - Issue object
# project - Project owning the issue
# author - User performing the change
#
# Example Note text:
#
# "Made the issue confidential"
#
# Returns the created Note object
def self.change_issue_confidentiality(issue, project, author)
body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible'
create_note(noteable: issue, project: project, author: author, note: body)
end
# Called when a branch in Noteable is changed # Called when a branch in Noteable is changed
# #
# noteable - Noteable object # noteable - Noteable object
......
...@@ -80,6 +80,30 @@ class TodoService ...@@ -80,6 +80,30 @@ class TodoService
mark_pending_todos_as_done(merge_request, current_user) mark_pending_todos_as_done(merge_request, current_user)
end end
# When a build fails on the HEAD of a merge request we should:
#
# * create a todo for that user to fix it
#
def merge_request_build_failed(merge_request)
create_build_failed_todo(merge_request)
end
# When a new commit is pushed to a merge request we should:
#
# * mark all pending todos related to the merge request for that user as done
#
def merge_request_push(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user)
end
# When a build is retried to a merge request we should:
#
# * mark all pending todos related to the merge request for the author as done
#
def merge_request_build_retried(merge_request)
mark_pending_todos_as_done(merge_request, merge_request.author)
end
# When create a note we should: # When create a note we should:
# #
# * mark all pending todos related to the noteable for the note author as done # * mark all pending todos related to the noteable for the note author as done
...@@ -153,6 +177,12 @@ class TodoService ...@@ -153,6 +177,12 @@ class TodoService
create_todos(mentioned_users, attributes) create_todos(mentioned_users, attributes)
end end
def create_build_failed_todo(merge_request)
author = merge_request.author
attributes = attributes_for_todo(merge_request.project, merge_request, author, Todo::BUILD_FAILED)
create_todos(author, attributes)
end
def attributes_for_target(target) def attributes_for_target(target)
attributes = { attributes = {
project_id: target.project.id, project_id: target.project.id,
......
...@@ -9,8 +9,6 @@ ...@@ -9,8 +9,6 @@
%span.runner-state.runner-state-specific %span.runner-state.runner-state-specific
Specific Specific
- if @runner.shared? - if @runner.shared?
.bs-callout.bs-callout-success .bs-callout.bs-callout-success
%h4 This runner will process builds from ALL UNASSIGNED projects %h4 This runner will process builds from ALL UNASSIGNED projects
......
%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} } %li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} }
.todo-item.todo-block .todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:'' = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
.todo-title.title .todo-title.title
- unless todo.build_failed?
%span.author-name %span.author-name
- if todo.author - if todo.author
= link_to_author(todo) = link_to_author(todo)
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
= icon('bookmark fw') = icon('bookmark fw')
%span %span
Projects Projects
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
Todos Todos
%span.count.todos-pending-count= number_with_delimiter(todos_pending_count) %span.count.todos-pending-count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
= icon('dashboard fw') = icon('dashboard fw')
%span %span
Activity Activity
...@@ -26,13 +26,13 @@ ...@@ -26,13 +26,13 @@
%span %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
%span %span
Issues Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count) %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
= icon('tasks fw') = icon('tasks fw')
%span %span
Merge Requests Merge Requests
......
%ul.nav.nav-sidebar - if current_user
- if @project.group .controls
= nav_link do - access = user_max_access_in_project(current_user.id, @project)
= link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do - can_edit = can?(current_user, :admin_project, @project)
= icon('caret-square-o-left fw') .dropdown.project-settings-dropdown
%span %a.dropdown-new.btn.btn-default#project-settings-button{href: '#', 'data-toggle' => 'dropdown'}
Go to group = icon('cog')
- else = icon('caret-down')
= nav_link do %ul.dropdown-menu.dropdown-menu-align-right
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do = render 'layouts/nav/project_settings'
= icon('caret-square-o-left fw') %li.divider
%span - if can_edit
Go to dashboard %li
= link_to edit_project_path(@project) do
%li.separate-item Edit Project
- if access
%li
= link_to leave_namespace_project_project_members_path(@project.namespace, @project),
data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
Leave Project
%ul.nav-links
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('bookmark fw') = icon('bookmark fw')
...@@ -38,13 +44,21 @@ ...@@ -38,13 +44,21 @@
%span %span
Commits Commits
- if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
= icon('ship fw')
%span
Pipelines
%span.badge.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count)
- if project_nav_tab? :builds - if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do = nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
= icon('cubes fw') = icon('cubes fw')
%span %span
Builds Builds
%span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) %span.badge.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
- if project_nav_tab? :container_registry - if project_nav_tab? :container_registry
= nav_link(controller: %w(container_registry)) do = nav_link(controller: %w(container_registry)) do
...@@ -74,7 +88,7 @@ ...@@ -74,7 +88,7 @@
%span %span
Issues Issues
- if @project.default_issues_tracker? - if @project.default_issues_tracker?
%span.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count) %span.badge.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do = nav_link(controller: :merge_requests) do
...@@ -82,14 +96,7 @@ ...@@ -82,14 +96,7 @@
= icon('tasks fw') = icon('tasks fw')
%span %span
Merge Requests Merge Requests
%span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) %span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- if project_nav_tab? :team
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
%span
Members
- if project_nav_tab? :labels - if project_nav_tab? :labels
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
...@@ -105,13 +112,6 @@ ...@@ -105,13 +112,6 @@
%span %span
Wiki Wiki
- if project_nav_tab? :forks
= nav_link(controller: :forks, action: :index) do
= link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks' do
= icon('code-fork fw')
%span
Forks
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
...@@ -119,13 +119,6 @@ ...@@ -119,13 +119,6 @@
%span %span
Snippets Snippets
- if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
= link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
Settings
-# Global shortcut to network page for compatibility -# Global shortcut to network page for compatibility
- if project_nav_tab? :network - if project_nav_tab? :network
%li.hidden %li.hidden
......
%ul.nav.nav-sidebar - if project_nav_tab? :team
= nav_link do = nav_link(controller: [:project_members, :teams]) do
= link_to project_path(@project), title: 'Go to project', class: 'back-link' do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('caret-square-o-left fw')
%span %span
Go to project Members
%li.separate-item - if @project.allowed_to_share_with_group?
%ul.sidebar-subnav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), title: 'Project Settings' do
= icon('pencil-square-o fw')
%span
Project Settings
- if @project.allowed_to_share_with_group?
= nav_link(controller: :group_links) do = nav_link(controller: :group_links) do
= link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
= icon('share-square-o fw')
%span %span
Groups Groups
= nav_link(controller: :deploy_keys) do = nav_link(controller: :deploy_keys) do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
= icon('key fw')
%span %span
Deploy Keys Deploy Keys
= nav_link(controller: :hooks) do = nav_link(controller: :hooks) do
= link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do
= icon('link fw')
%span %span
Webhooks Webhooks
= nav_link(controller: :services) do = nav_link(controller: :services) do
= link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
= icon('cogs fw')
%span %span
Services Services
= nav_link(controller: :protected_branches) do = nav_link(controller: :protected_branches) do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
= icon('lock fw')
%span %span
Protected Branches Protected Branches
- if @project.builds_enabled? - if @project.builds_enabled?
= nav_link(controller: :runners) do = nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
= icon('cog fw')
%span %span
Runners Runners
= nav_link(controller: :variables) do = nav_link(controller: :variables) do
= link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
= icon('code fw')
%span %span
Variables Variables
= nav_link(controller: :triggers) do = nav_link(controller: :triggers) do
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw')
%span %span
Triggers Triggers
= nav_link(controller: :badges) do = nav_link(controller: :badges) do
= link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
= icon('star-half-empty fw')
%span %span
Badges Badges
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
%title %title
GitLab GitLab
= stylesheet_link_tag 'notify' = stylesheet_link_tag 'notify'
= yield :head
%body %body
%div.content %div.content
= yield = yield
......
- page_title @project.name_with_namespace - page_title @project.name_with_namespace
- page_description @project.description unless page_description - page_description @project.description unless page_description
- header_title project_title(@project) unless header_title - header_title project_title(@project) unless header_title
- sidebar "project" unless sidebar - nav "project"
- content_for :scripts_body_top do - content_for :scripts_body_top do
- project = @target_project || @project - project = @target_project || @project
......
- page_title "Settings" - page_title "Settings"
- header_title project_title(@project, "Settings", edit_project_path(@project)) - nav "project"
- sidebar "project_settings"
= render template: "layouts/project" = render template: "layouts/project"
= content_for :head do
= stylesheet_link_tag 'mailers/repository_push_email'
%h3 %h3
#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))} at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
...@@ -43,26 +46,38 @@ ...@@ -43,26 +46,38 @@
= diff.new_path = diff.new_path
- unless @message.disable_diffs? - unless @message.disable_diffs?
- diff_files = @message.diffs
- if @message.compare_timeout
%h5 The diff was not included because it is too large.
- else
%h4 Changes: %h4 Changes:
- @message.diffs.each_with_index do |diff, i| - diff_files.each_with_index do |diff_file, i|
%li{id: "diff-#{i}"} %li{id: "diff-#{i}"}
%a{href: @message.target_url + "#diff-#{i}"} %a{href: @message.target_url + "#diff-#{i}"}<
- if diff.deleted_file - if diff_file.deleted_file
%strong %strong<
= diff.old_path = diff_file.old_path
deleted deleted
- elsif diff.renamed_file - elsif diff_file.renamed_file
%strong %strong<
= diff.old_path = diff_file.old_path
&rarr; &rarr;
%strong %strong<
= diff.new_path = diff_file.new_path
- else
%strong<
= diff_file.new_path
- if diff_file.too_large?
The diff for this file was not included because it is too large.
- else - else
%strong
= diff.new_path
%hr %hr
= color_email_diff(diff.diff) - diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last
- blob = @message.project.repository.blob_for_diff(diff_commit, diff_file)
- if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob)
%table.code.white
- diff_file.highlighted_diff_lines.each do |line|
= render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true}
- else
No preview for this file type
%br %br
- if @message.compare_timeout
%h5 Huge diff. To prevent performance issues changes are hidden
...@@ -25,24 +25,28 @@ ...@@ -25,24 +25,28 @@
- else - else
\- #{diff.new_path} \- #{diff.new_path}
- unless @message.disable_diffs? - unless @message.disable_diffs?
- if @message.compare_timeout
\
\
The diff was not included because it is too large.
- else
\ \
\ \
Changes: Changes:
- @message.diffs.each do |diff| - @message.diffs.each do |diff_file|
\ \
\===================================== \=====================================
- if diff.deleted_file - if diff_file.deleted_file
#{diff.old_path} deleted #{diff_file.old_path} deleted
- elsif diff.renamed_file - elsif diff_file.renamed_file
#{diff.old_path}#{diff.new_path} #{diff_file.old_path}#{diff_file.new_path}
- else - else
= diff.new_path = diff_file.new_path
\===================================== \=====================================
!= diff.diff - if diff_file.too_large?
- if @message.compare_timeout The diff for this file was not included because it is too large.
\ - else
\ != diff_file.diff.diff
Huge diff. To prevent performance issues it was hidden
- if @message.target_url - if @message.target_url
\ \
\ \
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment