summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-04-26 16:36:17 +0200
committerKamil Trzcinski <ayufan@ayufan.eu>2016-04-26 16:36:17 +0200
commitcb90368a692149b4e01b50d7c6682c48cffa438b (patch)
tree6236d6f4fd56acad1015950d483db0ba8fa3ada9 /app
parentc6f19aed51736e5945283a611eae09f32a9b5aeb (diff)
parentf127edd012bd8b6f76ac67d69aadbd7d4837258f (diff)
downloadgitlab-ce-cb90368a692149b4e01b50d7c6682c48cffa438b.tar.gz
Merge remote-tracking branch 'origin/master' into with-pipeline-view
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js.coffee11
-rw-r--r--app/assets/javascripts/application.js.coffee14
-rw-r--r--app/assets/javascripts/awards_handler.coffee19
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js.coffee6
-rw-r--r--app/assets/javascripts/blob/blob_license_selector.js.coffee30
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee67
-rw-r--r--app/assets/javascripts/blob/new_blob.js.coffee20
-rw-r--r--app/assets/javascripts/commits.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee15
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee10
-rw-r--r--app/assets/javascripts/due_date_select.js.coffee64
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee63
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee28
-rw-r--r--app/assets/javascripts/gl_form.js.coffee51
-rw-r--r--app/assets/javascripts/importer_status.js.coffee39
-rw-r--r--app/assets/javascripts/issuable.js.coffee84
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee38
-rw-r--r--app/assets/javascripts/issue.js.coffee23
-rw-r--r--app/assets/javascripts/issues.js.coffee51
-rw-r--r--app/assets/javascripts/labels_select.js.coffee145
-rw-r--r--app/assets/javascripts/lib/animate.js.coffee34
-rw-r--r--app/assets/javascripts/lib/url_utility.js.coffee16
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee37
-rw-r--r--app/assets/javascripts/merge_requests.js.coffee35
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee6
-rw-r--r--app/assets/javascripts/notes.js.coffee92
-rw-r--r--app/assets/javascripts/profile.js.coffee5
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee62
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee41
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee20
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee1
-rw-r--r--app/assets/javascripts/subscription.js.coffee2
-rw-r--r--app/assets/javascripts/todos.js.coffee46
-rw-r--r--app/assets/javascripts/users_select.js.coffee5
-rw-r--r--app/assets/stylesheets/framework/buttons.scss4
-rw-r--r--app/assets/stylesheets/framework/calendar.scss4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss132
-rw-r--r--app/assets/stylesheets/framework/files.scss11
-rw-r--r--app/assets/stylesheets/framework/gfm.scss18
-rw-r--r--app/assets/stylesheets/framework/header.scss6
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss2
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss22
-rw-r--r--app/assets/stylesheets/framework/mobile.scss7
-rw-r--r--app/assets/stylesheets/framework/selects.scss3
-rw-r--r--app/assets/stylesheets/framework/timeline.scss5
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss1
-rw-r--r--app/assets/stylesheets/framework/typography.scss8
-rw-r--r--app/assets/stylesheets/framework/variables.scss10
-rw-r--r--app/assets/stylesheets/highlight/dark.scss6
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss8
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss6
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss6
-rw-r--r--app/assets/stylesheets/highlight/white.scss14
-rw-r--r--app/assets/stylesheets/pages/commits.scss5
-rw-r--r--app/assets/stylesheets/pages/confirmation.scss18
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss14
-rw-r--r--app/assets/stylesheets/pages/diff.scss43
-rw-r--r--app/assets/stylesheets/pages/editor.scss9
-rw-r--r--app/assets/stylesheets/pages/graph.scss3
-rw-r--r--app/assets/stylesheets/pages/help.scss2
-rw-r--r--app/assets/stylesheets/pages/import.scss21
-rw-r--r--app/assets/stylesheets/pages/issuable.scss72
-rw-r--r--app/assets/stylesheets/pages/issues.scss40
-rw-r--r--app/assets/stylesheets/pages/labels.scss27
-rw-r--r--app/assets/stylesheets/pages/note_form.scss23
-rw-r--r--app/assets/stylesheets/pages/notes.scss50
-rw-r--r--app/assets/stylesheets/print.scss44
-rw-r--r--app/controllers/admin/application_settings_controller.rb12
-rw-r--r--app/controllers/admin/hooks_controller.rb2
-rw-r--r--app/controllers/admin/projects_controller.rb12
-rw-r--r--app/controllers/application_controller.rb33
-rw-r--r--app/controllers/autocomplete_controller.rb9
-rw-r--r--app/controllers/concerns/filter_projects.rb2
-rw-r--r--app/controllers/confirmations_controller.rb9
-rw-r--r--app/controllers/groups_controller.rb1
-rw-r--r--app/controllers/help_controller.rb1
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb5
-rw-r--r--app/controllers/projects/application_controller.rb3
-rw-r--r--app/controllers/projects/builds_controller.rb10
-rw-r--r--app/controllers/projects/commit_controller.rb30
-rw-r--r--app/controllers/projects/group_links_controller.rb10
-rw-r--r--app/controllers/projects/issues_controller.rb51
-rw-r--r--app/controllers/projects/merge_requests_controller.rb8
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects/services_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb17
-rw-r--r--app/controllers/registrations_controller.rb4
-rw-r--r--app/controllers/users_controller.rb23
-rw-r--r--app/finders/issuable_finder.rb42
-rw-r--r--app/helpers/application_helper.rb9
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb11
-rw-r--r--app/helpers/commits_helper.rb31
-rw-r--r--app/helpers/diff_helper.rb11
-rw-r--r--app/helpers/import_helper.rb18
-rw-r--r--app/helpers/issuables_helper.rb28
-rw-r--r--app/helpers/issues_helper.rb14
-rw-r--r--app/helpers/page_layout_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb71
-rw-r--r--app/helpers/selects_helper.rb21
-rw-r--r--app/helpers/sorting_helper.rb18
-rw-r--r--app/helpers/tab_helper.rb8
-rw-r--r--app/helpers/tree_helper.rb2
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/mailers/repository_check_mailer.rb14
-rw-r--r--app/models/ability.rb29
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/ci/build.rb41
-rw-r--r--app/models/ci/commit.rb11
-rw-r--r--app/models/commit.rb18
-rw-r--r--app/models/commit_status.rb25
-rw-r--r--app/models/concerns/internal_id.rb10
-rw-r--r--app/models/concerns/issuable.rb9
-rw-r--r--app/models/concerns/statuseable.rb (renamed from app/models/concerns/ci_status.rb)8
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/external_issue.rb6
-rw-r--r--app/models/group.rb1
-rw-r--r--app/models/hooks/project_hook.rb3
-rw-r--r--app/models/hooks/system_hook.rb3
-rw-r--r--app/models/hooks/web_hook.rb3
-rw-r--r--app/models/issue.rb48
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/merge_request.rb7
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/note.rb1
-rw-r--r--app/models/oauth_access_token.rb19
-rw-r--r--app/models/project.rb63
-rw-r--r--app/models/project_import_data.rb15
-rw-r--r--app/models/project_services/bamboo_service.rb20
-rw-r--r--app/models/project_services/builds_email_service.rb10
-rw-r--r--app/models/project_services/hipchat_service.rb4
-rw-r--r--app/models/project_services/slack_service.rb5
-rw-r--r--app/models/project_services/slack_service/merge_message.rb2
-rw-r--r--app/models/project_services/slack_service/note_message.rb2
-rw-r--r--app/models/project_services/slack_service/wiki_page_message.rb53
-rw-r--r--app/models/project_services/teamcity_service.rb33
-rw-r--r--app/models/repository.rb99
-rw-r--r--app/models/service.rb4
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/services/commits/change_service.rb47
-rw-r--r--app/services/commits/cherry_pick_service.rb19
-rw-r--r--app/services/commits/revert_service.rb46
-rw-r--r--app/services/git_push_service.rb6
-rw-r--r--app/services/git_tag_push_service.rb29
-rw-r--r--app/services/issuable_base_service.rb28
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/services/merge_requests/base_service.rb3
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/projects/housekeeping_service.rb14
-rw-r--r--app/services/projects/participants_service.rb43
-rw-r--r--app/services/projects/transfer_service.rb7
-rw-r--r--app/services/system_hooks_service.rb12
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/services/wiki_pages/base_service.rb27
-rw-r--r--app/services/wiki_pages/create_service.rb13
-rw-r--r--app/services/wiki_pages/update_service.rb11
-rw-r--r--app/views/admin/application_settings/_form.html.haml36
-rw-r--r--app/views/admin/hooks/index.html.haml36
-rw-r--r--app/views/admin/logs/show.html.haml3
-rw-r--r--app/views/admin/projects/index.html.haml10
-rw-r--r--app/views/admin/projects/show.html.haml36
-rw-r--r--app/views/admin/users/_form.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml1
-rw-r--r--app/views/devise/confirmations/almost_there.haml10
-rw-r--r--app/views/devise/shared/_signup_box.html.haml9
-rw-r--r--app/views/doorkeeper/applications/index.html.haml3
-rw-r--r--app/views/events/_event.html.haml7
-rw-r--r--app/views/groups/milestones/new.html.haml4
-rw-r--r--app/views/help/_shortcuts.html.haml14
-rw-r--r--app/views/help/ui.html.haml12
-rw-r--r--app/views/import/base/create.js.haml4
-rw-r--r--app/views/import/bitbucket/status.html.haml20
-rw-r--r--app/views/import/fogbugz/status.html.haml15
-rw-r--r--app/views/import/github/status.html.haml19
-rw-r--r--app/views/import/gitlab/status.html.haml15
-rw-r--r--app/views/import/gitorious/status.html.haml15
-rw-r--r--app/views/import/google_code/status.html.haml19
-rw-r--r--app/views/layouts/_page.html.haml4
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise_empty.html.haml17
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml3
-rw-r--r--app/views/layouts/nav/_profile.html.haml14
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/layouts/profile.html.haml3
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/merge_request_status_email.html.haml2
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml2
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/notify/note_merge_request_email.text.erb2
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/profiles/audit_log.html.haml1
-rw-r--r--app/views/profiles/emails/index.html.haml1
-rw-r--r--app/views/profiles/keys/index.html.haml1
-rw-r--r--app/views/profiles/notifications/show.html.haml1
-rw-r--r--app/views/profiles/passwords/edit.html.haml1
-rw-r--r--app/views/profiles/preferences/show.html.haml1
-rw-r--r--app/views/projects/_activity.html.haml5
-rw-r--r--app/views/projects/_builds_settings.html.haml6
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/_readme.html.haml4
-rw-r--r--app/views/projects/_zen.html.haml4
-rw-r--r--app/views/projects/blob/_editor.html.haml6
-rw-r--r--app/views/projects/blob/diff.html.haml20
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/builds/index.html.haml2
-rw-r--r--app/views/projects/builds/show.html.haml7
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/commit/_change.html.haml (renamed from app/views/projects/commit/_revert.html.haml)20
-rw-r--r--app/views/projects/commit/_commit_box.html.haml1
-rw-r--r--app/views/projects/commit/show.html.haml3
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml20
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml8
-rw-r--r--app/views/projects/edit.html.haml1
-rw-r--r--app/views/projects/empty.html.haml6
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml13
-rw-r--r--app/views/projects/hooks/index.html.haml11
-rw-r--r--app/views/projects/issues/_form.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml7
-rw-r--r--app/views/projects/issues/show.html.haml127
-rw-r--r--app/views/projects/issues/update.js.haml3
-rw-r--r--app/views/projects/labels/_label.html.haml2
-rw-r--r--app/views/projects/merge_requests/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml32
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml17
-rw-r--r--app/views/projects/merge_requests/dropdowns/_branch.html.haml5
-rw-r--r--app/views/projects/merge_requests/dropdowns/_project.html.haml5
-rw-r--r--app/views/projects/merge_requests/edit.html.haml4
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml59
-rw-r--r--app/views/projects/merge_requests/update.js.haml3
-rw-r--r--app/views/projects/merge_requests/update_branches.html.haml8
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml5
-rw-r--r--app/views/projects/merge_requests/widget/_merged_buttons.haml11
-rw-r--r--app/views/projects/milestones/_form.html.haml5
-rw-r--r--app/views/projects/notes/_discussion.html.haml2
-rw-r--r--app/views/projects/notes/_edit_form.html.haml2
-rw-r--r--app/views/projects/notes/_form.html.haml4
-rw-r--r--app/views/projects/notes/_note.html.haml13
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml6
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml9
-rw-r--r--app/views/projects/notes/discussions/_diff.html.haml8
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml6
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml2
-rw-r--r--app/views/projects/releases/edit.html.haml12
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml7
-rw-r--r--app/views/projects/show.html.haml12
-rw-r--r--app/views/projects/tags/new.html.haml6
-rw-r--r--app/views/projects/wikis/_form.html.haml4
-rw-r--r--app/views/repository_check_mailer/notify.html.haml8
-rw-r--r--app/views/repository_check_mailer/notify.text.haml6
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/search/results/_note.html.haml2
-rw-r--r--app/views/shared/_label_row.html.haml2
-rw-r--r--app/views/shared/_labels_row.html.haml3
-rw-r--r--app/views/shared/_service_settings.html.haml8
-rw-r--r--app/views/shared/_sort_dropdown.html.haml5
-rw-r--r--app/views/shared/issuable/_filter.html.haml8
-rw-r--r--app/views/shared/issuable/_form.html.haml37
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml44
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml17
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml20
-rw-r--r--app/views/shared/issuable/_nav.html.haml10
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml69
-rw-r--r--app/views/shared/milestones/_issuable.html.haml2
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml13
-rw-r--r--app/views/shared/projects/_dropdown.html.haml11
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/views/votes/_votes_block.html.haml4
-rw-r--r--app/workers/admin_email_worker.rb12
-rw-r--r--app/workers/post_receive.rb6
-rw-r--r--app/workers/repository_check/batch_worker.rb63
-rw-r--r--app/workers/repository_check/clear_worker.rb17
-rw-r--r--app/workers/repository_check/single_repository_worker.rb36
285 files changed, 3141 insertions, 1449 deletions
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index f3ed9a66715..dd1bbb37551 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -5,6 +5,7 @@
group_projects_path: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json"
labels_path: "/api/:version/projects/:id/labels"
+ license_path: "/api/:version/licenses/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
@@ -92,6 +93,16 @@
).done (projects) ->
callback(projects)
+ # Return text for a specific license
+ licenseText: (key, data, callback) ->
+ url = Api.buildUrl(Api.license_path).replace(':key', key)
+
+ $.ajax(
+ url: url
+ data: data
+ ).done (license) ->
+ callback(license)
+
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/application.js.coffee b/app/assets/javascripts/application.js.coffee
index b05138ac1ac..5bac8eef1cb 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -22,7 +22,17 @@
#= require cal-heatmap
#= require turbolinks
#= require autosave
-#= require bootstrap
+#= require bootstrap/affix
+#= require bootstrap/alert
+#= require bootstrap/button
+#= require bootstrap/collapse
+#= require bootstrap/dropdown
+#= require bootstrap/modal
+#= require bootstrap/scrollspy
+#= require bootstrap/tab
+#= require bootstrap/transition
+#= require bootstrap/tooltip
+#= require bootstrap/popover
#= require select2
#= require raphael
#= require g.raphael
@@ -164,7 +174,7 @@ $ ->
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
- gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false)
+ gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
# Flash
if (flash = $(".flash-container")).length > 0
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index af4462ece38..fcba9818726 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,5 +1,5 @@
class @AwardsHandler
- constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
+ constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @unicodes) ->
$(".js-add-award").on "click", (event) =>
event.stopPropagation()
event.preventDefault()
@@ -31,6 +31,8 @@ class @AwardsHandler
awards_handler.addAward emoji
+ $(this).trigger 'blur'
+
didUserClickEmoji: (that, emoji) ->
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
$(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
@@ -55,7 +57,6 @@ class @AwardsHandler
, 200
addAward: (emoji) ->
- emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
@@ -64,7 +65,6 @@ class @AwardsHandler
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
- emoji = @normilizeEmojiName(emoji)
if @exist(emoji)
if @isActive(emoji)
@decrementCounter(emoji)
@@ -146,15 +146,7 @@ class @AwardsHandler
$('.award-control').tooltip()
resolveNameToCssClass: (emoji) ->
- emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
-
- if emoji_icon.length > 0
- unicodeName = emoji_icon.data("unicode-name")
- else
- # Find by alias
- unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name")
-
- "emoji-#{unicodeName}"
+ "emoji-#{@unicodes[emoji]}"
postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: {
@@ -173,9 +165,6 @@ class @AwardsHandler
scrollTop: $('.awards').offset().top - 80
}, 200)
- normilizeEmojiName: (emoji) ->
- @aliases[emoji] || emoji
-
addEmojiToFrequentlyUsedList: (emoji) ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
frequently_used_emojis.push(emoji)
diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee
index 6e29d374267..3cb96bacaa7 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js.coffee
+++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee
@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
e.preventDefault()
$form = $(e.target).closest('form')
- $form.find('input[type=submit], button[type=submit]').disable()
+ $submit_button = $form.find('input[type=submit], button[type=submit]')
+
+ return if $submit_button.attr('disabled')
+
+ $submit_button.disable()
$form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a
diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee
new file mode 100644
index 00000000000..e17eaa75dc1
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_license_selector.js.coffee
@@ -0,0 +1,30 @@
+class @BlobLicenseSelector
+ licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
+
+ constructor: (editor) ->
+ @$licenseSelector = $('.js-license-selector')
+ $fileNameInput = $('#file_name')
+
+ initialFileNameValue = if $fileNameInput.length
+ $fileNameInput.val()
+ else if $('.editor-file-name').length
+ $('.editor-file-name').text().trim()
+
+ @toggleLicenseSelector(initialFileNameValue)
+
+ if $fileNameInput
+ $fileNameInput.on 'keyup blur', (e) =>
+ @toggleLicenseSelector($(e.target).val())
+
+ $('select.license-select').on 'change', (e) ->
+ data =
+ project: $(this).data('project')
+ fullname: $(this).data('fullname')
+ Api.licenseText $(this).val(), data, (license) ->
+ editor.setValue(license.content, -1)
+
+ toggleLicenseSelector: (fileName) =>
+ if @licenseRegex.test(fileName)
+ @$licenseSelector.show()
+ else
+ @$licenseSelector.hide()
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index 390e41ed8d4..eea9aa972ee 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -1,44 +1,39 @@
class @EditBlob
- constructor: (assets_path, mode)->
- ace.config.set "modePath", assets_path + '/ace'
+ constructor: (assets_path, ace_mode = null) ->
+ ace.config.set "modePath", "#{assets_path}/ace"
ace.config.loadModule "ace/ext/searchbox"
- if mode
- ace_mode = mode
- editor = ace.edit("editor")
- editor.focus()
- @editor = editor
-
- if ace_mode
- editor.getSession().setMode "ace/mode/" + ace_mode
+ @editor = ace.edit("editor")
+ @editor.focus()
+ @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
# Before a form submission, move the content from the Ace editor into the
# submitted textarea
- $('form').submit ->
- $("#file-content").val(editor.getValue())
+ $('form').submit =>
+ $("#file-content").val(@editor.getValue())
+
+ @initModePanesAndLinks()
+ new BlobLicenseSelector(@editor)
- editModePanes = $(".js-edit-mode-pane")
- editModeLinks = $(".js-edit-mode a")
- editModeLinks.click (event) ->
- event.preventDefault()
- currentLink = $(this)
- paneId = currentLink.attr("href")
- currentPane = editModePanes.filter(paneId)
- editModeLinks.parent().removeClass "active hover"
- currentLink.parent().addClass "active hover"
- editModePanes.hide()
- if paneId is "#preview"
- currentPane.fadeIn 200
- $.post currentLink.data("preview-url"),
- content: editor.getValue()
- , (response) ->
- currentPane.empty().append response
- currentPane.syntaxHighlight()
- return
+ initModePanesAndLinks: ->
+ @$editModePanes = $(".js-edit-mode-pane")
+ @$editModeLinks = $(".js-edit-mode a")
+ @$editModeLinks.click @editModeLinkClickHandler
- else
- currentPane.fadeIn 200
- editor.focus()
- return
+ editModeLinkClickHandler: (event) =>
+ event.preventDefault()
+ currentLink = $(event.target)
+ paneId = currentLink.attr("href")
+ currentPane = @$editModePanes.filter(paneId)
+ @$editModeLinks.parent().removeClass "active hover"
+ currentLink.parent().addClass "active hover"
+ @$editModePanes.hide()
+ currentPane.fadeIn 200
+ if paneId is "#preview"
+ $.post currentLink.data("preview-url"),
+ content: @editor.getValue()
+ , (response) ->
+ currentPane.empty().append response
+ currentPane.syntaxHighlight()
- editor: ->
- return @editor
+ else
+ @editor.focus()
diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee
deleted file mode 100644
index 68c5e5195e3..00000000000
--- a/app/assets/javascripts/blob/new_blob.js.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-class @NewBlob
- constructor: (assets_path, mode)->
- ace.config.set "modePath", assets_path + '/ace'
- ace.config.loadModule "ace/ext/searchbox"
- if mode
- ace_mode = mode
- editor = ace.edit("editor")
- editor.focus()
- @editor = editor
-
- if ace_mode
- editor.getSession().setMode "ace/mode/" + ace_mode
-
- # Before a form submission, move the content from the Ace editor into the
- # submitted textarea
- $('form').submit ->
- $("#file-content").val(editor.getValue())
-
- editor: ->
- return @editor
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index ffd3627b1b0..0acb4c1955e 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,7 +1,7 @@
class @CommitsList
@timer = null
- @init: (ref, limit) ->
+ @init: (limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) ->
if event.target.nodeName != "A"
location.href = $(this).attr("url")
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 70fd6f50e9c..2fdb7562515 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -17,6 +17,7 @@ class Dispatcher
switch page
when 'projects:issues:index'
Issues.init()
+ Issuable.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
@@ -28,26 +29,26 @@ class Dispatcher
new Todos()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
- new DropzoneInput($('.milestone-form'))
+ new GLForm($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
when 'projects:compare:show'
new Diff()
when 'projects:issues:new','projects:issues:edit'
shortcut_handler = new ShortcutsNavigation()
- new DropzoneInput($('.issue-form'))
+ new GLForm($('.issue-form'))
new IssuableForm($('.issue-form'))
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
new Diff()
shortcut_handler = new ShortcutsNavigation()
- new DropzoneInput($('.merge-request-form'))
+ new GLForm($('.merge-request-form'))
new IssuableForm($('.merge-request-form'))
when 'projects:tags:new'
new ZenMode()
- new DropzoneInput($('.tag-form'))
+ new GLForm($('.tag-form'))
when 'projects:releases:edit'
new ZenMode()
- new DropzoneInput($('.release-form'))
+ new GLForm($('.release-form'))
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
@@ -57,7 +58,7 @@ class Dispatcher
new ZenMode()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
- MergeRequests.init()
+ Issuable.init()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
@@ -137,7 +138,7 @@ class Dispatcher
new Wikis()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
- new DropzoneInput($('.wiki-form'))
+ new GLForm($('.wiki-form'))
when 'snippets'
shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show'
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index b502131a99d..e2194589b38 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -15,11 +15,13 @@ class @DropzoneInput
project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10
- form_textarea = $(form).find("textarea.markdown-area")
+ form_textarea = $(form).find(".js-gfm-input")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) =>
handlePaste(event)
+ $mdArea = $(form_textarea).closest('.md-area')
+
$(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone')
@@ -49,17 +51,17 @@ class @DropzoneInput
$(".div-dropzone-alert").alert "close"
dragover: ->
- form_textarea.addClass "div-dropzone-focus"
+ $mdArea.addClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0.7
return
dragleave: ->
- form_textarea.removeClass "div-dropzone-focus"
+ $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0
return
drop: ->
- form_textarea.removeClass "div-dropzone-focus"
+ $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus()
return
diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
new file mode 100644
index 00000000000..a4304786cbb
--- /dev/null
+++ b/app/assets/javascripts/due_date_select.js.coffee
@@ -0,0 +1,64 @@
+class @DueDateSelect
+ constructor: ->
+ $loading = $('.js-issuable-update .due_date')
+ .find('.block-loading')
+ .hide()
+
+ $('.js-due-date-select').each (i, dropdown) ->
+ $dropdown = $(dropdown)
+ $dropdownParent = $dropdown.closest('.dropdown')
+ $datePicker = $dropdownParent.find('.js-due-date-calendar')
+ $block = $dropdown.closest('.block')
+ $selectbox = $dropdown.closest('.selectbox')
+ $value = $block.find('.value')
+ $sidebarValue = $('.js-due-date-sidebar-value', $block)
+
+ fieldName = $dropdown.data('field-name')
+ abilityName = $dropdown.data('ability-name')
+ issueUpdateURL = $dropdown.data('issue-update')
+
+ $dropdown.glDropdown(
+ hidden: ->
+ $selectbox.hide()
+ $value.removeAttr('style')
+ )
+
+ addDueDate = ->
+ # Create the post date
+ value = $("input[name='#{fieldName}']").val()
+ date = new Date value.replace(new RegExp('-', 'g'), ',')
+ mediumDate = $.datepicker.formatDate 'M d, yy', date
+
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].due_date = value
+
+ $.ajax(
+ type: 'PUT'
+ url: issueUpdateURL
+ data: data
+ beforeSend: ->
+ $loading.fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
+ $selectbox.hide()
+ $value.removeAttr('style')
+
+ $value.html(mediumDate)
+ $sidebarValue.html(mediumDate)
+ ).done (data) ->
+ $dropdown.trigger('loaded.gl.dropdown')
+ $dropdown.dropdown('toggle')
+ $loading.fadeOut()
+
+ $datePicker.datepicker(
+ dateFormat: 'yy-mm-dd',
+ defaultDate: $("input[name='#{fieldName}']").val()
+ altField: "input[name='#{fieldName}']"
+ onSelect: ->
+ addDueDate()
+ )
+
+ $(document)
+ .off 'click', '.ui-datepicker-header a'
+ .on 'click', '.ui-datepicker-header a', (e) ->
+ e.stopImmediatePropagation()
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 4718bcf7a1e..61e3f811e73 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -2,6 +2,8 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
+ dataLoading: false
+
dataSource: ''
# Emoji
@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input.
- setup: ->
- input = $('.js-gfm-input')
+ setup: (wrap) ->
+ @input = $('.js-gfm-input')
+
+ # destroy previous instances
+ @destroyAtWho()
+
+ # set up instances
+ @setupAtWho()
+
+ if @dataSource
+ if !@dataLoading
+ @dataLoading = true
+ # We should wait until initializations are done
+ # and only trigger the last .setup since
+ # The previous .dataSource belongs to the previous issuable
+ # and the last one will have the **proper** .dataSource property
+ # TODO: Make this a singleton and turn off events when moving to another page
+ setTimeout( =>
+ fetch = @fetchData(@dataSource)
+ fetch.done (data) =>
+ @dataLoading = false
+ @loadData(data)
+ , 1000)
+
+
+ setupAtWho: ->
# Emoji
- input.atwho
+ @input.atwho
at: ':'
displayTpl: @Emoji.template
insertTpl: ':${name}:'
# Team Members
- input.atwho
+ @input.atwho
at: '@'
displayTpl: @Members.template
insertTpl: '${atwho-at}${username}'
@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title)
search: sanitize("#{m.username} #{m.name}")
- input.atwho
+ @input.atwho
at: '#'
alias: 'issues'
searchKey: 'search'
@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
- input.atwho
+ @input.atwho
at: '!'
alias: 'mergerequests'
searchKey: 'search'
@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
- if @dataSource
- $.getJSON(@dataSource).done (data) ->
- # load members
- input.atwho 'load', '@', data.members
- # load issues
- input.atwho 'load', 'issues', data.issues
- # load merge requests
- input.atwho 'load', 'mergerequests', data.mergerequests
- # load emojis
- input.atwho 'load', ':', data.emojis
+ destroyAtWho: ->
+ @input.atwho('destroy')
+
+ fetchData: (dataSource) ->
+ $.getJSON(dataSource)
+
+ loadData: (data) ->
+ # load members
+ @input.atwho 'load', '@', data.members
+ # load issues
+ @input.atwho 'load', 'issues', data.issues
+ # load merge requests
+ @input.atwho 'load', 'mergerequests', data.mergerequests
+ # load emojis
+ @input.atwho 'load', ':', data.emojis
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 2dc37257e22..29466e9f2ed 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -32,10 +32,8 @@ class GitLabDropdownFilter
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS
- if keyCode is 13 and @input.val() isnt ""
- if @options.enterCallback
- @options.enterCallback()
- return
+ if keyCode is 13
+ return false
clearTimeout timeout
timeout = setTimeout =>
@@ -132,7 +130,6 @@ class GitLabDropdown
@filterInput = @getElement(FILTER_INPUT)
@highlight = false
@filterInputBlur = true
- @enterCallback = true
} = @options
self = @
@@ -157,6 +154,9 @@ class GitLabDropdown
@fullData = data
@parseData @fullData
+
+ if @options.filterable
+ @filterInput.trigger 'keyup'
}
# Init filterable
@@ -178,9 +178,6 @@ class GitLabDropdown
callback: (data) =>
currentIndex = -1
@parseData data
- enterCallback: =>
- if @enterCallback
- @selectRowAtIndex 0
# Event listeners
@@ -224,6 +221,9 @@ class GitLabDropdown
menu.toggleClass PAGE_TWO_CLASS
+ # Focus first visible input on active page
+ @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
+
parseData: (data) ->
@renderedData = data
@@ -243,7 +243,8 @@ class GitLabDropdown
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
- if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
+
+ if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
e.stopPropagation()
return false
else
@@ -378,7 +379,6 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
@@ -389,13 +389,13 @@ class GitLabDropdown
else
selectedObject
else
- if !value?
- field.remove()
-
- if not @options.multiSelect
+ if not @options.multiSelect or el.hasClass('dropdown-clear-active')
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
@dropdown.parent().find("input[name='#{fieldName}']").remove()
+ if !value?
+ field.remove()
+
# Toggle active class for the tick mark
el.addClass ACTIVE_CLASS
diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee
new file mode 100644
index 00000000000..d540cc4dc46
--- /dev/null
+++ b/app/assets/javascripts/gl_form.js.coffee
@@ -0,0 +1,51 @@
+class @GLForm
+ constructor: (@form) ->
+ @textarea = @form.find('textarea.js-gfm-input')
+
+ # Before we start, we should clean up any previous data for this form
+ @destroy()
+
+ # Setup the form
+ @setupForm()
+
+ @form.data 'gl-form', @
+
+ destroy: ->
+ # Clean form listeners
+ @clearEventListeners()
+ @form.data 'gl-form', null
+
+ setupForm: ->
+ isNewForm = @form.is(':not(.gfm-form)')
+
+ @form.removeClass 'js-new-note-form'
+
+ if isNewForm
+ @form.find('.div-dropzone').remove()
+ @form.addClass('gfm-form')
+ disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
+
+ # remove notify commit author checkbox for non-commit notes
+ GitLab.GfmAutoComplete.setup()
+ new DropzoneInput(@form)
+
+ autosize(@textarea)
+
+ # form and textarea event listeners
+ @addEventListeners()
+
+ # hide discard button
+ @form.find('.js-note-discard').hide()
+
+ @form.show()
+
+ clearEventListeners: ->
+ @textarea.off 'focus'
+ @textarea.off 'blur'
+
+ addEventListeners: ->
+ @textarea.on 'focus', ->
+ $(@).closest('.md-area').addClass 'is-focused'
+
+ @textarea.on 'blur', ->
+ $(@).closest('.md-area').removeClass 'is-focused'
diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee
index be8d225e73b..b0edc895649 100644
--- a/app/assets/javascripts/importer_status.js.coffee
+++ b/app/assets/javascripts/importer_status.js.coffee
@@ -4,18 +4,33 @@ class @ImporterStatus
this.setAutoUpdate()
initStatusPage: ->
- $(".js-add-to-import").click (event) =>
- new_namespace = null
- tr = $(event.currentTarget).closest("tr")
- id = tr.attr("id").replace("repo_", "")
- if tr.find(".import-target input").length > 0
- new_namespace = tr.find(".import-target input").prop("value")
- tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name"))
- $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
-
- $(".js-import-all").click (event) =>
- $(".js-add-to-import").each ->
- $(this).click()
+ $('.js-add-to-import')
+ .off 'click'
+ .on 'click', (e) =>
+ new_namespace = null
+ $btn = $(e.currentTarget)
+ $tr = $btn.closest('tr')
+ id = $tr.attr('id').replace('repo_', '')
+ if $tr.find('.import-target input').length > 0
+ new_namespace = $tr.find('.import-target input').prop('value')
+ $tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
+
+ $btn
+ .disable()
+ .addClass 'is-loading'
+
+ $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
+
+ $('.js-import-all')
+ .off 'click'
+ .on 'click', (e) ->
+ $btn = $(@)
+ $btn
+ .disable()
+ .addClass 'is-loading'
+
+ $('.js-add-to-import').each ->
+ $(this).trigger('click')
setAutoUpdate: ->
setInterval (=>
diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee
new file mode 100644
index 00000000000..afffed63ac5
--- /dev/null
+++ b/app/assets/javascripts/issuable.js.coffee
@@ -0,0 +1,84 @@
+@Issuable =
+ init: ->
+ Issuable.initTemplates()
+ Issuable.initSearch()
+
+ initTemplates: ->
+ Issuable.labelRow = _.template(
+ '<% _.each(labels, function(label){ %>
+ <span class="label-row">
+ <a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a>
+ </span>
+ <% }); %>'
+ )
+
+ initSearch: ->
+ @timer = null
+ $('#issue_search')
+ .off 'keyup'
+ .on 'keyup', ->
+ clearTimeout(@timer)
+ @timer = setTimeout( ->
+ Issuable.filterResults $('#issue_search_form')
+ , 500)
+
+ toggleLabelFilters: ->
+ $filteredLabels = $('.filtered-labels')
+ if $filteredLabels.find('.label-row').length > 0
+ $filteredLabels.removeClass('hidden')
+ else
+ $filteredLabels.addClass('hidden')
+
+ filterResults: (form) =>
+ formData = form.serialize()
+
+ $('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
+ formAction = form.attr('action')
+ issuesUrl = formAction
+ issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
+ issuesUrl += formData
+ $.ajax
+ type: 'GET'
+ url: formAction
+ data: formData
+ complete: ->
+ $('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
+ success: (data) ->
+ $('.issues-holder, .merge-requests-holder').html(data.html)
+ # Change url so if user reload a page - search results are saved
+ history.replaceState {page: issuesUrl}, document.title, issuesUrl
+ Issuable.reload()
+ Issuable.updateStateFilters()
+ $filteredLabels = $('.filtered-labels')
+
+ if typeof Issuable.labelRow is 'function'
+ $filteredLabels.html(Issuable.labelRow(data))
+
+ Issuable.toggleLabelFilters()
+
+ dataType: "json"
+
+ reload: ->
+ if Issues.created
+ Issues.initChecks()
+
+ $('#filter_issue_search').val($('#issue_search').val())
+
+ updateStateFilters: ->
+ stateFilters = $('.issues-state-filters')
+ newParams = {}
+ paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
+
+ for paramKey in paramKeys
+ newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
+
+ if stateFilters.length
+ stateFilters.find('a').each ->
+ initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
+ labelNameValues = gl.utils.getParameterValues('label_name[]')
+ if labelNameValues
+ labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
+ newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
+ else
+ newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
+ $(this).attr 'href', newUrl
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 2f19513a831..3c491ebfc4c 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -9,21 +9,29 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
- $(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
- $block = $(@).parents('.block')
- $selectbox = $block.find('.selectbox')
- if $selectbox.is(':visible')
- $selectbox.hide()
- $block.find('.value').show()
- else
- $selectbox.show()
- $block.find('.value').hide()
-
- if $selectbox.is(':visible')
- setTimeout (->
- $block.find('.dropdown-menu-toggle').trigger 'click'
- ), 0
-
+ $(document)
+ .off 'click', '.issuable-sidebar .dropdown-content a'
+ .on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
+ e.preventDefault()
+
+ $(document)
+ .off 'click', '.edit-link'
+ .on 'click', '.edit-link', (e) ->
+ e.preventDefault()
+
+ $block = $(@).parents('.block')
+ $selectbox = $block.find('.selectbox')
+ if $selectbox.is(':visible')
+ $selectbox.hide()
+ $block.find('.value').show()
+ else
+ $selectbox.show()
+ $block.find('.value').hide()
+
+ if $selectbox.is(':visible')
+ setTimeout ->
+ $block.find('.dropdown-menu-toggle').trigger 'click'
+ , 0
$(".right-sidebar").niceScroll()
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 946d83b7bdd..c7d74a12f99 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -10,6 +10,9 @@ class @Issue
@initTaskList()
@initIssueBtnEventListeners()
+ @initMergeRequests()
+ @initRelatedBranches()
+
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
@@ -69,3 +72,23 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
+
+ initMergeRequests: ->
+ $container = $('#merge-requests')
+
+ $.getJSON($container.data('url'))
+ .error ->
+ new Flash('Failed to load referenced merge requests', 'alert')
+ .success (data) ->
+ if 'html' of data
+ $container.html(data.html)
+
+ initRelatedBranches: ->
+ $container = $('#related-branches')
+
+ $.getJSON($container.data('url'))
+ .error ->
+ new Flash('Failed to load related branches', 'alert')
+ .success (data) ->
+ if 'html' of data
+ $container.html(data.html)
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 0d9f2094c2a..3330e6c68ad 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -1,6 +1,6 @@
@Issues =
init: ->
- Issues.initSearch()
+ Issues.created = true
Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
@@ -15,10 +15,6 @@
else
$(this).html totalIssues - 1
- reload: ->
- Issues.initChecks()
- $('#filter_issue_search').val($('#issue_search').val())
-
initChecks: ->
$(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked)
@@ -26,51 +22,6 @@
$(".selected_issue").bind "change", Issues.checkChanged
- # Update state filters if present in page
- updateStateFilters: ->
- stateFilters = $('.issues-state-filters')
- newParams = {}
- paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
-
- for paramKey in paramKeys
- newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
-
- if stateFilters.length
- stateFilters.find('a').each ->
- initialUrl = $(this).attr 'href'
- $(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
-
- # Make sure we trigger ajax request only after user stop typing
- initSearch: ->
- @timer = null
- $("#issue_search").keyup ->
- clearTimeout(@timer)
- @timer = setTimeout( ->
- Issues.filterResults $("#issue_search_form")
- , 500)
-
- filterResults: (form) =>
- $('.issues-holder, .merge-requests-holder').css("opacity", '0.5')
- formAction = form.attr('action')
- formData = form.serialize()
- issuesUrl = formAction
- issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}")
- issuesUrl += formData
-
- $.ajax
- type: "GET"
- url: formAction
- data: formData
- complete: ->
- $('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
- success: (data) ->
- $('.issues-holder, .merge-requests-holder').html(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: issuesUrl}, document.title, issuesUrl
- Issues.reload()
- Issues.updateStateFilters()
- dataType: "json"
-
checkChanged: ->
checked_issues = $(".selected_issue:checked")
if checked_issues.length > 0
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 90385621879..85517b18c5a 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -6,7 +6,7 @@ class @LabelsSelect
labelUrl = $dropdown.data('labels')
issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected')
- if selectedLabel?
+ if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
@@ -16,33 +16,32 @@ class @LabelsSelect
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
+ $form = $dropdown.closest('form')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
- $loading = $block.find('.block-loading').fadeOut()
-
- if newLabelField.length
- $newLabelCreateButton = $('.js-new-label-btn')
- $colorPreview = $('.js-dropdown-label-color-preview')
- $newLabelError = $dropdown.parent().find('.js-label-error')
- $newLabelError.hide()
+ $newLabelError = $('.js-label-error')
+ $colorPreview = $('.js-dropdown-label-color-preview')
+ $newLabelCreateButton = $('.js-new-label-btn')
- # Suggested colors in the dropdown to chose from pre-chosen colors
- $('.suggest-colors-dropdown a').on 'click', (e) ->
+ $newLabelError.hide()
+ $loading = $block.find('.block-loading').fadeOut()
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
- <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
- <span class="label color-label" style="background-color: <%= label.color %>;">
- <%= label.title %>
+ <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= _.escape(label.title) %>">
+ <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;">
+ <%= _.escape(label.title) %>
</span>
</a>
<% }); %>'
- );
+ )
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
- if newLabelField.length and $dropdown.hasClass 'js-extra-options'
+ if newLabelField.length
+
+ # Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
@@ -81,26 +80,25 @@ class @LabelsSelect
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
+ saveLabel = ->
+ # Create new label with API
+ Api.newLabel projectId, {
+ name: newLabelField.val()
+ color: newColorField.val()
+ }, (label) ->
+ $newLabelCreateButton.enable()
+
+ if label.message?
+ $newLabelError
+ .text label.message
+ .show()
+ else
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+
newLabelField.on 'keyup change', enableLabelCreateButton
newColorField.on 'keyup change', enableLabelCreateButton
@@ -111,24 +109,7 @@ class @LabelsSelect
.on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
-
- if newLabelField.val() isnt '' and newColorField.val() isnt ''
- $newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+ saveLabel()
saveLabelData = ->
selected = $dropdown
@@ -165,11 +146,13 @@ class @LabelsSelect
.html(template)
$sidebarCollapsedValue.text(labelCount)
+ $('.has-tooltip', $value).tooltip(container: 'body')
+
$value
.find('a')
.each((i) ->
setTimeout(=>
- glAnimate($(@), 'pulse')
+ gl.animate.animate($(@), 'pulse')
,200 * i
)
)
@@ -198,18 +181,23 @@ class @LabelsSelect
callback data
renderRow: (label) ->
- selectedClass = ''
- if $selectbox.find("input[type='hidden']\
- [name='#{$dropdown.data('field-name')}']\
- [value='#{label.id}']").length
- selectedClass = 'is-active'
+ removesAll = label.id is 0 or not label.id?
+
+ selectedClass = []
+ if $form.find("input[type='hidden']\
+ [name='#{$dropdown.data('fieldName')}']\
+ [value='#{this.id(label)}']").length
+ selectedClass.push 'is-active'
+
+ if $dropdown.hasClass('js-multiselect') and removesAll
+ selectedClass.push 'dropdown-clear-active'
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
"<li>
- <a href='#' class='#{selectedClass}'>
+ <a href='#' class='#{selectedClass.join(' ')}'>
#{color}
- #{label.title}
+ #{_.escape(label.title)}
</a>
</li>"
filterable: true
@@ -217,37 +205,56 @@ class @LabelsSelect
fields: ['title']
selectable: true
- toggleLabel: (selected) ->
+ toggleLabel: (selected, el) ->
+ selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
+
if selected and selected.title?
- selected.title
+ if selected_labels.length > 1
+ "#{selected.title} +#{selected_labels.length - 1} more"
+ else
+ selected.title
+ else if not selected and selected_labels.length isnt 0
+ if selected_labels.length > 1
+ "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
+ else if selected_labels.length is 1
+ $(selected_labels).text()
else
defaultLabel
fieldName: $dropdown.data('field-name')
id: (label) ->
- if label.isAny?
- ''
- else if $dropdown.hasClass "js-filter-submit"
- label.title
+ if $dropdown.hasClass("js-filter-submit") and not label.isAny?
+ _.escape label.title
else
label.id
hidden: ->
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is 'projects:merge_requests:index'
+
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect'
- saveLabelData()
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ selectedLabels = $dropdown
+ .closest('form')
+ .find("input:hidden[name='#{$dropdown.data('fieldName')}']")
+ Issuable.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass('js-filter-submit')
+ $dropdown.closest('form').submit()
+ else
+ saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is page is 'projects:merge_requests:index'
-
+ isMRIndex = page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- selectedLabel = label.title
-
- Issues.filterResults $dropdown.closest('form')
+ if not $dropdown.hasClass 'js-multiselect'
+ selectedLabel = label.title
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee
index 8f892b5a2b9..ec3b44d6126 100644
--- a/app/assets/javascripts/lib/animate.js.coffee
+++ b/app/assets/javascripts/lib/animate.js.coffee
@@ -1,13 +1,39 @@
((w) ->
+ if not w.gl? then w.gl = {}
+ if not gl.animate? then gl.animate = {}
- w.glAnimate = ($el, animation, done) ->
+ gl.animate.animate = ($el, animation, options, done) ->
+ if options?.cssStart?
+ $el.css(options.cssStart)
$el
- .removeClass()
+ .removeClass(animation + ' animated')
.addClass(animation + ' animated')
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
- $(this).removeClass()
+ $(this).removeClass(animation + ' animated')
+ if done?
+ done()
+ if options?.cssEnd?
+ $el.css(options.cssEnd)
return
return
- return
+ gl.animate.animateEach = ($els, animation, time, options, done) ->
+ dfd = $.Deferred()
+ if not $els.length
+ dfd.resolve()
+ $els.each((i) ->
+ setTimeout(=>
+ $this = $(@)
+ gl.animate.animate($this, animation, options, =>
+ if i is $els.length - 1
+ dfd.resolve()
+ if done?
+ done()
+ )
+ ,time * i
+ )
+ return
+ )
+ return dfd.promise()
+ return
) window \ No newline at end of file
diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/url_utility.js.coffee
index abd556e0b4e..6a00932c028 100644
--- a/app/assets/javascripts/lib/url_utility.js.coffee
+++ b/app/assets/javascripts/lib/url_utility.js.coffee
@@ -3,16 +3,20 @@
w.gl ?= {}
w.gl.utils ?= {}
- w.gl.utils.getUrlParameter = (sParam) ->
+ # Returns an array containing the value(s) of the
+ # of the key passed as an argument
+ w.gl.utils.getParameterValues = (sParam) ->
sPageURL = decodeURIComponent(window.location.search.substring(1))
sURLVariables = sPageURL.split('&')
sParameterName = undefined
+ values = []
i = 0
while i < sURLVariables.length
sParameterName = sURLVariables[i].split('=')
if sParameterName[0] is sParam
- return if sParameterName[1] is undefined then true else sParameterName[1]
+ values.push(sParameterName[1])
i++
+ values
# #
# @param {Object} params - url keys and value to merge
@@ -28,4 +32,12 @@
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
newUrl
+ # removes parameter query string from url. returns the modified url
+ w.gl.utils.removeParamQueryString = (url, param) ->
+ url = decodeURIComponent(url)
+ urlVariables = url.split('&')
+ (
+ variables for variables in urlVariables when variables.indexOf(param) is -1
+ ).join('&')
+
) window
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index ef0b534a709..372732d0aac 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -85,8 +85,10 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
- $el = $("div#{container} #{window.location.hash}")
- $('body').scrollTo($el.offset().top) if $el.length
+ navBarHeight = $('.navbar-gitlab').outerHeight()
+
+ $el = $("#{container} #{window.location.hash}:not(.match)")
+ $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
@@ -152,12 +154,39 @@ class @MergeRequestTabs
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
- document.querySelector("div#diffs").innerHTML = data.html
+ $('#diffs').html data.html
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
- $('div#diffs .js-syntax-highlight').syntaxHighlight()
+ $('#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
+ @highlighSelectedLine()
+
+ $(document)
+ .off 'click', '.diff-line-num a'
+ .on 'click', '.diff-line-num a', (e) =>
+ e.preventDefault()
+ window.location.hash = $(e.currentTarget).attr 'href'
+ @highlighSelectedLine()
+ @scrollToElement("#diffs")
+
+ highlighSelectedLine: ->
+ $('.hll').removeClass 'hll'
+ locationHash = window.location.hash
+
+ if locationHash isnt ''
+ hashClassString = ".#{locationHash.replace('#', '')}"
+ $diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
+
+ if not $diffLine.is 'tr'
+ $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
+ else
+ $diffLine = $diffLine.find('td')
+
+ if $diffLine.length
+ $diffLine.addClass 'hll'
+ diffLineTop = $diffLine.offset().top
+ navBarHeight = $('.navbar-gitlab').outerHeight()
loadBuilds: (source) ->
return if @buildsLoaded
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
deleted file mode 100644
index b3c73ffce5d..00000000000
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# * Filter merge requests
-#
-@MergeRequests =
- init: ->
- MergeRequests.initSearch()
-
- # Make sure we trigger ajax request only after user stop typing
- initSearch: ->
- @timer = null
- $("#issue_search").keyup ->
- clearTimeout(@timer)
- @timer = setTimeout(MergeRequests.filterResults, 500)
-
- filterResults: =>
- form = $("#issue_search_form")
- search = $("#issue_search").val()
- $('.merge-requests-holder').css("opacity", '0.5')
- issues_url = form.attr('action') + '?' + form.serialize()
-
- $.ajax
- type: "GET"
- url: form.attr('action')
- data: form.serialize()
- complete: ->
- $('.merge-requests-holder').css("opacity", '1.0')
- success: (data) ->
- $('.merge-requests-holder').html(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: issues_url}, document.title, issues_url
- MergeRequests.reload()
- dataType: "json"
-
- reload: ->
- $('#filter_issue_search').val($('#issue_search').val())
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index 6bd4e885a03..345a0e447af 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -24,7 +24,7 @@ class @MilestoneSelect
if issueUpdateURL
milestoneLinkTemplate = _.template(
- '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
+ '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
@@ -71,7 +71,7 @@ class @MilestoneSelect
defaultLabel
fieldName: $dropdown.data('field-name')
text: (milestone) ->
- milestone.title
+ _.escape(milestone.title)
id: (milestone) ->
if !useId
milestone.name
@@ -97,7 +97,7 @@ class @MilestoneSelect
selectedMilestone = selected.name
else
selectedMilestone = ''
- Issues.filterResults $dropdown.closest('form')
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index a67890200dd..82e210fed7d 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -75,6 +75,9 @@ class @Notes
# when issue status changes, we need to refresh data
$(document).on "issuable:change", @refresh
+ # when a key is clicked on the notes
+ $(document).on "keydown", ".js-note-text", @keydownNoteText
+
cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form"
@@ -92,10 +95,19 @@ class @Notes
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
$(document).off "click", ".js-note-discard"
+ $(document).off "keydown", ".js-note-text"
$('.note .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.note .js-task-list-container'
+ keydownNoteText: (e) ->
+ $this = $(this)
+ if $this.val() is '' and e.which is 38 #aka the up key
+ myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
+ if myLastNote.length
+ myLastNoteEditBtn = myLastNote.find('.js-note-edit')
+ myLastNoteEditBtn.trigger('click', [true, myLastNote])
+
initRefresh: ->
clearInterval(Notes.interval)
Notes.interval = setInterval =>
@@ -283,32 +295,10 @@ class @Notes
show the form
###
setupNoteForm: (form) ->
- disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
- form.removeClass "js-new-note-form"
- form.find('.div-dropzone').remove()
-
- # hide discard button
- form.find('.js-note-discard').hide()
-
- # setup preview buttons
- previewButton = form.find(".js-md-preview-button")
+ new GLForm form
textarea = form.find(".js-note-text")
- textarea.on "input", ->
- if $(this).val().trim() isnt ""
- previewButton.removeClass("turn-off").addClass "turn-on"
- else
- previewButton.removeClass("turn-on").addClass "turn-off"
-
- textarea.on 'focus', ->
- $(this).closest('.md-area').addClass 'is-focused'
-
- textarea.on 'blur', ->
- $(this).closest('.md-area').removeClass 'is-focused'
-
- autosize(textarea)
-
new Autosave textarea, [
"Note"
form.find("#note_commit_id").val()
@@ -317,11 +307,6 @@ class @Notes
form.find("#note_noteable_id").val()
]
- # remove notify commit author checkbox for non-commit notes
- GitLab.GfmAutoComplete.setup()
- new DropzoneInput(form)
- form.show()
-
###
Called in response to the new note form being submitted
@@ -370,39 +355,38 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels
###
- showEditForm: (e) ->
+ showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault()
note = $(this).closest(".note")
note.addClass "is-editting"
form = note.find(".note-edit-form")
- isNewForm = form.is(':not(.gfm-form)')
- if isNewForm
- form.addClass('gfm-form')
+
form.addClass('current-note-edit-form')
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
- # Setup markdown form
- if isNewForm
- GitLab.GfmAutoComplete.setup()
- new DropzoneInput(form)
-
- textarea = form.find("textarea")
- textarea.focus()
-
- if isNewForm
- autosize(textarea)
-
- # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
- # The textarea has the correct value, Chrome just won't show it unless we
- # modify it, so let's clear it and re-set it!
- value = textarea.val()
- textarea.val ""
- textarea.val value
-
- if isNewForm
- disableButtonIfEmptyField textarea, form.find(".js-comment-button")
+ done = ($noteText) ->
+ # Neat little trick to put the cursor at the end
+ noteTextVal = $noteText.val()
+ $noteText.val('').val(noteTextVal);
+
+ new GLForm form
+ if scrollTo? and myLastNote?
+ # scroll to the bottom
+ # so the open of the last element doesn't make a jump
+ $('html, body').scrollTop($(document).height());
+ $('html, body').animate({
+ scrollTop: myLastNote.offset().top - 150
+ }, 500, ->
+ $noteText = form.find(".js-note-text")
+ $noteText.focus()
+ done($noteText)
+ );
+ else
+ $noteText = form.find('.js-note-text')
+ $noteText.focus()
+ done($noteText)
###
Called in response to clicking the edit note link
@@ -559,6 +543,9 @@ class @Notes
removeDiscussionNoteForm: (form)->
row = form.closest("tr")
+ glForm = form.data 'gl-form'
+ glForm.destroy()
+
form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies)
@@ -570,7 +557,6 @@ class @Notes
# only remove the form
form.remove()
-
cancelDiscussionForm: (e) =>
e.preventDefault()
form = $(e.target).closest(".js-discussion-note-form")
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index f4a2562885d..26a12423521 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -45,9 +45,10 @@ class @Profile
saveForm: ->
self = @
-
formData = new FormData(@form[0])
- formData.append('user[avatar]', @avatarGlCrop.getBlob(), 'avatar.png')
+
+ avatarBlob = @avatarGlCrop.getBlob()
+ formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
$.ajax
url: @form.attr('action')
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
index 67403554340..2d084b76cfe 100644
--- a/app/assets/javascripts/right_sidebar.js.coffee
+++ b/app/assets/javascripts/right_sidebar.js.coffee
@@ -1,10 +1,12 @@
class @Sidebar
constructor: (currentUser) ->
+ @sidebar = $('aside')
+
@addEventListeners()
addEventListeners: ->
- $('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
- $('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
+ @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
+ $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
@@ -30,26 +32,56 @@ class @Sidebar
else
i.show()
-
sidebarCollapseClicked: (e) ->
+ sidebar = e.data
e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.openDropdown($block);
- $('aside')
- .find('.gutter-toggle')
- .trigger('click')
- $editLink = $block.find('.edit-link')
+ openDropdown: (blockOrName) ->
+ $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName
+
+ $block.find('.edit-link').trigger('click')
- if $editLink.length
- $editLink.trigger('click')
- $block.addClass('collapse-after-update')
- $('.page-with-sidebar').addClass('with-overlay')
+ if not @isOpen()
+ @setCollapseAfterUpdate($block)
+ @toggleSidebar('open')
- sidebarDropdownHidden: (e) ->
+ setCollapseAfterUpdate: ($block) ->
+ $block.addClass('collapse-after-update')
+ $('.page-with-sidebar').addClass('with-overlay')
+
+ onSidebarDropdownHidden: (e) ->
+ sidebar = e.data
+ e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.sidebarDropdownHidden($block)
+
+ sidebarDropdownHidden: ($block) ->
if $block.hasClass('collapse-after-update')
$block.removeClass('collapse-after-update')
$('.page-with-sidebar').removeClass('with-overlay')
- $('aside')
- .find('.gutter-toggle')
- .trigger('click') \ No newline at end of file
+ @toggleSidebar('hide')
+
+ triggerOpenSidebar: ->
+ @sidebar
+ .find('.js-sidebar-toggle')
+ .trigger('click')
+
+ toggleSidebar: (action = 'toggle') ->
+ if action is 'toggle'
+ @triggerOpenSidebar()
+
+ if action is 'open'
+ @triggerOpenSidebar() if not @isOpen()
+
+ if action is 'hide'
+ @triggerOpenSidebar() is @isOpen()
+
+ isOpen: ->
+ @sidebar.is('.right-sidebar-expanded')
+
+ getBlock: (name) ->
+ @sidebar.find(".block.#{name}")
+
+
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index 100e3aac535..f3d66004138 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -2,34 +2,35 @@ class @Shortcuts
constructor: ->
@enabledHelp = []
Mousetrap.reset()
- Mousetrap.bind('?', @selectiveHelp)
+ Mousetrap.bind('?', @onToggleHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
- selectiveHelp: (e) =>
- Shortcuts.showHelp(e, @enabledHelp)
+ onToggleHelp: (e) =>
+ e.preventDefault()
+ @toggleHelp(@enabledHelp)
toggleMarkdownPreview: (e) =>
$(document).triggerHandler('markdown-preview:toggle', [e])
- @showHelp: (e, location) ->
- if $('#modal-shortcuts').length > 0
- $('#modal-shortcuts').modal('show')
- else
- url = '/help/shortcuts'
- url = gon.relative_url_root + url if gon.relative_url_root?
- $.ajax(
- url: url,
- dataType: 'script',
- success: (e) ->
- if location and location.length > 0
- $(l).show() for l in location
- else
- $('.hidden-shortcut').show()
- $('.js-more-help-button').remove()
- )
- e.preventDefault()
+ toggleHelp: (location) ->
+ $modal = $('#modal-shortcuts')
+
+ if $modal.length
+ $modal.modal('toggle')
+ return
+
+ $.ajax(
+ url: gon.shortcuts_path,
+ dataType: 'script',
+ success: (e) ->
+ if location and location.length > 0
+ $(l).show() for l in location
+ else
+ $('.hidden-shortcut').show()
+ $('.js-more-help-button').remove()
+ )
@focusSearch: (e) ->
$('#search').focus()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index bbf02f1db24..ad9b3c1c6bf 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -4,18 +4,8 @@
class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
- Mousetrap.bind('a', ->
- $('.block.assignee .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('m', ->
- $('.block.milestone .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('r', =>
- @replyWithSelectedText()
- return false
- )
+ Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
+ Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
Mousetrap.bind('j', =>
@prevIssue()
return false
@@ -28,7 +18,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@editIssue()
return false
)
-
+ Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
@@ -71,3 +61,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
editIssue: ->
$editBtn = $('.issuable-edit')
Turbolinks.visit($editBtn.attr('href'))
+
+ openSidebarDropdown: (name) ->
+ sidebar.openDropdown(name)
+ return false
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 8decaedd87b..f39504e0645 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -14,6 +14,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
+ Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndFollowLink: (selector) ->
diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee
index e4b7a3172ec..1a430f3aa47 100644
--- a/app/assets/javascripts/subscription.js.coffee
+++ b/app/assets/javascripts/subscription.js.coffee
@@ -2,7 +2,7 @@ class @Subscription
constructor: (container) ->
$container = $(container)
@url = $container.attr('data-url')
- @subscribe_button = $container.find('.subscribe-button')
+ @subscribe_button = $container.find('.js-subscribe-button')
@subscription_status = $container.find('.subscription-status')
@subscribe_button.unbind('click').click(@toggleSubscription)
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index 00d2b641723..10bef96f43d 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -1,5 +1,11 @@
class @Todos
- constructor: (@name) ->
+ constructor: (opts = {}) ->
+ {
+ @el = $('.js-todos-options')
+ } = opts
+
+ @perPage = @el.data('perPage')
+
@clearListeners()
@initBtnListeners()
@@ -26,6 +32,7 @@ class @Todos
dataType: 'json'
data: '_method': 'delete'
success: (data) =>
+ @redirectIfNeeded data.count
@clearDone $this.closest('li')
@updateBadges data
@@ -57,11 +64,46 @@ class @Todos
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
+ getTotalPages: ->
+ @el.data('totalPages')
+
+ getCurrentPage: ->
+ @el.data('currentPage')
+
+ getTodosPerPage: ->
+ @el.data('perPage')
+
+ redirectIfNeeded: (total) ->
+ currPages = @getTotalPages()
+ currPage = @getCurrentPage()
+
+ # Refresh if no remaining Todos
+ if not total
+ location.reload()
+ return
+
+ # Do nothing if no pagination
+ return if not currPages
+
+ newPages = Math.ceil(total / @getTodosPerPage())
+ url = location.href # Includes query strings
+
+ # If new total of pages is different than we have now
+ if newPages isnt currPages
+ # Redirect to previous page if there's one available
+ if currPages > 1 and currPage is currPages
+ pageParams =
+ page: currPages - 1
+ url = gl.utils.mergeUrlParams(pageParams, url)
+
+ Turbolinks.visit(url)
+
goToTodoUrl: (e)->
todoLink = $(this).data('url')
return unless todoLink
- if e.metaKey
+ # Allow Meta-Click or Mouse3-click to open in a new tab
+ if e.metaKey or e.which is 2
e.preventDefault()
window.open(todoLink,'_blank')
else
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index eee9b6e690e..b80b1b861cc 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user')
+ @authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate')
@@ -157,7 +158,7 @@ class @UsersSelect
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedId = user.id
- Issues.filterResults $dropdown.closest('form')
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
@@ -207,6 +208,7 @@ class @UsersSelect
@projectId = $(select).data('project-id')
@groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user')
+ @authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user')
@@ -312,6 +314,7 @@ class @UsersSelect
project_id: @projectId
group_id: @groupId
current_user: @showCurrentUser
+ author_id: @authorId
dataType: "json"
).done (users) ->
callback(users)
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e8c0172680d..18a74fe21a0 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -144,6 +144,10 @@
}
}
+.btn-lg {
+ padding: 12px 20px;
+}
+
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 0b3af592d4a..11f39d583bd 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -54,6 +54,10 @@
fill: #254e77 !important;
}
+ .future {
+ visibility: hidden;
+ }
+
.domain-background {
fill: none;
shape-rendering: crispedges;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ba6c7930cdc..239eaf15cc1 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -248,7 +248,7 @@
.dropdown-title {
position: relative;
- padding: 0 25px 15px;
+ padding: 0 25px 10px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
@@ -278,7 +278,7 @@
right: 5px;
width: 20px;
height: 20px;
- top: -1px;
+ top: -3px;
}
.dropdown-menu-back {
@@ -320,7 +320,7 @@
}
}
-.dropdown-input-field {
+.dropdown-input-field, .default-dropdown-input {
width: 100%;
padding: 0 7px;
color: $dropdown-input-color;
@@ -358,6 +358,13 @@
border-top: 1px solid $dropdown-divider-color;
}
+.dropdown-due-date-footer {
+ padding-top: 0;
+ margin-left: 10px;
+ margin-right: 10px;
+ border-top: 0;
+}
+
.dropdown-footer-list {
font-size: 14px;
@@ -395,3 +402,122 @@
height: 15px;
border-radius: $border-radius-base;
}
+
+.dropdown-menu-due-date {
+ .dropdown-content {
+ max-height: 230px;
+ }
+
+ .ui-widget {
+ table {
+ margin: 0;
+ }
+
+ &.ui-datepicker-inline {
+ padding: 0 10px;
+ border: 0;
+ width: 100%;
+ }
+
+ .ui-datepicker-header {
+ padding: 0 8px 10px;
+ border: 0;
+
+ .ui-icon {
+ background: none;
+ font-size: 20px;
+ text-indent: 0;
+
+ &:before {
+ display: block;
+ position: relative;
+ top: -2px;
+ color: $dropdown-title-btn-color;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+ }
+ }
+
+ .ui-state-active,
+ .ui-state-hover {
+ color: $md-link-color;
+ background-color: $calendar-hover-bg;
+ }
+
+ .ui-datepicker-prev,
+ .ui-datepicker-next {
+ top: 0;
+ height: 15px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: transparent;
+ border: 0;
+
+ .ui-icon:before {
+ color: $md-link-color;
+ }
+ }
+ }
+
+ .ui-datepicker-prev {
+ left: 0;
+
+ .ui-icon:before {
+ content: '\f104';
+ text-align: left;
+ }
+ }
+
+ .ui-datepicker-next {
+ right: 0;
+
+ .ui-icon:before {
+ content: '\f105';
+ text-align: right;
+ }
+ }
+
+ td {
+ padding: 0;
+ border: 1px solid $calendar-border-color;
+
+ &:first-child {
+ border-left: 0;
+ }
+
+ &:last-child {
+ border-right: 0;
+ }
+
+ a {
+ line-height: 17px;
+ border: 0;
+ border-radius: 0;
+ }
+ }
+
+ .ui-datepicker-title {
+ color: $gl-gray;
+ font-size: 15px;
+ line-height: 1;
+ font-weight: normal;
+ }
+ }
+
+ th {
+ padding: 2px 0;
+ color: $calendar-header-color;
+ font-weight: normal;
+ text-transform: lowercase;
+ border-top: 1px solid $calendar-border-color;
+ }
+
+ .ui-datepicker-unselectable {
+ background-color: $calendar-unselectable-bg;
+ }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 789df42fb66..61d9954c6c8 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -38,12 +38,14 @@
.filename {
&.old {
+ display: inline-block;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
+ display: inline-block;
span.idiff {
background-color: #a6f3a6;
}
@@ -82,10 +84,6 @@
}
}
- &.blob_file {
-
- }
-
&.blob-no-preview {
background: #eee;
text-shadow: 0 1px 2px #fff;
@@ -129,6 +127,11 @@
td.line-numbers {
float: none;
border-left: 1px solid #ddd;
+
+ i {
+ float: none;
+ margin-right: 0;
+ }
}
td.lines {
padding: 0;
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 5ae0520fd7b..f4d35c4b4b1 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -1,24 +1,6 @@
/**
* Styles that apply to all GFM related forms.
*/
-.issue-form, .merge-request-form, .wiki-form {
- .description {
- height: 16em;
- border-top-left-radius: 0;
- }
-}
-
-.wiki-form {
- .description {
- height: 26em;
- }
-}
-
-.milestone-form {
- .description {
- height: 14em;
- }
-}
.gfm-commit, .gfm-commit_range {
font-family: $monospace_font;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 3f015427d07..c303380764b 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -71,7 +71,7 @@ header {
.header-content {
position: relative;
height: $header-height;
- padding-right: 20px;
+ padding-right: 40px;
@media (min-width: $screen-sm-min) {
padding-right: 0;
@@ -122,6 +122,10 @@ header {
}
}
+ .project-item-select-holder {
+ display: inline;
+ }
+
.impersonation i {
color: $red-normal;
}
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 7f7b7c806e7..8bfc0d583c5 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -5,7 +5,7 @@
*/
.status-box {
-
+
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding: 5px 11px;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index c8f86d60e3b..fd885b38680 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -1,22 +1,15 @@
.div-dropzone-wrapper {
.div-dropzone {
position: relative;
- margin-bottom: -5px;
-
- .div-dropzone-focus {
- border-color: #66afe9 !important;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important;
- outline: 0 !important;
- }
.div-dropzone-hover {
position: absolute;
top: 50%;
left: 50%;
- margin-top: -0.5em;
- margin-left: -0.6em;
+ margin-top: -11.5px;
+ margin-left: -15px;
opacity: 0;
- font-size: 50px;
+ font-size: 30px;
transition: opacity 200ms ease-in-out;
pointer-events: none;
}
@@ -97,3 +90,12 @@
box-shadow: none;
width: 100%;
}
+
+.md {
+ &.md-preview-holder {
+ code {
+ white-space: pre-wrap;
+ word-break: keep-all;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 66180f38a4f..7eb451c124e 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -70,13 +70,6 @@
display: none;
}
- .issue-details {
- .creator,
- .page-title .btn-close {
- display: none;
- }
- }
-
%ul.notes .note-role, .note-actions {
display: none;
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b2fab387e17..eae5f062dda 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -121,9 +121,6 @@
}
}
-.select2-container-multi .select2-choices .select2-search-choice {
-}
-
.select2-drop-active {
margin-top: 6px;
font-size: 14px;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index b91f2f6f898..29501069d27 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -11,7 +11,7 @@
border-bottom: 1px solid $border-white-light;
&:target {
- background: $row-hover;
+ background: $line-target-blue;
}
.avatar {
@@ -39,8 +39,7 @@
.diff-file {
border: 1px solid $border-color;
border-bottom: none;
- margin-left: 0;
- margin-right: 0;
+ margin: 0;
}
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index dd42db1840f..96bab7880c2 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -43,7 +43,6 @@
@import "bootstrap/modals";
@import "bootstrap/tooltip";
@import "bootstrap/popovers";
-@import "bootstrap/carousel";
// Utility classes
.clearfix {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 7b2aada5a0d..0a5b4b8834c 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -250,14 +250,6 @@ a > code {
* Textareas intended for GFM
*
*/
-.js-gfm-input {
- font-family: $monospace_font;
- color: $gl-text-color;
-}
-
-.md-preview {
-}
-
.strikethrough {
text-decoration: line-through;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0b6be86ce6a..ef37ade3b7b 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -150,6 +150,7 @@ $light-grey-header: #faf9f9;
*/
$gl-primary: $blue-normal;
$gl-success: $green-normal;
+$gl-success-focus: rgba($gl-success, .4);
$gl-info: $blue-normal;
$gl-warning: $orange-normal;
$gl-danger: $red-normal;
@@ -167,8 +168,12 @@ $line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
+$line-number-select: #fbf2da;
$match-line: #fafafa;
$table-border-gray: #f0f0f0;
+$line-target-blue: #eaf3fc;
+$line-select-yellow: #fcf8e7;
+$line-select-yellow-dark: #f0e2bd;
/*
* Fonts
*/
@@ -240,3 +245,8 @@ $note-form-border-color: #e5e5e5;
$note-toolbar-color: #959494;
$zen-control-hover-color: #111;
+
+$calendar-header-color: #b8b8b8;
+$calendar-hover-bg: #ecf3fe;
+$calendar-border-color: rgba(#000, .1);
+$calendar-unselectable-bg: #faf9f9;
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 47673944896..77a73dc379b 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #557;
+ border-color: darken(#557, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 806401c21ae..80a509a7c1a 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #49483e;
+ border-color: darken(#49483e, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
}
@@ -105,8 +111,6 @@
.vg { color: #f8f8f2 } /* Name.Variable.Global */
.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
-
- .gh { } /* Generic Heading & Diff Header */
.gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
.gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
.gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 6a809d4dfd2..c62bd021aef 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #174652;
+ border-color: darken(#174652, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
}
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index c482a1258f7..524cfaf90c3 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #ddd8c5;
+ border-color: darken(#ddd8c5, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 28331f59754..31a4e3deaac 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -21,6 +21,7 @@
// Diff line
.line_holder {
+
.diff-line-num {
&.old {
background-color: $line-number-old;
@@ -31,11 +32,16 @@
background-color: $line-number-new;
border-color: $line-added-dark;
}
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-number-select;
+ border-color: $line-select-yellow-dark;
+ }
}
.line_content {
&.old {
- background: $line-removed;
+ background-color: $line-removed;
span.idiff {
background-color: $line-removed-dark;
@@ -52,7 +58,11 @@
&.match {
color: $black-transparent;
- background: $match-line;
+ background-color: $match-line;
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-select-yellow;
}
}
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 6453c91d955..c8c6bbde084 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -75,6 +75,11 @@ li.commit {
}
}
+ .item-title {
+ display: inline-block;
+ max-width: 70%;
+ }
+
.commit-row-description {
font-size: 14px;
border-left: 1px solid #eee;
diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss
new file mode 100644
index 00000000000..125f495d6d4
--- /dev/null
+++ b/app/assets/stylesheets/pages/confirmation.scss
@@ -0,0 +1,18 @@
+.well-confirmation {
+ margin-bottom: 20px;
+ border-bottom: 1px solid #eee;
+
+ > h1 {
+ font-weight: 400;
+ }
+
+ .lead {
+ margin-bottom: 20px;
+ }
+}
+
+.confirmation-content {
+ a {
+ color: $md-link-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 5917f089720..3438dbe4958 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -1,5 +1,5 @@
.detail-page-header {
- padding: 11px 0;
+ padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color;
color: #5c5d5e;
font-size: 16px;
@@ -16,11 +16,6 @@
.issue_created_ago, .author_link {
white-space: nowrap;
}
-
- .issue-meta {
- display: inline-block;
- line-height: 20px;
- }
}
.detail-page-description {
@@ -41,4 +36,11 @@
}
}
}
+
+ .wiki {
+ code {
+ white-space: pre-wrap;
+ word-break: keep-all;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index d0855f66911..e7c8198ba45 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -34,6 +34,7 @@
background: #fff;
color: #333;
border-radius: 0 0 3px 3px;
+ -webkit-overflow-scrolling: auto;
.unfold {
cursor: pointer;
@@ -67,8 +68,26 @@
line-height: $code_line_height;
font-size: $code_font_size;
+ &.noteable_line {
+ position: relative;
+
+ &.old {
+ &:before {
+ content: '-';
+ position: absolute;
+ }
+ }
+
+ &.new {
+ &:before {
+ content: '+';
+ position: absolute;
+ }
+ }
+ }
+
span {
- white-space: pre;
+ white-space: pre-wrap;
}
}
}
@@ -317,7 +336,7 @@
}
.diff-file .line_content {
- white-space: pre;
+ white-space: pre-wrap;
}
.diff-wrap-lines .line_content {
@@ -391,3 +410,23 @@
margin-bottom: 0;
}
}
+
+.file-holder {
+ .diff-line-num:not(.js-unfold-bottom) {
+ a {
+ &:before {
+ content: attr(data-linenumber);
+ }
+ }
+ }
+}
+
+.discussion {
+ .diff-content {
+ .diff-line-num {
+ &:before {
+ content: attr(data-linenumber);
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 0f0592a0ab8..8981f070a20 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -26,6 +26,10 @@
line-height: 42px;
padding-top: 7px;
padding-bottom: 7px;
+
+ .pull-right {
+ height: 20px;
+ }
}
.editor-ref {
@@ -53,4 +57,9 @@
.select2 {
float: right;
}
+
+ .encoding-selector,
+ .license-selector {
+ display: inline-block;
+ }
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 4e5c4ed84b6..f7f9a9bb770 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -18,9 +18,6 @@
}
.graphs {
- .graph-author-commits-count {
- }
-
.graph-author-email {
float: right;
color: #777;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 604f1700cf8..ee95bdf488e 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -59,8 +59,10 @@
position: relative;
overflow-y: auto;
padding: 15px;
+
.form-actions {
margin: -$gl-padding+1;
+ margin-top: 15px;
}
}
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index 6a99cd9cb94..84cc35239f9 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -16,3 +16,24 @@ i.icon-gitorious-big {
width: 18px;
height: 18px;
}
+
+.import-jobs-from-col,
+.import-jobs-to-col {
+ width: 40%;
+}
+
+.import-jobs-status-col {
+ width: 20%;
+}
+
+.btn-import {
+ .loading-icon {
+ display: none;
+ }
+
+ &.is-loading {
+ .loading-icon {
+ display: inline-block;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 8b6f37f21b5..1cf3023ecc9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -128,6 +128,7 @@
top: 58px;
bottom: 0;
right: 0;
+ z-index: 10;
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
@@ -173,12 +174,6 @@
}
}
- .subscribe-button {
- span {
- margin-top: 0;
- }
- }
-
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
display: none;
@@ -247,16 +242,20 @@
}
}
- .btn {
+ .issuable-pager {
background: $gray-normal;
border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
+
+ &.btn-primary {
+ @extend .btn-primary
+ }
}
- a:not(.btn) {
+ a:not(.issuable-pager) {
&:hover {
color: $md-link-color;
text-decoration: none;
@@ -279,10 +278,6 @@
}
}
-.btn-default.gutter-toggle {
- margin-top: 4px;
-}
-
.detail-page-description {
small {
color: $gray-darkest;
@@ -322,3 +317,56 @@
color: #8c8c8c;
}
}
+
+.issuable-form-padding-top {
+ @media (min-width: $screen-sm-min) {
+ padding-top: 7px;
+ }
+}
+
+.issuable-status-box {
+ float: none;
+ display: inline-block;
+ margin-top: 0;
+
+ @media (max-width: $screen-xs-max) {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+}
+
+.issuable-header {
+ position: relative;
+ padding-left: 45px;
+ padding-right: 45px;
+ line-height: 35px;
+
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+
+.issuable-actions {
+ padding-top: 10px;
+
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ padding-top: 0;
+ }
+}
+
+.issuable-gutter-toggle {
+ @media (max-width: $screen-sm-max) {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+}
+
+.issuable-meta {
+ display: inline-block;
+ line-height: 18px;
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 6a1d28590c2..fc9db97132d 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -86,41 +86,9 @@ form.edit-issue {
@media (max-width: $screen-xs-max) {
.issue-btn-group {
width: 100%;
- margin-top: 5px;
-
- .btn-group {
- width: 100%;
-
- ul {
- width: 100%;
- text-align: center;
- }
- }
.btn {
width: 100%;
-
- &:first-child:not(:last-child) {
-
- }
-
- &:not(:first-child):not(:last-child) {
- margin-top: 10px;
- }
-
- &:last-child:not(:first-child) {
- margin-top: 10px;
- }
- }
- }
-
- .issue {
- &:hover .issue-actions {
- display: none !important;
- }
-
- .issue-updated-at {
- display: none;
}
}
}
@@ -133,11 +101,3 @@ form.edit-issue {
color: $gl-text-color;
margin-left: 52px;
}
-
-.editor-details {
- display: block;
-
- @media (min-width: $screen-sm-min) {
- display: inline-block;
- }
-}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 3e0a3140be7..e179bdf0048 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -79,19 +79,30 @@
color: $white-light;
}
+@mixin labels-mobile {
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ width: 100%;
+ margin-left: 0;
+ padding: 10px 0;
+ }
+}
+
+
.manage-labels-list {
- .prepend-left-10 {
+ .prepend-left-10, .prepend-description-left {
display: inline-block;
width: 40%;
vertical-align: middle;
- @media (max-width: $screen-xs-min) {
- display: block;
- width: 100%;
- margin-left: 0;
- padding: 10px 0;
- }
+ @include labels-mobile;
+ }
+
+ .prepend-description-left {
+ width: 57%;
+
+ @include labels-mobile;
}
.pull-info-right {
@@ -106,7 +117,7 @@
padding: 6px;
color: $gl-text-color;
- &.subscribe-button {
+ &.label-subscribe-button {
padding-left: 0;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index f4da17fadaa..7fa13e66b43 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -40,7 +40,9 @@
}
.note-textarea {
+ display: block;
padding: 10px 0;
+ color: $gl-gray;
font-family: $regular_font;
border: 0;
@@ -60,11 +62,11 @@
padding: $gl-padding-top $gl-padding;
border: 1px solid $note-form-border-color;
border-radius: $border-radius-base;
+ transition: border-color ease-in-out 0.15s,
+ box-shadow ease-in-out 0.15s;
&.is-focused {
- border-color: $focus-border-color;
- box-shadow: 0 0 2px rgba(#000, .2),
- 0 0 4px rgba($focus-border-color, .4);
+ @extend .form-control:focus;
.comment-toolbar,
.nav-links {
@@ -72,15 +74,14 @@
}
}
- p {
- code {
- white-space: normal;
- }
+ &.is-dropzone-hover {
+ border-color: $gl-success;
+ box-shadow: 0 0 2px $black-transparent,
+ 0 0 4px $gl-success-focus;
- pre {
- code {
- white-space: pre;
- }
+ .comment-toolbar,
+ .nav-links {
+ border-color: $gl-success;
}
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index e421a31549a..55b1ad97eb0 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -81,16 +81,8 @@ ul.notes {
@include md-typography;
// On diffs code should wrap nicely and not overflow
- p {
- code {
- white-space: normal;
- }
-
- pre {
- code {
- white-space: pre;
- }
- }
+ code {
+ white-space: pre-wrap;
}
// Reset ul style types since we're nested inside a ul already
@@ -117,6 +109,10 @@ ul.notes {
border-color: darken(#f5f5f5, 8%);
margin: 10px 0;
}
+
+ code {
+ word-break: keep-all;
+ }
}
a {
@@ -137,7 +133,7 @@ ul.notes {
margin-right: 10px;
}
.line_content {
- white-space: pre;
+ white-space: pre-wrap;
}
}
@@ -191,6 +187,9 @@ ul.notes {
}
}
+ .author_link {
+ color: $gl-gray;
+ }
}
.note-headline-light,
@@ -198,6 +197,12 @@ ul.notes {
color: $notes-light-color;
}
+.discussion-headline-light {
+ a {
+ color: $gl-link-color;
+ }
+}
+
/**
* Actions for Discussions/Notes
*/
@@ -209,6 +214,17 @@ ul.notes {
color: $notes-action-color;
}
+.discussion-actions {
+ @media (max-width: $screen-sm-max) {
+ float: none;
+ margin-left: 0;
+
+ .note-action-button {
+ margin-left: 0;
+ }
+ }
+}
+
.note-action-button,
.discussion-action-button {
display: inline-block;
@@ -276,8 +292,7 @@ ul.notes {
.diff-file tr.line_holder {
@mixin show-add-diff-note {
- filter: alpha(opacity=100);
- opacity: 1.0;
+ display: inline-block;
}
.add-diff-note {
@@ -287,17 +302,12 @@ ul.notes {
padding: 4px;
font-size: 16px;
color: $gl-link-color;
- margin-left: -60px;
+ margin-left: -56px;
position: absolute;
z-index: 10;
width: 32px;
-
- transition: all 0.2s ease;
-
// "hide" it by default
- opacity: 0.0;
- filter: alpha(opacity=0);
-
+ display: none;
&:hover {
background: $gl-info;
color: #fff;
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index 1be0551ad3b..a30b6492572 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -1,17 +1,37 @@
-/* Generic print styles */
-header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
-.profiler-results {display: none;}
-
-/* Styles targeted specifically at printing files */
-.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
-.file-title {display: none;}
-.file-holder {border: none;}
-
.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
.wiki h1 {font-size: 30px;}
.wiki h2 {font-size: 22px;}
.wiki h3 {font-size: 18px; font-weight: bold; }
-.sidebar-wrapper { display: none; }
-.nav { display: none; }
-.btn { display: none; }
+header,
+nav,
+nav.main-nav,
+nav.navbar-collapse,
+nav.navbar-collapse.collapse,
+.profiler-results,
+.tree-ref-holder,
+.tree-holder .breadcrumb,
+.blob-commit-info,
+.file-title,
+.file-holder,
+.sidebar-wrapper,
+.nav,
+.btn,
+ul.notes-form,
+.merge-request-ci-status .ci-status-link:after,
+.issuable-gutter-toggle,
+.gutter-toggle,
+.issuable-details .content-block-small,
+.edit-link,
+.note-action-button {
+ display: none!important;
+}
+
+.page-gutter {
+ padding-top: 0;
+ padding-left: 0;
+}
+
+.right-sidebar {
+ top: 0;
+}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index f010436bd36..ec22548ddeb 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -19,6 +19,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path
end
+ def clear_repository_check_states
+ RepositoryCheck::ClearWorker.perform_async
+
+ redirect_to(
+ admin_application_settings_path,
+ notice: 'Started asynchronous removal of all repository check states.'
+ )
+ end
+
private
def set_application_setting
@@ -66,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:admin_notification_email,
:user_oauth_applications,
:shared_runners_enabled,
+ :shared_runners_text,
:max_artifacts_size,
:metrics_enabled,
:metrics_host,
@@ -82,6 +92,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_enabled,
:akismet_api_key,
:email_author_in_body,
+ :repository_checks_enabled,
+ :metrics_packet_size,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 0bd19c49d8f..93c4894ea0f 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :enable_ssl_verification)
+ params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events)
end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index c6b3105544a..87986fdf8b1 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,5 +1,5 @@
class Admin::ProjectsController < Admin::ApplicationController
- before_action :project, only: [:show, :transfer]
+ before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer]
def index
@@ -8,6 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
+ @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present?
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
@@ -30,6 +31,15 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to admin_namespace_project_path(@project.namespace, @project)
end
+ def repository_check
+ RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
+
+ redirect_to(
+ admin_namespace_project_path(@project.namespace, @project),
+ notice: 'Repository check was triggered.'
+ )
+ end
+
protected
def project
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 97d53acde94..1c53b0b21a3 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,6 +3,7 @@ require 'fogbugz'
class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings
+ include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
@@ -13,7 +14,7 @@ class ApplicationController < ActionController::Base
before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check
- before_action :sentry_user_context
+ before_action :sentry_context
before_action :default_headers
before_action :add_gon_variables
before_action :configure_permitted_parameters, if: :devise_controller?
@@ -40,13 +41,15 @@ class ApplicationController < ActionController::Base
protected
- def sentry_user_context
- if Rails.env.production? && current_application_settings.sentry_enabled && current_user
- Raven.user_context(
- id: current_user.id,
- email: current_user.email,
- username: current_user.username,
- )
+ def sentry_context
+ if Rails.env.production? && current_application_settings.sentry_enabled
+ if current_user
+ Raven.user_context(
+ id: current_user.id,
+ email: current_user.email,
+ username: current_user.username,
+ )
+ end
Raven.tags_context(program: sentry_program_context)
end
@@ -158,20 +161,6 @@ class ApplicationController < ActionController::Base
end
end
- def add_gon_variables
- gon.api_version = API::API.version
- gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
- gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
- gon.max_file_size = current_application_settings.max_attachment_size
- gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
- gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
-
- if current_user
- gon.current_user_id = current_user.id
- gon.api_token = current_user.private_token
- end
- end
-
def validate_user_service_ticket!
return unless signed_in? && session[:service_tickets]
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 81ba58ce49c..eb0abc80ab4 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user] && current_user
- @users = [*@users, current_user].uniq
+ @users = [*@users, current_user]
end
+
+ if params[:author_id].present?
+ author = User.find_by_id(params[:author_id])
+ @users = [author, *@users] if author
+ end
+
+ @users.uniq!
end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb
index f63b703d101..586f97c5eb4 100644
--- a/app/controllers/concerns/filter_projects.rb
+++ b/app/controllers/concerns/filter_projects.rb
@@ -10,6 +10,8 @@ module FilterProjects
def filter_projects(projects)
projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
projects = projects.non_archived if params[:archived].blank?
+ projects = projects.personal(current_user) if params[:personal].present? && current_user
+
projects
end
end
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index af1faca93f6..7b66ad3f92c 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -1,7 +1,16 @@
class ConfirmationsController < Devise::ConfirmationsController
+ def almost_there
+ flash[:notice] = nil
+ render layout: "devise_empty"
+ end
+
protected
+ def after_resending_confirmation_instructions_path_for(resource)
+ users_almost_there_path
+ end
+
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
after_sign_in_path_for(resource)
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index c1adc999567..ee4fcc4e360 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
+ @projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 55050615473..9b5c43b17e2 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -51,6 +51,7 @@ class HelpController < ApplicationController
end
def ui
+ @user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
end
private
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index d1e4ac10f6c..c6bdd0602c1 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -1,9 +1,11 @@
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings
+ include Gitlab::GonHelper
include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
+ before_action :add_gon_variables
layout 'profile'
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index b88c080352b..a12549d6bcb 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id])
end
+ # Back-compat: We need to support this URL since git-annex webapp points to it
+ def new
+ redirect_to profile_keys_path
+ end
+
def create
@key = current_user.keys.new(key_params)
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 74150ad606b..be872a93fee 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -83,8 +83,7 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
- view = params[:view] || cookies[:diff_view]
- cookies.permanent[:diff_view] = params[:view] = view if view
+ cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
def builds_enabled
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index f159e169f6d..b8b9e78427d 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,7 +1,7 @@
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
- before_action :authorize_update_build!, except: [:index, :show, :status]
+ before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project'
def index
@@ -62,6 +62,14 @@ class Projects::BuildsController < Projects::ApplicationController
notice: "Build has been sucessfully erased!"
end
+ def raw
+ if @build.has_trace?
+ send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline'
+ else
+ render_404
+ end
+ end
+
private
def build
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 72078c3cc68..a202cb38692 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
before_action :define_show_vars, only: [:show, :builds]
- before_action :authorize_edit_tree!, only: [:revert]
+ before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
def show
apply_diff_view_cookie!
@@ -60,27 +60,32 @@ class Projects::CommitController < Projects::ApplicationController
end
def revert
- assign_revert_commit_vars
+ assign_change_commit_vars(@commit.revert_branch_name)
return render_404 if @target_branch.blank?
- create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.",
- success_path: successful_revert_path, failure_path: failed_revert_path)
+ create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
+ success_path: successful_change_path, failure_path: failed_change_path)
end
+
+ def cherry_pick
+ assign_change_commit_vars(@commit.cherry_pick_branch_name)
+
+ return render_404 if @target_branch.blank?
- private
-
- def revert_type_title
- @commit.merged_merge_request ? 'merge request' : 'commit'
+ create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
+ success_path: successful_change_path, failure_path: failed_change_path)
end
- def successful_revert_path
+ private
+
+ def successful_change_path
return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commits_url(@project.namespace, @project, @target_branch)
end
- def failed_revert_path
+ def failed_change_path
return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commit_url(@project.namespace, @project, params[:id])
@@ -116,14 +121,13 @@ class Projects::CommitController < Projects::ApplicationController
@builds = Ci::Build.where(commit: ci_commits)
end
- def assign_revert_commit_vars
+ def assign_change_commit_vars(mr_source_branch)
@commit = project.commit(params[:id])
@target_branch = params[:target_branch]
- @mr_source_branch = @commit.revert_branch_name
+ @mr_source_branch = mr_source_branch
@mr_target_branch = @target_branch
@commit_params = {
commit: @commit,
- revert_type_title: revert_type_title,
create_merge_request: params[:create_merge_request].present? || different_project?
}
end
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 4159e53bfa9..606552fa853 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController
end
def create
- link = project.project_group_links.new
- link.group_id = params[:link_group_id]
- link.group_access = params[:link_group_access]
- link.save
+ group = Group.find(params[:link_group_id])
+ return render_404 unless can?(current_user, :read_group, group)
+
+ project.project_group_links.create(
+ group: group, group_access: params[:link_group_access]
+ )
redirect_to namespace_project_group_links_path(project.namespace, project)
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 6d649e72f84..7d4fc361ce2 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions
before_action :module_enabled
- before_action :issue, only: [:edit, :update, :show]
+ before_action :issue,
+ only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
- # Cross-reference merge requests
- before_action :closed_by_merge_requests, only: [:show]
-
respond_to :html
def index
@@ -35,14 +33,15 @@ class Projects::IssuesController < Projects::ApplicationController
end
@issues = @issues.page(params[:page])
- @label = @project.labels.find_by(title: params[:label_name])
+ @labels = @project.labels.where(title: params[:label_name])
respond_to do |format|
format.html
format.atom { render layout: false }
format.json do
render json: {
- html: view_to_html_string("projects/issues/_issues")
+ html: view_to_html_string("projects/issues/_issues"),
+ labels: @labels.as_json(methods: :text_color)
}
end
end
@@ -62,11 +61,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- @note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.nonawards.with_associations.fresh
+ @note = @project.notes.new(noteable: @issue)
+ @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
- @merge_requests = @issue.referenced_merge_requests(current_user)
- @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_to do |format|
format.html
@@ -104,7 +101,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
respond_to do |format|
- format.js
format.html do
if @issue.valid?
redirect_to issue_path(@issue)
@@ -113,7 +109,32 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
- render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
+ end
+ end
+ end
+
+ def referenced_merge_requests
+ @merge_requests = @issue.referenced_merge_requests(current_user)
+ @closed_by_merge_requests = @issue.closed_by_merge_requests(current_user)
+
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: view_to_html_string('projects/issues/_merge_requests')
+ }
+ end
+ end
+ end
+
+ def related_branches
+ @related_branches = @issue.related_branches(current_user)
+
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: view_to_html_string('projects/issues/_related_branches')
+ }
end
end
end
@@ -123,10 +144,6 @@ class Projects::IssuesController < Projects::ApplicationController
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
- def closed_by_merge_requests
- @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
- end
-
protected
def issue
@@ -174,7 +191,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential,
- :milestone_id, :state_event, :task_num, label_ids: []
+ :milestone_id, :due_date, :state_event, :task_num, label_ids: []
)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4b8fc049047..9c147b3689e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -38,13 +38,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
- @label = @project.labels.find_by(title: params[:label_name])
+ @labels = @project.labels.where(title: params[:label_name])
respond_to do |format|
format.html
format.json do
render json: {
- html: view_to_html_string("projects/merge_requests/_merge_requests")
+ html: view_to_html_string("projects/merge_requests/_merge_requests"),
+ labels: @labels.as_json(methods: :text_color)
}
end
end
@@ -148,13 +149,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.valid?
respond_to do |format|
- format.js
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
@merge_request.target_project, @merge_request])
end
format.json do
- render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
else
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index e457db2f0b7..33b2625c0ac 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -1,6 +1,6 @@
class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
- before_action :authorize_admin_project_member!, except: :leave
+ before_action :authorize_admin_project_member!, except: [:leave, :index]
def index
@project_members = @project.project_members
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 8b2577aebe1..739681f4085 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -6,7 +6,7 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
- :note_events, :build_events,
+ :note_events, :build_events, :wiki_page_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 9f3a4a69721..c02bc28acef 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -44,7 +44,7 @@ class Projects::WikisController < Projects::ApplicationController
return render('empty') unless can?(current_user, :create_wiki, @project)
- if @page.update(content, format, message)
+ if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
@@ -55,9 +55,9 @@ class Projects::WikisController < Projects::ApplicationController
end
def create
- @page = WikiPage.new(@project_wiki)
+ @page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute
- if @page.create(wiki_params)
+ if @page.persisted?
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
@@ -122,15 +122,4 @@ class Projects::WikisController < Projects::ApplicationController
params[:wiki].slice(:title, :content, :format, :message)
end
- def content
- params[:wiki][:content]
- end
-
- def format
- params[:wiki][:format]
- end
-
- def message
- params[:wiki][:message]
- end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index c48175a4c5a..059b88e2253 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -31,11 +31,11 @@ class RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
private
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 8e7956da48f..2ae180c8a12 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,6 +1,7 @@
class UsersController < ApplicationController
skip_before_action :authenticate_user!
- before_action :set_user
+ before_action :user
+ before_action :authorize_read_user!, only: [:show]
def show
respond_to do |format|
@@ -75,22 +76,26 @@ class UsersController < ApplicationController
private
- def set_user
- @user = User.find_by_username!(params[:username])
+ def authorize_read_user!
+ render_404 unless can?(current_user, :read_user, user)
+ end
+
+ def user
+ @user ||= User.find_by_username!(params[:username])
end
def contributed_projects
- ContributedProjectsFinder.new(@user).execute(current_user)
+ ContributedProjectsFinder.new(user).execute(current_user)
end
def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar.
- new(contributed_projects, @user)
+ new(contributed_projects, user)
end
def load_events
# Get user activity feed for projects common for both users
- @events = @user.recent_events.
+ @events = user.recent_events.
merge(projects_for_current_user).
references(:project).
with_associations.
@@ -99,16 +104,16 @@ class UsersController < ApplicationController
def load_projects
@projects =
- PersonalProjectsFinder.new(@user).execute(current_user)
+ PersonalProjectsFinder.new(user).execute(current_user)
.page(params[:page])
end
def load_contributed_projects
- @contributed_projects = contributed_projects.joined(@user)
+ @contributed_projects = contributed_projects.joined(user)
end
def load_groups
- @groups = JoinedGroupsFinder.new(@user).execute(current_user)
+ @groups = JoinedGroupsFinder.new(user).execute(current_user)
end
def projects_for_current_user
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f1df6832bf6..f00f3f709e9 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -39,6 +39,7 @@ class IssuableFinder
items = by_assignee(items)
items = by_author(items)
items = by_label(items)
+ items = by_due_date(items)
sort(items)
end
@@ -117,7 +118,7 @@ class IssuableFinder
end
def filter_by_no_label?
- labels? && params[:label_name] == Label::None.title
+ labels? && params[:label_name].include?(Label::None.title)
end
def labels
@@ -271,7 +272,6 @@ class IssuableFinder
items = items.without_label
else
items = items.with_label(label_names)
-
if projects
items = items.where(labels: { project_id: projects })
end
@@ -281,8 +281,44 @@ class IssuableFinder
items
end
+ def by_due_date(items)
+ if due_date?
+ if filter_by_no_due_date?
+ items = items.without_due_date
+ elsif filter_by_overdue?
+ items = items.due_before(Date.today)
+ elsif filter_by_due_this_week?
+ items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
+ elsif filter_by_due_this_month?
+ items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
+ end
+ end
+
+ items
+ end
+
+ def filter_by_no_due_date?
+ due_date? && params[:due_date] == Issue::NoDueDate.name
+ end
+
+ def filter_by_overdue?
+ due_date? && params[:due_date] == Issue::Overdue.name
+ end
+
+ def filter_by_due_this_week?
+ due_date? && params[:due_date] == Issue::DueThisWeek.name
+ end
+
+ def filter_by_due_this_month?
+ due_date? && params[:due_date] == Issue::DueThisMonth.name
+ end
+
+ def due_date?
+ params[:due_date].present? && klass.column_names.include?('due_date')
+ end
+
def label_names
- params[:label_name].split(',')
+ params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
end
def current_user_related?
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 16e5b8ac223..3e0074da394 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -254,11 +254,11 @@ module ApplicationHelper
def page_filter_path(options = {})
without = options.delete(:without)
+ add_label = options.delete(:label)
exist_opts = {
state: params[:state],
scope: params[:scope],
- label_name: params[:label_name],
milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
@@ -275,6 +275,13 @@ module ApplicationHelper
path = request.path
path << "?#{options.to_param}"
+ if add_label
+ if params[:label_name].present? and params[:label_name].respond_to?('any?')
+ params[:label_name].each do |label|
+ path << "&label_name[]=#{label}"
+ end
+ end
+ end
path
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 60a0ff32c9c..914b0ef6042 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -15,6 +15,10 @@ module ApplicationSettingsHelper
current_application_settings.sign_in_text
end
+ def shared_runners_text
+ current_application_settings.shared_runners_text
+ end
+
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 9e59a295fc4..a4d7c425d0f 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -173,4 +173,15 @@ module BlobHelper
response.etag = @blob.id
!stale
end
+
+ def licenses_for_select
+ return @licenses_for_select if defined?(@licenses_for_select)
+
+ licenses = Licensee::License.all
+
+ @licenses_for_select = {
+ Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
+ Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
+ }
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 35ba543cef1..b59c3982edd 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -126,12 +126,10 @@ module CommitsHelper
def revert_commit_link(commit, continue_to_path, btn_class: nil)
return unless current_user
- tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request"
+ tooltip = "Revert this #{commit.change_type_title} in a new merge request"
if can_collaborate_with_project?
- content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do
- link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}"
- end
+ link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
elsif can?(current_user, :fork_project, @project)
continue_params = {
to: continue_to_path,
@@ -146,11 +144,24 @@ module CommitsHelper
end
end
- def revert_commit_type(commit)
- if commit.merged_merge_request
- 'merge request'
- else
- 'commit'
+ def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil)
+ return unless current_user
+
+ tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
+
+ if can_collaborate_with_project?
+ link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
+ elsif can?(current_user, :fork_project, @project)
+ continue_params = {
+ to: continue_to_path,
+ notice: edit_in_new_fork_notice + ' Try to cherry-pick this commit again.',
+ notice_now: edit_in_new_fork_notice_now
+ }
+ fork_path = namespace_project_forks_path(@project.namespace, @project,
+ namespace_key: current_user.namespace.id,
+ continue: continue_params)
+
+ link_to 'Cherry-pick', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip
end
end
@@ -183,7 +194,7 @@ module CommitsHelper
options = {
class: "commit-#{options[:source]}-link has-tooltip",
- data: { 'original-title'.to_sym => sanitize(source_email) }
+ title: source_email
}
if user.nil?
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index ff32e834499..97466d532f4 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -9,7 +9,13 @@ module DiffHelper
end
def diff_view
- params[:view] == 'parallel' ? 'parallel' : 'inline'
+ diff_views = %w(inline parallel)
+
+ if diff_views.include?(cookies[:diff_view])
+ cookies[:diff_view]
+ else
+ diff_views.first
+ end
end
def diff_hard_limit_enabled?
@@ -40,10 +46,11 @@ module DiffHelper
(unfold) ? 'unfold js-unfold' : ''
end
- def diff_line_content(line)
+ def diff_line_content(line, line_type = nil)
if line.blank?
" &nbsp;".html_safe
else
+ line[0] = ' ' if %w[new old].include?(line_type)
line
end
end
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
new file mode 100644
index 00000000000..109bc1a02d1
--- /dev/null
+++ b/app/helpers/import_helper.rb
@@ -0,0 +1,18 @@
+module ImportHelper
+ def github_project_link(path_with_namespace)
+ link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank'
+ end
+
+ private
+
+ def github_project_url(path_with_namespace)
+ "#{github_root_url}/#{path_with_namespace}"
+ end
+
+ def github_root_url
+ return @github_url if defined?(@github_url)
+
+ provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' }
+ @github_url = provider.fetch('url', 'https://github.com') if provider
+ end
+end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b14b8218d02..39474217286 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -16,6 +16,25 @@ module IssuablesHelper
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
end
+ def multi_label_name(current_labels, default_label)
+ # current_labels may be a string from before
+ if current_labels.is_a?(Array)
+ if current_labels.count > 1
+ "#{current_labels[0]} +#{current_labels.count - 1} more"
+ else
+ current_labels[0]
+ end
+ elsif current_labels.is_a?(String)
+ if current_labels.nil? || current_labels.empty?
+ default_label
+ else
+ current_labels
+ end
+ else
+ default_label
+ end
+ end
+
def issuable_json_path(issuable)
project = issuable.project
@@ -55,6 +74,15 @@ module IssuablesHelper
h(milestone_title.presence || default_label)
end
+ def issuable_meta(issuable, project, text)
+ output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
+ output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
+ output << content_tag(:strong) do
+ author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
+ author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 4cb8adcebad..afe1e11a0da 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -131,7 +131,7 @@ module IssuesHelper
class: "icon emoji-icon emoji-#{unicode}",
title: name,
data: data
- else
+ else
# Emoji icons displayed separately, used for the awards already given
# to an issue or merge request.
content_tag :img, "",
@@ -172,6 +172,18 @@ module IssuesHelper
end.to_h
end
+ def due_date_options
+ options = [
+ Issue::AnyDueDate,
+ Issue::NoDueDate,
+ Issue::DueThisWeek,
+ Issue::DueThisMonth,
+ Issue::Overdue
+ ]
+
+ options_from_collection_for_select(options, 'name', 'title', params[:due_date])
+ end
+
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 82f805fa444..e4e8b934bc8 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -84,6 +84,14 @@ module PageLayoutHelper
end
end
+ def nav(name = nil)
+ if name
+ @nav = name
+ else
+ @nav
+ end
+ end
+
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = (current_user && current_user.layout == "fluid") || enabled
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7e00aacceaa..3d5e61d2c18 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -52,7 +52,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
- link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
+ link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe
end
end
@@ -123,6 +123,18 @@ module ProjectsHelper
end
end
+ def license_short_name(project)
+ no_license_key = project.repository.license_key.nil? ||
+ # Back-compat if cache contains 'no-license', can be removed in a few weeks
+ project.repository.license_key == 'no-license'
+
+ return 'LICENSE' if no_license_key
+
+ license = Licensee::License.new(project.repository.license_key)
+
+ license.nickname || license.name
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -144,6 +156,10 @@ module ProjectsHelper
nav_tabs << :settings
end
+ if can?(current_user, :read_project_member, project)
+ nav_tabs << :team
+ end
+
if can?(current_user, :read_issue, project)
nav_tabs << :issues
end
@@ -216,40 +232,14 @@ module ProjectsHelper
end
end
- def add_contribution_guide_path(project)
- if project && !project.repository.contribution_guide
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "CONTRIBUTING.md",
- commit_message: "Add contribution guide"
- )
- end
- end
-
- def add_changelog_path(project)
- if project && !project.repository.changelog
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "CHANGELOG",
- commit_message: "Add changelog"
- )
- end
- end
-
- def add_license_path(project)
- if project && !project.repository.license
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "LICENSE",
- commit_message: "Add license"
- )
- end
+ def add_special_file_path(project, file_name:, commit_message: nil)
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ project.default_branch || 'master',
+ file_name: file_name,
+ commit_message: commit_message || "Add #{file_name.downcase}"
+ )
end
def contribution_guide_path(project)
@@ -272,7 +262,7 @@ module ProjectsHelper
end
def license_path(project)
- filename_path(project, :license)
+ filename_path(project, :license_blob)
end
def version_path(project)
@@ -306,6 +296,13 @@ module ProjectsHelper
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
end
+ def new_license_path
+ ref = @repository.root_ref if @repository
+ ref ||= 'master'
+
+ namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
+ end
+
def last_push_event
if current_user
current_user.recent_push(@project.id)
@@ -335,8 +332,6 @@ module ProjectsHelper
@ref || @repository.try(:root_ref)
end
- private
-
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
namespace_project_blob_path(
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 05386d790ca..e951a87a212 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -2,30 +2,29 @@ module SelectsHelper
def users_select_tag(id, opts = {})
css_class = "ajax-users-select "
css_class << "multiselect " if opts[:multiple]
+ css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
- placeholder = opts[:placeholder] || 'Search for a user'
- null_user = opts[:null_user] || false
- any_user = opts[:any_user] || false
- email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
- current_user = opts[:current_user] || false
- project = opts[:project] || @project
html = {
class: css_class,
data: {
- placeholder: placeholder,
- null_user: null_user,
- any_user: any_user,
- email_user: email_user,
+ placeholder: opts[:placeholder] || 'Search for a user',
+ null_user: opts[:null_user] || false,
+ any_user: opts[:any_user] || false,
+ email_user: opts[:email_user] || false,
first_user: first_user,
- current_user: current_user
+ current_user: opts[:current_user] || false,
+ "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
+ author_id: opts[:author_id] || ''
}
}
unless opts[:scope] == :all
+ project = opts[:project] || @project
+
if project
html['data-project-id'] = project.id
elsif @group
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 2f2d2721d6d..630e10ea892 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -8,6 +8,8 @@ module SortingHelper
sort_value_oldest_created => sort_title_oldest_created,
sort_value_milestone_soon => sort_title_milestone_soon,
sort_value_milestone_later => sort_title_milestone_later,
+ sort_value_due_date_soon => sort_title_due_date_soon,
+ sort_value_due_date_later => sort_title_due_date_later,
sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
@@ -50,6 +52,14 @@ module SortingHelper
'Milestone due later'
end
+ def sort_title_due_date_soon
+ 'Due soon'
+ end
+
+ def sort_title_due_date_later
+ 'Due later'
+ end
+
def sort_title_name
'Name'
end
@@ -98,6 +108,14 @@ module SortingHelper
'milestone_due_desc'
end
+ def sort_value_due_date_soon
+ 'due_date_asc'
+ end
+
+ def sort_value_due_date_later
+ 'due_date_desc'
+ end
+
def sort_value_name
'name_asc'
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 04e53fe7c61..96a83671009 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -110,4 +110,12 @@ module TabHelper
'active'
end
end
+
+ def profile_tab_class
+ if controller.controller_path =~ /\Aprofiles/
+ return 'active'
+ end
+
+ 'active' if current_controller?('oauth/applications')
+ end
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 4920ca5af6e..dbedf417fa5 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -66,7 +66,7 @@ module TreeHelper
ref
else
project = tree_edit_project(project)
- project.repository.next_patch_branch
+ project.repository.next_branch('patch')
end
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 55bb4f65270..9dd11d20ea6 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -56,7 +56,7 @@ module Emails
{
from: sender(sender_id),
to: recipient(recipient_id),
- subject: subject("#{@merge_request.title} (##{@merge_request.iid})")
+ subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})")
}
end
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index f9650df9a74..cdc40b81ee1 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -38,7 +38,7 @@ module Emails
{
from: sender(@note.author_id),
to: recipient(recipient_id),
- subject: subject("#{@note.noteable.title} (##{@note.noteable.iid})")
+ subject: subject("#{@note.noteable.title} (#{@note.noteable.to_reference})")
}
end
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
new file mode 100644
index 00000000000..21db2fe04a0
--- /dev/null
+++ b/app/mailers/repository_check_mailer.rb
@@ -0,0 +1,14 @@
+class RepositoryCheckMailer < BaseMailer
+ def notify(failed_count)
+ if failed_count == 1
+ @message = "One project failed its last repository check"
+ else
+ @message = "#{failed_count} projects failed their last repository check"
+ end
+
+ mail(
+ to: User.admins.pluck(:email),
+ subject: "GitLab Admin | #{@message}"
+ )
+ end
+end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index ec5ac54c277..d3e8a3fa79a 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -18,6 +18,7 @@ class Ability
when Namespace then namespace_abilities(user, subject)
when GroupMember then group_member_abilities(user, subject)
when ProjectMember then project_member_abilities(user, subject)
+ when User then user_abilities
else []
end.concat(global_abilities(user))
end
@@ -35,6 +36,8 @@ class Ability
anonymous_project_abilities(subject)
when subject.is_a?(Group) || subject.respond_to?(:group)
anonymous_group_abilities(subject)
+ when subject.is_a?(User)
+ anonymous_user_abilities
else
[]
end
@@ -81,17 +84,17 @@ class Ability
end
def anonymous_group_abilities(subject)
+ rules = []
+
group = if subject.is_a?(Group)
subject
else
subject.group
end
- if group && group.public?
- [:read_group]
- else
- []
- end
+ rules << :read_group if group.public?
+
+ rules
end
def anonymous_personal_snippet_abilities(snippet)
@@ -110,9 +113,14 @@ class Ability
end
end
+ def anonymous_user_abilities
+ [:read_user] unless restricted_public_level?
+ end
+
def global_abilities(user)
rules = []
rules << :create_group if user.can_create_group
+ rules << :read_users_list
rules
end
@@ -163,7 +171,7 @@ class Ability
@public_project_rules ||= project_guest_rules + [
:download_code,
:fork_project,
- :read_commit_status,
+ :read_commit_status
]
end
@@ -289,7 +297,6 @@ class Ability
def group_abilities(user, group)
rules = []
-
rules << :read_group if can_read_group?(user, group)
# Only group masters and group owners can create new projects
@@ -461,6 +468,10 @@ class Ability
rules
end
+ def user_abilities
+ [:read_user]
+ end
+
def abilities
@abilities ||= begin
abilities = Six.new
@@ -475,6 +486,10 @@ class Ability
private
+ def restricted_public_level?
+ current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
+ end
+
def named_abilities(name)
[
:"read_#{name}",
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 052cd874733..36f88154232 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -153,7 +153,8 @@ class ApplicationSetting < ActiveRecord::Base
require_two_factor_authentication: false,
two_factor_grace_period: 48,
recaptcha_enabled: false,
- akismet_enabled: false
+ akismet_enabled: false,
+ repository_checks_enabled: true,
)
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index c0b334d3600..553cd447971 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -224,12 +224,33 @@ module Ci
end
end
+ def trace_length
+ if raw_trace
+ raw_trace.length
+ else
+ 0
+ end
+ end
+
def trace=(trace)
+ recreate_trace_dir
+ File.write(path_to_trace, trace)
+ end
+
+ def recreate_trace_dir
unless Dir.exists?(dir_to_trace)
FileUtils.mkdir_p(dir_to_trace)
end
+ end
+ private :recreate_trace_dir
- File.write(path_to_trace, trace)
+ def append_trace(trace_part, offset)
+ recreate_trace_dir
+
+ File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
+ File.open(path_to_trace, 'a') do |f|
+ f.write(trace_part)
+ end
end
def dir_to_trace
@@ -359,11 +380,23 @@ module Ci
self.update(erased_by: user, erased_at: Time.now)
end
- private
-
def yaml_variables
+ global_yaml_variables + job_yaml_variables
+ end
+
+ def global_yaml_variables
+ if commit.config_processor
+ commit.config_processor.global_variables.map do |key, value|
+ { key: key, value: value, public: true }
+ end
+ else
+ []
+ end
+ end
+
+ def job_yaml_variables
if commit.config_processor
- commit.config_processor.variables.map do |key, value|
+ commit.config_processor.job_variables(name).map do |key, value|
{ key: key, value: value, public: true }
end
else
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 6abeddeeae6..fadf5c62984 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -19,14 +19,17 @@
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
- include CiStatus
+ include Statuseable
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
+ delegate :stages, to: :statuses
+
validates_presence_of :sha
+ validates_presence_of :status
validate :valid_commit_sha
# Invalidate object and save if when touched
@@ -40,10 +43,6 @@ module Ci
CommitStatus.where(commit: all).stages
end
- def stages
- statuses.stages
- end
-
def project_id
project.id
end
@@ -82,7 +81,7 @@ module Ci
def retryable?
builds.latest.any? do |build|
- build.failed? || build.retryable?
+ build.failed? && build.retryable?
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 9ffdcc59128..562c3ed15b2 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -15,8 +15,8 @@ class Commit
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
# Commits above this size will not be rendered in HTML
- DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
- DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES)
+ DIFF_HARD_LIMIT_FILES = 1000
+ DIFF_HARD_LIMIT_LINES = 50000
class << self
def decorate(commits, project)
@@ -154,7 +154,7 @@ class Commit
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
- url: commit_url,
+ url: Gitlab::UrlBuilder.build(self),
author: {
name: author_name,
email: author_email
@@ -168,10 +168,6 @@ class Commit
data
end
- def commit_url
- project.present? ? "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{id}" : ""
- end
-
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
def closes_issues(current_user = self.committer)
@@ -223,6 +219,10 @@ class Commit
def revert_branch_name
"revert-#{short_id}"
end
+
+ def cherry_pick_branch_name
+ project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
+ end
def revert_description
if merged_merge_request
@@ -258,6 +258,10 @@ class Commit
end.any? { |commit_ref| commit_ref.reverts_commit?(self) }
end
+ def change_type_title
+ merged_merge_request ? 'merge request' : 'commit'
+ end
+
private
def repo_changes
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 66eb5dcecf9..aa56314aa16 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -33,7 +33,7 @@
#
class CommitStatus < ActiveRecord::Base
- include CiStatus
+ include Statuseable
self.table_name = 'ci_builds'
@@ -81,7 +81,11 @@ class CommitStatus < ActiveRecord::Base
end
end
- delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false
+ delegate :sha, :short_sha, to: :commit
+
+ def before_sha
+ commit.before_sha || Gitlab::Git::BLANK_SHA
+ end
def self.stages
order_by = 'max(stage_idx)'
@@ -89,7 +93,10 @@ class CommitStatus < ActiveRecord::Base
end
def self.stages_status
- Hash[group(:stage).pluck(:stage, self.status_sql)]
+ all.stages.inject({}) do |h, stage|
+ h[stage] = all.where(stage: stage).status
+ h
+ end
end
def ignored?
@@ -97,11 +104,13 @@ class CommitStatus < ActiveRecord::Base
end
def duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.now - started_at
- end
+ duration =
+ if started_at && finished_at
+ finished_at - started_at
+ elsif started_at
+ Time.now - started_at
+ end
+ duration
end
def stuck?
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
index 51288094ef1..5382dde6765 100644
--- a/app/models/concerns/internal_id.rb
+++ b/app/models/concerns/internal_id.rb
@@ -7,11 +7,13 @@ module InternalId
end
def set_iid
- records = project.send(self.class.name.tableize)
- records = records.with_deleted if self.paranoid?
- max_iid = records.maximum(:iid)
+ if iid.blank?
+ records = project.send(self.class.name.tableize)
+ records = records.with_deleted if self.paranoid?
+ max_iid = records.maximum(:iid)
- self.iid = max_iid.to_i + 1
+ self.iid = max_iid.to_i + 1
+ end
end
def to_param
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index afa2ca039ae..d5166e81474 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -37,7 +37,6 @@ module Issuable
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
- scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
@@ -122,6 +121,14 @@ module Issuable
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
+
+ def with_label(title)
+ if title.is_a?(Array) && title.count > 1
+ joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}")
+ else
+ joins(:labels).where(labels: { title: title })
+ end
+ end
end
def today?
diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/statuseable.rb
index 8190b2a20c6..8a293b7b76e 100644
--- a/app/models/concerns/ci_status.rb
+++ b/app/models/concerns/statuseable.rb
@@ -1,4 +1,4 @@
-module CiStatus
+module Statuseable
extend ActiveSupport::Concern
AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
@@ -7,7 +7,7 @@ module CiStatus
def status_sql
builds = all.select('count(*)').to_sql
success = all.success.select('count(*)').to_sql
- ignored = all.ignored.select('count(*)').to_sql if all.try(:ignored)
+ ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
ignored ||= '0'
pending = all.pending.select('count(*)').to_sql
running = all.running.select('count(*)').to_sql
@@ -33,7 +33,7 @@ module CiStatus
def duration
duration_array = all.map(&:duration).compact
- duration_array.reduce(:+).to_i
+ duration_array.reduce(:+)
end
def started_at
@@ -41,7 +41,7 @@ module CiStatus
end
def finished_at
- all.minimum(:finished_at)
+ all.maximum(:finished_at)
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 12183524b79..897518aadc7 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -345,7 +345,7 @@ class Event < ActiveRecord::Base
end
def reset_project_activity
- if project
+ if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain
project.update_column(:last_activity_at, self.created_at)
end
end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index b8585d4e577..b7894c99846 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -37,4 +37,10 @@ class ExternalIssue
def to_reference(_from_project = nil)
id
end
+
+ def reference_link_text(from_project = nil)
+ return "##{id}" if /^\d+$/.match(id)
+
+ id
+ end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 9a04ac70d35..1f8432e3320 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -15,7 +15,6 @@
#
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Group < Namespace
include Gitlab::ConfigHelper
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index fe923fafbe0..bc6e0f98c3c 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -21,10 +21,9 @@
class ProjectHook < WebHook
belongs_to :project
- scope :push_hooks, -> { where(push_events: true) }
- scope :tag_push_hooks, -> { where(tag_push_events: true) }
scope :issue_hooks, -> { where(issues_events: true) }
scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :build_hooks, -> { where(build_events: true) }
+ scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
end
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index c147d8762a9..15dddcc2447 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -19,4 +19,7 @@
#
class SystemHook < WebHook
+ def async_execute(data, hook_name)
+ Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
+ end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7a13c3f0a39..3a2e4f546f7 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -30,6 +30,9 @@ class WebHook < ActiveRecord::Base
default_value_for :build_events, false
default_value_for :enable_ssl_verification, true
+ scope :push_hooks, -> { where(push_events: true) }
+ scope :tag_push_hooks, -> { where(tag_push_events: true) }
+
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
diff --git a/app/models/issue.rb b/app/models/issue.rb
index e064b0f8b95..ea1bfb776ee 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -20,7 +20,6 @@
#
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Issue < ActiveRecord::Base
include InternalId
@@ -29,6 +28,13 @@ class Issue < ActiveRecord::Base
include Sortable
include Taskable
+ DueDateStruct = Struct.new(:title, :name).freeze
+ NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
+ AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
+ Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
+ DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
+ DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
+
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
@@ -40,6 +46,13 @@ class Issue < ActiveRecord::Base
scope :open_for, ->(user) { opened.assigned_to(user) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
+ scope :without_due_date, -> { where(due_date: nil) }
+ scope :due_before, ->(date) { where('issues.due_date < ?', date) }
+ scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
+
+ scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
+ scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
@@ -83,6 +96,15 @@ class Issue < ActiveRecord::Base
@link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end
+ def self.sort(method)
+ case method.to_s
+ when 'due_date_asc' then order_due_date_asc
+ when 'due_date_desc' then order_due_date_desc
+ else
+ super
+ end
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -104,10 +126,16 @@ class Issue < ActiveRecord::Base
end
end
- def related_branches
- project.repository.branch_names.select do |branch|
- branch.end_with?("-#{iid}")
+ # All branches containing the current issue's ID, except for
+ # those with a merge request open referencing the current issue.
+ def related_branches(current_user)
+ branches_with_iid = project.repository.branch_names.select do |branch|
+ branch =~ /\A#{iid}-(?!\d+-stable)/i
end
+
+ branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch)
+
+ branches_with_iid - branches_with_merge_request
end
# Reset issue events cache
@@ -151,13 +179,21 @@ class Issue < ActiveRecord::Base
end
def to_branch_name
- "#{title.parameterize}-#{iid}"
+ if self.confidential?
+ "#{iid}-confidential-issue"
+ else
+ "#{iid}-#{title.parameterize}"
+ end
end
def can_be_worked_on?(current_user)
!self.closed? &&
!self.project.forked? &&
- self.related_branches.empty? &&
+ self.related_branches(current_user).empty? &&
self.closed_by_merge_requests(current_user).empty?
end
+
+ def overdue?
+ due_date.try(:past?) || false
+ end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 55c01cae762..60bdce32952 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -113,6 +113,10 @@ class Label < ActiveRecord::Base
template
end
+ def text_color
+ LabelsHelper::text_color_for_bg(self.color)
+ end
+
private
def label_format_reference(format = :id)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 6766e4d1afa..d00919c3b0c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -27,9 +27,6 @@
# merge_commit_sha :string
#
-require Rails.root.join("app/models/commit")
-require Rails.root.join("lib/static_model")
-
class MergeRequest < ActiveRecord::Base
include InternalId
include Issuable
@@ -605,4 +602,8 @@ class MergeRequest < ActiveRecord::Base
def can_be_reverted?(current_user = nil)
merge_commit && !merge_commit.has_been_reverted?(current_user, self)
end
+
+ def can_be_cherry_picked?
+ merge_commit
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 33884118595..0580cafdd1b 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -11,8 +11,6 @@
# updated_at :datetime
#
-require Rails.root.join("app/models/commit")
-
class MergeRequestDiff < ActiveRecord::Base
include Sortable
diff --git a/app/models/note.rb b/app/models/note.rb
index 87ced65c650..71b4293d57a 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -20,7 +20,6 @@
#
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Note < ActiveRecord::Base
include Gitlab::CurrentSettings
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
new file mode 100644
index 00000000000..c78c7f4aa0e
--- /dev/null
+++ b/app/models/oauth_access_token.rb
@@ -0,0 +1,19 @@
+# == Schema Information
+#
+# Table name: oauth_access_tokens
+#
+# id :integer not null, primary key
+# resource_owner_id :integer
+# application_id :integer
+# token :string not null
+# refresh_token :string
+# expires_in :integer
+# revoked_at :datetime
+# created_at :datetime not null
+# scopes :string
+#
+
+class OauthAccessToken < ActiveRecord::Base
+ belongs_to :resource_owner, class_name: 'User'
+ belongs_to :application, class_name: 'Doorkeeper::Application'
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index e48830115bc..0420c6a61ae 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -40,7 +40,6 @@
#
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Project < ActiveRecord::Base
include Gitlab::ConfigHelper
@@ -409,6 +408,35 @@ class Project < ActiveRecord::Base
self.import_data.destroy if self.import_data
end
+ def import_url=(value)
+ import_url = Gitlab::ImportUrl.new(value)
+ create_or_update_import_data(credentials: import_url.credentials)
+ super(import_url.sanitized_url)
+ end
+
+ def import_url
+ if import_data && super
+ import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
+ import_url.full_url
+ else
+ super
+ end
+ end
+
+ def create_or_update_import_data(data: nil, credentials: nil)
+ project_import_data = import_data || build_import_data
+ if data
+ project_import_data.data ||= {}
+ project_import_data.data = project_import_data.data.merge(data)
+ end
+ if credentials
+ project_import_data.credentials ||= {}
+ project_import_data.credentials = project_import_data.credentials.merge(credentials)
+ end
+
+ project_import_data.save
+ end
+
def import?
external_import? || forked?
end
@@ -792,18 +820,16 @@ class Project < ActiveRecord::Base
wiki = Repository.new("#{old_path}.wiki", self)
if repo.exists?
- repo.expire_cache
- repo.expire_emptiness_caches
+ repo.before_delete
end
if wiki.exists?
- wiki.expire_cache
- wiki.expire_emptiness_caches
+ wiki.before_delete
end
end
- def hook_attrs
- {
+ def hook_attrs(backward: true)
+ attrs = {
name: name,
description: description,
web_url: web_url,
@@ -814,12 +840,19 @@ class Project < ActiveRecord::Base
visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
default_branch: default_branch,
- # Backward compatibility
- homepage: web_url,
- url: url_to_repo,
- ssh_url: ssh_url_to_repo,
- http_url: http_url_to_repo
}
+
+ # Backward compatibility
+ if backward
+ attrs.merge!({
+ homepage: web_url,
+ url: url_to_repo,
+ ssh_url: ssh_url_to_repo,
+ http_url: http_url_to_repo
+ })
+ end
+
+ attrs
end
# Reset events cache related to this project
@@ -865,7 +898,9 @@ class Project < ActiveRecord::Base
def change_head(branch)
repository.before_change_head
- gitlab_shell.update_repository_head(self.path_with_namespace, branch)
+ repository.rugged.references.create('HEAD',
+ "refs/heads/#{branch}",
+ force: true)
reload_default_branch
end
@@ -927,7 +962,7 @@ class Project < ActiveRecord::Base
end
def ci_commit(sha, ref)
- ci_commits.find_by(sha: sha, ref: ref)
+ ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
end
def ensure_ci_commit(sha, ref)
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index cd3319f077e..2c0ae312f1b 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -8,12 +8,23 @@
#
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base
belongs_to :project
-
+ attr_encrypted :credentials,
+ key: Gitlab::Application.secrets.db_key_base,
+ marshal: true,
+ encode: true,
+ mode: :per_attribute_iv_and_salt
+
serialize :data, JSON
validates :project, presence: true
+
+ before_validation :symbolize_credentials
+
+ def symbolize_credentials
+ # bang doesn't work here - attr_encrypted makes it not to work
+ self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
+ end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 9e7f642180e..060062aaf7a 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -82,17 +82,17 @@ class BambooService < CiService
end
def build_info(sha)
- url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
+ url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s
if username.blank? && password.blank?
- @response = HTTParty.get(parsed_url.to_s, verify: false)
+ @response = HTTParty.get(url, verify: false)
else
- get_url = "#{url}&os_authType=basic"
+ url << '&os_authType=basic'
auth = {
- username: username,
- password: password,
+ username: username,
+ password: password
}
- @response = HTTParty.get(get_url, verify: false, basic_auth: auth)
+ @response = HTTParty.get(url, verify: false, basic_auth: auth)
end
end
@@ -101,11 +101,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page.
- "#{bamboo_url}/browse/#{build_key}"
+ URI.join(bamboo_url, "/browse/#{build_key}").to_s
else
# If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key']
- "#{bamboo_url}/browse/#{result_key}"
+ URI.join(bamboo_url, "/browse/#{result_key}").to_s
end
end
@@ -134,7 +134,7 @@ class BambooService < CiService
return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data.
- self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
- verify: false)
+ url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s
+ self.class.get(url, verify: false)
end
end
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index f9f04838766..6ab6d7417b7 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -23,7 +23,7 @@ class BuildsEmailService < Service
prop_accessor :recipients
boolean_accessor :add_pusher
boolean_accessor :notify_only_broken_builds
- validates :recipients, presence: true, if: :activated?
+ validates :recipients, presence: true, if: ->(s) { s.activated? && !s.add_pusher? }
def initialize_properties
if properties.nil?
@@ -87,10 +87,14 @@ class BuildsEmailService < Service
end
def all_recipients(data)
- all_recipients = recipients.split(',').compact.reject(&:blank?)
+ all_recipients = []
+
+ unless recipients.blank?
+ all_recipients += recipients.split(',').compact.reject(&:blank?)
+ end
if add_pusher? && data[:user][:email]
- all_recipients << "#{data[:user][:email]}"
+ all_recipients << data[:user][:email]
end
all_recipients
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 0e3fa4a40fe..064ef8e8674 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -183,7 +183,7 @@ class HipchatService < Service
title = obj_attr[:title]
merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
- merge_request_link = "<a href=\"#{merge_request_url}\">merge request ##{merge_request_id}</a>"
+ merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
message = "#{user_name} #{state} #{merge_request_link} in " \
"#{project_link}: <b>#{title}</b>"
@@ -224,7 +224,7 @@ class HipchatService < Service
when "MergeRequest"
subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
subject_id = subj_attr[:iid]
- subject_desc = "##{subject_id}"
+ subject_desc = "!#{subject_id}"
subject_type = "merge request"
title = format_title(subj_attr[:title])
when "Snippet"
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index d89cf6d17b2..fd65027f084 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -60,7 +60,7 @@ class SlackService < Service
end
def supported_events
- %w(push issue merge_request note tag_push build)
+ %w(push issue merge_request note tag_push build wiki_page)
end
def execute(data)
@@ -90,6 +90,8 @@ class SlackService < Service
NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
+ when "wiki_page"
+ WikiPageMessage.new(data)
end
opt = {}
@@ -133,3 +135,4 @@ require "slack_service/push_message"
require "slack_service/merge_message"
require "slack_service/note_message"
require "slack_service/build_message"
+require "slack_service/wiki_page_message"
diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb
index e792c258f73..11fc691022b 100644
--- a/app/models/project_services/slack_service/merge_message.rb
+++ b/app/models/project_services/slack_service/merge_message.rb
@@ -50,7 +50,7 @@ class SlackService
end
def merge_request_link
- "[merge request ##{merge_request_id}](#{merge_request_url})"
+ "[merge request !#{merge_request_id}](#{merge_request_url})"
end
def merge_request_url
diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb
index b15d9a14677..89ba51cb662 100644
--- a/app/models/project_services/slack_service/note_message.rb
+++ b/app/models/project_services/slack_service/note_message.rb
@@ -58,7 +58,7 @@ class SlackService
def create_merge_note(merge_request)
commented_on_message(
- "[merge request ##{merge_request[:iid]}](#{@note_url})",
+ "[merge request !#{merge_request[:iid]}](#{@note_url})",
format_title(merge_request[:title]))
end
diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb
new file mode 100644
index 00000000000..f336d9e7691
--- /dev/null
+++ b/app/models/project_services/slack_service/wiki_page_message.rb
@@ -0,0 +1,53 @@
+class SlackService
+ class WikiPageMessage < BaseMessage
+ attr_reader :user_name
+ attr_reader :title
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :wiki_page_url
+ attr_reader :action
+ attr_reader :description
+
+ def initialize(params)
+ @user_name = params[:user][:name]
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+
+ obj_attr = params[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ @title = obj_attr[:title]
+ @wiki_page_url = obj_attr[:url]
+ @description = obj_attr[:content]
+
+ @action =
+ case obj_attr[:action]
+ when "create"
+ "created"
+ when "update"
+ "edited"
+ end
+ end
+
+ def attachments
+ description_message
+ end
+
+ private
+
+ def message
+ "#{user_name} #{action} #{wiki_page_link} in #{project_link}: *#{title}*"
+ end
+
+ def description_message
+ [{ text: format(@description), color: attachment_color }]
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def wiki_page_link
+ "[wiki page](#{wiki_page_url})"
+ end
+ end
+end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index b8e9416131a..8dceee5e2c5 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -85,13 +85,15 @@ class TeamcityService < CiService
end
def build_info(sha)
- url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
- "branch:unspecified:any,number:#{sha}")
+ url = URI.join(
+ teamcity_url,
+ "/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
+ ).to_s
auth = {
username: username,
- password: password,
+ password: password
}
- @response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
+ @response = HTTParty.get(url, verify: false, basic_auth: auth)
end
def build_page(sha, ref)
@@ -100,12 +102,14 @@ class TeamcityService < CiService
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
- "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
+ URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
- "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
- "&buildTypeId=#{build_type}"
+ URI.join(
+ teamcity_url,
+ "/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
+ ).to_s
end
end
@@ -140,12 +144,13 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref])
- self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
- body: "<build branchName=\"#{branch}\">"\
- "<buildType id=\"#{build_type}\"/>"\
- '</build>',
- headers: { 'Content-type' => 'application/xml' },
- basic_auth: auth
- )
+ self.class.post(
+ URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s,
+ body: "<build branchName=\"#{branch}\">"\
+ "<buildType id=\"#{build_type}\"/>"\
+ '</build>',
+ headers: { 'Content-type' => 'application/xml' },
+ basic_auth: auth
+ )
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 0b2289cfa39..61c8dce6060 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -12,11 +12,13 @@ class Repository
attr_accessor :path_with_namespace, :project
def self.clean_old_archives
- repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
+ Gitlab::Metrics.measure(:clean_old_archives) do
+ repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
- return unless File.directory?(repository_downloads_path)
+ return unless File.directory?(repository_downloads_path)
- Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
+ Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
+ end
end
def initialize(path_with_namespace, project)
@@ -169,7 +171,12 @@ class Repository
def rm_tag(tag_name)
before_remove_tag
- gitlab_shell.rm_tag(path_with_namespace, tag_name)
+ begin
+ rugged.tags.delete(tag_name)
+ true
+ rescue Rugged::ReferenceError
+ false
+ end
end
def branch_names
@@ -221,7 +228,8 @@ class Repository
def cache_keys
%i(size branch_names tag_names commit_count
- readme version contribution_guide changelog license)
+ readme version contribution_guide changelog
+ license_blob license_key)
end
def build_cache
@@ -454,27 +462,21 @@ class Repository
end
end
- def license
- cache.fetch(:license) do
- licenses = tree(:head).blobs.find_all do |file|
- file.name =~ /\A(copying|license|licence)/i
- end
-
- preferences = [
- /\Alicen[sc]e\z/i, # LICENSE, LICENCE
- /\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
- /\Acopying\z/i, # COPYING
- /\Acopying\.(?!lesser)/i, # COPYING.txt
- /Acopying.lesser/i # COPYING.LESSER
- ]
+ def license_blob
+ return nil if !exists? || empty?
- license = nil
- preferences.each do |r|
- license = licenses.find { |l| l.name =~ r }
- break if license
+ cache.fetch(:license_blob) do
+ tree(:head).blobs.find do |file|
+ file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
end
+ end
+ end
- license
+ def license_key
+ return nil if !exists? || empty?
+
+ cache.fetch(:license_key) do
+ Licensee.license(path).try(:key)
end
end
@@ -542,15 +544,18 @@ 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/)
+ def next_branch(name, opts={})
+ branch_ids = self.branch_names.map do |n|
+ next 1 if n == name
+ result = n.match(/\A#{name}-([0-9]+)\z/)
result[1].to_i if result
end.compact
- highest_patch_branch_id = patch_branch_ids.max || 0
+ highest_branch_id = branch_ids.max || 0
- "patch-#{highest_patch_branch_id + 1}"
+ return name if opts[:mild] && 0 == highest_branch_id
+
+ "#{name}-#{highest_branch_id + 1}"
end
# Remove archives older than 2 hours
@@ -753,6 +758,28 @@ class Repository
end
end
+ def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
+ source_sha = find_branch(base_branch).target
+ cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
+
+ return false unless cherry_pick_tree_id
+
+ commit_with_hooks(user, base_branch) do |ref|
+ committer = user_to_committer(user)
+ source_sha = Rugged::Commit.create(rugged,
+ message: commit.message,
+ author: {
+ email: commit.author_email,
+ name: commit.author_name,
+ time: commit.authored_date
+ },
+ committer: committer,
+ tree: cherry_pick_tree_id,
+ parents: [rugged.lookup(source_sha)],
+ update_ref: ref)
+ end
+ end
+
def check_revert_content(commit, base_branch)
source_sha = find_branch(base_branch).target
args = [commit.id, source_sha]
@@ -767,6 +794,20 @@ class Repository
tree_id
end
+ def check_cherry_pick_content(commit, base_branch)
+ source_sha = find_branch(base_branch).target
+ args = [commit.id, source_sha]
+ args << 1 if commit.merge_commit?
+
+ cherry_pick_index = rugged.cherrypick_commit(*args)
+ return false if cherry_pick_index.conflicts?
+
+ tree_id = cherry_pick_index.write_tree(rugged)
+ return false unless diff_exists?(source_sha, tree_id)
+
+ tree_id
+ end
+
def diff_exists?(sha1, sha2)
rugged.diff(sha1, sha2).size > 0
end
@@ -797,7 +838,7 @@ class Repository
def search_files(query, ref)
offset = 2
- args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
+ args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 721273250ea..2645b8321d7 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -32,6 +32,7 @@ class Service < ActiveRecord::Base
default_value_for :tag_push_events, true
default_value_for :note_events, true
default_value_for :build_events, true
+ default_value_for :wiki_page_events, true
after_initialize :initialize_properties
@@ -53,6 +54,7 @@ class Service < ActiveRecord::Base
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
+ scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
default_value_for :category, 'common'
@@ -94,7 +96,7 @@ class Service < ActiveRecord::Base
end
def supported_events
- %w(push tag_push issue merge_request)
+ %w(push tag_push issue merge_request wiki_page)
end
def execute(data)
diff --git a/app/models/user.rb b/app/models/user.rb
index 031315debd7..ab48f8f1960 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -63,7 +63,6 @@
#
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class User < ActiveRecord::Base
extend Gitlab::ConfigHelper
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 526760779a4..3d5fd9d3ee9 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -29,6 +29,10 @@ class WikiPage
# new Page values before writing to the Gollum repository.
attr_accessor :attributes
+ def hook_attrs
+ attributes
+ end
+
def initialize(wiki, page = nil, persisted = false)
@wiki = wiki
@page = page
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
new file mode 100644
index 00000000000..6b69cb53b2c
--- /dev/null
+++ b/app/services/commits/change_service.rb
@@ -0,0 +1,47 @@
+module Commits
+ class ChangeService < ::BaseService
+ class ValidationError < StandardError; end
+ class ChangeError < StandardError; end
+
+ def execute
+ @source_project = params[:source_project] || @project
+ @target_branch = params[:target_branch]
+ @commit = params[:commit]
+ @create_merge_request = params[:create_merge_request].present?
+
+ check_push_permissions unless @create_merge_request
+ commit
+ rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
+ ValidationError, ChangeError => ex
+ error(ex.message)
+ end
+
+ def commit
+ raise NotImplementedError
+ end
+
+ private
+
+ def check_push_permissions
+ allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
+
+ unless allowed
+ raise ValidationError.new('You are not allowed to push into this branch')
+ end
+
+ true
+ end
+
+ def create_target_branch(new_branch)
+ # Temporary branch exists and contains the change commit
+ return success if repository.find_branch(new_branch)
+
+ result = CreateBranchService.new(@project, current_user)
+ .execute(new_branch, @target_branch, source_project: @source_project)
+
+ if result[:status] == :error
+ raise ChangeError, "There was an error creating the source branch: #{result[:message]}"
+ end
+ end
+ end
+end
diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb
new file mode 100644
index 00000000000..f9a4efa7182
--- /dev/null
+++ b/app/services/commits/cherry_pick_service.rb
@@ -0,0 +1,19 @@
+module Commits
+ class CherryPickService < ChangeService
+ def commit
+ cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch
+ cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch)
+
+ if cherry_pick_tree_id
+ create_target_branch(cherry_pick_into) if @create_merge_request
+
+ repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id)
+ success
+ else
+ error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically.
+ It may have already been cherry-picked, or a more recent commit may have updated some of its content."
+ raise ChangeError, error_msg
+ end
+ end
+ end
+end
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
index a3c950ede1f..c7de9f6f35e 100644
--- a/app/services/commits/revert_service.rb
+++ b/app/services/commits/revert_service.rb
@@ -1,21 +1,5 @@
module Commits
- class RevertService < ::BaseService
- class ValidationError < StandardError; end
- class ReversionError < StandardError; end
-
- def execute
- @source_project = params[:source_project] || @project
- @target_branch = params[:target_branch]
- @commit = params[:commit]
- @create_merge_request = params[:create_merge_request].present?
-
- check_push_permissions unless @create_merge_request
- commit
- rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
- ValidationError, ReversionError => ex
- error(ex.message)
- end
-
+ class RevertService < ChangeService
def commit
revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
revert_tree_id = repository.check_revert_content(@commit, @target_branch)
@@ -26,34 +10,10 @@ module Commits
repository.revert(current_user, @commit, revert_into, revert_tree_id)
success
else
- error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically.
+ error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically.
It may have already been reverted, or a more recent commit may have updated some of its content."
- raise ReversionError, error_msg
+ raise ChangeError, error_msg
end
end
-
- private
-
- def create_target_branch(new_branch)
- # Temporary branch exists and contains the revert commit
- return success if repository.find_branch(new_branch)
-
- result = CreateBranchService.new(@project, current_user)
- .execute(new_branch, @target_branch, source_project: @source_project)
-
- if result[:status] == :error
- raise ReversionError, "There was an error creating the source branch: #{result[:message]}"
- end
- end
-
- def check_push_permissions
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
-
- unless allowed
- raise ValidationError.new('You are not allowed to push into this branch')
- end
-
- true
- end
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index dc74c02760b..1e1be8cd04b 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -73,6 +73,7 @@ class GitPushService < BaseService
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
EventCreateService.new.push(@project, current_user, build_push_data)
+ SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
@@ -138,6 +139,11 @@ class GitPushService < BaseService
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
end
+ def build_push_data_system_hook
+ @push_data_system ||= Gitlab::PushDataBuilder.
+ build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
+ end
+
def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index c88c7672805..64271d8bc5c 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -1,16 +1,16 @@
-class GitTagPushService
- attr_accessor :project, :user, :push_data
+class GitTagPushService < BaseService
+ attr_accessor :push_data
- def execute(project, user, oldrev, newrev, ref)
+ def execute
project.repository.before_push_tag
- @project, @user = project, user
- @push_data = build_push_data(oldrev, newrev, ref)
+ @push_data = build_push_data
- EventCreateService.new.push(project, user, @push_data)
+ EventCreateService.new.push(project, current_user, @push_data)
+ SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
- CreateCommitBuildsService.new.execute(project, @user, @push_data)
+ CreateCommitBuildsService.new.execute(project, current_user, @push_data)
ProjectCacheWorker.perform_async(project.id)
true
@@ -18,14 +18,14 @@ class GitTagPushService
private
- def build_push_data(oldrev, newrev, ref)
+ def build_push_data
commits = []
message = nil
- if !Gitlab::Git.blank_ref?(newrev)
- tag_name = Gitlab::Git.ref_name(ref)
+ if !Gitlab::Git.blank_ref?(params[:newrev])
+ tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name)
- if tag && tag.target == newrev
+ if tag && tag.target == params[:newrev]
commit = project.commit(tag.target)
commits = [commit].compact
message = tag.message
@@ -33,6 +33,11 @@ class GitTagPushService
end
Gitlab::PushDataBuilder.
- build(project, user, oldrev, newrev, ref, commits, message)
+ build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
+ end
+
+ def build_system_push_data
+ Gitlab::PushDataBuilder.
+ build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 18f76d3f650..2b16089df1b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -37,8 +37,9 @@ class IssuableBaseService < BaseService
end
def filter_params(issuable_ability_name = :issue)
- params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
- params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
+ filter_assignee
+ filter_milestone
+ filter_labels
ability = :"admin_#{issuable_ability_name}"
@@ -49,6 +50,29 @@ class IssuableBaseService < BaseService
end
end
+ def filter_assignee
+ if params[:assignee_id] == IssuableFinder::NONE
+ params[:assignee_id] = ''
+ end
+ end
+
+ def filter_milestone
+ milestone_id = params[:milestone_id]
+ return unless milestone_id
+
+ if milestone_id == IssuableFinder::NONE ||
+ project.milestones.find_by(id: milestone_id).nil?
+ params[:milestone_id] = ''
+ end
+ end
+
+ def filter_labels
+ return if params[:label_ids].to_a.empty?
+
+ params[:label_ids] =
+ project.labels.where(id: params[:label_ids]).pluck(:id)
+ end
+
def update(issuable)
change_state(issuable)
filter_params
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 770f32de944..772f5c5fffa 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -3,7 +3,7 @@ module Issues
def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user)
- issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
+ issue_url = Gitlab::UrlBuilder.build(issue)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue_data
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index ac5b58db862..e6837a18696 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -20,8 +20,7 @@ module MergeRequests
def hook_data(merge_request, action)
hook_data = merge_request.to_hook_data(current_user)
- merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id)
- hook_data[:object_attributes][:url] = merge_request_url
+ hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
hook_data[:object_attributes][:action] = action
hook_data
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 6e9152e444e..fa34753c4fd 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -51,7 +51,7 @@ module MergeRequests
# be interpreted as the use wants to close that issue on this project
# Pattern example: 112-fix-mep-mep
# Will lead to appending `Closes #112` to the description
- if match = merge_request.source_branch.match(/-(\d+)\z/)
+ if match = merge_request.source_branch.match(/\A(\d+)-/)
iid = match[1]
closes_issue = "Closes ##{iid}"
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index a0973c5d260..3b7c36f0908 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -26,7 +26,9 @@ module Projects
GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure
- @project.update_column(:pushes_since_gc, 0)
+ Gitlab::Metrics.measure(:reset_pushes_since_gc) do
+ @project.update_column(:pushes_since_gc, 0)
+ end
end
def needed?
@@ -34,14 +36,18 @@ module Projects
end
def increment!
- @project.increment!(:pushes_since_gc)
+ Gitlab::Metrics.measure(:increment_pushes_since_gc) do
+ @project.increment!(:pushes_since_gc)
+ end
end
private
def try_obtain_lease
- lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
- lease.try_obtain
+ Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
+ lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
+ lease.try_obtain
+ end
end
end
end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 0004a399f47..02c4eee3d02 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -1,28 +1,37 @@
module Projects
class ParticipantsService < BaseService
- def execute(note_type, note_id)
- participating =
- if note_type && note_id
- participants_in(note_type, note_id)
- else
- []
- end
+ def execute(noteable_type, noteable_id)
+ @noteable_type = noteable_type
+ @noteable_id = noteable_id
project_members = sorted(project.team.members)
- participants = all_members + groups + project_members + participating
+ participants = target_owner + participants_in_target + all_members + groups + project_members
participants.uniq
end
- def participants_in(type, id)
- target =
- case type
+ def target
+ @target ||=
+ case @noteable_type
when "Issue"
- project.issues.find_by_iid(id)
+ project.issues.find_by_iid(@noteable_id)
when "MergeRequest"
- project.merge_requests.find_by_iid(id)
+ project.merge_requests.find_by_iid(@noteable_id)
when "Commit"
- project.commit(id)
+ project.commit(@noteable_id)
+ else
+ nil
end
-
+ end
+
+ def target_owner
+ return [] unless target && target.author.present?
+
+ [{
+ name: target.author.name,
+ username: target.author.username
+ }]
+ end
+
+ def participants_in_target
return [] unless target
users = target.participants(current_user)
@@ -30,13 +39,13 @@ module Projects
end
def sorted(users)
- users.uniq.to_a.compact.sort_by(&:username).map do |user|
+ users.uniq.to_a.compact.sort_by(&:username).map do |user|
{ username: user.username, name: user.name }
end
end
def groups
- current_user.authorized_groups.sort_by(&:path).map do |group|
+ current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
{ username: group.path, name: group.name, count: count }
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 2e734654466..111b3ec05ea 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -34,8 +34,11 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
- # Apply new namespace id
+ project.expire_caches_before_rename(old_path)
+
+ # Apply new namespace id and visibility level
project.namespace = new_namespace
+ project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
project.save!
# Notifications
@@ -56,7 +59,7 @@ module Projects
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
project.old_path_with_namespace = old_path
-
+
SystemHooksService.new.execute_hooks_for(project, :transfer)
true
end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index f0615ec7420..e43b5b51e5b 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -3,17 +3,13 @@ class SystemHooksService
execute_hooks(build_event_data(model, event))
end
- private
-
- def execute_hooks(data)
- SystemHook.all.each do |sh|
- async_execute_hook(sh, data, 'system_hooks')
+ def execute_hooks(data, hooks_scope = :all)
+ SystemHook.send(hooks_scope).each do |hook|
+ hook.async_execute(data, 'system_hooks')
end
end
- def async_execute_hook(hook, data, hook_name)
- Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name)
- end
+ private
def build_event_data(model, event)
data = {
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 658b086496f..82a0e2fd1f5 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -222,7 +222,7 @@ class SystemNoteService
# Called when a branch is created from the 'new branch' button on a issue
# Example note text:
#
- # "Started branch `issue-branch-button-201`"
+ # "Started branch `201-issue-branch-button`"
def self.new_issue_branch(issue, project, author, branch)
h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
new file mode 100644
index 00000000000..9162f128602
--- /dev/null
+++ b/app/services/wiki_pages/base_service.rb
@@ -0,0 +1,27 @@
+module WikiPages
+ class BaseService < ::BaseService
+
+ def hook_data(page, action)
+ hook_data = {
+ object_kind: page.class.name.underscore,
+ user: current_user.hook_attrs,
+ project: @project.hook_attrs,
+ object_attributes: page.hook_attrs,
+ # DEPRECATED
+ repository: @project.hook_attrs.slice(:name, :url, :description, :homepage)
+ }
+
+ page_url = Gitlab::UrlBuilder.build(page)
+ hook_data[:object_attributes].merge!(url: page_url, action: action)
+ hook_data
+ end
+
+ private
+
+ def execute_hooks(page, action = 'create')
+ page_data = hook_data(page, action)
+ @project.execute_hooks(page_data, :wiki_page_hooks)
+ @project.execute_services(page_data, :wiki_page_hooks)
+ end
+ end
+end
diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb
new file mode 100644
index 00000000000..988c663b9d0
--- /dev/null
+++ b/app/services/wiki_pages/create_service.rb
@@ -0,0 +1,13 @@
+module WikiPages
+ class CreateService < WikiPages::BaseService
+ def execute
+ page = WikiPage.new(@project.wiki)
+
+ if page.create(@params)
+ execute_hooks(page, 'create')
+ end
+
+ page
+ end
+ end
+end
diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb
new file mode 100644
index 00000000000..8f6a50da838
--- /dev/null
+++ b/app/services/wiki_pages/update_service.rb
@@ -0,0 +1,11 @@
+module WikiPages
+ class UpdateService < WikiPages::BaseService
+ def execute(page)
+ if page.update(@params[:content], @params[:format], @params[:message])
+ execute_hooks(page, 'update')
+ end
+
+ page
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index a8cca1a81cb..e0d8d16a954 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -26,7 +26,9 @@
.btn-group{ data: data_attrs }
- restricted_level_checkboxes('restricted-visibility-help').each do |level|
= level
- %span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets
+ %span.help-block#restricted-visibility-help
+ Selected levels cannot be used by non-admin users for projects or snippets.
+ If the public level is restricted, user profiles are only visible to logged in users.
.form-group
= f.label :import_sources, class: 'control-label col-sm-2'
.col-sm-10
@@ -153,7 +155,11 @@
= f.label :shared_runners_enabled do
= f.check_box :shared_runners_enabled
Enable shared runners for new projects
-
+ .form-group
+ = f.label :shared_runners_text, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_area :shared_runners_text, class: 'form-control', rows: 4
+ .help-block Markdown enabled
.form-group
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
.col-sm-10
@@ -212,6 +218,13 @@
.help-block
The sampling interval in seconds. Sampled data includes memory usage,
retained Ruby objects, file descriptors and so on.
+ .form-group
+ = f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :metrics_packet_size, class: 'form-control'
+ .help-block
+ The amount of points to store in a single UDP packet. More points
+ results in fewer but larger UDP packets being sent.
%fieldset
%legend Spam and Anti-bot Protection
@@ -271,5 +284,24 @@
.col-sm-10
= f.text_field :sentry_dsn, class: 'form-control'
+ %fieldset
+ %legend Repository Checks
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :repository_checks_enabled do
+ = f.check_box :repository_checks_enabled
+ Enable Repository Checks
+ .help-block
+ GitLab will periodically run
+ %a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
+ in all project and wiki repositories to look for silent disk corruption issues.
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ = link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
+ .help-block
+ If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
+
+
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index ad952052f25..67d23c80233 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -17,6 +17,27 @@
.col-sm-10
= f.text_field :url, class: "form-control"
.form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ .col-sm-10.prepend-top-10
+ %div
+ System hook will be triggered on set of events like creating project
+ or adding ssh key. But you can also enable extra triggers like Push events.
+
+ %div.prepend-top-default
+ = f.check_box :push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :push_events, class: 'list-label' do
+ %strong Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ %div
+ = f.check_box :tag_push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :tag_push_events, class: 'list-label' do
+ %strong Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
+ .form-group
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
.col-sm-10
.checkbox
@@ -31,13 +52,16 @@
.panel.panel-default
.panel-heading
System hooks (#{@hooks.count})
- %ul.well-list
+ %ul.content-list
- @hooks.each do |hook|
%li
- .list-item-name
- %strong= hook.url
- %p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
-
- .pull-right
+ .controls
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
+ .monospace= hook.url
+ %div
+ - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
+ - if hook.send(trigger)
+ %span.label.label-gray= trigger.titleize
+ %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index af9fdeb0734..4b475a4d8fa 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,6 +1,7 @@
- page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
- Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
+ Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
+ Gitlab::RepositoryCheckLogger]
%ul.nav-links.log-tabs
- loggers.each do |klass|
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index d39c0f44031..aa07afa0d62 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -3,7 +3,7 @@
.row.prepend-top-default
%aside.col-md-3
- .admin-filter
+ .panel.admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do
.form-group
= label_tag :name, 'Name:'
@@ -38,7 +38,13 @@
%span.descr
= visibility_level_icon(level)
= label
- %hr
+ %fieldset
+ %strong Problems
+ .checkbox
+ = label_tag :last_repository_check_failed do
+ = check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed]
+ %span Last repository check failed
+
= hidden_field_tag :sort, params[:sort]
= button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index c638c32a654..73986d21bcf 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -5,6 +5,16 @@
%i.fa.fa-pencil-square-o
Edit
%hr
+- if @project.last_repository_check_failed?
+ .row
+ .col-md-12
+ .panel
+ .panel-heading.alert.alert-danger
+ Last repository check
+ = "(#{time_ago_in_words(@project.last_repository_check_at)} ago)"
+ failed. See
+ = link_to 'repocheck.log', admin_logs_path
+ for error messages.
.row
.col-md-6
.panel.panel-default
@@ -95,6 +105,32 @@
.col-sm-offset-2.col-sm-10
= f.submit 'Transfer', class: 'btn btn-primary'
+ .panel.panel-default.repository-check
+ .panel-heading
+ Repository check
+ .panel-body
+ = form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f|
+ .form-group
+ - if @project.last_repository_check_at.nil?
+ This repository has never been checked.
+ - else
+ This repository was last checked
+ = @project.last_repository_check_at.to_s(:medium) + '.'
+ The check
+ - if @project.last_repository_check_failed?
+ = succeed '.' do
+ %strong.cred failed
+ See
+ = link_to 'repocheck.log', admin_logs_path
+ for error messages.
+ - else
+ passed.
+
+ = link_to icon('question-circle'), help_page_path('administration', 'repository_checks')
+
+ .form-group
+ = f.submit 'Trigger repository check', class: 'btn btn-primary'
+
.col-md-6
- if @group
.panel.panel-default
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index b05fdbd5552..fe0b9d3a491 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -7,17 +7,17 @@
.form-group
= f.label :name, class: 'control-label'
.col-sm-10
- = f.text_field :name, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group
= f.label :username, class: 'control-label'
.col-sm-10
- = f.text_field :username, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group
= f.label :email, class: 'control-label'
.col-sm-10
- = f.text_field :email, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
- if @user.new_record?
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f9ec3a89158..49ab8aad1d5 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -45,6 +45,7 @@
.prepend-top-default
- if @todos.any?
+ .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group|
.panel.panel-default.panel-small.js-todos-list
- project = group[0]
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
new file mode 100644
index 00000000000..3c3830a3f10
--- /dev/null
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -0,0 +1,10 @@
+.well-confirmation.text-center
+ %h1.prepend-top-0
+ Almost there...
+ %p.lead
+ Please check your email to confirm your account
+%p.confirmation-content.text-center
+ No confirmation email received? Please check your spam folder or
+.append-bottom-20.prepend-top-20.text-center
+ %a.btn.btn-lg.btn-success{ href: new_user_confirmation_path }
+ Request new confirmation email
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index cb93ff2465e..e5607dacd0d 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -6,18 +6,17 @@
.login-heading
%h3 Create an account
.login-body
- - user = params[:user].present? ? params[:user] : {}
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
.devise-errors
= devise_error_messages!
%div
- = f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true
+ = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
%div
- = f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true
+ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div
- = f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true
+ = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
.form-group.append-bottom-20#password-strength
- = f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true
+ = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
%div
- if current_application_settings.recaptcha_enabled
= recaptcha_tags
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 55f4a6f287d..79df17ba612 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Applications"
-- header_title page_title, applications_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
@@ -68,7 +67,7 @@
%td= app.name
%td= token.created_at
%td= token.scopes
- %td= render 'delete_form', application: app
+ %td= render 'doorkeeper/authorized_applications/delete_form', application: app
- @authorized_anonymous_tokens.each do |token|
%tr
%td
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 4d20dd5830e..5d622582088 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -4,7 +4,12 @@
#{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.2"] do
- = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - if event.author
+ = link_to user_path(event.author.username) do
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - else
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 4290e0bf72e..7d9d27ae1fc 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -8,7 +8,7 @@
This will create milestone in every selected project
%hr
-= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f|
+= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
.row
- if @milestone.errors.any?
#error_explanation
@@ -27,7 +27,7 @@
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
.clearfix
.error-alert
.form-group
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index da3c3711cdd..70e88da7aae 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -21,7 +21,7 @@
%tr
%td.shortcut
.key ?
- %td Show this dialog
+ %td Show/hide this dialog
%tr
%td.shortcut
- if browser.mac?
@@ -169,6 +169,10 @@
%td.shortcut
.key t
%td Go to finding file
+ %tr
+ %td.shortcut
+ .key i
+ %td New issue
.col-lg-4
%table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' }
@@ -241,6 +245,10 @@
%td.shortcut
.key e
%td Edit issue
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
%tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
%tr
%th
@@ -261,3 +269,7 @@
%td.shortcut
.key e
%td Edit merge request
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index d084559abc3..f12df5c8ffe 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -345,11 +345,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.example
%div
@@ -372,11 +372,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.dropdown-page-two
.dropdown-title
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml
index d8af0295b2d..dfebf7768d9 100644
--- a/app/views/import/base/create.js.haml
+++ b/app/views/import/base/create.js.haml
@@ -20,10 +20,10 @@
job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target")
target_field.empty()
- target_field.append('<strong>#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}</strong>')
+ target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
$("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
- else
:plain
job = $("tr#repo_#{@repo_id}")
- job.find(".import-actions").html("<i class='fa fa-exclamation-circle'> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}</i>")
+ job.find(".import-actions").html("<i class='fa fa-exclamation-circle'></i> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}")
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index aec2e836c9f..6e993e58f0d 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -10,13 +10,19 @@
%hr
%p
- if @incompatible_repos.any?
- = button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all compatible projects
+ = icon("spinner spin", class: "loading-icon")
- else
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From Bitbucket
@@ -28,7 +34,7 @@
%td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -47,7 +53,9 @@
%td.import-target
= "#{repo["owner"]}/#{repo["slug"]}"
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
%td
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index 6ee16c8be4b..d3d3c595c17 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -13,10 +13,15 @@
how FogBugz email addresses and usernames are imported into GitLab.
%hr
%p
- = button_tag 'Import all projects', class: 'btn btn-success js-import-all'
+ = button_tag class: 'btn btn-import btn-success js-import-all' do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From FogBugz
@@ -28,7 +33,7 @@
%td
= project.import_source
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -47,7 +52,9 @@
%td.import-target
= "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 1416ee5bd5a..5b7f11440c1 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -8,10 +8,15 @@
Select projects you want to import.
%hr
%p
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From GitHub
@@ -21,9 +26,9 @@
- @already_added_projects.each do |project|
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
%td
- = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
+ = github_project_link(project.import_source)
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -38,11 +43,13 @@
- @repos.each do |repo|
%tr{id: "repo_#{repo.id}"}
%td
- = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
+ = github_project_link(repo.full_name)
%td.import-target
= repo.full_name
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index 911a55eb85d..e3a356b5379 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -8,10 +8,15 @@
Select projects you want to import.
%hr
%p
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From GitLab.com
@@ -23,7 +28,7 @@
%td
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -42,7 +47,9 @@
%td.import-target
= repo["path_with_namespace"]
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 6b0fa1edf8c..267eee4f262 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -8,10 +8,15 @@
Select projects you want to import.
%hr
%p
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From Gitorious.org
@@ -23,7 +28,7 @@
%td
= link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -42,7 +47,9 @@
%td.import-target
= repo.full_name
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 175ef6921cd..5ada6b174eb 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -14,12 +14,19 @@
%hr
%p
- if @incompatible_repos.any?
- = button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all compatible projects
+ = icon("spinner spin", class: "loading-icon")
- else
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From Google Code
@@ -31,7 +38,7 @@
%td
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -50,7 +57,9 @@
%td.import-target
= "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo.id}"}
%td
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c799e9c588d..ca9c2a0bf2e 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -25,6 +25,10 @@
.content-wrapper
= render "layouts/flash"
= yield :flash_message
+ - if defined?(nav) && nav
+ .layout-nav
+ %div{ class: container_class }
+ = render "layouts/nav/#{nav}"
%div{ class: (container_class unless @no_container) }
.content
.clearfix
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index babfb032236..e4d1c773d03 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -6,6 +6,6 @@
= yield :scripts_body_top
= render "layouts/header/default", title: header_title
- = render 'layouts/page', sidebar: sidebar
+ = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
new file mode 100644
index 00000000000..7c061dd531f
--- /dev/null
+++ b/app/views/layouts/devise_empty.html.haml
@@ -0,0 +1,17 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head"
+ %body.ui_charcoal.login-page.application.navless
+ = render "layouts/header/empty"
+ = render "layouts/broadcast"
+ .container.navless-container
+ .content
+ = render "layouts/flash"
+ = yield
+
+ %hr
+ .container
+ .footer-links
+ = link_to "Explore", explore_root_path
+ = link_to "Help", help_path
+ = link_to "About GitLab", "https://about.gitlab.com/"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 44339293095..3beb8ff7c0d 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -8,7 +8,7 @@
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
- = render 'layouts/search'
+ = render 'layouts/search' unless current_controller?(:search)
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5cef652da14..ca49c313ff7 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -48,8 +48,7 @@
%span
Help
- %li.separate-item
- = nav_link(controller: :profile) do
+ = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 3b9d31a6fc5..d730840d63a 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,17 +1,9 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
-
+%ul.nav-links
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
- Profile Settings
+ Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
@@ -27,7 +19,6 @@
= icon('envelope-o fw')
%span
Emails
- %span.count= number_with_delimiter(current_user.emails.count + 1)
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
@@ -45,7 +36,6 @@
= icon('key fw')
%span
SSH Keys
- %span.count= number_with_delimiter(current_user.keys.count)
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index f4797a85bb7..6c3f8efc74e 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -84,7 +84,7 @@
Merge Requests
%span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- - if project_nav_tab? :settings
+ - if project_nav_tab? :team
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
@@ -131,3 +131,8 @@
%li.hidden
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
Network
+
+ -# Shortcut to create a new issue
+ %li.hidden
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ Create a new issue
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index dfa6cc5702e..b77d3402a2e 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,6 @@
- page_title "Profile Settings"
- header_title "Profile Settings", profile_path unless header_title
-- sidebar "profile"
+- sidebar "dashboard"
+- nav "profile"
= render template: "layouts/application"
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 574e8bfef24..81c7c88fc96 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
+ = "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index 59db86b08bc..b435067d5a6 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
+= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml
index c9bf04f514e..41a320d6bd8 100644
--- a/app/views/notify/merge_request_status_email.html.haml
+++ b/app/views/notify/merge_request_status_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}"
+ = "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index b96dd0fd8ab..7a5074a1dc3 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}"
+= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index 6762fae7f64..fbe506d4f4d 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request ##{@merge_request.iid} was merged"
+ = "Merge Request #{@merge_request.to_reference} was merged"
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 34dbc60e19b..bfbae01094f 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request ##{@merge_request.iid} was merged"
+= "Merge Request #{@merge_request.to_reference} was merged"
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index bdcca6e4ab7..d4aad8d1862 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-New Merge Request #<%= @merge_request.iid %>
+New Merge Request <%= @merge_request.to_reference %>
<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb
index 1d1411992a6..8cdab63829e 100644
--- a/app/views/notify/note_merge_request_email.text.erb
+++ b/app/views/notify/note_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-New comment for Merge Request <%= @merge_request.iid %>
+New comment for Merge Request <%= @merge_request.to_reference %>
<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 6efd119f260..afd3d79321f 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Account"
-- header_title page_title, profile_account_path
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index f630c03e5f6..9c404b6935f 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,5 +1,4 @@
- page_title "Audit Log"
-- header_title page_title, audit_log_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 3f328f96cea..57527361eb6 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Emails"
-- header_title page_title, profile_emails_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index e0f8c9a5733..6a067a03535 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,5 +1,4 @@
- page_title "SSH Keys"
-- header_title page_title, profile_keys_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index a2a505c082b..7696f112bb3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Notifications"
-- header_title page_title, profile_notifications_path
%div
- if @user.errors.any?
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 5ac8a8b9d09..243428b690e 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Password"
-- header_title page_title, edit_profile_password_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index f80211669fb..bfe53be6854 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,5 +1,4 @@
- page_title 'Preferences'
-- header_title page_title, profile_preferences_path
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 961b61d2e76..48b0dd6b121 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -9,4 +9,7 @@
= spinner
:javascript
- new Activities();
+ var activity = new Activities();
+ $(document).on('page:restore', function (event) {
+ activity.reloadActivities()
+ })
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index 9ae6964aaac..0de019983ca 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -52,6 +52,12 @@
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
+ %li
+ gcovr (C/C++) -
+ %code ^TOTAL.*\s+(\d+\%)$
+ %li
+ tap --coverage-report=text-summary (Node.js) -
+ %code ^Statements\s*:\s*([^%]+)
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 7a78d61a611..8de44a6c914 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,6 +1,6 @@
.md-area
.md-header
- %ul.nav-links
+ %ul.nav-links.clearfix
%li.active
%a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
Write
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index d1191928d4f..a9908eaecca 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -9,7 +9,7 @@
- else
.gray-content-block.second-block.center
%h3.page-title
- This project does not have README yet
+ This project does not have a README yet
- if can?(current_user, :push_code, @project)
%p
A
@@ -18,5 +18,5 @@
distributed with computer software, forming part of its documentation.
%p
We recommend you to
- = link_to "add README", new_readme_path, class: 'underlined-link'
+ = link_to "add a README", new_readme_path, class: 'underlined-link'
file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index bddff5cdcbc..e1e35013968 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,8 +1,8 @@
.zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area'
- if defined?(f) && f
- = f.text_area attr, class: classes, placeholder: "Write a comment or drag your files here..."
+ = f.text_area attr, class: classes, placeholder: placeholder
- else
- = text_area_tag attr, nil, class: classes, placeholder: "Write a comment or drag your files here..."
+ = text_area_tag attr, nil, class: classes, placeholder: placeholder
%a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" }
= icon('compress')
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index f8b6fa253c4..fefa652a3da 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -13,7 +13,11 @@
required: true, class: 'form-control new-file-name'
.pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
+ .license-selector.js-license-selector.hide
+ = select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name}
+
+ .encoding-selector
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
.file-content.code
%pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index abcfca4cd11..5926d181ba3 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -1,20 +1,20 @@
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
- %tr.line_holder{ id: @form.since }
- = render "projects/diffs/match_line", {line: @match_line,
- line_old: @form.since, line_new: @form.since, bottom: false, new_file: false}
+ %tr.line_holder
+ = render "projects/diffs/match_line", { line: @match_line,
+ line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
- %tr.line_holder
- %td.old_line.diff-line-num{data: {linenumber: line_old}}
- = link_to raw(line_old), "#"
- %td.new_line.diff-line-num
- = link_to raw(line_new) , "#"
+ %tr.line_holder{ id: line_old }
+ %td.old_line.diff-line-num{ data: { linenumber: line_old } }
+ = link_to raw(line_old), "##{line_old}"
+ %td.new_line.diff-line-num{ data: { linenumber: line_old } }
+ = link_to raw(line_new) , "##{line_old}"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
- = render "projects/diffs/match_line", {line: @match_line,
- line_old: @form.to, line_new: @form.to, bottom: true, new_file: false}
+ = render "projects/diffs/match_line", { line: @match_line,
+ line_old: @form.to, line_new: @form.to, bottom: true, new_file: false }
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 1dd2b5c0af7..0459699432e 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -14,5 +14,5 @@
cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
:javascript
- blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
+ blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}")
new NewCommitForm($('.js-new-blob-form'))
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index aa85f495e39..0406fc21d77 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -58,6 +58,6 @@
%th Coverage
%th
- = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
+ = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= paginate @builds, theme: 'gitlab'
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index b50b2cf3764..99d72aa7935 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -10,7 +10,7 @@
- merge_request = @build.merge_request
- if merge_request
via
- = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
+ = link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
#up-build-trace
- builds = @build.commit.builds.latest.to_a
@@ -110,7 +110,7 @@
= icon('folder-open')
Browse
- .build-widget
+ .build-widget.build-controls
%h4.title
Build ##{@build.id}
- if can?(current_user, :update_build, @project)
@@ -127,6 +127,9 @@
data: { confirm: 'Are you sure you want to erase this build?' } do
= icon('eraser')
Erase
+ - if @build.has_trace?
+ = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build),
+ class: 'btn btn-sm btn-success', target: '_blank'
.clearfix
- if @build.duration
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 7ded4828b2f..745ff461d76 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -21,7 +21,7 @@
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
- - if !defined?(ref) || ref
+ - if defined?(ref) && ref
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
diff --git a/app/views/projects/commit/_revert.html.haml b/app/views/projects/commit/_change.html.haml
index 52ca3ed5b14..44ef1fdbbe3 100644
--- a/app/views/projects/commit/_revert.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -1,13 +1,21 @@
-#modal-revert-commit.modal
+- case type.to_s
+- when 'revert'
+ - label = 'Revert'
+ - target_label = 'Revert in branch'
+- when 'cherry-pick'
+ - label = 'Cherry-pick'
+ - target_label = 'Pick into branch'
+
+.modal{id: "modal-#{type}-commit"}
.modal-dialog
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3.page-title== Revert this #{revert_commit_type(commit)}
+ %h3.page-title== #{label} this #{commit.change_type_title}
.modal-body
- = form_tag revert_namespace_project_commit_path(@project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
+ = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do
.form-group.branch
- = label_tag 'target_branch', 'Revert in branch', class: 'control-label'
+ = label_tag 'target_branch', target_label, class: 'control-label'
.col-sm-10
= select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch"
- if can?(current_user, :push_code, @project)
@@ -20,7 +28,7 @@
- else
= hidden_field_tag 'create_merge_request', 1
.form-actions
- = submit_tag "Revert", class: 'btn btn-create'
+ = submit_tag label, class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project)
@@ -28,4 +36,4 @@
= commit_in_fork_help
:javascript
- new NewCommitForm($('.js-create-dir-form'))
+ new NewCommitForm($('.js-#{type}-form'))
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 9cb14b6a90f..b21acac757d 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -18,6 +18,7 @@
Browse Files
- unless @commit.has_been_reverted?(current_user)
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
+ = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
%div
%p
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 096f7058bd4..e5e3d696035 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -13,4 +13,5 @@
diff_refs: @diff_refs
= render "projects/notes/notes_with_form"
- if can_collaborate_with_project?
- = render "projects/commit/revert", commit: @commit, title: @commit.title
+ - %w(revert cherry-pick).each do |type|
+ = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index b231b584eab..655cb0ac3cb 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -10,7 +10,7 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title
- %span.item-title.str-truncated
+ %span.item-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
@@ -30,5 +30,5 @@
by
= commit_author_link(commit, avatar: true, size: 24)
.committed_ago
- #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
+ #{time_ago_with_tooltip(commit.committed_date)} &nbsp;
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index c52cf25d40a..bcdb09208aa 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -39,4 +39,4 @@
= spinner
:javascript
- CommitsList.init("#{@ref}", #{@limit});
+ CommitsList.init(#{@limit});
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 9464c8dc996..107097ad963 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,26 +1,26 @@
- type = line.type
-%tr.line_holder{id: line_code, class: type}
+%tr.line_holder{ id: line_code, class: type }
- case type
- when 'match'
- = render "projects/diffs/match_line", {line: line.text,
- line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
+ = render "projects/diffs/match_line", { line: line.text,
+ line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file }
- when 'nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
- %td.old_line.diff-line-num{class: type}
- - link_text = raw(type == "new" ? "&nbsp;" : line.old_pos)
+ %td.old_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
+ - link_text = type == "new" ? "&nbsp;".html_safe : line.old_pos
- if defined?(plain) && plain
= link_text
- else
- = link_to link_text, "##{line_code}", id: line_code
+ = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code)
- %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
- - link_text = raw(type == "old" ? "&nbsp;" : line.new_pos)
+ %td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
+ - link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
- if defined?(plain) && plain
= link_text
- else
- = link_to link_text, "##{line_code}", id: line_code
- %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
+ = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
+ %td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code } }= diff_line_content(line.text, type)
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index d7c49068745..81948513e43 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -14,11 +14,11 @@
%td.new_line.diff-line-num
%td.line_content.parallel.match= left[:text]
- else
- %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"}
+ %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old')
- %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
+ %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
- if right[:type] == 'new'
- new_line_class = 'new'
@@ -27,11 +27,11 @@
- new_line_class = nil
- new_line_code = left[:line_code]
- %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }}
+ %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
= link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(right[:line_code], 'new')
- %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
+ %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
- if @reply_allowed
- comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 6d872cd0b21..76a4f41193c 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -210,6 +210,7 @@
%li Be careful. Changing the project's namespace can have unintended side effects.
%li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
+ %li Project visibility level will be changed to match namespace rules when transfering to a group.
.form-actions
= f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 6ad7b05155a..52d093871b4 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -14,8 +14,10 @@
%p
If you already have files you can push them using command line instructions below.
%p
- Otherwise you can start with
- = link_to "adding README", new_readme_path, class: 'underlined-link'
+ Otherwise you can start with adding a
+ = link_to "README", new_readme_path, class: 'underlined-link'
+ or a
+ = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
file to this project.
- if can?(current_user, :push_code, @project)
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index c15386b4883..f21c864e35c 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -15,12 +15,13 @@
- if defined?(commit_sha) && commit_sha
%td
= link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
-
- %td
- - if generic_commit_status.ref
- = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
- - else
- .light none
+
+ - if defined?(ref) && ref
+ %td
+ - if generic_commit_status.ref
+ = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+ - else
+ .light none
- if defined?(runner) && runner
%td
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index e39224d86c6..aae3abcad4b 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -74,16 +74,15 @@
.panel.panel-default
.panel-heading
Webhooks (#{@hooks.count})
- %ul.well-list
+ %ul.content-list
- @hooks.each do |hook|
%li
- .pull-right
+ .controls
= link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
= link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
- .clearfix
- %span.monospace= hook.url
- %p
+ .monospace= hook.url
+ %div
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray= trigger.titleize
- SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+ %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 33c48199ba5..7076f5db015 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-quick-submit js-requires-input' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form common-note-form js-quick-submit js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
:javascript
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 4aa92d0b39e..9ad86ed71c9 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -27,7 +27,7 @@
= icon('thumbs-down')
= downvotes
- - note_count = issue.notes.user.count
+ - note_count = issue.notes.user.nonawards.count
- if note_count > 0
%li
= link_to issue_path(issue) + "#notes" do
@@ -48,6 +48,11 @@
= link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
= icon('clock-o')
= issue.milestone.title
+ - if issue.due_date
+ %span{class: "#{'cred' if issue.overdue?}"}
+ &nbsp;
+ = icon('calendar')
+ = issue.due_date.to_s(:medium)
- if issue.labels.any?
&nbsp;
- issue.labels.each do |label|
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 6fa059cbe68..bde80bbb54b 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,80 +1,79 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
+- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
-= render "header_title"
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
+ = icon('check', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs
+ Closed
+ .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
+ = icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs Open
-.issue
- .detail-page-header.issuable-header
- .pull-left
- .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
- %span.hidden-xs
- Closed
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('check')
- .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
- %span.hidden-xs
- Open
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('circle-o')
-
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left')
- .issue-meta
+ .issuable-meta
= confidential_icon(@issue)
- %strong.identifier
- Issue ##{@issue.iid}
- %span.creator
- opened
- .editor-details
- .editor-details
- = time_ago_with_tooltip(@issue.created_at)
- by
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = issuable_meta(@issue, @project, "Issue")
+
+ - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ - if can?(current_user, :create_issue, @project)
+ %li
+ = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+ - if can?(current_user, :update_issue, @issue)
+ %li
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ %li
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ %li
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
+ = icon('plus')
+ New issue
+ - if can?(current_user, :update_issue, @issue)
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
+ = icon('pencil-square-o')
+ Edit
- .pull-right.issue-btn-group
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
- = icon('plus')
- New issue
- - if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
- = icon('pencil-square-o')
- Edit
+.issue-details.issuable-details
+ .detail-page-description.content-block
+ %h2.title
+ = markdown escape_once(@issue.title), pipeline: :single_line
+ - if @issue.description.present?
+ .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
+ .wiki
+ = preserve do
+ = markdown(@issue.description, cache_key: [@issue, "description"])
+ %textarea.hidden.js-task-list-field
+ = @issue.description
+ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
- .issue-details.issuable-details
- .detail-page-description.content-block
- %h2.title
- = markdown escape_once(@issue.title), pipeline: :single_line
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"])
- %textarea.hidden.js-task-list-field
- = @issue.description
- = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
+ #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ // This element is filled in using JavaScript.
- .merge-requests
- = render 'merge_requests'
- = render 'related_branches'
+ #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ // This element is filled in using JavaScript.
- .content-block.content-block-small
- = render 'new_branch'
- = render 'votes/votes_block', votable: @issue
+ .content-block.content-block-small
+ = render 'new_branch'
+ = render 'votes/votes_block', votable: @issue
- .row
- %section.col-md-12
- .issuable-discussion
- = render 'projects/issues/discussion'
+ %section.issuable-discussion
+ = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
deleted file mode 100644
index 986d8c220db..00000000000
--- a/app/views/projects/issues/update.js.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
-$('aside.right-sidebar').effect('highlight');
-new IssuableContext();
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 097a65969a6..8bf544b8371 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -14,7 +14,7 @@
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
.subscription-status{data: {status: label_subscription_status(label)}}
- %a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}}
+ %button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } }
%span= label_subscription_toggle_button_text(label)
- if can? current_user, :admin_label, @project
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 1e6724fc92b..88525f4036a 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input js-quick-submit' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 7bfde8b1c57..330d750a681 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -35,7 +35,7 @@
= icon('thumbs-down')
= downvotes
- - note_count = merge_request.mr_and_commit_notes.user.count
+ - note_count = merge_request.mr_and_commit_notes.user.nonawards.count
- if note_count > 0
%li
= link_to merge_request_path(merge_request) + "#notes" do
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 7d7c487e970..b08524574e4 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -16,11 +16,9 @@
= dropdown_title("Select source project")
= dropdown_filter("Search projects")
= dropdown_content do
- - is_active = f.object.source_project_id == @merge_request.source_project.id
- %ul
- %li
- %a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
- = @merge_request.source_project_path
+ = render 'projects/merge_requests/dropdowns/project',
+ projects: [@merge_request.source_project],
+ selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
@@ -28,11 +26,9 @@
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
- %ul
- - @merge_request.source_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
- = branch
+ = render 'projects/merge_requests/dropdowns/branch',
+ branches: @merge_request.source_branches,
+ selected: f.object.source_branch
.panel-footer
= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
@@ -50,11 +46,9 @@
= dropdown_title("Select target project")
= dropdown_filter("Search projects")
= dropdown_content do
- %ul
- - projects.each do |project|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
- = project.path_with_namespace
+ = render 'projects/merge_requests/dropdowns/project',
+ projects: projects,
+ selected: f.object.target_project_id
.merge-request-select.dropdown
= f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
@@ -62,11 +56,9 @@
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
- %ul
- - @merge_request.target_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
- = branch
+ = render 'projects/merge_requests/dropdowns/branch',
+ branches: @merge_request.target_branches,
+ selected: f.object.target_branch
.panel-footer
= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 9e59f7df71b..2f14a91e64f 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -10,7 +10,7 @@
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 1dd8f721f7e..290753d57c6 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,10 +1,9 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
+- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
-= render "header_title"
-
-- if params[:view] == 'parallel'
+- if diff_view == 'parallel'
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
@@ -32,8 +31,8 @@
%span Request to merge
%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
+ %span.label-branch
+ = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
@@ -51,7 +50,7 @@
%li.notes-tab
= 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
+ %span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
@@ -87,8 +86,10 @@
= spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
-- if @merge_request.can_be_reverted?
- = render "projects/commit/revert", commit: @merge_request.merge_commit, title: @merge_request.title
+- if @merge_request.can_be_reverted?(current_user)
+ = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
+- if @merge_request.can_be_cherry_picked?
+ = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/dropdowns/_branch.html.haml b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
new file mode 100644
index 00000000000..ba8d9a5835c
--- /dev/null
+++ b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
@@ -0,0 +1,5 @@
+%ul
+ - branches.each do |branch|
+ %li
+ %a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
+ = branch
diff --git a/app/views/projects/merge_requests/dropdowns/_project.html.haml b/app/views/projects/merge_requests/dropdowns/_project.html.haml
new file mode 100644
index 00000000000..25d5dc92f8a
--- /dev/null
+++ b/app/views/projects/merge_requests/dropdowns/_project.html.haml
@@ -0,0 +1,5 @@
+%ul
+ - projects.each do |project|
+ %li
+ %a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
+ = project.path_with_namespace
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index fc62bb5bce9..b31ea5e5321 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,7 +1,7 @@
-- page_title "Edit", "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title"
%h3.page-title
- Edit Merge Request ##{@merge_request.iid}
+ Edit Merge Request #{@merge_request.to_reference}
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index fc03ee73a3d..f5bf16ef3ad 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title"
.merge-request
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index ab4b1f14be5..36c275e8be1 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,35 +1,32 @@
-.detail-page-header
- .status-box{ class: status_box_class(@merge_request) }
- %span.hidden-xs
- = @merge_request.state_human_name
- %span.hidden-sm.hidden-md.hidden-lg
- = icon(@merge_request.state_icon_name)
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
- .issue-meta
- %strong.identifier
- %span.hidden-sm.hidden-md.hidden-lg
- MR
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
+ = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
%span.hidden-xs
- Merge Request
- !#{@merge_request.iid}
- %span.creator
- opened
- .editor-details
- = time_ago_with_tooltip(@merge_request.created_at)
- by
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = @merge_request.state_human_name
- .issue-btn-group.pull-right
- - if can?(current_user, :update_merge_request, @merge_request)
- - if @merge_request.open?
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
+ = icon('angle-double-left')
+
+ .issuable-meta
+ = issuable_meta(@merge_request, @project, "Merge Request")
+
+ - if can?(current_user, :update_merge_request, @merge_request)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ %li{ class: issue_button_visibility(@merge_request, true) }
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
+ %li{ class: issue_button_visibility(@merge_request, false) }
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
+ %li
+ = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit" do
= icon('pencil-square-o')
Edit
- - if @merge_request.closed?
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
deleted file mode 100644
index 9cce5660e1c..00000000000
--- a/app/views/projects/merge_requests/update.js.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
-$('aside.right-sidebar').effect('highlight');
-new IssuableContext();
diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml
index 1b93188a10c..64482973a89 100644
--- a/app/views/projects/merge_requests/update_branches.html.haml
+++ b/app/views/projects/merge_requests/update_branches.html.haml
@@ -1,5 +1,3 @@
-%ul
- - @target_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
- = branch
+= render 'projects/merge_requests/dropdowns/branch',
+branches: @target_branches,
+selected: nil
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 3abae9f0bf6..ec4beae9727 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -44,3 +44,8 @@
$('.remove_source_branch_in_progress').hide();
$('.remove_source_branch_widget.failed').show();
});
+ - else
+ %p
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
+ = render 'projects/merge_requests/widget/merged_buttons'
diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml
index 85a3a6ba9e2..56167509af9 100644
--- a/app/views/projects/merge_requests/widget/_merged_buttons.haml
+++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml
@@ -1,11 +1,14 @@
-- source_branch_exists = local_assigns.fetch(:source_branch_exists, false)
-- mr_can_be_reverted = @merge_request.can_be_reverted?
+- can_remove_source_branch = local_assigns.fetch(:source_branch_exists, false) && @merge_request.can_remove_source_branch?(current_user)
+- mr_can_be_reverted = @merge_request.can_be_reverted?(current_user)
+- mr_can_be_cherry_picked = @merge_request.can_be_cherry_picked?
-- if source_branch_exists || mr_can_be_reverted
+- if can_remove_source_branch || mr_can_be_reverted || mr_can_be_cherry_picked
.btn-group
- - if source_branch_exists
+ - if can_remove_source_branch
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-grouped btn-sm remove_source_branch" do
= icon('trash-o')
Remove Source Branch
- if mr_can_be_reverted
= revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
+ - if mr_can_be_cherry_picked
+ = cherry_pick_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index b2dae1c70ee..687222fa92f 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,6 +1,5 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input'} do |f|
= form_errors(@milestone)
-
.row
.col-md-6
.form-group
@@ -11,7 +10,7 @@
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
= render 'projects/notes/hints'
.clearfix
.error-alert
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index b8068835b3a..572b00a38c7 100644
--- a/app/views/projects/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -1,5 +1,5 @@
- note = discussion_notes.first
-.timeline-entry
+%li.note.note-discussion.timeline-entry
.timeline-entry-inner
.timeline-icon
= link_to user_path(note.author) do
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 23e4f93eab5..c87a3fadf72 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -2,7 +2,7 @@
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f|
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.note-form-actions.clearfix
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index c446ecec2c3..d0ac380f216 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form" }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
= note_target_fields(@note)
@@ -8,7 +8,7 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.error-alert
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 03a44ca99c0..aeb7c1d5ee4 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,4 +1,5 @@
-%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
+- note_editable = note_editable?(note)
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
.timeline-entry-inner
.timeline-icon
%a{href: user_path(note.author)}
@@ -7,7 +8,9 @@
.note-header
= link_to_member(note.project, note.author, avatar: false)
.inline.note-headline-light
- = "#{note.author.to_reference} commented"
+ = "#{note.author.to_reference}"
+ - if !note.system
+ commented
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
.note-actions
@@ -15,16 +18,16 @@
- if access
%span.note-role
= access
- - if note_editable?(note)
+ - if note_editable
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
= icon('trash-o')
- .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
+ .note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- - if note_editable?(note)
+ - if note_editable
= render 'projects/notes/edit_form', note: note
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index cc42aab5c52..1c39ce897a3 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -1,6 +1,6 @@
%ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes"
-%ul.notes.timeline
+%ul.notes.notes-form.timeline
%li.timeline-entry
- if can? current_user, :create_note, @project
.timeline-icon.hidden-xs.hidden-sm
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index cd8a5f0bd02..0ea8862a684 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -6,15 +6,11 @@
= "#{note.author.to_reference} started a discussion"
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
on the diff
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- .last-update.hide.js-toggle-content
- - last_note = discussion_notes.last
- last updated by
- = link_to_member(@project, last_note.author, avatar: false)
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 46f2ba4bbcf..2a2ead58eeb 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -8,21 +8,18 @@
= "#{note.author.to_reference} started a discussion on #{commit_description}"
- if commit
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- .last-update.hide.js-toggle-content
- - last_note = discussion_notes.last
- last updated by
- = link_to_member(@project, last_note.author, avatar: false)
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
- if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else
.panel.panel-default
.notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
- = render discussion_notes
+ %ul.notes.timeline
+ = render discussion_notes
.discussion-reply-holder
= link_to_reply_diff(discussion_notes.first)
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
index 820e31ccd61..d46aab000c3 100644
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ b/app/views/projects/notes/discussions/_diff.html.haml
@@ -20,11 +20,9 @@
%td.new_line.diff-line-num= "..."
%td.line_content.match= line.text
- else
- %td.old_line.diff-line-num
- = raw(type == "new" ? "&nbsp;" : line.old_pos)
- %td.new_line.diff-line-num
- = raw(type == "old" ? "&nbsp;" : line.new_pos)
- %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text)
+ %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
+ %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
+ %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index f8e000b424f..45141bcd1df 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -5,14 +5,10 @@
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion"
on the outdated diff
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down
Show/hide discussion
- .last-update.hide.js-toggle-content
- - last_note = discussion_notes.last
- last updated by
- = link_to_member(@project, last_note.author, avatar: false)
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 62888e41935..ae13f8428f0 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -8,7 +8,7 @@
group, members with
%strong #{group_links.human_access}
role (#{shared_group_users_count})
- - if current_user.can?(:admin_group, shared_group)
+ - if can?(current_user, :admin_group, shared_group)
.panel-head-actions
= link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index c4a3f06ee06..6f0b32aa165 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -9,11 +9,11 @@
%strong #{@tag.name}
.prepend-top-default
- = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form js-quick-submit' }) do |f|
+ = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'projects/notes/hints'
- .error-alert
- .form-actions.prepend-top-default
- = f.submit 'Save changes', class: 'btn btn-save'
- = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
+ .error-alert
+ .form-actions.prepend-top-default
+ = f.submit 'Save changes', class: 'btn btn-save'
+ = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 6a37f444bb7..9fa4127c948 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -1,7 +1,10 @@
%h3 Shared runners
-.bs-callout.bs-callout-warning
- GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
+.bs-callout.bs-callout-warning.shared-runners-description
+ - if shared_runners_text.present?
+ = markdown(shared_runners_text, pipeline: 'plain_markdown')
+ - else
+ Shared runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com).
%hr
- if @project.shared_runners_enabled?
= link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 4310f038fc9..d854ac21725 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -36,9 +36,9 @@
%li
= link_to 'Changelog', changelog_path(@project)
- - if @repository.license
+ - if @repository.license_blob
%li
- = link_to 'License', license_path(@project)
+ = link_to license_short_name(@project), license_path(@project)
- if @repository.contribution_guide
%li
@@ -47,15 +47,15 @@
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
- = link_to add_changelog_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- - unless @repository.license
+ - unless @repository.license_blob
%li.missing
- = link_to add_license_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
- = link_to add_contribution_guide_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- if @repository.commit
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 77c7c4d23de..b40a6e5cb2d 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -10,7 +10,7 @@
New Tag
%hr
-= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-quick-submit js-requires-input" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal common-note-form tag-form js-quick-submit js-requires-input" do
.form-group
= label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
@@ -30,9 +30,9 @@
= label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', attr: :release_description, classes: 'description form-control'
+ = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'projects/notes/hints'
- .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
+ .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 812876e2835..797a1a59e9f 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f|
= form_errors(@page)
= f.hidden_field :title, value: @page.title
@@ -11,7 +11,7 @@
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'projects/notes/hints'
.clearfix
diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml
new file mode 100644
index 00000000000..a585147ddd1
--- /dev/null
+++ b/app/views/repository_check_mailer/notify.html.haml
@@ -0,0 +1,8 @@
+%p
+ #{@message}.
+
+%p
+ = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1)
+
+%p
+ You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}.
diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml
new file mode 100644
index 00000000000..93db151329e
--- /dev/null
+++ b/app/views/repository_check_mailer/notify.text.haml
@@ -0,0 +1,6 @@
+#{@message}.
+\
+View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)}
+
+You are receiving this message because you are a GitLab administrator
+for #{Gitlab.config.gitlab.url}.
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index faeb2b55c6f..333f6533213 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -2,7 +2,7 @@
%h4
= link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
%span.term.str-truncated= merge_request.title
- .pull-right ##{merge_request.iid}
+ .pull-right #{merge_request.to_reference}
- if merge_request.description.present?
.description.term
= preserve do
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 9544e3d3e17..d9400b1d9fa 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,5 +1,5 @@
- project = note.project
-- note_url = Gitlab::UrlBuilder.new(:note).build(note.id)
+- note_url = Gitlab::UrlBuilder.build(note)
- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
.search-result-row
%h5.note-search-caption.str-truncated
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index b38c5e18efb..9ce5562e667 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -2,4 +2,4 @@
%span.label-name
= link_to_label(label, tooltip: false)
%span.prepend-left-10
- = markdown(label.description, pipeline: :single_line)
+ = markdown(label.description, pipeline: :single_line) \ No newline at end of file
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
new file mode 100644
index 00000000000..dc89e36419c
--- /dev/null
+++ b/app/views/shared/_labels_row.html.haml
@@ -0,0 +1,3 @@
+- labels.each do |label|
+ %span.label-row
+ = link_to_label(label, tooltip: false)
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index fc935166bf6..4eaf7c2a025 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -62,6 +62,14 @@
%strong Build events
%p.light
This url will be triggered when a build status changes
+ - if @service.supported_events.include?("wiki_page")
+ %div
+ = form.check_box :wiki_page_events, class: 'pull-left'
+ .prepend-left-20
+ = form.label :wiki_page_events, class: 'list-label' do
+ %strong Wiki Page events
+ %p.light
+ This url will be triggered when a wiki page is created/updated
- @service.fields.each do |field|
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index e3a6a5a68b6..d327bd0a96f 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -20,6 +20,11 @@
= sort_title_milestone_soon
= link_to page_filter_path(sort: sort_value_milestone_later) do
= sort_title_milestone_later
+ - if controller.controller_name == 'issues' || controller.action_name == 'issues'
+ = link_to page_filter_path(sort: sort_value_due_date_soon) do
+ = sort_title_due_date_soon
+ = link_to page_filter_path(sort: sort_value_due_date_later) do
+ = sort_title_due_date_later
= link_to page_filter_path(sort: sort_value_upvotes) do
= sort_title_upvotes
= link_to page_filter_path(sort: sort_value_downvotes) do
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 921eaefd79a..fc1101646fb 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -23,6 +23,7 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown"
+
.pull-right
= render 'shared/sort_dropdown'
@@ -46,9 +47,10 @@
.filter-item.inline
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
-- if @label
- .gray-content-block.second-block
- = render "shared/label_row", label: @label
+ - if !@labels.nil?
+ .gray-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
+ - if @labels.any?
+ = render "shared/labels_row", labels: @labels
:javascript
new UsersSelect();
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 757a3812deb..18b091df39b 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -4,7 +4,7 @@
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
- class: 'form-control pad js-gfm-input', required: true
+ class: 'form-control pad', required: true
- if issuable.is_a?(MergeRequest)
%p.help-block
@@ -29,7 +29,8 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
+ classes: 'note-textarea',
+ placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.clearfix
.error-alert
@@ -70,13 +71,14 @@
- if can? current_user, :admin_milestone, issuable.project
= link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group
+ - has_labels = issuable.project.labels.any?
= f.label :label_ids, "Labels", class: 'control-label'
- .col-sm-10
- - if issuable.project.labels.any?
- = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
+ .col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) }
+ - if has_labels
+ .issuable-form-select-holder
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
- .prepend-top-10
%span.light No labels yet.
&nbsp;
- if can? current_user, :admin_label, issuable.project
@@ -87,9 +89,10 @@
.form-group
= label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10
- - projects = project_options(issuable, current_user, ability: :admin_issue)
- = select_tag(:move_to_project_id, projects, include_blank: true,
- class: 'select2', data: { placeholder: 'Select project' })
+ .issuable-form-select-holder
+ - projects = project_options(issuable, current_user, ability: :admin_issue)
+ = select_tag(:move_to_project_id, projects, include_blank: true,
+ class: 'select2', data: { placeholder: 'Select project' })
&nbsp;
%span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
@@ -101,13 +104,15 @@
.form-group
= f.label :source_branch, class: 'control-label'
.col-sm-10
- = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
+ .issuable-form-select-holder
+ = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group
= f.label :target_branch, class: 'control-label'
.col-sm-10
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
+ .issuable-form-select-holder
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
- if @merge_request.new_record?
- %p.help-block
+ &nbsp;
= link_to 'Change branches', mr_change_branches_path(@merge_request)
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
@@ -128,8 +133,6 @@
- else
.pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
- = link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
- method: :delete, class: 'btn btn-grouped' do
- = icon('trash-o')
- Delete
+ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
+ method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index f722e61eeac..61fd1e9c335 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,44 +1,14 @@
- if params[:label_name].present?
- = hidden_field_tag(:label_name, params[:label_name])
+ - if params[:label_name].respond_to?('any?')
+ - params[:label_name].each do |label|
+ = hidden_field_tag "label_name[]", label, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
+ %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
%span.dropdown-toggle-text
- = h(params[:label_name].presence || "Label")
+ = h(multi_label_name(params[:label_name], "Label"))
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Filter by label")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
+ = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" }
- if can? current_user, :admin_label, @project and @project
- .dropdown-page-two.dropdown-new-label
- = dropdown_title("Create new label", back: true)
- = dropdown_content do
- .dropdown-labels-error.js-label-error
- %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
- .suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
- .dropdown-label-color-input
- .dropdown-label-color-preview.js-dropdown-label-color-preview
- %input#new_label_color.dropdown-input-field{ type: "text" }
- .clearfix
- %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"}
- Create
- %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"}
- Cancel
+ = render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
new file mode 100644
index 00000000000..3bc57d3d2ac
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -0,0 +1,17 @@
+.dropdown-page-two.dropdown-new-label
+ = dropdown_title("Create new label", back: true)
+ = dropdown_content do
+ .dropdown-labels-error.js-label-error
+ %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" }
+ .suggest-colors.suggest-colors-dropdown
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp
+ .dropdown-label-color-input
+ .dropdown-label-color-preview.js-dropdown-label-color-preview
+ %input#new_label_color.default-dropdown-input{ type: "text" }
+ .clearfix
+ %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" }
+ Create
+ %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" }
+ Cancel
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
new file mode 100644
index 00000000000..7f4867417f7
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -0,0 +1,20 @@
+- title = local_assigns.fetch(:title, 'Assign labels')
+- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
+.dropdown-page-one
+ = dropdown_title(title)
+ = dropdown_filter(filter_placeholder)
+ = dropdown_content
+ - if @project
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_label, @project
+ %li
+ %a.dropdown-toggle-page{href: "#"}
+ Create new
+ %li
+ = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
+ = dropdown_loading \ No newline at end of file
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index a6970b7eebb..1d9b09a5ef1 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -4,22 +4,22 @@
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
- = link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
+ = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
- = link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
+ = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
+ = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
+ = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
- = link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
+ = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 94affa4b59a..ed1b8a8da2a 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -10,17 +10,17 @@
= sidebar_gutter_toggle_icon
.issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if prev_issuable = prev_issuable_for(issuable)
- = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
+ = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn issuable-pager'
- else
- %a.btn.btn-default.disabled{href: '#'}
+ %a.btn.btn-default.issuable-pager.disabled{href: '#'}
Prev
- if next_issuable = next_issuable_for(issuable)
- = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn'
+ = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn issuable-pager'
- else
- %a.btn.btn-default.disabled{href: '#'}
+ %a.btn.btn-default.issuable-pager.disabled{href: '#'}
Next
- = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
.sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
- if issuable.assignee
@@ -49,7 +49,7 @@
.selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
- = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
+ = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone
.sidebar-collapsed-icon
@@ -58,7 +58,7 @@
- if issuable.milestone
= issuable.milestone.title
- else
- No
+ None
.title.hide-collapsed
Milestone
= icon('spinner spin', class: 'block-loading')
@@ -75,6 +75,34 @@
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
= dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }})
+ - if issuable.has_attribute?(:due_date)
+ .block.due_date
+ .sidebar-collapsed-icon
+ = icon('calendar')
+ %span.js-due-date-sidebar-value
+ = issuable.due_date.try(:to_s, :medium) || 'None'
+ .title.hide-collapsed
+ Due date
+ = icon('spinner spin', class: 'block-loading')
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.bold.hide-collapsed
+ - if issuable.due_date
+ = issuable.due_date.to_s(:medium)
+ - else
+ .light None
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .selectbox.hide-collapsed
+ = f.hidden_field :due_date, value: issuable.due_date
+ .dropdown
+ %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } }
+ %span.dropdown-toggle-text Due date
+ = icon('chevron-down')
+ .dropdown-menu.dropdown-menu-due-date
+ = dropdown_title('Due date')
+ = dropdown_content do
+ .js-due-date-calendar
+
- if issuable.project.labels.any?
.block.labels
.sidebar-collapsed-icon
@@ -101,23 +129,9 @@
Label
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Assign labels")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
+ = render partial: "shared/issuable/label_page_default"
+ - if can? current_user, :admin_label, @project and @project
+ = render partial: "shared/issuable/label_page_create"
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
@@ -128,7 +142,7 @@
.title.hide-collapsed
Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- %button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
+ %button.btn.btn-block.btn-gray.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status.hide-collapsed{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
@@ -150,6 +164,7 @@
:javascript
new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
new LabelsSelect();
- new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
+ new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
new Subscription('.subscription')
- new Sidebar(); \ No newline at end of file
+ new DueDateSelect();
+ sidebar = new Sidebar();
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index e1127b2311c..47b66d44e43 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -23,5 +23,5 @@
- if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
- class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
+ class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index 868b2357003..b15e8ea73fe 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -4,15 +4,16 @@
%li
%span.label-row
- = link_to milestones_label_path(options) do
- - render_colored_label(label, tooltip: false)
- %span.prepend-left-10
+ %span.label-name
+ = link_to milestones_label_path(options) do
+ - render_colored_label(label, tooltip: false)
+ %span.prepend-description-left
= markdown(label.description, pipeline: :single_line)
- .pull-right
- %strong.issues-count
+ .pull-info-right
+ %span.append-right-20
= link_to milestones_label_path(options.merge(state: 'opened')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
- %strong.issues-count
+ %span.append-right-20
= link_to milestones_label_path(options.merge(state: 'closed')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index e7e04621ff4..1169bed0382 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,4 +1,5 @@
- @sort ||= sort_value_recently_updated
+- personal = params[:personal]
- archived = params[:archived]
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -10,7 +11,7 @@
Sort by
- projects_sort_options_hash.each do |value, title|
%li
- = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
+ = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
= title
%li.divider
@@ -20,3 +21,11 @@
%li
= link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
Show archived projects
+ - if current_user
+ %li.divider
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do
+ Owned by anyone
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do
+ Owned by me
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 0c4b6a5618b..3028491e5b6 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -6,8 +6,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
-= render 'shared/show_aside'
-
.user-profile
.cover-block
.cover-controls
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index dc249155b92..59e12798691 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -19,12 +19,12 @@
var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
var noteable_type = "#{votable.class.name.underscore}";
var noteable_id = "#{votable.id}";
- var aliases = #{AwardEmoji.aliases.to_json};
+ var unicodes = #{AwardEmoji.unicode.to_json};
window.awards_handler = new AwardsHandler(
get_emojis_url,
post_emoji_url,
noteable_type,
noteable_id,
- aliases
+ unicodes
);
diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb
new file mode 100644
index 00000000000..667fff031dd
--- /dev/null
+++ b/app/workers/admin_email_worker.rb
@@ -0,0 +1,12 @@
+class AdminEmailWorker
+ include Sidekiq::Worker
+
+ sidekiq_options retry: false # this job auto-repeats via sidekiq-cron
+
+ def perform
+ repository_check_failed_count = Project.where(last_repository_check_failed: true).count
+ return if repository_check_failed_count.zero?
+
+ RepositoryCheckMailer.notify(repository_check_failed_count).deliver_now
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 3cc232ef1ae..f3327ca9e61 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -39,15 +39,15 @@ class PostReceive
end
if Gitlab::Git.tag_ref?(ref)
- GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref)
- else
+ GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
+ elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end
end
end
private
-
+
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb
new file mode 100644
index 00000000000..44b3145d50f
--- /dev/null
+++ b/app/workers/repository_check/batch_worker.rb
@@ -0,0 +1,63 @@
+module RepositoryCheck
+ class BatchWorker
+ include Sidekiq::Worker
+
+ RUN_TIME = 3600
+
+ sidekiq_options retry: false
+
+ def perform
+ start = Time.now
+
+ # This loop will break after a little more than one hour ('a little
+ # more' because `git fsck` may take a few minutes), or if it runs out of
+ # projects to check. By default sidekiq-cron will start a new
+ # RepositoryCheckWorker each hour so that as long as there are repositories to
+ # check, only one (or two) will be checked at a time.
+ project_ids.each do |project_id|
+ break if Time.now - start >= RUN_TIME
+ break unless current_settings.repository_checks_enabled
+
+ next unless try_obtain_lease(project_id)
+
+ SingleRepositoryWorker.new.perform(project_id)
+ end
+ end
+
+ private
+
+ # Project.find_each does not support WHERE clauses and
+ # Project.find_in_batches does not support ordering. So we just build an
+ # array of ID's. This is OK because we do it only once an hour, because
+ # getting ID's from Postgres is not terribly slow, and because no user
+ # has to sit and wait for this query to finish.
+ def project_ids
+ limit = 10_000
+ never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit).
+ pluck(:id)
+ old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago).
+ reorder('last_repository_check_at ASC').limit(limit).pluck(:id)
+ never_checked_projects + old_check_projects
+ end
+
+ def try_obtain_lease(id)
+ # Use a 24-hour timeout because on servers/projects where 'git fsck' is
+ # super slow we definitely do not want to run it twice in parallel.
+ Gitlab::ExclusiveLease.new(
+ "project_repository_check:#{id}",
+ timeout: 24.hours
+ ).try_obtain
+ end
+
+ def current_settings
+ # No caching of the settings! If we cache them and an admin disables
+ # this feature, an active RepositoryCheckWorker would keep going for up
+ # to 1 hour after the feature was disabled.
+ if Rails.env.test?
+ Gitlab::CurrentSettings.fake_application_settings
+ else
+ ApplicationSetting.current
+ end
+ end
+ end
+end
diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb
new file mode 100644
index 00000000000..b7202ddff34
--- /dev/null
+++ b/app/workers/repository_check/clear_worker.rb
@@ -0,0 +1,17 @@
+module RepositoryCheck
+ class ClearWorker
+ include Sidekiq::Worker
+
+ sidekiq_options retry: false
+
+ def perform
+ # Do small batched updates because these updates will be slow and locking
+ Project.select(:id).find_in_batches(batch_size: 100) do |batch|
+ Project.where(id: batch.map(&:id)).update_all(
+ last_repository_check_failed: nil,
+ last_repository_check_at: nil,
+ )
+ end
+ end
+ end
+end
diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb
new file mode 100644
index 00000000000..a76729e3c74
--- /dev/null
+++ b/app/workers/repository_check/single_repository_worker.rb
@@ -0,0 +1,36 @@
+module RepositoryCheck
+ class SingleRepositoryWorker
+ include Sidekiq::Worker
+
+ sidekiq_options retry: false
+
+ def perform(project_id)
+ project = Project.find(project_id)
+ project.update_columns(
+ last_repository_check_failed: !check(project),
+ last_repository_check_at: Time.now,
+ )
+ end
+
+ private
+
+ def check(project)
+ repositories = [project.repository]
+ repositories << project.wiki.repository if project.wiki_enabled?
+ # Use 'map do', not 'all? do', to prevent short-circuiting
+ repositories.map { |repository| git_fsck(repository.path_to_repo) }.all?
+ end
+
+ def git_fsck(path)
+ cmd = %W(nice git --git-dir=#{path} fsck)
+ output, status = Gitlab::Popen.popen(cmd)
+
+ if status.zero?
+ true
+ else
+ Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}")
+ false
+ end
+ end
+ end
+end