Commit 6e3df42c authored by James Lopez's avatar James Lopez

fix merge conflicts

parents f2f345e6 53ad33e4
...@@ -21,6 +21,7 @@ AllCops: ...@@ -21,6 +21,7 @@ AllCops:
- 'lib/email_validator.rb' - 'lib/email_validator.rb'
- 'lib/gitlab/upgrader.rb' - 'lib/gitlab/upgrader.rb'
- 'lib/gitlab/seeder.rb' - 'lib/gitlab/seeder.rb'
- 'generator_templates/**/*'
##################### Style ################################## ##################### Style ##################################
......
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.8.2 (unreleased)
- Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources
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
...@@ -21,6 +38,7 @@ v 8.8.0 (unreleased) ...@@ -21,6 +38,7 @@ v 8.8.0 (unreleased)
- Bump mail_room to 0.7.0 to fix stuck IDLE connections - Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph. - Remove future dates from contribution calendar graph.
- Support e-mail notifications for comments on project snippets - Support e-mail notifications for comments on project snippets
- Fix API leak of notes of unauthorized issues, snippets and merge requests
- Use ActionDispatch Remote IP for Akismet checking - Use ActionDispatch Remote IP for Akismet checking
- Fix error when visiting commit builds page before build was updated - Fix error when visiting commit builds page before build was updated
- Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
...@@ -36,13 +54,15 @@ v 8.8.0 (unreleased) ...@@ -36,13 +54,15 @@ 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
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
- 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)
...@@ -58,10 +78,16 @@ v 8.8.0 (unreleased) ...@@ -58,10 +78,16 @@ v 8.8.0 (unreleased)
- Add counter metrics for rails cache - Add counter metrics for rails cache
- 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)
- 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)
- Fix import by `Any Git URL` broken if the URL contains a space - Fix import by `Any Git URL` broken if the URL contains a space
- Fix import from GitLab.com to a private instance failure. !4181
- Fix external imports not finding the import data. !4106
v 8.7.5 v 8.7.5
- Fix relative links in wiki pages. !4050 - Fix relative links in wiki pages. !4050
...@@ -83,6 +109,7 @@ v 8.7.3 ...@@ -83,6 +109,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
...@@ -891,7 +918,7 @@ v 8.1.3 ...@@ -891,7 +918,7 @@ v 8.1.3
- Use issue editor as cross reference comment author when issue is edited with a new mention - Use issue editor as cross reference comment author when issue is edited with a new mention
- Add Facebook authentication - Add Facebook authentication
v 8.1.2 v 8.1.1
- Fix cloning Wiki repositories via HTTP (Stan Hu) - Fix cloning Wiki repositories via HTTP (Stan Hu)
- Add migration to remove satellites directory - Add migration to remove satellites directory
- Fix specific runners visibility - Fix specific runners visibility
...@@ -1516,20 +1543,17 @@ v 7.10.0 ...@@ -1516,20 +1543,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
......
...@@ -325,7 +325,7 @@ gem "mail_room", "~> 0.7" ...@@ -325,7 +325,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)
...@@ -883,7 +885,7 @@ PLATFORMS ...@@ -883,7 +885,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
......
@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")
......
...@@ -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 ''
results = fuzzaldrinPlus.filter(data, search_text, # When data is an array of objects therefore [object Array] e.g.
key: @options.keys # [
) # { prop: 'foo' },
# { prop: 'baz' }
# ]
if _.isArray(data)
results = fuzzaldrinPlus.filter(data, search_text,
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
selectedObject = @renderedData[selectedIndex] groupName = el.data('group')
if groupName
selectedIndex = el.data('index')
selectedObject = @renderedData[groupName][selectedIndex]
else
selectedIndex = el.closest('li').index()
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
...@@ -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)
...@@ -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;
}
...@@ -36,22 +36,6 @@ ...@@ -36,22 +36,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;
} }
......
...@@ -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;
/* /*
...@@ -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
*/ */
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
} }
} }
.zen-cotrol { .zen-control {
padding: 0; padding: 0;
color: #555; color: #555;
background: none; background: none;
......
@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;
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
.editor-file-name { .editor-file-name {
@extend .monospace; @extend .monospace;
float: left; float: left;
margin-right: 10px; margin-right: 10px;
} }
...@@ -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;
}
} }
} }
.pipeline-stage {
overflow: hidden;
text-overflow: ellipsis;
}
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
} }
.no-ssh-key-message, .project-limit-message { .no-ssh-key-message, .project-limit-message {
background-color: #f28d35; background-color: #f28d35;
margin-bottom: 16px; margin-bottom: 0;
} }
.new_project, .new_project,
.edit_project { .edit_project {
...@@ -149,6 +149,10 @@ ...@@ -149,6 +149,10 @@
white-space: nowrap; white-space: nowrap;
margin: 0 11px 0 4px; margin: 0 11px 0 4px;
a {
color: inherit;
}
&:hover { &:hover {
background: #fff; background: #fff;
} }
...@@ -161,7 +165,7 @@ ...@@ -161,7 +165,7 @@
display: inline-table; display: inline-table;
margin-right: 12px; margin-right: 12px;
a { > a {
margin: -1px; margin: -1px;
} }
} }
......
...@@ -9,6 +9,6 @@ class Admin::AbuseReportsController < Admin::ApplicationController ...@@ -9,6 +9,6 @@ class Admin::AbuseReportsController < Admin::ApplicationController
abuse_report.remove_user(deleted_by: current_user) if params[:remove_user] abuse_report.remove_user(deleted_by: current_user) if params[:remove_user]
abuse_report.destroy abuse_report.destroy
render nothing: true head :ok
end end
end end
...@@ -32,7 +32,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -32,7 +32,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default(default: { action: 'index' }) } format.html { redirect_back_or_default(default: { action: 'index' }) }
format.js { render nothing: true } format.js { head :ok }
end end
end end
......
...@@ -6,7 +6,7 @@ class Admin::KeysController < Admin::ApplicationController ...@@ -6,7 +6,7 @@ class Admin::KeysController < Admin::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.js { render nothing: true } format.js { head :ok }
end end
end end
......
...@@ -9,23 +9,18 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -9,23 +9,18 @@ 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
else
assign_builds_and_projects
render 'show'
end end
end end
...@@ -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
...@@ -11,7 +11,7 @@ class Admin::SpamLogsController < Admin::ApplicationController ...@@ -11,7 +11,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed." redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
else else
spam_log.destroy spam_log.destroy
render nothing: true head :ok
end end
end end
end end
...@@ -154,7 +154,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -154,7 +154,7 @@ class Admin::UsersController < Admin::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") } format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
format.js { render nothing: true } format.js { head :ok }
end end
end end
......
...@@ -6,7 +6,7 @@ module ToggleSubscriptionAction ...@@ -6,7 +6,7 @@ module ToggleSubscriptionAction
subscribable_resource.toggle_subscription(current_user) subscribable_resource.toggle_subscription(current_user)
render nothing: true head :ok
end end
private private
......
...@@ -12,7 +12,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -12,7 +12,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: todo_notice } format.html { redirect_to dashboard_todos_path, notice: todo_notice }
format.js { render nothing: true } format.js { head :ok }
format.json do format.json do
render json: { count: @todos.size, done_count: current_user.todos.done.count } render json: { count: @todos.size, done_count: current_user.todos.done.count }
end end
...@@ -24,7 +24,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -24,7 +24,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { render nothing: true } format.js { head :ok }
format.json do format.json do
find_todos find_todos
render json: { count: @todos.size, done_count: current_user.todos.done.count } render json: { count: @todos.size, done_count: current_user.todos.done.count }
......
...@@ -40,7 +40,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -40,7 +40,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true } format.js { head :ok }
end 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
......
...@@ -24,7 +24,7 @@ class Profiles::EmailsController < Profiles::ApplicationController ...@@ -24,7 +24,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to profile_emails_url } format.html { redirect_to profile_emails_url }
format.js { render nothing: true } format.js { head :ok }
end end
end end
......
...@@ -32,7 +32,7 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -32,7 +32,7 @@ class Profiles::KeysController < Profiles::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to profile_keys_url } format.html { redirect_to profile_keys_url }
format.js { render nothing: true } format.js { head :ok }
end end
end end
......
class Projects::ContainerRegistryController < Projects::ApplicationController
before_action :verify_registry_enabled
before_action :authorize_read_container_image!
before_action :authorize_update_container_image!, only: [:destroy]
layout 'project'
def index
@tags = container_registry_repository.tags
end
def destroy
url = namespace_project_container_registry_index_path(project.namespace, project)
if tag.delete
redirect_to url
else
redirect_to url, alert: 'Failed to remove tag'
end
end
private
def verify_registry_enabled
render_404 unless Gitlab.config.registry.enabled
end
def container_registry_repository
@container_registry_repository ||= project.container_registry_repository
end
def tag
@tag ||= container_registry_repository.tag(params[:id])
end
end
...@@ -20,6 +20,7 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -20,6 +20,7 @@ class Projects::ImportsController < Projects::ApplicationController
@project.import_retry @project.import_retry
else else
@project.import_start @project.import_start
@project.add_import_job
end end
end end
......
...@@ -334,7 +334,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -334,7 +334,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
......
...@@ -75,7 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -75,7 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to namespace_project_milestones_path } format.html { redirect_to namespace_project_milestones_path }
format.js { render nothing: true } format.js { head :ok }
end end
end end
......
...@@ -43,7 +43,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -43,7 +43,7 @@ class Projects::NotesController < Projects::ApplicationController
end end
respond_to do |format| respond_to do |format|
format.js { render nothing: true } format.js { head :ok }
end end
end end
...@@ -52,7 +52,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -52,7 +52,7 @@ class Projects::NotesController < Projects::ApplicationController
note.update_attribute(:attachment, nil) note.update_attribute(:attachment, nil)
respond_to do |format| respond_to do |format|
format.js { render nothing: true } format.js { head :ok }
end end
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
...@@ -55,7 +55,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -55,7 +55,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
format.html do format.html do
redirect_to namespace_project_project_members_path(@project.namespace, @project) redirect_to namespace_project_project_members_path(@project.namespace, @project)
end end
format.js { render nothing: true } format.js { head :ok }
end end
end end
...@@ -81,7 +81,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -81,7 +81,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_projects_path, notice: "You left the project." } format.html { redirect_to dashboard_projects_path, notice: "You left the project." }
format.js { render nothing: true } format.js { head :ok }
end end
else else
if current_user == @project.owner if current_user == @project.owner
......
...@@ -39,7 +39,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -39,7 +39,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to namespace_project_protected_branches_path } format.html { redirect_to namespace_project_protected_branches_path }
format.js { render nothing: true } format.js { head :ok }
end end
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 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
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: AwardEmoji.urls, emojis: 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
......
...@@ -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
......
...@@ -33,6 +33,10 @@ module GitlabRoutingHelper ...@@ -33,6 +33,10 @@ module GitlabRoutingHelper
namespace_project_builds_path(project.namespace, project, *args) namespace_project_builds_path(project.namespace, project, *args)
end end
def project_container_registry_path(project, *args)
namespace_project_container_registry_index_path(project.namespace, project, *args)
end
def activity_project_path(project, *args) def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args) activity_namespace_project_path(project.namespace, project, *args)
end end
......
...@@ -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,10 +144,18 @@ module ProjectsHelper ...@@ -148,10 +144,18 @@ 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
if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
nav_tabs << :container_registry
end
if can?(current_user, :admin_project, project) if can?(current_user, :admin_project, project)
nav_tabs << :settings nav_tabs << :settings
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
...@@ -110,6 +110,10 @@ class Namespace < ActiveRecord::Base ...@@ -110,6 +110,10 @@ class Namespace < ActiveRecord::Base
# Ensure old directory exists before moving it # Ensure old directory exists before moving it
gitlab_shell.add_namespace(path_was) gitlab_shell.add_namespace(path_was)
if any_project_has_container_registry_tags?
raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry')
end
if gitlab_shell.mv_namespace(path_was, path) if gitlab_shell.mv_namespace(path_was, path)
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
...@@ -131,6 +135,10 @@ class Namespace < ActiveRecord::Base ...@@ -131,6 +135,10 @@ class Namespace < ActiveRecord::Base
end end
end end
def any_project_has_container_registry_tags?
projects.any?(&:has_container_registry_tags?)
end
def send_update_instructions def send_update_instructions
projects.each do |project| projects.each do |project|
project.send_move_instructions("#{path_was}/#{project.path}") project.send_move_instructions("#{path_was}/#{project.path}")
......
...@@ -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,23 +204,10 @@ class Project < ActiveRecord::Base ...@@ -204,23 +204,10 @@ class Project < ActiveRecord::Base
state :finished state :finished
state :failed state :failed
after_transition any => :started, do: :schedule_add_import_job after_transition any => :finished, do: :reset_cache_and_import_attrs
after_transition any => :finished, do: :clear_import_data
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
...@@ -282,10 +269,6 @@ class Project < ActiveRecord::Base ...@@ -282,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
...@@ -316,10 +299,6 @@ class Project < ActiveRecord::Base ...@@ -316,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
...@@ -330,12 +309,30 @@ class Project < ActiveRecord::Base ...@@ -330,12 +309,30 @@ class Project < ActiveRecord::Base
@repository ||= Repository.new(path_with_namespace, self) @repository ||= Repository.new(path_with_namespace, self)
end end
def container_registry_url def container_registry_repository
if container_registry_enabled? && Gitlab.config.registry.enabled return unless Gitlab.config.registry.enabled
"#{Gitlab.config.registry.host_with_port}/#{path_with_namespace}"
@container_registry_repository ||= begin
token = Auth::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace)
url = Gitlab.config.registry.api_url
host_port = Gitlab.config.registry.host_port
registry = ContainerRegistry::Registry.new(url, token: token, path: host_port)
registry.repository(path_with_namespace)
end end
end end
def container_registry_repository_url
if Gitlab.config.registry.enabled
"#{Gitlab.config.registry.host_port}/#{path_with_namespace}"
end
end
def has_container_registry_tags?
return unless container_registry_repository
container_registry_repository.tags.any?
end
def commit(id = 'HEAD') def commit(id = 'HEAD')
repository.commit(id) repository.commit(id)
end end
...@@ -349,10 +346,6 @@ class Project < ActiveRecord::Base ...@@ -349,10 +346,6 @@ class Project < ActiveRecord::Base
id && persisted? id && persisted?
end end
def schedule_add_import_job
run_after_commit(:add_import_job)
end
def add_import_job def add_import_job
if forked? if forked?
job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path) job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
...@@ -367,7 +360,7 @@ class Project < ActiveRecord::Base ...@@ -367,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)
...@@ -376,14 +369,14 @@ class Project < ActiveRecord::Base ...@@ -376,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
...@@ -433,12 +426,7 @@ class Project < ActiveRecord::Base ...@@ -433,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
...@@ -751,6 +739,11 @@ class Project < ActiveRecord::Base ...@@ -751,6 +739,11 @@ class Project < ActiveRecord::Base
expire_caches_before_rename(old_path_with_namespace) expire_caches_before_rename(old_path_with_namespace)
if has_container_registry_tags?
# we currently doesn't support renaming repository if it contains tags in container registry
raise Exception.new('Project cannot be renamed, because tags are present in its container registry')
end
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users. # If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository # However we cannot allow rollback since we moved repository
......
...@@ -34,7 +34,12 @@ class SlackService ...@@ -34,7 +34,12 @@ class SlackService
private private
def message def message
"#{user_name} #{state} #{issue_link} in #{project_link}: *#{title}*" case state
when "opened"
"[#{project_link}] Issue #{state} by #{user_name}"
else
"[#{project_link}] Issue #{issue_link} #{state} by #{user_name}"
end
end end
def opened_issue? def opened_issue?
...@@ -42,7 +47,11 @@ class SlackService ...@@ -42,7 +47,11 @@ class SlackService
end end
def description_message def description_message
[{ text: format(description), color: attachment_color }] [{
title: issue_title,
title_link: issue_url,
text: format(description),
color: "#C95823" }]
end end
def project_link def project_link
...@@ -50,7 +59,11 @@ class SlackService ...@@ -50,7 +59,11 @@ class SlackService
end end
def issue_link def issue_link
"[issue ##{issue_iid}](#{issue_url})" "[#{issue_title}](#{issue_url})"
end
def issue_title
"##{issue_iid} #{title}"
end end
end end
end end
...@@ -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
......
...@@ -397,11 +397,6 @@ class User < ActiveRecord::Base ...@@ -397,11 +397,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
...@@ -491,10 +486,6 @@ class User < ActiveRecord::Base ...@@ -491,10 +486,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('forbidden', 403) 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
...@@ -14,6 +14,17 @@ module Auth ...@@ -14,6 +14,17 @@ module Auth
{ token: authorized_token(scope).encoded } { token: authorized_token(scope).encoded }
end end
def self.full_access_token(*names)
registry = Gitlab.config.registry
token = JSONWebToken::RSAToken.new(registry.key)
token.issuer = registry.issuer
token.audience = AUDIENCE
token[:access] = names.map do |name|
{ type: 'repository', name: name, actions: %w(*) }
end
token.encoded
end
private private
def authorized_token(*accesses) def authorized_token(*accesses)
......
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,19 +18,16 @@ class CreateCommitBuildsService ...@@ -18,19 +18,16 @@ 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
return false return false
end
# Create a new ci_commit
commit.save!
end end
# Create a new ci_commit
commit.save!
# 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?
# Create builds for commit # Create builds for commit
......
...@@ -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
......
...@@ -6,6 +6,7 @@ module Projects ...@@ -6,6 +6,7 @@ module Projects
def execute def execute
forked_from_project_id = params.delete(:forked_from_project_id) forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
@project = Project.new(params) @project = Project.new(params)
...@@ -49,16 +50,14 @@ module Projects ...@@ -49,16 +50,14 @@ module Projects
@project.build_forked_project_link(forked_from_project_id: forked_from_project_id) @project.build_forked_project_link(forked_from_project_id: forked_from_project_id)
end end
Project.transaction do save_project_and_import_data(import_data)
@project.save
if @project.persisted? && !@project.import? @project.import_start if @project.import?
raise 'Failed to create repository' unless @project.create_repository
end
end
after_create_actions if @project.persisted? after_create_actions if @project.persisted?
@project.add_import_job if @project.import?
@project @project
rescue => e rescue => e
message = "Unable to save project: #{e.message}" message = "Unable to save project: #{e.message}"
...@@ -93,8 +92,16 @@ module Projects ...@@ -93,8 +92,16 @@ module Projects
unless @project.group unless @project.group
@project.team << [current_user, :master, current_user] @project.team << [current_user, :master, current_user]
end end
end
@project.import_start if @project.import? def save_project_and_import_data(import_data)
Project.transaction do
@project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
if @project.save && !@project.import?
raise 'Failed to create repository' unless @project.create_repository
end
end
end end
end end
end end
...@@ -26,6 +26,10 @@ module Projects ...@@ -26,6 +26,10 @@ module Projects
Project.transaction do Project.transaction do
project.destroy! project.destroy!
unless remove_registry_tags
raise_error('Failed to remove project container registry. Please try again or contact administrator')
end
unless remove_repository(repo_path) unless remove_repository(repo_path)
raise_error('Failed to remove project repository. Please try again or contact administrator') raise_error('Failed to remove project repository. Please try again or contact administrator')
end end
...@@ -59,6 +63,12 @@ module Projects ...@@ -59,6 +63,12 @@ module Projects
end end
end end
def remove_registry_tags
return true unless Gitlab.config.registry.enabled
project.container_registry_repository.delete_tags
end
def raise_error(message) def raise_error(message)
raise DestroyError.new(message) raise DestroyError.new(message)
end end
......
...@@ -34,6 +34,11 @@ module Projects ...@@ -34,6 +34,11 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists") raise TransferError.new("Project with same path in target namespace already exists")
end end
if project.has_container_registry_tags?
# we currently doesn't support renaming repository if it contains tags in container registry
raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
end
project.expire_caches_before_rename(old_path) project.expire_caches_before_rename(old_path)
# Apply new namespace id and visibility level # Apply new namespace id and visibility level
......
...@@ -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
...@@ -145,6 +169,12 @@ class TodoService ...@@ -145,6 +169,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
%span.author-name - unless todo.build_failed?
- if todo.author %span.author-name
= link_to_author(todo) - if todo.author
- else = link_to_author(todo)
(removed) - else
(removed)
%span.todo-label %span.todo-label
= todo_action_name(todo) = todo_action_name(todo)
- if todo.target - if todo.target
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.nav-block .nav-block
- if current_user - if current_user
.controls .controls
= link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do = link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter' = render 'shared/event_filter'
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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