Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorEirik Lygre <eirik.lygre@gmail.com>2015-12-09 17:07:10 +0300
committerEirik Lygre <eirik.lygre@gmail.com>2015-12-09 17:07:10 +0300
commitbb79573c01ad77d6b52245d3af262bc56f79693f (patch)
tree1bd51c1c8adc50462f22591d6ab5af8315cf9eb6 /app
parent94dc9ef9e1a85b8a4506358479a549dc3a1306b6 (diff)
parent9bfd6c44e23754b6f699586f6a0cec2879e107e0 (diff)
Merge branch 'master' into default_clone_protocol_based_on_user_keys
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js.coffee31
-rw-r--r--app/assets/javascripts/awards_handler.coffee7
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/flash.js.coffee16
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee22
-rw-r--r--app/assets/javascripts/new_commit_form.js.coffee6
-rw-r--r--app/assets/javascripts/notes.js.coffee11
-rw-r--r--app/assets/javascripts/project_select.js.coffee39
-rw-r--r--app/assets/stylesheets/framework/callout.scss5
-rw-r--r--app/assets/stylesheets/framework/common.scss30
-rw-r--r--app/assets/stylesheets/framework/files.scss1
-rw-r--r--app/assets/stylesheets/framework/flash.scss10
-rw-r--r--app/assets/stylesheets/framework/lists.scss7
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss7
-rw-r--r--app/assets/stylesheets/framework/mobile.scss3
-rw-r--r--app/assets/stylesheets/framework/panels.scss6
-rw-r--r--app/assets/stylesheets/framework/typography.scss1
-rw-r--r--app/assets/stylesheets/pages/builds.scss5
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss10
-rw-r--r--app/assets/stylesheets/pages/profile.scss6
-rw-r--r--app/assets/stylesheets/pages/ui_dev_kit.scss5
-rw-r--r--app/controllers/concerns/global_milestones.rb2
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb4
-rw-r--r--app/controllers/projects/blob_controller.rb12
-rw-r--r--app/controllers/projects/commit_controller.rb8
-rw-r--r--app/controllers/projects/graphs_controller.rb22
-rw-r--r--app/controllers/projects/hooks_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb76
-rw-r--r--app/controllers/projects/notes_controller.rb29
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb41
-rw-r--r--app/finders/milestones_finder.rb2
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb50
-rw-r--r--app/helpers/branches_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb16
-rw-r--r--app/helpers/milestones_helper.rb2
-rw-r--r--app/helpers/page_layout_helper.rb3
-rw-r--r--app/helpers/selects_helper.rb13
-rw-r--r--app/helpers/tree_helper.rb18
-rw-r--r--app/helpers/visibility_level_helper.rb26
-rw-r--r--app/models/application_setting.rb4
-rw-r--r--app/models/broadcast_message.rb8
-rw-r--r--app/models/ci/commit.rb10
-rw-r--r--app/models/ci/web_hook.rb3
-rw-r--r--app/models/commit.rb28
-rw-r--r--app/models/commit_status.rb58
-rw-r--r--app/models/global_milestone.rb31
-rw-r--r--app/models/hooks/web_hook.rb39
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/lfs_object.rb24
-rw-r--r--app/models/lfs_objects_project.rb11
-rw-r--r--app/models/merge_request.rb71
-rw-r--r--app/models/namespace.rb14
-rw-r--r--app/models/note.rb33
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_services/bamboo_service.rb7
-rw-r--r--app/models/project_services/drone_ci_service.rb29
-rw-r--r--app/models/project_services/external_wiki_service.rb6
-rw-r--r--app/models/project_services/teamcity_service.rb10
-rw-r--r--app/models/repository.rb69
-rw-r--r--app/models/sent_notification.rb2
-rw-r--r--app/models/user.rb7
-rw-r--r--app/services/create_branch_service.rb5
-rw-r--r--app/services/delete_branch_service.rb4
-rw-r--r--app/services/files/base_service.rb4
-rw-r--r--app/services/git_hooks_service.rb28
-rw-r--r--app/services/merge_requests/merge_service.rb16
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb55
-rw-r--r--app/services/merge_requests/refresh_service.rb6
-rw-r--r--app/services/notes/create_service.rb13
-rw-r--r--app/services/system_note_service.rb14
-rw-r--r--app/validators/color_validator.rb20
-rw-r--r--app/validators/email_validator.rb18
-rw-r--r--app/validators/line_code_validator.rb12
-rw-r--r--app/validators/namespace_name_validator.rb10
-rw-r--r--app/validators/namespace_validator.rb50
-rw-r--r--app/validators/url_validator.rb36
-rw-r--r--app/views/admin/application_settings/_form.html.haml4
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/dashboard/issues.html.haml20
-rw-r--r--app/views/dashboard/merge_requests.html.haml12
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml11
-rw-r--r--app/views/dashboard/milestones/index.html.haml10
-rw-r--r--app/views/groups/issues.html.haml27
-rw-r--r--app/views/groups/merge_requests.html.haml19
-rw-r--r--app/views/groups/milestones/index.html.haml24
-rw-r--r--app/views/help/ui.html.haml48
-rw-r--r--app/views/profiles/accounts/show.html.haml10
-rw-r--r--app/views/profiles/keys/new.html.haml2
-rw-r--r--app/views/projects/_last_commit.html.haml2
-rw-r--r--app/views/projects/_md_preview.html.haml17
-rw-r--r--app/views/projects/blob/_actions.html.haml16
-rw-r--r--app/views/projects/blob/_blob.html.haml6
-rw-r--r--app/views/projects/blob/_download.html.haml2
-rw-r--r--app/views/projects/blob/show.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml22
-rw-r--r--app/views/projects/builds/index.html.haml5
-rw-r--r--app/views/projects/builds/show.html.haml16
-rw-r--r--app/views/projects/commit/_builds.html.haml67
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml5
-rw-r--r--app/views/projects/commit/builds.html.haml68
-rw-r--r--app/views/projects/commit_statuses/_commit_status.html.haml11
-rw-r--r--app/views/projects/diffs/_file.html.haml7
-rw-r--r--app/views/projects/graphs/_head.html.haml2
-rw-r--r--app/views/projects/graphs/languages.html.haml32
-rw-r--r--app/views/projects/issues/_discussion.html.haml7
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml6
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml5
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml12
-rw-r--r--app/views/projects/merge_requests/_show.html.haml16
-rw-r--r--app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml2
-rw-r--r--app/views/projects/merge_requests/merge.js.haml6
-rw-r--r--app/views/projects/merge_requests/show/_builds.html.haml1
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml39
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml70
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml26
-rw-r--r--app/views/projects/milestones/_milestone.html.haml6
-rw-r--r--app/views/projects/milestones/index.html.haml15
-rw-r--r--app/views/projects/network/_head.html.haml9
-rw-r--r--app/views/projects/project_members/_group_members.html.haml11
-rw-r--r--app/views/projects/protected_branches/index.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml12
-rw-r--r--app/views/projects/tags/show.html.haml8
-rw-r--r--app/views/projects/tree/_tree_header.html.haml6
-rw-r--r--app/views/shared/_milestone_expired.html.haml5
-rw-r--r--app/views/shared/_new_commit_form.html.haml15
-rw-r--r--app/views/shared/_new_project_item_select.html.haml20
-rw-r--r--app/views/shared/issuable/_participants.html.haml (renamed from app/views/projects/merge_requests/show/_participants.html.haml)3
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/merge_worker.rb13
-rw-r--r--app/workers/stuck_ci_builds_worker.rb3
137 files changed, 1500 insertions, 628 deletions
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 9e5d594c861..746fa3cea87 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -2,6 +2,8 @@
groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.json"
+ group_projects_path: "/api/:version/groups/:id/projects.json"
+ projects_path: "/api/:version/projects.json"
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
@@ -44,6 +46,35 @@
).done (namespaces) ->
callback(namespaces)
+ # Return projects list. Filtered by query
+ projects: (query, callback) ->
+ url = Api.buildUrl(Api.projects_path)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ dataType: "json"
+ ).done (projects) ->
+ callback(projects)
+
+ # Return group projects list. Filtered by query
+ groupProjects: (group_id, query, callback) ->
+ url = Api.buildUrl(Api.group_projects_path)
+ url = url.replace(':id', group_id)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ dataType: "json"
+ ).done (projects) ->
+ callback(projects)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 09b48fe5572..96fd8f8773e 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -88,4 +88,9 @@ class @AwardsHandler
callback.call()
findEmojiIcon: (emoji) ->
- $(".icon[data-emoji='" + emoji + "']") \ No newline at end of file
+ $(".icon[data-emoji='" + emoji + "']")
+
+ scrollToAwards: ->
+ $('body, html').animate({
+ scrollTop: $('.awards').offset().top - 80
+ }, 200)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 4059fc39c67..599b4c49540 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -83,7 +83,7 @@ class Dispatcher
when 'projects:project_members:index'
new ProjectMembers()
new UsersSelect()
- when 'groups:new', 'groups:edit', 'admin:groups:edit'
+ when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
new GroupAvatar()
when 'projects:tree:show'
new TreeView()
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index b39ab0c4475..9b59d4e57f7 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,12 +1,16 @@
class @Flash
constructor: (message, type)->
- flash = $(".flash-container")
- flash.html("")
+ @flash = $(".flash-container")
+ @flash.html("")
- $('<div/>',
+ innerDiv = $('<div/>',
class: "flash-#{type}",
text: message
- ).appendTo(".flash-container")
+ )
+ innerDiv.appendTo(".flash-container")
- flash.click -> $(@).fadeOut()
- flash.show()
+ @flash.click -> $(@).fadeOut()
+ @flash.show()
+
+ pin: ->
+ @flash.addClass('flash-pinned flash-raised')
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 593a8f42130..b0eeb1db536 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -43,6 +43,7 @@
#
class @MergeRequestTabs
diffsLoaded: false
+ buildsLoaded: false
commitsLoaded: false
constructor: (@opts = {}) ->
@@ -54,6 +55,12 @@ class @MergeRequestTabs
bindEvents: ->
$(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown
+ $(document).on 'click', '.js-show-tab', @showTab
+
+ showTab: (event) =>
+ event.preventDefault()
+
+ @activateTab $(event.target).data('action')
tabShown: (event) =>
$target = $(event.target)
@@ -63,6 +70,8 @@ class @MergeRequestTabs
@loadCommits($target.attr('href'))
else if action == 'diffs'
@loadDiff($target.attr('href'))
+ else if action == 'builds'
+ @loadBuilds($target.attr('href'))
@setCurrentAction(action)
@@ -101,7 +110,7 @@ class @MergeRequestTabs
action = 'notes' if action == 'show'
# Remove a trailing '/commits' or '/diffs'
- new_state = @_location.pathname.replace(/\/(commits|diffs)(\.html)?\/?$/, '')
+ new_state = @_location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '')
# Append the new action if we're on a tab other than 'notes'
unless action == 'notes'
@@ -139,6 +148,17 @@ class @MergeRequestTabs
@diffsLoaded = true
@scrollToElement("#diffs")
+ loadBuilds: (source) ->
+ return if @buildsLoaded
+
+ @_get
+ url: "#{source}.json"
+ success: (data) =>
+ document.getElementById('builds').innerHTML = data.html
+ $('.js-timeago').timeago()
+ @buildsLoaded = true
+ @scrollToElement("#builds")
+
# Show or hide the loading spinner
#
# status - Boolean, true to show, false to hide
diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee
index 2e561dea3e1..3c7b776155f 100644
--- a/app/assets/javascripts/new_commit_form.js.coffee
+++ b/app/assets/javascripts/new_commit_form.js.coffee
@@ -3,7 +3,7 @@ class @NewCommitForm
@newBranch = form.find('.js-new-branch')
@originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request')
- @createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
+ @createMergeRequestContainer = form.find('.js-create-merge-request-container')
@renderDestination()
@newBranch.keyup @renderDestination
@@ -12,10 +12,10 @@ class @NewCommitForm
different = @newBranch.val() != @originalBranch.val()
if different
- @createMergeRequestFormGroup.show()
+ @createMergeRequestContainer.show()
@createMergeRequest.prop('checked', true) unless @wasDifferent
else
- @createMergeRequestFormGroup.hide()
+ @createMergeRequestContainer.hide()
@createMergeRequest.prop('checked', false)
@wasDifferent = different
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 7de7632201d..533d00bfb0c 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -111,6 +111,12 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote
###
renderNote: (note) ->
+ unless note.valid
+ if note.award
+ flash = new Flash('You have already used this award emoji!', 'alert')
+ flash.pin()
+ return
+
# render note if it not present in loaded list
# or skip if rendered
if @isNewNote(note) && !note.award
@@ -122,6 +128,7 @@ class @Notes
if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
+ awards_handler.scrollToAwards()
###
Check if note does not exists on page
@@ -362,8 +369,8 @@ class @Notes
note = $(this).closest(".note")
note.find(".note-attachment").remove()
note.find(".note-body > .note-text").show()
- note.find(".js-note-attachment-delete").hide()
- note.find(".note-edit-form").hide()
+ note.find(".note-header").show()
+ note.find(".current-note-edit-form").remove()
###
Called when clicking on the "reply" button for a diff line.
diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee
new file mode 100644
index 00000000000..0ae274f3363
--- /dev/null
+++ b/app/assets/javascripts/project_select.js.coffee
@@ -0,0 +1,39 @@
+class @ProjectSelect
+ constructor: ->
+ $('.ajax-project-select').each (i, select) ->
+ @groupId = $(select).data('group-id')
+ @includeGroups = $(select).data('include-groups')
+
+ placeholder = "Search for project"
+ placeholder += " or group" if @includeGroups
+
+ $(select).select2
+ placeholder: placeholder
+ minimumInputLength: 0
+ query: (query) =>
+ finalCallback = (projects) ->
+ data = { results: projects }
+ query.callback(data)
+
+ if @includeGroups
+ projectsCallback = (projects) ->
+ groupsCallback = (groups) ->
+ data = groups.concat(projects)
+ finalCallback(data)
+
+ Api.groups query.term, false, groupsCallback
+ else
+ projectsCallback = finalCallback
+
+ if @groupId
+ Api.groupProjects @groupId, query.term, projectsCallback
+ else
+ Api.projects query.term, projectsCallback
+
+ id: (project) ->
+ project.web_url
+
+ text: (project) ->
+ project.name_with_namespace || project.name
+
+ dropdownCssClass: "ajax-project-dropdown"
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index f3ce4e3c219..20a9bfb9816 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -7,8 +7,8 @@
/* Common styles for all types */
.bs-callout {
- margin: 20px 0;
- padding: 20px;
+ margin: $gl-padding 0;
+ padding: $gl-padding;
border-left: 3px solid $border-color;
color: $text-color;
background: $background-color;
@@ -42,4 +42,3 @@
border-color: #5cA64d;
color: #3c763d;
}
-
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index d2f491daf78..2e8515668f6 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -333,7 +333,7 @@ table {
}
.well {
- margin-bottom: 0;
+ margin-bottom: $gl-padding;
}
.search_box {
@@ -379,9 +379,8 @@ table {
text-align: center;
margin-top: 5px;
margin-bottom: $gl-padding;
- height: 56px;
+ height: auto;
margin-top: -$gl-padding;
- padding-top: $gl-padding;
&.no-bottom {
margin-bottom: 0;
@@ -390,6 +389,18 @@ table {
&.no-top {
margin-top: 0;
}
+
+ li a {
+ display: inline-block;
+ padding-top: $gl-padding;
+ padding-bottom: 11px;
+ margin-bottom: -1px;
+ }
+
+ &.bottom-border {
+ border-bottom: 1px solid $border-color;
+ height: 57px;
+ }
}
.center-middle-menu {
@@ -437,3 +448,16 @@ table {
.alert, .progress {
margin-bottom: $gl-padding;
}
+
+.new-project-item-select-holder {
+ display: inline-block;
+ position: relative;
+
+ .new-project-item-select {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 250px !important;
+ visibility: hidden;
+ }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 6bf2857e83a..cbfd4bc29b6 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -21,7 +21,6 @@
position: relative;
background: $background-color;
border-bottom: 1px solid $border-color;
- text-shadow: 0 1px 1px #fff;
margin: 0;
text-align: left;
padding: 10px $gl-padding;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 82eb50ad4be..1b723021d76 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -15,3 +15,13 @@
@extend .alert-danger;
}
}
+
+.flash-pinned {
+ position: fixed;
+ top: 80px;
+ width: 80%;
+}
+
+.flash-raised {
+ z-index: 1000;
+}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index a798ae812e3..927641216e4 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -72,13 +72,6 @@
}
}
-ol, ul {
- &.styled {
- li {
- padding: 2px;
- }
- }
-}
/** light list with border-bottom between li **/
ul.bordered-list {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index cc660529cb4..2b044786738 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -73,11 +73,8 @@
}
.referenced-users {
- padding: 10px 0;
- color: #999;
- margin-left: 10px;
- margin-top: 1px;
- margin-right: 130px;
+ color: #4c4e54;
+ padding-top: 10px;
}
.md-preview-holder {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index cea47fba192..6f44c323732 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -82,9 +82,6 @@
}
.center-top-menu {
- height: 45px;
- margin-bottom: 30px;
-
li a {
font-size: 14px;
padding: 19px 10px;
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 406aff3d72c..61053aff91a 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -1,9 +1,11 @@
.panel {
margin-bottom: $gl-padding;
-
+
.panel-heading {
- padding: 10px $gl-padding;
+ padding: 7px $gl-padding;
+ line-height: 42px !important;
}
+
.panel-body {
padding: $gl-padding;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index aef338cfa56..c3e4ad0ad00 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -220,6 +220,7 @@ pre {
.monospace {
font-family: $monospace_font;
+ font-size: 90%;
}
code {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index da9965f007a..3c2997c1d5a 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -67,9 +67,4 @@
color: #3084bb !important;
}
}
-
- .build-top-menu {
- margin-top: 0;
- margin-bottom: 2px;
- }
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f21ad694d06..fc8c7161991 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -18,6 +18,7 @@
.accept-merge-holder {
.accept-action {
display: inline-block;
+ float: left;
.accept_merge_request {
&.ci-pending,
@@ -36,14 +37,15 @@
.accept-control {
display: inline-block;
+ float: left;
margin: 0;
margin-left: 20px;
padding: 5px;
+ padding-top: 12px;
line-height: 20px;
&.right {
float: right;
- padding-top: 12px;
a {
color: $gl-gray;
}
@@ -81,6 +83,10 @@
&.ci-error {
color: $gl-danger;
}
+
+ a.monospace {
+ color: inherit;
+ }
}
.mr-widget-body,
@@ -136,7 +142,7 @@
font-family: $monospace_font;
font-weight: bold;
overflow: hidden;
- font-size: 14px;
+ font-size: 90%;
margin: 0 3px;
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 1d6ca0dfc13..95fc26a608a 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -5,12 +5,6 @@
}
}
-.btn-build-token {
- float: left;
- padding: 6px 20px;
- margin-right: 12px;
-}
-
.profile-avatar-form-option {
hr {
margin: 10px 0;
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 277afa1db9e..185f3622e64 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -1,9 +1,6 @@
.gitlab-ui-dev-kit {
> h2 {
- font-size: 27px;
- border-bottom: 1px solid #CCC;
- color: #666;
- margin: 30px 0;
+ margin: 35px 0 20px;
font-weight: bold;
}
}
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
index b428249acd3..3e4c0e63601 100644
--- a/app/controllers/concerns/global_milestones.rb
+++ b/app/controllers/concerns/global_milestones.rb
@@ -2,8 +2,10 @@ module GlobalMilestones
extend ActiveSupport::Concern
def milestones
+ epoch = DateTime.parse('1970-01-01')
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
+ @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 10233222ee1..0c2a350bc39 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def milestone_path(title)
- group_milestone_path(@group, title.parameterize, title: title)
+ group_milestone_path(@group, title.to_slug.to_s, title: title)
end
def projects
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index d3f926b62bc..7d0d57858e0 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -21,14 +21,14 @@ class Projects::ApplicationController < ApplicationController
unless @repository.branch_names.include?(@ref)
redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref),
- notice: "This action is not allowed unless you are on top of a branch"
+ notice: "This action is not allowed unless you are on a branch"
)
end
end
private
- def ci_enabled
+ def builds_enabled
return render_404 unless @project.builds_enabled?
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 31a33bfd237..62163682936 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -162,12 +162,20 @@ class Projects::BlobController < Projects::ApplicationController
end
def sanitized_new_branch_name
- @new_branch ||= sanitize(strip_tags(params[:new_branch]))
+ sanitize(strip_tags(params[:new_branch]))
end
def editor_variables
@current_branch = @ref
- @new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
+
+ @new_branch =
+ if params[:new_branch].present?
+ sanitized_new_branch_name
+ elsif ::Gitlab::GitAccess.new(current_user, @project).can_push_to_branch?(@ref)
+ @ref
+ else
+ @repository.next_patch_branch
+ end
@file_path =
if action_name.to_s == 'create'
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 3f137440e28..e8af205b788 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -37,7 +37,7 @@ class Projects::CommitController < Projects::ApplicationController
def cancel_builds
ci_commit.builds.running_or_pending.each(&:cancel)
- redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha)
+ redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def retry_builds
@@ -47,7 +47,7 @@ class Projects::CommitController < Projects::ApplicationController
end
end
- redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha)
+ redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def branches
@@ -74,8 +74,8 @@ class Projects::CommitController < Projects::ApplicationController
end
@notes_count = commit.notes.count
-
- @builds = ci_commit.builds if ci_commit
+
+ @statuses = ci_commit.statuses if ci_commit
end
def authorize_manage_builds!
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 418b92040bc..a8f47069bb4 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -5,7 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
- before_action :ci_enabled, only: :ci
+ before_action :builds_enabled, only: :ci
def show
respond_to do |format|
@@ -34,6 +34,26 @@ class Projects::GraphsController < Projects::ApplicationController
@charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
end
+ def languages
+ @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages
+ total = @languages.map(&:last).sum
+
+ @languages = @languages.map do |language|
+ name, share = language
+ color = Digest::SHA256.hexdigest(name)[0...6]
+ {
+ value: (share.to_f * 100 / total).round(2),
+ label: name,
+ color: "##{color}",
+ highlight: "##{color}"
+ }
+ end
+
+ @languages.sort! do |x, y|
+ y[:value] <=> x[:value]
+ end
+ end
+
private
def fetch_graph
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index c7569541899..6a62880cb71 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -25,13 +25,12 @@ class Projects::HooksController < Projects::ApplicationController
def test
if !@project.empty_repo?
- status = TestHookService.new.execute(hook, current_user)
+ status, message = TestHookService.new.execute(hook, current_user)
if status
flash[:notice] = 'Hook successfully executed.'
else
- flash[:alert] = 'Hook execution failed. '\
- 'Ensure hook URL is correct and service is up.'
+ flash[:alert] = "Hook execution failed: #{message}"
end
else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 3f47f2ddb2c..530f3d3dcb8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,13 +1,14 @@
class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
- :edit, :update, :show, :diffs, :commits, :merge, :merge_check,
- :ci_status, :toggle_subscription
+ :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
+ :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds
]
- before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
- before_action :validates_merge_request, only: [:show, :diffs, :commits]
- before_action :define_show_vars, only: [:show, :diffs, :commits]
- before_action :ensure_ref_fetched, only: [:show, :commits, :diffs]
+ before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
+ before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
+ before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
+ before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds]
+ before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
before_action :authorize_read_merge_request!
@@ -79,6 +80,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def builds
+ @ci_project = @merge_request.source_project.gitlab_ci_project
+
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
+ end
+ end
+
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
@@ -91,20 +101,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project
@source_project = merge_request.source_project
- @commits = @merge_request.compare_commits
+ @commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs
+
+ @ci_project = @source_project.gitlab_ci_project
+ @ci_commit = @merge_request.ci_commit
+ @statuses = @ci_commit.statuses if @ci_commit
+
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
end
- def edit
- @source_project = @merge_request.source_project
- @target_project = @merge_request.target_project
- @target_branches = @merge_request.target_project.repository.branch_names
- end
-
def create
@target_branches ||= []
@merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
@@ -118,6 +127,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def edit
+ @source_project = @merge_request.source_project
+ @target_project = @merge_request.target_project
+ @target_branches = @merge_request.target_project.repository.branch_names
+ end
+
def update
@merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
@@ -150,15 +165,29 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
+ def cancel_merge_when_build_succeeds
+ return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+
+ MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request)
+ end
+
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
- if @merge_request.mergeable?
- @merge_request.update(merge_error: nil)
- MergeWorker.perform_async(@merge_request.id, current_user.id, params)
- @status = true
+ unless @merge_request.mergeable?
+ @status = :failed
+ return
+ end
+
+ @merge_request.update(merge_error: nil)
+
+ if params[:merge_when_build_succeeds] && @merge_request.ci_commit && @merge_request.ci_commit.active?
+ MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
+ .execute(@merge_request)
+ @status = :merge_when_build_succeeds
else
- @status = false
+ MergeWorker.perform_async(@merge_request.id, current_user.id, params)
+ @status = :success
end
end
@@ -264,12 +293,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff
+ @ci_commit = @merge_request.ci_commit
+ @statuses = @ci_commit.statuses if @ci_commit
+
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
end
+ def define_widget_vars
+ @ci_commit = @merge_request.ci_commit
+ end
+
def invalid_mr
# Render special view for MR with removed source or target branch
render 'invalid'
@@ -283,6 +319,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
)
end
+ def merge_params
+ params.permit(:should_remove_source_branch, :commit_message)
+ end
+
# Make sure merge requests created before 8.0
# have head file in refs/merge-requests/
def ensure_ref_fetched
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 5ac18446aa7..88b949a27ab 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -131,16 +131,25 @@ class Projects::NotesController < Projects::ApplicationController
end
def render_note_json(note)
- render json: {
- id: note.id,
- discussion_id: note.discussion_id,
- html: note_to_html(note),
- award: note.is_award,
- emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
- note: note.note,
- discussion_html: note_to_discussion_html(note),
- discussion_with_diff_html: note_to_discussion_with_diff_html(note)
- }
+ if note.valid?
+ render json: {
+ valid: true,
+ id: note.id,
+ discussion_id: note.discussion_id,
+ html: note_to_html(note),
+ award: note.is_award,
+ emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
+ note: note.note,
+ discussion_html: note_to_discussion_html(note),
+ discussion_with_diff_html: note_to_discussion_with_diff_html(note)
+ }
+ else
+ render json: {
+ valid: false,
+ award: note.is_award,
+ errors: note.errors
+ }
+ end
end
def authorize_admin_note!
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 07eb94e4f48..8364fc293b7 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -23,7 +23,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_members = @group_members.where(user_id: users)
end
- @group_members = @group_members.order('access_level DESC').limit(20)
+ @group_members = @group_members.order('access_level DESC')
end
@project_member = @project.project_members.new
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index d5ee6ac8663..be7d5c187fe 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -10,15 +10,13 @@ class Projects::RawController < Projects::ApplicationController
@blob = @repository.blob_at(@commit.id, @path)
if @blob
- type = get_blob_type
-
headers['X-Content-Type-Options'] = 'nosniff'
- send_data(
- @blob.data,
- type: type,
- disposition: 'inline'
- )
+ if @blob.lfs_pointer?
+ send_lfs_object
+ else
+ stream_data
+ end
else
render_404
end
@@ -35,4 +33,33 @@ class Projects::RawController < Projects::ApplicationController
'application/octet-stream'
end
end
+
+ def stream_data
+ type = get_blob_type
+
+ send_data(
+ @blob.data,
+ type: type,
+ disposition: 'inline'
+ )
+ end
+
+ def send_lfs_object
+ lfs_object = find_lfs_object
+
+ if lfs_object && lfs_object.project_allowed_access?(@project)
+ send_file lfs_object.file.path, filename: @blob.name, disposition: 'attachment'
+ else
+ render_404
+ end
+ end
+
+ def find_lfs_object
+ lfs_object = LfsObject.find_by_oid(@blob.lfs_oid)
+ if lfs_object && lfs_object.file.exists?
+ lfs_object
+ else
+ nil
+ end
+ end
end
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index b704e878903..630c73c2a94 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,7 +1,7 @@
class MilestonesFinder
def execute(projects, params)
milestones = Milestone.of_projects(projects)
- milestones = milestones.order("due_date ASC")
+ milestones = milestones.reorder("due_date ASC")
case params[:state]
when 'closed' then milestones.closed
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 3230ff1b004..21f962df206 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -209,7 +209,7 @@ module ApplicationHelper
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
- element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
+ element += javascript_tag "$('.js-timeago').last().timeago()" unless skip_js
element
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index df5f5fae23c..68e5d5be600 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -30,26 +30,24 @@ module BlobHelper
nil
end
- if blob && blob.text?
- text = 'Edit'
- after = options[:after] || ''
- from_mr = options[:from_merge_request_id]
- link_opts = {}
- link_opts[:from_merge_request_id] = from_mr if from_mr
- cls = 'btn btn-small'
- if allowed_tree_edit?(project, ref)
- link_to(text,
- namespace_project_edit_blob_path(project.namespace, project,
- tree_join(ref, path),
- link_opts),
- class: cls
- )
- else
- content_tag :span, text, class: cls + ' disabled'
- end + after.html_safe
- else
- ''
- end
+ return unless blob && blob.text? && blob_editable?(blob)
+
+ text = 'Edit'
+ after = options[:after] || ''
+ from_mr = options[:from_merge_request_id]
+ link_opts = {}
+ link_opts[:from_merge_request_id] = from_mr if from_mr
+ cls = 'btn btn-small'
+ link_to(text,
+ namespace_project_edit_blob_path(project.namespace, project,
+ tree_join(ref, path),
+ link_opts),
+ class: cls
+ ) + after.html_safe
+ end
+
+ def blob_editable?(blob, project = @project, ref = @ref)
+ !blob.lfs_pointer? && allowed_tree_edit?(project, ref)
end
def leave_edit_message
@@ -71,4 +69,16 @@ module BlobHelper
def blob_icon(mode, name)
icon("#{file_type_icon_class('file', mode, name)} fw")
end
+
+ def blob_viewable?(blob)
+ blob && blob.text? && !blob.lfs_pointer?
+ end
+
+ def blob_size(blob)
+ if blob.lfs_pointer?
+ blob.lfs_size
+ else
+ blob.size
+ end
+ end
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index d6eaa7d57bc..e39548e17e1 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -11,7 +11,7 @@ module BranchesHelper
def can_push_branch?(project, branch_name)
return false unless project.repository.branch_names.include?(branch_name)
-
+
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 0ecf77bb45e..70f8c9ae221 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -8,6 +8,10 @@ module CiStatusHelper
ci_icon_for_status(ci_commit.status)
end
+ def ci_status_label(ci_commit)
+ ci_label_for_status(ci_commit.status)
+ end
+
def ci_status_color(ci_commit)
case ci_commit.status
when 'success'
@@ -23,7 +27,15 @@ module CiStatusHelper
def ci_status_with_icon(status)
content_tag :span, class: "ci-status ci-#{status}" do
- ci_icon_for_status(status) + '&nbsp;'.html_safe + status
+ ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
+ end
+ end
+
+ def ci_label_for_status(status)
+ if status == 'success'
+ 'passed'
+ else
+ status
end
end
@@ -46,7 +58,7 @@ module CiStatusHelper
def render_ci_status(ci_commit)
link_to ci_status_path(ci_commit),
class: "c#{ci_status_color(ci_commit)}",
- title: "Build status: #{ci_commit.status}",
+ title: "Build status: #{ci_status_label(ci_commit)}",
data: { toggle: 'tooltip', placement: 'left' } do
ci_status_icon(ci_commit)
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index ad43892b639..a42cbcff182 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -28,7 +28,9 @@ module MilestonesHelper
Milestone.where(project_id: @projects)
end.active
+ epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
+ grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any)
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 775cf5a3dd4..9bf750124b2 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -4,7 +4,8 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any?
- @page_title.join(" | ")
+ # Segments are seperated by middot
+ @page_title.join(" \u00b7 ")
end
def header_title(title = nil, title_url = nil)
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 7e175d0de8a..05386d790ca 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -48,6 +48,19 @@ module SelectsHelper
select2_tag(id, opts)
end
+ def project_select_tag(id, opts = {})
+ opts[:class] ||= ''
+ opts[:class] << ' ajax-project-select'
+
+ unless opts.delete(:scope) == :all
+ if @group
+ opts['data-group-id'] = @group.id
+ end
+ end
+
+ hidden_field_tag(id, opts[:selected], opts)
+ end
+
def select2_tag(id, opts = {})
css_class = ''
css_class << 'multiselect ' if opts[:multiple]
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 03a49e119b8..886a1e734b5 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -46,12 +46,26 @@ module TreeHelper
File.join(*args)
end
+ def on_top_of_branch?(project = @project, ref = @ref)
+ project.repository.branch_names.include?(ref)
+ end
+
def allowed_tree_edit?(project = nil, ref = nil)
project ||= @project
ref ||= @ref
- return false unless project.repository.branch_names.include?(ref)
+ return false unless on_top_of_branch?(project, ref)
+
+ can?(current_user, :push_code, project)
+ end
- ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
+ def tree_edit_branch(project = @project, ref = @ref)
+ if allowed_tree_edit?(project, ref)
+ if can_push_branch?(project, ref)
+ ref
+ else
+ project.repository.next_patch_branch
+ end
+ end
end
def tree_breadcrumbs(tree, max_links = 2)
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 72c65030f94..2e69ce923a2 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -12,22 +12,22 @@ module VisibilityLevelHelper
# Return the description for the +level+ argument.
#
- # +level+ One of the Gitlab::VisibilityLevel constants
- # +form_model+ Either a model object (Project, Snippet, etc.) or the name of
- # a Project or Snippet class.
+ # +level+ One of the Gitlab::VisibilityLevel constants
+ # +form_model+ Either a model object (Project, Snippet, etc.) or the name of
+ # a Project or Snippet class.
def visibility_level_description(level, form_model)
- case form_model.is_a?(String) ? form_model : form_model.class.name
- when 'PersonalSnippet', 'ProjectSnippet', 'Snippet'
- snippet_visibility_level_description(level)
- when 'Project'
+ case form_model
+ when Project
project_visibility_level_description(level)
+ when Snippet
+ snippet_visibility_level_description(level, form_model)
end
end
def project_visibility_level_description(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
- "Project access must be granted explicitly for each user."
+ "Project access must be granted explicitly to each user."
when Gitlab::VisibilityLevel::INTERNAL
"The project can be cloned by any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
@@ -35,12 +35,16 @@ module VisibilityLevelHelper
end
end
- def snippet_visibility_level_description(level)
+ def snippet_visibility_level_description(level, snippet = nil)
case level
when Gitlab::VisibilityLevel::PRIVATE
- "The snippet is visible only for me."
+ if snippet.is_a? ProjectSnippet
+ "The snippet is visible only to project members."
+ else
+ "The snippet is visible only to me."
+ end
when Gitlab::VisibilityLevel::INTERNAL
- "The snippet is visible for any logged in user."
+ "The snippet is visible to any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
"The snippet can be accessed without any authentication."
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 5ddcf3d9a0b..1880ad9f33c 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -43,12 +43,12 @@ class ApplicationSetting < ActiveRecord::Base
validates :home_page_url,
allow_blank: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
+ url: true,
if: :home_page_url_column_exist
validates :after_sign_out_path,
allow_blank: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+ url: true
validates :admin_notification_email,
allow_blank: true,
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 05f5e979695..ad514706160 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -16,12 +16,12 @@
class BroadcastMessage < ActiveRecord::Base
include Sortable
- validates :message, presence: true
+ validates :message, presence: true
validates :starts_at, presence: true
- validates :ends_at, presence: true
+ validates :ends_at, presence: true
- validates :color, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
- validates :font, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
+ validates :color, allow_blank: true, color: true
+ validates :font, allow_blank: true, color: true
def self.current
where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 971e899de84..75465685e98 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -165,6 +165,14 @@ module Ci
status == 'canceled'
end
+ def active?
+ running? || pending?
+ end
+
+ def complete?
+ canceled? || success? || failed?
+ end
+
def duration
duration_array = latest_statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
@@ -199,7 +207,7 @@ module Ci
end
def ci_yaml_file
- gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
+ @ci_yaml_file ||= gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
rescue
nil
end
diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb
index 7ca16a1bde8..0dc15eb6683 100644
--- a/app/models/ci/web_hook.rb
+++ b/app/models/ci/web_hook.rb
@@ -20,8 +20,7 @@ module Ci
# HTTParty timeout
default_timeout 10
- validates :url, presence: true,
- format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
+ validates :url, presence: true, url: true
def execute(data)
parsed_url = URI.parse(url)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index c0998a45709..8ae5325d16a 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -147,10 +147,10 @@ class Commit
description.present?
end
- def hook_attrs
+ def hook_attrs(with_changed_files: false)
path_with_namespace = project.path_with_namespace
- {
+ data = {
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
@@ -160,6 +160,12 @@ class Commit
email: author_email
}
}
+
+ if with_changed_files
+ data.merge!(repo_changes)
+ end
+
+ data
end
# Discover issues should be closed when this commit is pushed to a project's
@@ -208,4 +214,22 @@ class Commit
def status
ci_commit.try(:status) || :not_found
end
+
+ private
+
+ def repo_changes
+ changes = { added: [], modified: [], removed: [] }
+
+ diffs.each do |diff|
+ if diff.deleted_file
+ changes[:removed] << diff.old_path
+ elsif diff.renamed_file || diff.new_file
+ changes[:added] << diff.new_path
+ else
+ changes[:modified] << diff.new_path
+ end
+ end
+
+ changes
+ end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index e70f4d37184..ff619965a57 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,34 +1,30 @@
# == Schema Information
#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
+# project_id integer
+# status string
+# finished_at datetime
+# trace text
+# created_at datetime
+# updated_at datetime
+# started_at datetime
+# runner_id integer
+# coverage float
+# commit_id integer
+# commands text
+# job_id integer
+# name string
+# deploy boolean default: false
+# options text
+# allow_failure boolean default: false, null: false
+# stage string
+# trigger_request_id integer
+# stage_idx integer
+# tag boolean
+# ref string
+# user_id integer
+# type string
+# target_url string
+# description string
#
class CommitStatus < ActiveRecord::Base
@@ -79,6 +75,10 @@ class CommitStatus < ActiveRecord::Base
build.update_attributes finished_at: Time.now
end
+ after_transition [:pending, :running] => :success do |build, transition|
+ MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.gl_project, nil).trigger(build)
+ end
+
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 1321ccd963f..8bfc79d88f8 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -16,7 +16,15 @@ class GlobalMilestone
end
def safe_title
- @title.parameterize
+ @title.to_slug.to_s
+ end
+
+ def expired?
+ if due_date
+ due_date.past?
+ else
+ false
+ end
end
def projects
@@ -98,4 +106,25 @@ class GlobalMilestone
def complete?
total_items_count == closed_items_count
end
+
+ def due_date
+ return @due_date if defined?(@due_date)
+
+ @due_date =
+ if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
+ @milestones.first.due_date
+ else
+ nil
+ end
+ end
+
+ def expires_at
+ if due_date
+ if due_date.past?
+ "expired at #{due_date.stamp("Aug 21, 2011")}"
+ else
+ "expires at #{due_date.stamp("Aug 21, 2011")}"
+ end
+ end
+ end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index d6c6f415c4a..715ec5908b7 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -31,37 +31,38 @@ class WebHook < ActiveRecord::Base
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
- validates :url, presence: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+ validates :url, presence: true, url: true
def execute(data, hook_name)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
- WebHook.post(url,
- body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
- verify: enable_ssl_verification)
+ response = WebHook.post(url,
+ body: data.to_json,
+ headers: {
+ "Content-Type" => "application/json",
+ "X-Gitlab-Event" => hook_name.singularize.titleize
+ },
+ verify: enable_ssl_verification)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
username: URI.decode(parsed_url.user),
password: URI.decode(parsed_url.password),
}
- WebHook.post(post_url,
- body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
- verify: enable_ssl_verification,
- basic_auth: auth)
+ response = WebHook.post(post_url,
+ body: data.to_json,
+ headers: {
+ "Content-Type" => "application/json",
+ "X-Gitlab-Event" => hook_name.singularize.titleize
+ },
+ verify: enable_ssl_verification,
+ basic_auth: auth)
end
- rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
+
+ [response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)]
+ rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
- false
+ [false, e.to_s]
end
def async_execute(data, hook_name)
diff --git a/app/models/label.rb b/app/models/label.rb
index bef6063fe88..220da10a6ab 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -27,9 +27,7 @@ class Label < ActiveRecord::Base
has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
- validates :color,
- format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
- allow_blank: false
+ validates :color, color: true, allow_blank: false
validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow '?', '&', and ',' for label titles
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 3c1426f59d0..86b1b7e2f99 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,3 +1,15 @@
+# == Schema Information
+#
+# Table name: lfs_objects
+#
+# id :integer not null, primary key
+# oid :string(255) not null
+# size :integer not null
+# created_at :datetime
+# updated_at :datetime
+# file :string(255)
+#
+
class LfsObject < ActiveRecord::Base
has_many :lfs_objects_projects, dependent: :destroy
has_many :projects, through: :lfs_objects_projects
@@ -5,4 +17,16 @@ class LfsObject < ActiveRecord::Base
validates :oid, presence: true, uniqueness: true
mount_uploader :file, LfsObjectUploader
+
+ def storage_project(project)
+ if project && project.forked?
+ storage_project(project.forked_from_project)
+ else
+ project
+ end
+ end
+
+ def project_allowed_access?(project)
+ projects.exists?(storage_project(project).id)
+ end
end
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 0fd5f089db9..890736bfc80 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,3 +1,14 @@
+# == Schema Information
+#
+# Table name: lfs_objects_projects
+#
+# id :integer not null, primary key
+# lfs_object_id :integer not null
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+#
+
class LfsObjectsProject < ActiveRecord::Base
belongs_to :project
belongs_to :lfs_object
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2a4aee7e5d9..60fd2b9a757 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -2,25 +2,28 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime
+# updated_at :datetime
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
+# description :text
+# position :integer default(0)
+# locked_at :datetime
+# updated_by_id :integer
+# merge_error :string(255)
+# merge_params :text (serialized to hash)
+# merge_when_build_succeeds :boolean default(false), not null
+# merge_user_id :integer
#
require Rails.root.join("app/models/commit")
@@ -35,9 +38,12 @@ class MergeRequest < ActiveRecord::Base
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
+ belongs_to :merge_user, class_name: "User"
has_one :merge_request_diff, dependent: :destroy
+ serialize :merge_params, Hash
+
after_create :create_merge_request_diff
after_update :update_merge_request_diff
@@ -121,6 +127,7 @@ class MergeRequest < ActiveRecord::Base
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
+ validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches
validate :validate_fork
@@ -258,6 +265,16 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def can_cancel_merge_when_build_succeeds?(current_user)
+ can_be_merged_by?(current_user) || self.author == current_user
+ end
+
+ def can_remove_source_branch?(current_user)
+ !source_project.protected_branch?(source_branch) &&
+ !source_project.root_ref?(source_branch) &&
+ Ability.abilities.allowed?(current_user, :push_code, source_project)
+ end
+
def mr_and_commit_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
@@ -295,7 +312,7 @@ class MergeRequest < ActiveRecord::Base
work_in_progress: work_in_progress?
}
- unless last_commit.nil?
+ if last_commit
attrs.merge!(last_commit: last_commit.hook_attrs)
end
@@ -393,6 +410,16 @@ class MergeRequest < ActiveRecord::Base
message
end
+ def reset_merge_when_build_succeeds
+ return unless merge_when_build_succeeds?
+
+ self.merge_when_build_succeeds = false
+ self.merge_user = nil
+ self.merge_params = nil
+
+ self.save
+ end
+
# Return array of possible target branches
# depends on target project of MR
def target_branches
@@ -480,8 +507,6 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
- if last_commit and source_project
- source_project.ci_commit(last_commit.id)
- end
+ @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 20b92e68d61..1c4e101cc10 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -23,19 +23,17 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
- presence: true, uniqueness: true,
length: { within: 0..255 },
- format: { with: Gitlab::Regex.namespace_name_regex,
- message: Gitlab::Regex.namespace_name_regex_message }
+ namespace_name: true,
+ presence: true,
+ uniqueness: true
validates :description, length: { within: 0..255 }
validates :path,
- uniqueness: { case_sensitive: false },
- presence: true,
length: { within: 1..255 },
- exclusion: { in: Gitlab::Blacklist.path },
- format: { with: Gitlab::Regex.namespace_regex,
- message: Gitlab::Regex.namespace_regex_message }
+ namespace: true,
+ presence: true,
+ uniqueness: { case_sensitive: false }
delegate :name, to: :owner, allow_nil: true, prefix: true
diff --git a/app/models/note.rb b/app/models/note.rb
index 1c6345e735c..98c29ddc4cd 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -16,6 +16,7 @@
# system :boolean default(FALSE), not null
# st_diff :text
# updated_by_id :integer
+# is_award :boolean default(FALSE), not null
#
require 'carrierwave/orm/activerecord'
@@ -39,9 +40,12 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
+ before_validation :set_award!
+
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
- validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+ validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
+ validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -348,4 +352,31 @@ class Note < ActiveRecord::Base
def editable?
!system?
end
+
+ # Checks if note is an award added as a comment
+ #
+ # If note is an award, this method sets is_award to true
+ # and changes content of the note to award name.
+ #
+ # Method is executed as a before_validation callback.
+ #
+ def set_award!
+ return unless awards_supported? && contains_emoji_only?
+ self.is_award = true
+ self.note = award_emoji_name
+ end
+
+ private
+
+ def awards_supported?
+ noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
+ end
+
+ def contains_emoji_only?
+ note =~ /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/
+ end
+
+ def award_emoji_name
+ note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 6010770a5f2..cb965ce1b9e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -28,6 +28,7 @@
# import_type :string(255)
# import_source :string(255)
# commit_count :integer default(0)
+# import_error :text
#
require 'carrierwave/orm/activerecord'
@@ -152,7 +153,7 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
- format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
+ url: { protocols: %w(ssh git http https) },
if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d31b12f539e..0a61ad96a0e 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -23,10 +23,7 @@ class BambooService < CiService
prop_accessor :bamboo_url, :build_key, :username, :password
- validates :bamboo_url,
- presence: true,
- format: { with: /\A#{URI.regexp}\z/ },
- if: :activated?
+ validates :bamboo_url, presence: true, url: true, if: :activated?
validates :build_key, presence: true, if: :activated?
validates :username,
presence: true,
@@ -84,7 +81,7 @@ class BambooService < CiService
def supported_events
%w(push)
end
-
+
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 06c3922593c..08e5ccb3855 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -19,14 +19,11 @@
#
class DroneCiService < CiService
-
+
prop_accessor :drone_url, :token, :enable_ssl_verification
- validates :drone_url,
- presence: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
- validates :token,
- presence: true,
- if: :activated?
+
+ validates :drone_url, presence: true, url: true, if: :activated?
+ validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
@@ -58,16 +55,16 @@ class DroneCiService < CiService
end
def merge_request_status_path(iid, sha = nil, ref = nil)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
"?access_token=#{token}"]
URI.join(*url).to_s
end
def commit_status_path(sha, ref)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
@@ -114,15 +111,15 @@ class DroneCiService < CiService
end
def merge_request_page(iid, sha, ref)
- url = [drone_url,
+ url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
URI.join(*url).to_s
end
def commit_page(sha, ref)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"]
URI.join(*url).to_s
@@ -163,10 +160,10 @@ class DroneCiService < CiService
end
def push_valid?(data)
- opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
+ opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
source_branch: Gitlab::Git.ref_name(data[:ref]))
- opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
+ opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
!Gitlab::Git.blank_ref?(data[:after])
end
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 9c46af7e721..74c57949b4d 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -22,10 +22,8 @@ class ExternalWikiService < Service
include HTTParty
prop_accessor :external_wiki_url
- validates :external_wiki_url,
- presence: true,
- format: { with: /\A#{URI.regexp}\z/ },
- if: :activated?
+
+ validates :external_wiki_url, presence: true, url: true, if: :activated?
def title
'External Wiki'
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 0b022461250..29d4236745a 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -23,16 +23,16 @@ class TeamcityService < CiService
prop_accessor :teamcity_url, :build_type, :username, :password
- validates :teamcity_url,
- presence: true,
- format: { with: /\A#{URI.regexp}\z/ }, if: :activated?
+ validates :teamcity_url, presence: true, url: true, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username,
presence: true,
- if: ->(service) { service.password? }, if: :activated?
+ if: ->(service) { service.password? },
+ if: :activated?
validates :password,
presence: true,
- if: ->(service) { service.username? }, if: :activated?
+ if: ->(service) { service.username? },
+ if: :activated?
attr_accessor :response
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d247b0f5012..1edec52c09e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1,7 +1,6 @@
require 'securerandom'
class Repository
- class PreReceiveError < StandardError; end
class CommitError < StandardError; end
include Gitlab::ShellAdapter
@@ -101,17 +100,26 @@ class Repository
end
def find_branch(name)
- branches.find { |branch| branch.name == name }
+ raw_repository.branches.find { |branch| branch.name == name }
end
def find_tag(name)
- tags.find { |tag| tag.name == name }
+ raw_repository.tags.find { |tag| tag.name == name }
end
- def add_branch(branch_name, ref)
- expire_branches_cache
+ def add_branch(user, branch_name, target)
+ oldrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+ target = commit(target).try(:id)
+
+ return false unless target
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+ rugged.branches.create(branch_name, target)
+ end
- gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
+ expire_branches_cache
+ find_branch(branch_name)
end
def add_tag(tag_name, ref, message = nil)
@@ -120,10 +128,20 @@ class Repository
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
- def rm_branch(branch_name)
+ def rm_branch(user, branch_name)
expire_branches_cache
- gitlab_shell.rm_branch(path_with_namespace, branch_name)
+ branch = find_branch(branch_name)
+ oldrev = branch.try(:target)
+ newrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
+ rugged.branches.delete(branch_name)
+ end
+
+ expire_branches_cache
+ true
end
def rm_tag(tag_name)
@@ -311,6 +329,17 @@ class Repository
commit(sha)
end
+ def next_patch_branch
+ patch_branch_ids = self.branch_names.map do |n|
+ result = n.match(/\Apatch-([0-9]+)\z/)
+ result[1].to_i if result
+ end.compact
+
+ highest_patch_branch_id = patch_branch_ids.max || 0
+
+ "patch-#{highest_patch_branch_id + 1}"
+ end
+
# Remove archives older than 2 hours
def branches_sorted_by(value)
case value
@@ -550,7 +579,6 @@ class Repository
def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
- gl_id = Gitlab::ShellEnv.gl_id(current_user)
was_empty = empty?
# Create temporary ref
@@ -569,15 +597,7 @@ class Repository
raise CommitError.new('Failed to create commit')
end
- # Run GitLab pre-receive hook
- pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
- pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
-
- # Run GitLab update hook
- update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
- update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
-
- if pre_receive_hook_status && update_hook_status
+ GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty
# Create branch
rugged.references.create(ref, newrev)
@@ -592,16 +612,11 @@ class Repository
raise CommitError.new('Commit was rejected because branch received new push')
end
end
-
- # Run GitLab post receive hook
- post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
- post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
- else
- # Remove tmp ref and return error to user
- rugged.references.delete(tmp_ref)
-
- raise PreReceiveError.new('Commit was rejected by git hook')
end
+ rescue GitHooksService::PreReceiveError
+ # Remove tmp ref and return error to user
+ rugged.references.delete(tmp_ref)
+ raise
end
private
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index d8fe65b06f6..f36eda1531b 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -21,7 +21,7 @@ class SentNotification < ActiveRecord::Base
validates :reply_key, uniqueness: true
validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
- validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+ validates :line_code, line_code: true, allow_blank: true
class << self
def reply_key
diff --git a/app/models/user.rb b/app/models/user.rb
index 719b49b16fe..7155dd2bea7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -56,6 +56,7 @@
# project_view :integer default(0)
# consumed_timestep :integer
# layout :integer default(0)
+# hide_project_limit :boolean default(FALSE)
#
require 'carrierwave/orm/activerecord'
@@ -148,11 +149,9 @@ class User < ActiveRecord::Base
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
+ namespace: true,
presence: true,
- uniqueness: { case_sensitive: false },
- exclusion: { in: Gitlab::Blacklist.path },
- format: { with: Gitlab::Regex.namespace_regex,
- message: Gitlab::Regex.namespace_regex_message }
+ uniqueness: { case_sensitive: false }
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index cf7ae4345f3..de18f3bc556 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
return error('Branch already exists')
end
- repository.add_branch(branch_name, ref)
- new_branch = repository.find_branch(branch_name)
+ new_branch = repository.add_branch(current_user, branch_name, ref)
if new_branch
push_data = build_push_data(project, current_user, new_branch)
@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
else
error('Invalid reference name')
end
+ rescue GitHooksService::PreReceiveError
+ error('Branch creation was rejected by Git hook')
end
def success(branch)
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index b19b112a0c4..22bf9dd935e 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
return error('You dont have push access to repo', 405)
end
- if repository.rm_branch(branch_name)
+ if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch)
EventCreateService.new.push(project, current_user, push_data)
@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
else
error('Failed to remove branch')
end
+ rescue GitHooksService::PreReceiveError
+ error('Branch deletion was rejected by Git hook')
end
def error(message, return_code = 400)
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 008833eed80..9a67b160940 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -26,7 +26,7 @@ module Files
else
error("Something went wrong. Your changes were not committed")
end
- rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
+ rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message)
end
@@ -53,7 +53,7 @@ module Files
unless project.empty_repo?
unless repository.branch_names.include?(@current_branch)
- raise_error("You can only create files if you are on top of a branch")
+ raise_error("You can only create or edit files when you are on a branch")
end
if @current_branch != @target_branch
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
new file mode 100644
index 00000000000..8f5c3393dfc
--- /dev/null
+++ b/app/services/git_hooks_service.rb
@@ -0,0 +1,28 @@
+class GitHooksService
+ PreReceiveError = Class.new(StandardError)
+
+ def execute(user, repo_path, oldrev, newrev, ref)
+ @repo_path = repo_path
+ @user = Gitlab::ShellEnv.gl_id(user)
+ @oldrev = oldrev
+ @newrev = newrev
+ @ref = ref
+
+ %w(pre-receive update).each do |hook_name|
+ unless run_hook(hook_name)
+ raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook")
+ end
+ end
+
+ yield
+
+ run_hook('post-receive')
+ end
+
+ private
+
+ def run_hook(name)
+ hook = Gitlab::Git::Hook.new(name, @repo_path)
+ hook.trigger(@user, @oldrev, @newrev, @ref)
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index d619b72e3c2..cabc3d8fabb 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -6,15 +6,12 @@ module MergeRequests
# Executed when you do merge via GitLab UI
#
class MergeService < MergeRequests::BaseService
- attr_reader :merge_request, :commit_message
+ attr_reader :merge_request
- def execute(merge_request, commit_message)
- @commit_message = commit_message
+ def execute(merge_request)
@merge_request = merge_request
- unless @merge_request.mergeable?
- return error('Merge request is not mergeable')
- end
+ return error('Merge request is not mergeable') unless @merge_request.mergeable?
merge_request.in_locked_state do
if commit
@@ -32,7 +29,7 @@ module MergeRequests
committer = repository.user_to_committer(current_user)
options = {
- message: commit_message,
+ message: params[:commit_message] || merge_request.merge_commit_message,
author: committer,
committer: committer
}
@@ -46,6 +43,11 @@ module MergeRequests
def after_merge
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
+
+ if params[:should_remove_source_branch]
+ DeleteBranchService.new(@merge_request.source_project, current_user).
+ execute(merge_request.source_branch)
+ end
end
end
end
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
new file mode 100644
index 00000000000..5cf7404a493
--- /dev/null
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -0,0 +1,55 @@
+module MergeRequests
+ class MergeWhenBuildSucceedsService < MergeRequests::BaseService
+ # Marks the passed `merge_request` to be merged when the build succeeds or
+ # updates the params for the automatic merge
+ def execute(merge_request)
+ merge_request.merge_params.merge!(params)
+
+ # The service is also called when the merge params are updated.
+ already_approved = merge_request.merge_when_build_succeeds?
+
+ unless already_approved
+ merge_request.merge_when_build_succeeds = true
+ merge_request.merge_user = @current_user
+
+ SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.last_commit)
+ end
+
+ merge_request.save
+ end
+
+ # Triggers the automatic merge of merge_request once the build succeeds
+ def trigger(build)
+ merge_requests = merge_request_from(build)
+
+ merge_requests.each do |merge_request|
+ next unless merge_request.merge_when_build_succeeds?
+
+ if merge_request.ci_commit && merge_request.ci_commit.success? && merge_request.mergeable?
+ MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
+ end
+ end
+ end
+
+ # Cancels the automatic merge
+ def cancel(merge_request)
+ if merge_request.merge_when_build_succeeds? && merge_request.open?
+ merge_request.reset_merge_when_build_succeeds
+ SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user)
+
+ success
+ else
+ error("Can't cancel the automatic merge", 406)
+ end
+ end
+
+ private
+
+ def merge_request_from(build)
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a
+ merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a
+
+ merge_requests.uniq.select(&:source_project)
+ end
+ end
+end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e180edb4bf3..b26c7513f5b 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -11,6 +11,7 @@ module MergeRequests
# empty diff during a manual merge
close_merge_requests
reload_merge_requests
+ reset_merge_when_build_succeeds
# Leave a system note if a branch was deleted/added
if branch_added? || branch_removed?
@@ -57,7 +58,6 @@ module MergeRequests
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
-
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_code
merge_request.mark_as_unchecked
@@ -76,6 +76,10 @@ module MergeRequests
end
end
+ def reset_merge_when_build_succeeds
+ merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds)
+ end
+
def find_new_commits
if branch_added?
@commits = []
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index dbff58dfb9c..a8486e6a5a1 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,11 +5,6 @@ module Notes
note.author = current_user
note.system = false
- if contains_emoji_only?(params[:note])
- note.is_award = true
- note.note = emoji_name(params[:note])
- end
-
if note.save
notification_service.new_note(note)
@@ -33,13 +28,5 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks)
end
-
- def contains_emoji_only?(note)
- note =~ /\A:[-_+[:alnum:]]*:\s?\z/
- end
-
- def emoji_name(note)
- note.match(/\A:([-_+[:alnum:]]*):\s?/)[1]
- end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 09c159510cd..6975b2ee55b 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -130,6 +130,20 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ # Called when 'merge when build succeeds' is executed
+ def self.merge_when_build_succeeds(noteable, project, author, last_commit)
+ body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when 'merge when build succeeds' is canceled
+ def self.cancel_merge_when_build_succeeds(noteable, project, author)
+ body = "Canceled the automatic merge"
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
# Called when the title of a Noteable is changed
#
# noteable - Noteable object that responds to `title`
diff --git a/app/validators/color_validator.rb b/app/validators/color_validator.rb
new file mode 100644
index 00000000000..571d0007aa2
--- /dev/null
+++ b/app/validators/color_validator.rb
@@ -0,0 +1,20 @@
+# ColorValidator
+#
+# Custom validator for web color codes. It requires the leading hash symbol and
+# will accept RGB triplet or hexadecimal formats.
+#
+# Example:
+#
+# class User < ActiveRecord::Base
+# validates :background_color, allow_blank: true, color: true
+# end
+#
+class ColorValidator < ActiveModel::EachValidator
+ PATTERN = /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/.freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ PATTERN
+ record.errors.add(attribute, "must be a valid color code")
+ end
+ end
+end
diff --git a/app/validators/email_validator.rb b/app/validators/email_validator.rb
new file mode 100644
index 00000000000..b35af100803
--- /dev/null
+++ b/app/validators/email_validator.rb
@@ -0,0 +1,18 @@
+# EmailValidator
+#
+# Based on https://github.com/balexand/email_validator
+#
+# Extended to use only strict mode with following allowed characters:
+# ' - apostrophe
+#
+# See http://www.remote.org/jochen/mail/info/chars.html
+#
+class EmailValidator < ActiveModel::EachValidator
+ PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ PATTERN
+ record.errors.add(attribute, options[:message] || :invalid)
+ end
+ end
+end
diff --git a/app/validators/line_code_validator.rb b/app/validators/line_code_validator.rb
new file mode 100644
index 00000000000..ed29e5aeb67
--- /dev/null
+++ b/app/validators/line_code_validator.rb
@@ -0,0 +1,12 @@
+# LineCodeValidator
+#
+# Custom validator for GitLab line codes.
+class LineCodeValidator < ActiveModel::EachValidator
+ PATTERN = /\A[a-z0-9]+_\d+_\d+\z/.freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ PATTERN
+ record.errors.add(attribute, "must be a valid line code")
+ end
+ end
+end
diff --git a/app/validators/namespace_name_validator.rb b/app/validators/namespace_name_validator.rb
new file mode 100644
index 00000000000..2e51af2982d
--- /dev/null
+++ b/app/validators/namespace_name_validator.rb
@@ -0,0 +1,10 @@
+# NamespaceNameValidator
+#
+# Custom validator for GitLab namespace name strings.
+class NamespaceNameValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless value =~ Gitlab::Regex.namespace_name_regex
+ record.errors.add(attribute, Gitlab::Regex.namespace_name_regex_message)
+ end
+ end
+end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
new file mode 100644
index 00000000000..10e35ce665a
--- /dev/null
+++ b/app/validators/namespace_validator.rb
@@ -0,0 +1,50 @@
+# NamespaceValidator
+#
+# Custom validator for GitLab namespace values.
+#
+# Values are checked for formatting and exclusion from a list of reserved path
+# names.
+class NamespaceValidator < ActiveModel::EachValidator
+ RESERVED = %w(
+ admin
+ all
+ assets
+ ci
+ dashboard
+ files
+ groups
+ help
+ hooks
+ issues
+ merge_requests
+ notes
+ profile
+ projects
+ public
+ repository
+ s
+ search
+ services
+ snippets
+ teams
+ u
+ unsubscribes
+ users
+ ).freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ Gitlab::Regex.namespace_regex
+ record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
+ end
+
+ if reserved?(value)
+ record.errors.add(attribute, "#{value} is a reserved name")
+ end
+ end
+
+ private
+
+ def reserved?(value)
+ RESERVED.include?(value)
+ end
+end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
new file mode 100644
index 00000000000..2848b9cd33d
--- /dev/null
+++ b/app/validators/url_validator.rb
@@ -0,0 +1,36 @@
+# UrlValidator
+#
+# Custom validator for URLs.
+#
+# By default, only URLs for the HTTP(S) protocols will be considered valid.
+# Provide a `:protocols` option to configure accepted protocols.
+#
+# Example:
+#
+# class User < ActiveRecord::Base
+# validates :personal_url, url: true
+#
+# validates :ftp_url, url: { protocols: %w(ftp) }
+#
+# validates :git_url, url: { protocols: %w(http https ssh git) }
+# end
+#
+class UrlValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless valid_url?(value)
+ record.errors.add(attribute, "must be a valid URL")
+ end
+ end
+
+ private
+
+ def default_options
+ @default_options ||= { protocols: %w(http https) }
+ end
+
+ def valid_url?(value)
+ options = default_options.merge(self.options)
+
+ value =~ /\A#{URI.regexp(options[:protocols])}\z/
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index ddaf0e0e8ff..6c355366948 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -14,11 +14,11 @@
.form-group.project-visibility-level-holder
= f.label :default_project_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: 'Project')
+ = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project)
.form-group.project-visibility-level-holder
= f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: 'Snippet')
+ = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: PersonalSnippet)
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 991e67b1cd3..2e77afb7525 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -2,7 +2,7 @@
= render 'shared/project_limit'
%ul.center-top-menu
- = nav_link(path: ['projects#index', 'root#index']) do
+ = nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
= nav_link(page: starred_dashboard_projects_path) do
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index cd602e897b7..2d3da01178a 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,14 +4,20 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
+.project-issuable-filter
+ .controls
+ .pull-left
+ - if current_user
+ .hidden-xs.pull-left
+ = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
+ %i.fa.fa-rss
-.append-bottom-20
- .pull-right
- - if current_user
- .hidden-xs.pull-left.prepend-top-20
- = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do
- %i.fa.fa-rss
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
-= render 'shared/issues'
+.gray-content-block.second-block
+ List all issues from all projects you have access to.
+
+.prepend-top-default
+ = render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index d1f332fa0d3..c5a5ec21f78 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,6 +1,14 @@
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
-.append-bottom-20
+.project-issuable-filter
+ .controls
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
+
= render 'shared/issuable/filter', type: :merge_requests
-= render 'shared/merge_requests'
+
+.gray-content-block.second-block
+ List all merge requests from all projects you have access to.
+
+.prepend-top-default
+ = render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 55080d6b3fe..7c882a32702 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -16,7 +16,10 @@
= milestone_progress_bar(milestone)
.row
.col-sm-6
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name_with_namespace
+ .expiration
+ = render 'shared/milestone_expired', milestone: milestone
+ .projects
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = milestone.project.name_with_namespace
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 635251e2374..bec1692a4de 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,12 +1,14 @@
- page_title "Milestones"
-- header_title "Milestones", dashboard_milestones_path
+- header_title "Milestones", dashboard_milestones_path
+.project-issuable-filter
+ .controls
+ = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
-= render 'shared/milestones_filter'
+ = render 'shared/milestones_filter'
.gray-content-block
- .oneline
- List all milestones from all projects you have access to.
+ List all milestones from all projects you have access to.
.milestones
%ul.content-list
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 08d97e418a3..90ade1e1680 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -4,21 +4,24 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
+.project-issuable-filter
+ .controls
+ .pull-left
+ - if current_user
+ .hidden-xs.pull-left
+ = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
+ %i.fa.fa-rss
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
+
+ = render 'shared/issuable/filter', type: :issues
-= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
- .pull-right
- - if current_user
- .hidden-xs.pull-left
- = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do
- %i.fa.fa-rss
- %div
- Only issues from
- %strong #{@group.name}
- group are listed here.
- - if current_user
- To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
+ Only issues from
+ %strong #{@group.name}
+ group are listed here.
+ - if current_user
+ To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
.prepend-top-default
= render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 425ad8331bf..f662f5a8c17 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,13 +1,18 @@
- page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
-= render 'shared/issuable/filter', type: :merge_requests
+.project-issuable-filter
+ .controls
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
+
+ = render 'shared/issuable/filter', type: :merge_requests
+
.gray-content-block.second-block
- %div
- Only merge requests from
- %strong #{@group.name}
- group are listed here.
- - if current_user
- To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+ Only merge requests from
+ %strong #{@group.name}
+ group are listed here.
+ - if current_user
+ To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+
.prepend-top-default
= render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 84ec77c6188..b221d3a89a4 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,18 +1,22 @@
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
-= render 'shared/milestones_filter'
+.project-issuable-filter
+ .controls
+ - if can?(current_user, :admin_milestones, @group)
+ .pull-right
+ %span.pull-right.hidden-xs
+ = link_to new_group_milestone_path(@group), class: "btn btn-new" do
+ = icon('plus')
+ New Milestone
+
+ = render 'shared/milestones_filter'
+
.gray-content-block
- - if can?(current_user, :admin_milestones, @group)
- .pull-right
- %span.pull-right.hidden-xs
- = link_to new_group_milestone_path(@group), class: "btn btn-new" do
- New Milestone
+ Only milestones from
+ %strong #{@group.name}
+ group are listed here.
- .oneline
- Only milestones from
- %strong #{@group.name}
- group are listed here.
.milestones
%ul.content-list
- if @milestones.blank?
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 2169a821fb2..d9ffda884c8 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -31,11 +31,9 @@
%h2#blocks Blocks
- %h3
+ %h4
%code .gray-content-block
-
-
.gray-content-block.middle-block
%h4 Normal block inside content
= lorem
@@ -45,9 +43,28 @@
= lorem
+ %h4
+ %code .cover-block
+ %br
+ .cover-block
+ .avatar-holder
+ = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
+ .cover-title
+ John Smith
+
+ .cover-desc
+ = lorem
+
+ .cover-controls
+ = link_to '#', class: 'btn btn-gray' do
+ = icon('pencil')
+ &nbsp;
+ = link_to '#', class: 'btn btn-gray' do
+ = icon('rss')
+
%h2#lists Lists
- %h3
+ %h4
%code .content-list
%ul.content-list
%li
@@ -57,7 +74,7 @@
%li
One item
- %h3
+ %h4
%code .well-list
%ul.well-list
%li
@@ -67,7 +84,7 @@
%li
One item
- %h3
+ %h4
%code .panel .well-list
.panel.panel-default
@@ -80,7 +97,7 @@
%li
One item
- %h3
+ %h4
%code .bordered-list
%ul.bordered-list
%li
@@ -121,7 +138,7 @@
%h2#navs Navigation
- %h3
+ %h4
%code .center-top-menu
.example
%ul.center-top-menu
@@ -130,7 +147,7 @@
%li
%a Closed
- %h3
+ %h4
%code .btn-group.btn-group-next
.example
%div.btn-group.btn-group-next
@@ -138,7 +155,7 @@
%a.btn Closed
- %h3
+ %h4
%code .nav.nav-tabs
.example
%ul.nav.nav-tabs
@@ -204,7 +221,7 @@
%h2#forms Forms
- %h3
+ %h4
%code form.horizontal-form
%form.form-horizontal
@@ -226,7 +243,7 @@
.col-sm-offset-2.col-sm-10
%button.btn.btn-default{:type => "submit"} Sign in
- %h3
+ %h4
%code form
%form
@@ -243,7 +260,7 @@
%button.btn.btn-default{:type => "submit"} Sign in
%h2#file File
- %h3
+ %h4
%code .file-holder
- blob = Snippet.new(content: "Wow\nSuch\nFile")
@@ -254,13 +271,12 @@
.file-actions
.btn-group
%a.btn Edit
- %a.btn Remove
+ %a.btn.btn-danger Remove
.file-contenta.code
= render 'shared/file_highlight', blob: blob
-
%h2#markdown Markdown
- %h3
+ %h4
%code .md or .wiki and others
Markdown rendering has a bit different css and presented in next UI elements:
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 319bdd57c39..17e47c622ce 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -26,11 +26,11 @@
- else
%span You don`t have one yet. Click generate to fix it.
- .form-actions
- - if current_user.private_token
- = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- - else
- = f.submit 'Generate', class: "btn btn-default btn-build-token"
+ .form-actions
+ - if current_user.private_token
+ = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
+ - else
+ = f.submit 'Generate', class: "btn btn-default"
- unless current_user.ldap_user?
.panel.panel-default
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index 2bf207a3221..11166dc6d99 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -9,7 +9,7 @@
$('#key_key').on('focusout', function(){
var title = $('#key_title'),
val = $('#key_key').val(),
- comment = val.match(/^\S+ \S+ (.+)$/);
+ comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){
$('#key_title').val( comment[1] );
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 7e1ee2b7fc1..386d72e7787 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -3,7 +3,7 @@
- if ci_commit
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
= ci_status_icon(ci_commit)
- = ci_commit.status
+ = ci_status_label(ci_commit)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 8218cf11201..54c818baaf4 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -8,17 +8,18 @@
%a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
Preview
- - if defined?(referenced_users) && referenced_users
- %span.referenced-users.pull-left.hide
+ %div
+ .md-write-holder
+ = yield
+ .md.md-preview-holder.hide
+ .js-md-preview{class: (preview_class if defined?(preview_class))}
+
+ - if defined?(referenced_users) && referenced_users
+ %div.referenced-users.hide
+ %span
= icon('exclamation-triangle')
You are about to add
%strong
%span.js-referenced-users-count 0
people
to the discussion. Proceed with caution.
-
- %div
- .md-write-holder
- = yield
- .md.md-preview-holder.hide
- .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index ba3e0c3c590..b1df8d19938 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -1,9 +1,8 @@
.btn-group.tree-btn-group
- = edit_blob_link(@project, @ref, @path)
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files
- - if @blob.text?
+ - if blob_viewable?(@blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm'
@@ -12,11 +11,16 @@
class: 'btn btn-sm' unless @blob.empty?
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
class: 'btn btn-sm'
- - if @ref != @commit.sha
- = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
- tree_join(@commit.sha, @path)), class: 'btn btn-sm'
+ = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+ tree_join(@commit.sha, @path)), class: 'btn btn-sm'
-- if allowed_tree_edit?
+- if blob_editable?(@blob)
.btn-group{ role: "group" }
+ = edit_blob_link(@project, @ref, @path)
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
%button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
+- elsif !on_top_of_branch?
+ .btn-group{ role: "group" }
+ %button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on a branch.", data: {container: 'body'}} Edit
+ %button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on a branch.", data: {container: 'body'}} Replace
+ %button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on a branch.", data: {container: 'body'}} Delete
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 42f632b38ef..2a3315da3db 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -29,10 +29,12 @@
%strong
= blob.name
%small
- = number_to_human_size(blob.size)
+ = number_to_human_size(blob_size(blob))
.file-actions.hidden-xs
= render "actions"
- - if blob.text?
+ - if blob.lfs_pointer?
+ = render "download", blob: blob
+ - elsif blob.text?
= render "text", blob: blob
- elsif blob.image?
= render "image", blob: blob
diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml
index f2c5e95ecf4..7908fcae3de 100644
--- a/app/views/projects/blob/_download.html.haml
+++ b/app/views/projects/blob/_download.html.haml
@@ -4,4 +4,4 @@
%h1.light
%i.fa.fa-download
%h4
- Download (#{number_to_human_size blob.size})
+ Download (#{number_to_human_size blob_size(blob)})
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index b7276868ce6..3f8d11ed8c8 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -6,7 +6,7 @@
%div#tree-holder.tree-holder
= render 'blob', blob: @blob
-- if allowed_tree_edit?
+- if blob_editable?(@blob)
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 3f95e2a1bf6..5081bae6801 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -3,17 +3,17 @@
%div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
%strong.str-truncated= branch.name
- &nbsp;
- - if branch.name == @repository.root_ref
- %span.label.label-primary default
- - elsif @repository.merged_to_root_ref? branch.name
- %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
- merged
+ &nbsp;
+ - if branch.name == @repository.root_ref
+ %span.label.label-primary default
+ - elsif @repository.merged_to_root_ref? branch.name
+ %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
+ merged
- - if @project.protected_branch? branch.name
- %span.label.label-success
- %i.fa.fa-lock
- protected
+ - if @project.protected_branch? branch.name
+ %span.label.label-success
+ %i.fa.fa-lock
+ protected
.controls.hidden-xs
- if create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
@@ -26,7 +26,7 @@
Compare
- if can_remove_branch?(@project, branch.name)
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, remote: true do
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if commit
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index dab7164153f..742676305a9 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -3,10 +3,10 @@
.project-issuable-filter
.controls
- - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+ - if @ci_project && can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
- = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+ = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
%ul.center-top-menu
%li{class: ('active' if @scope.nil?)}
@@ -50,4 +50,3 @@
= render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true
= paginate @builds, theme: 'gitlab'
-
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 907e1ce10bd..d5e81f84b56 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,17 +1,16 @@
-- page_title "#{@build.name} (#{@build.id})", "Builds"
+- page_title "#{@build.name} (##{@build.id})", "Builds"
= render "header_title"
.build-page
- .gray-content-block
+ .gray-content-block.top-block
Build ##{@build.id} for commit
- %strong.monospace
- = link_to @build.commit.short_sha, ci_status_path(@build.commit)
+ %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
#up-build-trace
- if @commit.matrix_for_ref?(@build.ref)
- %ul.center-top-menu.build-top-menu
+ %ul.center-top-menu.no-top.no-bottom
- @commit.latest_builds_for_ref(@build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
@@ -22,7 +21,6 @@
- else
= build.id
-
- if @build.retried?
%li.active
%a
@@ -31,7 +29,7 @@
%i.fa.fa-warning
This build was retried.
- .gray-content-block.second-block
+ .gray-content-block.middle-block
.build-head
.clearfix
= ci_status_with_icon(@build.status)
@@ -140,7 +138,7 @@
%h4.title
Commit
.pull-right
- %small
+ %small
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%p
%span.attr-name Branch:
@@ -162,7 +160,7 @@
- if @builds.present?
.build-widget
- %h4.title #{pluralize(@builds.count(:id), "other build")} for
+ %h4.title #{pluralize(@builds.count(:id), "other build")} for
= succeed ":" do
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%table.table.builds
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
new file mode 100644
index 00000000000..e4d81182c1a
--- /dev/null
+++ b/app/views/projects/commit/_builds.html.haml
@@ -0,0 +1,67 @@
+.gray-content-block.middle-block
+ .pull-right
+ - if @ci_project && can?(current_user, :manage_builds, @ci_commit.gl_project)
+ - if @ci_commit.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+
+ - if @ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+ .oneline
+ = pluralize @statuses.count(:id), "build"
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), class: "monospace"
+ - if @ci_commit.duration > 0
+ in
+ = time_interval_in_words @ci_commit.duration
+
+- if @ci_commit.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - @ci_commit.yaml_errors.split(",").each do |error|
+ %li= error
+
+- if @ci_commit.gl_project.builds_enabled? && !@ci_commit.ci_yaml_file
+ .bs-callout.bs-callout-warning
+ \.gitlab-ci.yml not found in this commit
+
+.table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ - @ci_commit.refs.each do |ref|
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
+ locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true, allow_retry: true }
+
+- if @ci_commit.retried.any?
+ .gray-content-block.second-block
+ Retried builds
+
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
+ locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true }
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index 76dc87a8824..f74f8b427ec 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -6,4 +6,4 @@
= nav_link(path: 'commit#builds') do
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Builds
- %span.badge= @builds.count(:id)
+ %span.badge= @statuses.count
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index d8bfe6a07ac..bb37e4a7049 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -20,7 +20,8 @@
%p
%span.light Commit
- = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+ = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace", data: { clipboard_text: @commit.id }
+ = clipboard_button
.commit-info-row
%span.light Authored by
%strong
@@ -44,7 +45,7 @@
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
= ci_status_icon(@ci_commit)
build:
- = @ci_commit.status
+ = ci_status_label(@ci_commit)
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml
index 00cf9c76102..99d62503a94 100644
--- a/app/views/projects/commit/builds.html.haml
+++ b/app/views/projects/commit/builds.html.haml
@@ -3,70 +3,4 @@
= render "commit_box"
= render "ci_menu"
-
-- if @ci_commit.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - @ci_commit.yaml_errors.split(",").each do |error|
- %li= error
-
-- unless @ci_commit.ci_yaml_file
- .bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
-
-.gray-content-block.second-block
- Latest builds
-
- .pull-right
- - if @ci_commit.duration > 0
- %i.fa.fa-time
- #{time_interval_in_words @ci_commit.duration}
-
- &nbsp;
-
- - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
- - if @ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-primary', method: :post
-
- - if @ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger', method: :post
-
-.table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_project && @ci_project.coverage_enabled?
- %th Coverage
- %th
- - @ci_commit.refs.each do |ref|
- = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
- locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true, allow_retry: true }
-
-- if @ci_commit.retried.any?
- .gray-content-block.second-block
- Retried builds
-
- .table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_project && @ci_project.coverage_enabled?
- %th Coverage
- %th
- = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
- locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true }
+= render "builds"
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
index 9a0e7bff3f1..a527bb2f84a 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -1,13 +1,18 @@
%tr.commit_status
%td.status
- = ci_status_with_icon(commit_status.status)
+ - if commit_status.target_url
+ = link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
+ = ci_icon_for_status(commit_status.status)
+ = commit_status.status
+ - else
+ = ci_status_with_icon(commit_status.status)
%td.commit_status-link
- if commit_status.target_url
= link_to commit_status.target_url do
- %strong Build ##{commit_status.id}
+ %strong ##{commit_status.id}
- else
- %strong Build ##{commit_status.id}
+ %strong ##{commit_status.id}
- if commit_status.show_warning?
%i.fa.fa-warning.text-warning
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index b3392d00e01..327e7d9245a 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -3,9 +3,8 @@
- if diff_file.diff.submodule?
%span
= icon('archive fw')
- - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
%strong
- = submodule_link(submodule_item, @commit.id, project.repository)
+ = submodule_link(blob, @commit.id, project.repository)
- else
%span
= blob_icon blob.mode, blob.name
@@ -25,7 +24,7 @@
= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
.diff-controls
- - if blob.text?
+ - if blob_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments
&nbsp;
@@ -40,7 +39,7 @@
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?')
- - if blob.text?
+ - if blob_viewable?(blob)
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 03d0733f913..a47643bd09c 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -3,6 +3,8 @@
= link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
= link_to 'Commits', commits_namespace_project_graph_path
+ = nav_link(action: :languages) do
+ = link_to 'Languages', languages_namespace_project_graph_path
- if @project.builds_enabled?
= nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
new file mode 100644
index 00000000000..a7fab5b6d72
--- /dev/null
+++ b/app/views/projects/graphs/languages.html.haml
@@ -0,0 +1,32 @@
+- page_title "Languages", "Graphs"
+= render "header_title"
+= render 'head'
+
+.gray-content-block.append-bottom-default
+ .oneline
+ Programming languages used in this repository
+
+.row
+ .col-md-8
+ %canvas#languages-chart{ height: 400 }
+ .col-md-4
+ %ul.bordered-list
+ - @languages.each do |language|
+ %li
+ %span{ style: "color: #{language[:color]}" }
+ = icon('circle')
+ &nbsp;
+ = language[:label]
+ .pull-right
+ = language[:value]
+ \%
+
+:javascript
+ var data = #{@languages.to_json};
+ var ctx = $("#languages-chart").get(0).getContext("2d");
+ var options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false
+ }
+ var myPieChart = new Chart(ctx).Pie(data, options);
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index b5f522f2079..f2011542ca7 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -12,15 +12,12 @@
.col-md-9
.votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @issue
- .participants
- %span= pluralize(@participants.count, 'participant')
- - @participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
+ = render "shared/issuable/participants"
.col-md-3
.input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue)
- = clipboard_button(clipboard_target: '#cross-project-reference')
+ = clipboard_button(clipboard_target: 'span#cross-project-reference')
.row
%section.col-md-9
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index ea462561668..d64b19ae91a 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -12,16 +12,16 @@
.col-md-9
.votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @merge_request
- = render "projects/merge_requests/show/participants"
+ = render "shared/issuable/participants"
.col-md-3
.input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
- = clipboard_button(clipboard_target: '#cross-project-reference')
+ = clipboard_button(clipboard_target: 'span#cross-project-reference')
.row
%section.col-md-9
- = render "projects/notes/notes_with_form"
+ .voting_notes#notes= render "projects/notes/notes_with_form"
%aside.col-md-3
.issuable-affix
.context
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 1d4c9b66c42..f7f932bdf36 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,11 +1,10 @@
-- ci_commit = merge_request.ci_commit
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
%span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
.pull-right.light
- - if ci_commit
- = render_ci_status(ci_commit)
+ - if merge_request.ci_commit
+ = render_ci_status(merge_request.ci_commit)
- if merge_request.merged?
%span
= icon('check')
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 156922cea41..4172d5a4e88 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -20,13 +20,18 @@
.mr-compare.merge-request
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.commits-tab
- = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
+ = link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
%li.diffs-tab.active
- = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
+ = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @diffs.size
+ - if @ci_commit
+ %li.builds-tab.active
+ = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
.tab-content
#commits.commits.tab-pane
@@ -42,6 +47,9 @@
.alert.alert-danger
%h4 This comparison includes a huge diff.
%p To preserve performance the line changes are not shown.
+ - if @ci_commit
+ #builds.builds.tab-pane
+ = render "projects/merge_requests/show/builds"
:javascript
$('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index f5aff0877e7..960d1561e73 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -26,8 +26,7 @@
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal
%span Request to merge
- %span.label-branch
- = source_branch_with_namespace(@merge_request)
+ %span.label-branch= source_branch_with_namespace(@merge_request)
%span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
@@ -44,17 +43,22 @@
- if @commits.present?
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.notes-tab
- = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
+ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
- = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
+ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
%li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @merge_request.diffs.size
+ - if @ci_commit
+ %li.builds-tab
+ = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
.tab-content
#notes.notes.tab-pane.voting_notes
@@ -63,6 +67,8 @@
- # This tab is always loaded via AJAX
#diffs.diffs.tab-pane
- # This tab is always loaded via AJAX
+ #builds.builds.tab-pane
+ - # This tab is always loaded via AJAX
.mr-loading-status
= spinner
diff --git a/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml
new file mode 100644
index 00000000000..eab5be488b5
--- /dev/null
+++ b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/accept'))}");
diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml
index 518ecb9f00f..92ce479d463 100644
--- a/app/views/projects/merge_requests/merge.js.haml
+++ b/app/views/projects/merge_requests/merge.js.haml
@@ -1,6 +1,10 @@
-- if @status
+- case @status
+- when :success
:plain
merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'});
+- when :merge_when_build_succeeds
+ :plain
+ $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}");
- else
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}");
diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml
new file mode 100644
index 00000000000..307a75d02ca
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_builds.html.haml
@@ -0,0 +1 @@
+= render "projects/commit/builds", link_to_commit: true
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index ba5ad22bca7..b05ab869215 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,30 +1,33 @@
-- ci_commit = @merge_request.ci_commit
-- if ci_commit
- - status = ci_commit.status
+- if @ci_commit
.mr-widget-heading
- .ci_widget{class: "ci-#{status}"}
- = ci_status_icon(ci_commit)
- %span CI build #{status}
- for #{@merge_request.last_commit_short_sha}.
+ .ci_widget{class: "ci-#{@ci_commit.status}"}
+ = ci_status_icon(@ci_commit)
+ %span
+ Build
+ = ci_status_label(@ci_commit)
+ for
+ = succeed "." do
+ = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
%span.ci-coverage
- = link_to "View build details", ci_status_path(ci_commit)
+ = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
- elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- # Remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading
- - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
+ - %w[success skipped canceled failed running pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
- - if status == :success
- - status = "passed"
- = icon("check-circle")
- - else
- = icon("circle")
- %span CI build #{status}
- for #{@merge_request.last_commit_short_sha}.
+ = ci_icon_for_status(status)
+ %span
+ CI build
+ = ci_label_for_status(status)
+ for
+ - commit = @merge_request.last_commit
+ = succeed "." do
+ = link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
%span.ci-coverage
- - if ci_build_details_path(@merge_request)
- = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+ - if details_path = ci_build_details_path(@merge_request)
+ = link_to "View details", details_path, :"data-no-turbolink" => "data-no-turbolink"
.ci_widget
= icon("spinner spin")
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 5c6fece8c5c..8c2b5366a06 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -14,7 +14,7 @@
= @merge_request.target_branch
The source branch has been removed.
- - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch)
+ - elsif @merge_request.can_remove_source_branch?(current_user)
.remove_source_branch_widget
%p
= succeed '.' do
@@ -50,5 +50,3 @@
$('.remove_source_branch_in_progress').hide();
$('.remove_source_branch_widget.failed').show();
});
-
-
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 0aad9bb3e88..e0013fb769a 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -13,6 +13,8 @@
= render 'projects/merge_requests/widget/open/conflicts'
- elsif @merge_request.work_in_progress?
= render 'projects/merge_requests/widget/open/wip'
+ - elsif @merge_request.merge_when_build_succeeds?
+ = render 'projects/merge_requests/widget/open/merge_when_build_succeeds'
- elsif !@merge_request.can_be_merged_by?(current_user)
= render 'projects/merge_requests/widget/open/not_allowed'
- elsif @merge_request.can_be_merged?
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 9b31014b581..c6bc4ca5beb 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -1,28 +1,62 @@
-- status_class = @merge_request.ci_commit ? " ci-#{@merge_request.ci_commit.status}" : nil
+- status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container
- .accept-action
- = f.button class: "btn btn-create accept_merge_request#{status_class}" do
- Accept Merge Request
- - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
- .accept-control.checkbox
- = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
- = check_box_tag :should_remove_source_branch
- Remove source branch
- .accept-control.right
- = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
- = icon('edit')
- Modify commit message
- .js-toggle-content.hide.prepend-top-20
+ .clearfix
+ .accept-action
+ - if @ci_commit && @ci_commit.active?
+ %span.btn-group
+ = link_to "#", class: "btn btn-create merge_when_build_succeeds" do
+ Merge When Build Succeeds
+ %a.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+ %span.caret
+ %span.sr-only
+ Select Merge Moment
+ %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
+ %li
+ = link_to "#", class: "merge_when_build_succeeds" do
+ = icon('check fw')
+ Merge When Build Succeeds
+ %li
+ = link_to "#", class: "accept_merge_request" do
+ = icon('warning fw')
+ Merge Immediately
+ - else
+ = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do
+ Accept Merge Request
+ - if @merge_request.can_remove_source_branch?(current_user)
+ .accept-control.checkbox
+ = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+ = check_box_tag :should_remove_source_branch
+ Remove source branch
+ .accept-control.right
+ = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
+ = icon('edit')
+ Modify commit message
+ .js-toggle-content.hide.prepend-top-default
= render 'shared/commit_message_container', params: params,
text: @merge_request.merge_commit_message,
rows: 14, hint: true
+ = hidden_field_tag :merge_when_build_succeeds, "", autocomplete: "off"
+
:javascript
- $('.accept-mr-form').on('ajax:before', function() {
- var btn = $('.accept_merge_request');
- btn.disable();
- btn.html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
+ $('.accept_merge_request').on('click', function() {
+ $(this).html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
+ });
+
+ $('.accept-mr-form').on('ajax:send', function() {
+ $(".accept-mr-form :input").disable();
+ });
+
+ $('a.accept_merge_request').on('click', function(e) {
+ e.preventDefault();
+ $(this).closest("form").submit();
+ });
+
+ $('a.merge_when_build_succeeds').on('click', function(e) {
+ e.preventDefault();
+ $("#merge_when_build_succeeds").val("1");
+ $(this).closest("form").submit();
});
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
new file mode 100644
index 00000000000..08af124274b
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -0,0 +1,26 @@
+%h4
+ Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
+ to be merged automatically when the build succeeds.
+%div
+ - should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present?
+ %p
+ = succeed '.' do
+ The changes will be merged into
+ %span.label-branch= @merge_request.target_branch
+ - if should_remove_source_branch
+ The source branch will be removed.
+ - else
+ The source branch will not be removed.
+
+ - remove_source_branch_button = @merge_request.can_remove_source_branch?(current_user) && !should_remove_source_branch
+ - user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+ - if remove_source_branch_button || user_can_cancel_automatic_merge
+ .clearfix.prepend-top-10
+ - if remove_source_branch_button
+ = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
+ = icon('times')
+ Remove Source Branch When Merged
+
+ - if user_can_cancel_automatic_merge
+ = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do
+ Cancel Automatic Merge
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 334172b976f..d6a44c9f0a1 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -18,11 +18,7 @@
.row
.col-sm-6
- - if milestone.expired? and not milestone.closed?
- %span.cred (Expired)
- - if milestone.expires_at
- %span
- = milestone.expires_at
+ = render 'shared/milestone_expired', milestone: milestone
.col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index a207385bd43..114b06457a5 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,15 +1,18 @@
- page_title "Milestones"
= render "header_title"
-= render 'shared/milestones_filter'
-.gray-content-block
- .pull-right
- - if can? current_user, :admin_milestone, @project
+
+.project-issuable-filter
+ .controls
+ - if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
%i.fa.fa-plus
New Milestone
- .oneline
- Milestone allows you to group issues and set due date for it
+
+ = render 'shared/milestones_filter'
+
+.gray-content-block
+ Milestone allows you to group issues and set due date for it
.milestones
%ul.content-list
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index 415c98ec6a6..9e0e0dc6bb0 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,3 +1,6 @@
-.append-bottom-20
- = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
- .pull-right.visible-lg.light You can move around the graph by using the arrow keys.
+.gray-content-block.top-block.append-bottom-default
+ .tree-ref-holder
+ = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
+
+ .oneline
+ You can move around the graph by using the arrow keys.
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 0c73d7e34ac..1c2458fa144 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -4,12 +4,13 @@
group members
%small
(#{members.count})
- .pull-right
- = link_to group_group_members_path(@group), class: 'btn' do
- = icon('pencil-square-o')
- Edit group members
+ - if can?(current_user, :admin_group_member, @group)
+ .pull-right
+ = link_to group_group_members_path(@group), class: 'btn' do
+ = icon('pencil-square-o')
+ Manage group members
%ul.content-list
- - members.each do |member|
+ - members.limit(20).each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false
- if members.count > 20
%li
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 2541105b007..cfd7e1534ca 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -3,7 +3,7 @@
%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
-.well.append-bottom-20
+.well
%p Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index e2c5178185e..28b706c5c7e 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -11,11 +11,17 @@
= strip_gpg_signature(tag.message)
.controls
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do
- = icon("pencil")
- - if can? current_user, :download_code, @project
+ - if can?(current_user, :download_code, @project)
= render 'projects/tags/download', ref: tag.name, project: @project
+ - if can?(current_user, :push_code, @project)
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has_tooltip', title: "Edit release notes" do
+ = icon("pencil")
+
+ - if can?(current_user, :admin_project, @project)
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = icon("trash-o")
+
- if commit
= render 'projects/branches/commit', commit: commit, project: @project
- else
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 879c6c7d310..b594d4f1f27 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -5,17 +5,17 @@
.gray-content-block
.pull-right
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has_tooltip', title: 'Edit release notes' do
= icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse source code' do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse files' do
= icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse commits' do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.pull-right
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+ = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.title
%strong= @tag.name
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 1115ca6b4ca..0e1f7076608 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -14,7 +14,7 @@
- if allowed_tree_edit?
%li
%span.dropdown
- %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
%li
@@ -30,3 +30,7 @@
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
= icon('folder fw')
New directory
+ - elsif !on_top_of_branch?
+ %li
+ %span.btn.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}}
+ = icon('plus')
diff --git a/app/views/shared/_milestone_expired.html.haml b/app/views/shared/_milestone_expired.html.haml
new file mode 100644
index 00000000000..b8eef15fbec
--- /dev/null
+++ b/app/views/shared/_milestone_expired.html.haml
@@ -0,0 +1,5 @@
+- if milestone.expired? and not milestone.closed?
+ %span.cred (Expired)
+- if milestone.expires_at
+ %span
+ = milestone.expires_at
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 31b02ed93d0..111219f2064 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -4,14 +4,13 @@
.form-group.branch
= label_tag 'new_branch', 'Target branch', class: 'control-label'
.col-sm-10
- = text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch"
+ = text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch"
- .form-group.js-create-merge-request-form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- - nonce = SecureRandom.hex
- = label_tag "create_merge_request-#{nonce}" do
- = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
- Start a <strong>new merge request</strong> with this commit
+ .js-create-merge-request-container
+ .checkbox
+ - nonce = SecureRandom.hex
+ = label_tag "create_merge_request-#{nonce}" do
+ = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+ Start a <strong>new merge request</strong> with these changes
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
new file mode 100644
index 00000000000..c4431d66927
--- /dev/null
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -0,0 +1,20 @@
+- if @projects.any?
+ .prepend-left-10.new-project-item-select-holder
+ = project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
+ %a.btn.btn-new.new-project-item-select-button
+ = icon('plus')
+ = local_assigns[:label]
+ %b.caret
+
+ :javascript
+ $('.new-project-item-select-button').on('click', function() {
+ $('.new-project-item-select').select2('open');
+ });
+
+ var relativePath = '#{local_assigns[:path]}';
+
+ $('.new-project-item-select').on('click', function() {
+ window.location = $(this).val() + '/' + relativePath;
+ });
+
+ new ProjectSelect()
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
index c67afe963e7..b4e0def48b6 100644
--- a/app/views/projects/merge_requests/show/_participants.html.haml
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -1,4 +1,5 @@
.participants
- %span #{@participants.count} participants
+ %span
+ = pluralize @participants.count, "participant"
- @participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 89c1d7122b0..eb0fd21c2d4 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,6 +1,6 @@
.issuable-details
.page-title
- .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level), data: { container: 'body' }}
+ .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
= visibility_level_icon(@snippet.visibility_level, fw: false)
= visibility_level_label(@snippet.visibility_level)
Snippet ##{@snippet.id}
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index d5a92cb816a..a0a6e2d9810 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -73,7 +73,7 @@
.user-calendar-activities
-%ul.center-middle-menu
+%ul.center-top-menu.no-top.no-bottom.bottom-border
%li.active
= link_to "#activity", 'data-toggle' => 'tab' do
Activity
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index 5d1a8555b7d..c87c0a252b1 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -8,16 +8,7 @@ class MergeWorker
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
- result = MergeRequests::MergeService.new(merge_request.target_project, current_user).
- execute(merge_request, params[:commit_message])
-
- if result[:status] == :success && params[:should_remove_source_branch].present?
- DeleteBranchService.new(merge_request.source_project, current_user).
- execute(merge_request.source_branch)
-
- merge_request.source_project.repository.expire_branch_names
- end
-
- result
+ MergeRequests::MergeService.new(merge_request.target_project, current_user, params).
+ execute(merge_request)
end
end
diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb
index 4e5eddbaba1..ca594e77e7c 100644
--- a/app/workers/stuck_ci_builds_worker.rb
+++ b/app/workers/stuck_ci_builds_worker.rb
@@ -1,11 +1,8 @@
class StuckCiBuildsWorker
include Sidekiq::Worker
- include Sidetiq::Schedulable
BUILD_STUCK_TIMEOUT = 1.day
- recurrence { daily }
-
def perform
Rails.logger.info 'Cleaning stuck builds'