summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.scss-lint.yml106
-rw-r--r--CHANGELOG26
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/awards_handler.coffee4
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee4
-rw-r--r--app/assets/javascripts/lib/notify.js.coffee30
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee72
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee12
-rw-r--r--app/assets/javascripts/sidebar.js.coffee1
-rw-r--r--app/assets/stylesheets/behaviors.scss4
-rw-r--r--app/assets/stylesheets/framework/calendar.scss6
-rw-r--r--app/assets/stylesheets/framework/common.scss4
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/forms.scss2
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss6
-rw-r--r--app/assets/stylesheets/framework/header.scss12
-rw-r--r--app/assets/stylesheets/framework/mobile.scss4
-rw-r--r--app/assets/stylesheets/framework/nav.scss14
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss123
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss6
-rw-r--r--app/assets/stylesheets/framework/variables.scss12
-rw-r--r--app/assets/stylesheets/pages/awards.scss2
-rw-r--r--app/assets/stylesheets/pages/ci_projects.scss2
-rw-r--r--app/assets/stylesheets/pages/commit.scss8
-rw-r--r--app/assets/stylesheets/pages/commits.scss2
-rw-r--r--app/assets/stylesheets/pages/editor.scss2
-rw-r--r--app/assets/stylesheets/pages/events.scss4
-rw-r--r--app/assets/stylesheets/pages/lint.scss4
-rw-r--r--app/assets/stylesheets/pages/login.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss135
-rw-r--r--app/assets/stylesheets/pages/todos.scss2
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb16
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/events_helper.rb2
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/commit_range.rb4
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/merge_request.rb5
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb16
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/repository.rb23
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/issues/move_service.rb28
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/projects/unlink_fork_service.rb19
-rw-r--r--app/services/system_hooks_service.rb22
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/services/todo_service.rb26
-rw-r--r--app/uploaders/file_uploader.rb17
-rw-r--r--app/views/admin/application_settings/_form.html.haml7
-rw-r--r--app/views/admin/builds/_build.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/deploy_keys/index.html.haml2
-rw-r--r--app/views/admin/groups/_group.html.haml28
-rw-r--r--app/views/admin/groups/index.html.haml45
-rw-r--r--app/views/admin/labels/index.html.haml12
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/dashboard/todos/_todo.html.haml5
-rw-r--r--app/views/events/event/_created_project.html.haml18
-rw-r--r--app/views/layouts/_collapse_button.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml18
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml5
-rw-r--r--app/views/layouts/nav/_group.html.haml10
-rw-r--r--app/views/layouts/nav/_profile.html.haml8
-rw-r--r--app/views/layouts/nav/_project.html.haml17
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml2
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml17
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/diffs/_image.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml24
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml17
-rw-r--r--app/views/projects/notes/_note.html.haml29
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml16
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml15
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml15
-rw-r--r--app/views/search/results/_note.html.haml18
-rw-r--r--app/views/shared/issuable/_filter.html.haml4
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/views/votes/_votes_block.html.haml2
-rw-r--r--app/workers/project_destroy_worker.rb2
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--db/migrate/20160324020319_remove_todos_for_deleted_issues.rb17
-rw-r--r--db/migrate/20160329144452_add_index_on_pending_delete_projects.rb6
-rw-r--r--db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb17
-rw-r--r--db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb5
-rw-r--r--db/schema.rb5
-rw-r--r--doc/api/projects.md166
-rw-r--r--doc/api/settings.md3
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/update/8.6-to-8.7.md146
-rw-r--r--features/dashboard/dashboard.feature8
-rw-r--r--features/groups.feature4
-rw-r--r--features/project/project.feature9
-rw-r--r--features/steps/dashboard/dashboard.rb19
-rw-r--r--features/steps/group/milestones.rb4
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/project/active_tab.rb4
-rw-r--r--features/steps/project/fork.rb2
-rw-r--r--features/steps/project/merge_requests.rb8
-rw-r--r--features/steps/project/project.rb12
-rw-r--r--features/steps/project/wiki.rb2
-rw-r--r--features/steps/shared/project_tab.rb2
-rw-r--r--lib/api/entities.rb5
-rw-r--r--lib/api/projects.rb28
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb140
-rw-r--r--lib/banzai/filter/commit_range_reference_filter.rb2
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb2
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb41
-rw-r--r--lib/banzai/filter/image_link_filter.rb27
-rw-r--r--lib/banzai/filter/label_reference_filter.rb2
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb2
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb2
-rw-r--r--lib/banzai/filter/reference_filter.rb148
-rw-r--r--lib/banzai/filter/snippet_reference_filter.rb2
-rw-r--r--lib/banzai/filter/user_reference_filter.rb27
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/gitlab/current_settings.rb1
-rw-r--r--lib/gitlab/email/message/repository_push.rb2
-rw-r--r--lib/gitlab/fogbugz_import/client.rb2
-rw-r--r--lib/gitlab/gfm/reference_rewriter.rb9
-rw-r--r--lib/gitlab/gfm/uploads_rewriter.rb51
-rw-r--r--lib/gitlab/note_data_builder.rb2
-rw-r--r--lib/gitlab/routing.rb13
-rw-r--r--lib/gitlab/url_builder.rb16
-rw-r--r--spec/controllers/admin/users_controller_spec.rb15
-rw-r--r--spec/factories/file_uploader.rb20
-rw-r--r--spec/factories/forked_project_links.rb5
-rw-r--r--spec/factories_spec.rb16
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb28
-rw-r--r--spec/features/search_spec.rb43
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb24
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/client_spec.rb24
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb66
-rw-r--r--spec/models/application_setting_spec.rb1
-rw-r--r--spec/models/concerns/issuable_spec.rb1
-rw-r--r--spec/models/project_spec.rb9
-rw-r--r--spec/models/repository_spec.rb39
-rw-r--r--spec/models/user_spec.rb7
-rw-r--r--spec/requests/api/projects_spec.rb72
-rw-r--r--spec/services/issues/move_service_spec.rb14
-rw-r--r--spec/services/projects/import_service_spec.rb17
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb32
-rw-r--r--spec/services/todo_service_spec.rb77
-rw-r--r--spec/support/filter_spec_helper.rb2
-rw-r--r--spec/support/markdown_feature.rb2
-rw-r--r--spec/support/test_env.rb1
-rw-r--r--spec/workers/merge_worker_spec.rb2
166 files changed, 1973 insertions, 733 deletions
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 937d3407b60..835a4a88c44 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -7,21 +7,44 @@ exclude:
- 'app/assets/stylesheets/pages/emojis.scss'
linters:
+ # Reports when you use improper spacing around ! (the "bang") in !default,
+ # !global, !important, and !optional flags.
BangFormat:
enabled: false
+ # Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
+ # Reports when you define a rule set using a selector with chained classes
+ # (a.k.a. adjoining classes).
+ ChainedClasses:
+ enabled: false
+
+ # Prefer hexadecimal color codes over color keywords.
+ # (e.g. `color: green` is a color keyword)
ColorKeyword:
enabled: false
+ # Prefer color literals (keywords or hexadecimal codes) to be used only in
+ # variable declarations. They should be referred to via variables everywhere
+ # else.
ColorVariable:
enabled: false
+ # Which form of comments to prefer in CSS.
Comment:
enabled: false
+
+ # Reports @debug statements (which you probably left behind accidentally).
+ DebugStatement:
+ enabled: false
+ # Rule sets should be ordered as follows:
+ # - @extend declarations
+ # - @include declarations without inner @content
+ # - properties, @include declarations with inner @content
+ # - nested rule sets.
DeclarationOrder:
enabled: false
@@ -32,15 +55,25 @@ linters:
DisableLinterReason:
enabled: true
+ # Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
+ # Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
enabled: false
+ # Reports when you have an empty rule set.
EmptyRule:
enabled: false
+ # Reports when you have an @extend directive.
+ ExtendDirective:
+ enabled: false
+
+ # Files should always have a final newline. This results in better diffs
+ # when adding lines to the file, since SCM systems such as git won't
+ # think that you touched the last line.
FinalNewline:
enabled: false
@@ -53,12 +86,17 @@ linters:
HexNotation:
enabled: true
+ # Avoid using ID selectors.
IdSelector:
enabled: false
+ # The basenames of @imported SCSS partials should not begin with an
+ # underscore and should not include the filename extension.
ImportPath:
enabled: false
+ # Avoid using !important in properties. It is usually indicative of a
+ # misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule:
enabled: false
@@ -67,33 +105,51 @@ linters:
enabled: true
width: 2
+ # Don't write leading zeros for numeric values with a decimal point.
LeadingZero:
enabled: false
+ # Reports when you define the same selector twice in a single sheet.
MergeableSelector:
enabled: false
+ # Functions, mixins, variables, and placeholders should be declared
+ # with all lowercase letters and hyphens instead of underscores.
NameFormat:
enabled: false
+ # Avoid nesting selectors too deeply.
NestingDepth:
enabled: false
+ # Always use placeholder selectors in @extend.
PlaceholderInExtend:
enabled: false
+ # Sort properties in a strict order.
PropertySortOrder:
enabled: false
+ # Reports when you use an unknown or disabled CSS property
+ # (ignoring vendor-prefixed properties).
PropertySpelling:
enabled: false
+ # Configure which units are allowed for property values.
+ PropertyUnits:
+ enabled: false
+
+ # Pseudo-elements, like ::before, and ::first-letter, should be declared
+ # with two colons. Pseudo-classes, like :hover and :first-child, should
+ # be declared with one colon.
PseudoElement:
enabled: false
+ # Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement:
enabled: false
+ # Don't write selectors with a depth of applicability greater than 3.
SelectorDepth:
enabled: false
@@ -113,9 +169,12 @@ linters:
enabled: true
allow_single_line_rule_sets: true
+ # Split selectors onto separate lines after each comma, and have each
+ # individual selector occupy a single line.
SingleLinePerSelector:
enabled: false
+ # Commas in lists should be followed by a space.
SpaceAfterComma:
enabled: false
@@ -128,28 +187,75 @@ linters:
# colon.
SpaceAfterPropertyName:
enabled: true
+
+ # Variables should be formatted with a single space separating the colon
+ # from the variable's value.
+ SpaceAfterVariableColon:
+ enabled: false
+
+ # Variables should be formatted with no space between the name and the
+ # colon.
+ SpaceAfterVariableName:
+ enabled: false
+ # Operators should be formatted with a single space on both sides of an
+ # infix operator.
SpaceAroundOperator:
enabled: false
+ # Opening braces should be preceded by a single space.
SpaceBeforeBrace:
+ enabled: true
+
+ # Parentheses should not be padded with spaces.
+ SpaceBetweenParens:
enabled: false
+ # Enforces that string literals should be written with a consistent form
+ # of quotes (single or double).
StringQuotes:
enabled: false
+ # Property values, @extend, @include, and @import directives, and variable
+ # declarations should always end with a semicolon.
TrailingSemicolon:
enabled: false
+ # Reports lines containing trailing whitespace.
TrailingWhitespace:
enabled: false
+ # Don't write trailing zeros for numeric values with a decimal point.
+ TrailingZero:
+ enabled: false
+
+ # Don't use the `all` keyword to specify transition properties.
+ TransitionAll:
+ enabled: false
+
+ # Numeric values should not contain unnecessary fractional portions.
UnnecessaryMantissa:
enabled: false
+ # Do not use parent selector references (&) when they would otherwise
+ # be unnecessary.
UnnecessaryParentReference:
enabled: false
+
+ # URLs should be valid and not contain protocols or domain names.
+ UrlFormat:
+ enabled: false
+
+ # URLs should always be enclosed within quotes.
+ UrlQuotes:
+ enabled: false
+
+ # Properties, like color and font, are easier to read and maintain
+ # when defined using variables rather than literals.
+ VariableForProperty:
+ enabled: false
+ # Avoid vendor prefixes. Or rather: don't write them yourself.
VendorPrefix:
enabled: false
diff --git a/CHANGELOG b/CHANGELOG
index 313a584d1d0..e1b6b32cff3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,19 +1,42 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
+ - All images in discussions and wikis now link to their source files !3464 (Connor Shea).
+ - Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu)
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API
- Fix avatar stretching by providing a cropping feature
- - Fix raw/rendered diff producing different results on merge requests !3450
+ - Add endpoints to archive or unarchive a project !3372
- Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu)
+ - Add default scope to projects to exclude projects pending deletion
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
+ - Fix creation of merge requests for orphaned branches (Stan Hu)
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
+ - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
+ - Improved UX of the navigation sidebar
+ - Build status notifications
+
+v 8.6.4
+ - Don't attempt to fetch any tags from a forked repo (Stan Hu)
+
+v 8.6.3
+ - Mentions on confidential issues doesn't create todos for non-members. !3374
+ - Destroy related todos when an Issue/MR is deleted. !3376
+ - Fix error 500 when target is nil on todo list. !3376
+ - Fix copying uploads when moving issue to another project. !3382
+ - Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432
+ - Fix raw/rendered diff producing different results on merge requests. !3450
+ - Fix commit comment alignment (Stan Hu). !3466
+ - Fix Error 500 when searching for a comment in a project snippet. !3468
+ - Allow temporary email as notification email. !3477
+ - Fix issue with dropdowns not selecting values. !3478
+ - Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280
v 8.6.2
- Fix dropdown alignment. !3298
@@ -108,6 +131,7 @@ v 8.6.0
- Add main language of a project in the list of projects (Tiago Botelho)
- Add #upcoming filter to Milestone filter (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages
+ - Remove fork link closes all merge requests opened on source project (Florent Baldino)
- Move group activity to separate page
- Create external users which are excluded of internal and private projects unless access was explicitly granted
- Continue parameters are checked to ensure redirection goes to the same instance
diff --git a/Gemfile b/Gemfile
index 006e53e0c10..6327227282a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -214,7 +214,7 @@ gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
-gem 'request_store', '~> 1.2.0'
+gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index bd41cc84198..229089f431d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -652,7 +652,7 @@ GEM
redis-store (~> 1.1.0)
redis-store (1.1.7)
redis (>= 2.2)
- request_store (1.2.1)
+ request_store (1.3.0)
rerun (0.11.0)
listen (~> 3.0)
responders (2.1.1)
@@ -1011,7 +1011,7 @@ DEPENDENCIES
redcarpet (~> 3.3.3)
redis-namespace
redis-rails (~> 4.0.0)
- request_store (~> 1.2.0)
+ request_store (~> 1.3.0)
rerun (~> 0.11.0)
responders (~> 2.0)
rouge (~> 1.10.1)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 47b080406d4..6a670d5e887 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,5 +1,5 @@
class @AwardsHandler
- constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
+ constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
$(".js-add-award").on "click", (event) =>
event.stopPropagation()
event.preventDefault()
@@ -34,7 +34,7 @@ class @AwardsHandler
$("#emoji_search").focus()
else
$('.js-add-award').addClass "is-loading"
- $.get "/emojis", (response) =>
+ $.get @get_emojis_url, (response) =>
$('.js-add-award').removeClass "is-loading"
$(".js-award-holder").append response
setTimeout =>
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 0fea2a69cb7..4f032a82e58 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -343,7 +343,7 @@ 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()
@@ -373,6 +373,8 @@ class GitLabDropdown
input = $(input)
.attr('id', @options.inputId)
@dropdown.before input
+ else
+ field.val value
return selectedObject
diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/notify.js.coffee
new file mode 100644
index 00000000000..3f9ca39912c
--- /dev/null
+++ b/app/assets/javascripts/lib/notify.js.coffee
@@ -0,0 +1,30 @@
+((w) ->
+ notificationGranted = (message, opts, onclick) ->
+ notification = new Notification(message, opts)
+
+ if onclick
+ notification.onclick = onclick
+
+ notifyPermissions = ->
+ if 'Notification' of window
+ Notification.requestPermission()
+
+ notifyMe = (message, body, icon, onclick) ->
+ opts =
+ body: body
+ icon: icon
+ # Let's check if the browser supports notifications
+ if !('Notification' of window)
+ # do nothing
+ else if Notification.permission == 'granted'
+ # If it's okay let's create a notification
+ notificationGranted message, opts, onclick
+ else if Notification.permission != 'denied'
+ Notification.requestPermission (permission) ->
+ # If the user accepts, let's create a notification
+ if permission == 'granted'
+ notificationGranted message, opts, onclick
+
+ w.notify = notifyMe
+ w.notifyPermissions = notifyPermissions
+) window
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 738ffc8343b..7102a0673e9 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -2,13 +2,18 @@ class @MergeRequestWidget
# Initialize MergeRequestWidget behavior
#
# check_enable - Boolean, whether to check automerge status
- # url_to_automerge_check - String, URL to use to check automerge status
- # current_status - String, current automerge status
- # ci_enable - Boolean, whether a CI service is enabled
- # url_to_ci_check - String, URL to use to check CI status
+ # merge_check_url - String, URL to use to check automerge status
+ # ci_status_url - String, URL to use to check CI status
#
+
constructor: (@opts) ->
- modal = $('#modal_merge_info').modal(show: false)
+ $('#modal_merge_info').modal(show: false)
+ @firstCICheck = true
+ @readyForCICheck = true
+ clearInterval @fetchBuildStatusInterval
+
+ @pollCIStatus()
+ notifyPermissions()
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
@@ -27,18 +32,57 @@ class @MergeRequestWidget
dataType: 'json'
getMergeStatus: ->
- $.get @opts.url_to_automerge_check, (data) ->
+ $.get @opts.merge_check_url, (data) ->
$('.mr-state-widget').replaceWith(data)
- getCiStatus: ->
- if @opts.ci_enable
- $.get @opts.url_to_ci_check, (data) =>
- this.showCiState data.status
+ ciLabelForStatus: (status) ->
+ if status == 'success'
+ 'passed'
+ else
+ status
+
+ pollCIStatus: ->
+ @fetchBuildStatusInterval = setInterval ( =>
+ return if not @readyForCICheck
+
+ @getCIStatus(true)
+
+ @readyForCICheck = false
+ ), 5000
+
+ getCIStatus: (showNotification) ->
+ _this = @
+ $('.ci-widget-fetching').show()
+
+ $.getJSON @opts.ci_status_url, (data) =>
+ @readyForCICheck = true
+
+ if @firstCICheck
+ @firstCICheck = false
+ @opts.ci_status = data.status
+
+ if data.status isnt @opts.ci_status
+ @showCIStatus data.status
if data.coverage
- this.showCiCoverage data.coverage
- , 'json'
+ @showCICoverage data.coverage
+
+ if showNotification
+ message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status))
+ message = message.replace('{{sha}}', data.sha)
+ message = message.replace('{{title}}', data.title)
+
+ notify(
+ "Build #{@ciLabelForStatus(data.status)}",
+ message,
+ @opts.gitlab_icon,
+ ->
+ @close()
+ Turbolinks.visit _this.opts.builds_path
+ )
+
+ @opts.ci_status = data.status
- showCiState: (state) ->
+ showCIStatus: (state) ->
$('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states
@@ -52,7 +96,7 @@ class @MergeRequestWidget
$('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger')
- showCiCoverage: (coverage) ->
+ showCICoverage: (coverage) ->
text = 'Coverage ' + coverage + '%'
$('.ci_widget:visible .ci-coverage').text(text)
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index e2a7d5fbba2..f73127f49f0 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -84,12 +84,16 @@ class @MilestoneSelect
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
- clicked: (e) ->
+ clicked: (selected) ->
if $dropdown.hasClass 'js-filter-bulk-update'
return
- if $dropdown.hasClass 'js-filter-submit'
- $dropdown.parents('form').submit()
+ if $dropdown.hasClass('js-filter-submit')
+ if selected.name?
+ selectedMilestone = selected.name
+ else
+ selectedMilestone = ''
+ Issues.filterResults $dropdown.closest('form')
else
selected = $selectbox
.find('input[type="hidden"]')
@@ -117,4 +121,4 @@ class @MilestoneSelect
else
$value.html(milestoneLinkNoneTemplate)
$sidebarCollapsedValue.find('span').text('No')
- ) \ No newline at end of file
+ )
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index 860d4f438d0..e1778511240 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
- $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( ->
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index 469f4f296ae..542a53f0377 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -13,10 +13,10 @@
// Toggle between two states.
.js-toggler-container {
- .turn-on { display: block; }
+ .turn-on { display: block; }
.turn-off { display: none; }
&.on {
- .turn-on { display: none; }
+ .turn-on { display: none; }
.turn-off { display: block; }
}
}
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index e3192823a1a..0b3af592d4a 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -1,3 +1,9 @@
+.calender-block {
+ @media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
+ overflow-x: scroll;
+ }
+}
+
.user-calendar-activities {
.calendar_onclick_hr {
padding: 0;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 9b676d759e0..db1a8b1bf78 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -121,7 +121,7 @@ p.time {
text-shadow: none;
}
-.thin_area{
+.thin_area {
height: 150px;
}
@@ -148,7 +148,7 @@ li.note {
}
}
-.wiki_content code, .readme code{
+.wiki_content code, .readme code {
background-color: inherit;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index b05c5df1bd8..9209347f9bc 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -3,7 +3,7 @@
vertical-align: top;
}
-@media (min-width: $screen-sm-min) {
+@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
.dropdown-menu-toggle {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 91b6451e68a..54cb5461113 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -91,7 +91,7 @@ label {
}
.form-control::-webkit-input-placeholder {
- color: #7f8fa4;
+ color: $gl-placeholder-color;
}
.input-group {
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index c83cf881596..fa9038ebaca 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -33,10 +33,15 @@
background: $color;
}
+ .complex-sidebar .nav-primary {
+ border-right: 1px solid lighten($color, 3%);
+ }
+
.sidebar-wrapper {
background: $color-darker;
.sidebar-user {
+ border-top: 1px solid lighten($color, 3%);
background: $color-darker;
color: $color-light;
@@ -62,7 +67,6 @@
.count {
color: $color-light;
- background: $color-dark;
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index a450167b04f..724980b2208 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -36,7 +36,7 @@ header {
padding: 0;
.nav > li > a {
- color: #7f8fa4;
+ color: $gl-icon-color;
font-size: 18px;
padding: 0;
margin: ($header-height - 28) / 2 0;
@@ -62,7 +62,7 @@ header {
background-color: #eee;
}
&.active {
- color: #7f8fa4;
+ color: $gl-icon-color;
}
}
}
@@ -81,14 +81,14 @@ header {
font-size: 19px;
line-height: $header-height;
font-weight: normal;
- color: #4c4e54;
+ color: $gl-text-color;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
a {
- color: #4c4e54;
+ color: $gl-text-color;
&:hover {
text-decoration: underline;
}
@@ -123,11 +123,11 @@ header {
}
@mixin collapsed-header {
- margin-left: $sidebar_collapsed_width;
+ margin-left: 40px;
}
.header-collapsed {
- margin-left: $sidebar_collapsed_width;
+ margin-left: 40px;
@media (min-width: $screen-md-min) {
@include collapsed-header;
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 5ea4f9a49db..66180f38a4f 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -107,7 +107,7 @@
}
.page-title {
- .note_created_ago, .new-issue-link {
+ .note-created-ago, .new-issue-link {
display: none;
}
}
@@ -116,7 +116,7 @@
display: none;
}
- aside:not(.right-sidebar){
+ aside:not(.right-sidebar) {
display: none;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 95bdd6d1ea3..94f5a12ff6a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -56,6 +56,17 @@
}
}
+ .nav-search {
+ display: inline-block;
+ width: 50%;
+ padding: 11px 0;
+
+ /* Small devices (phones, tablets, 768px and lower) */
+ @media (max-width: $screen-sm-min) {
+ width: 100%;
+ }
+ }
+
.nav-links {
display: inline-block;
width: 50%;
@@ -100,6 +111,7 @@
> form {
display: inline-block;
+ margin-top: -1px;
}
.icon-label {
@@ -110,7 +122,7 @@
height: 34px;
display: inline-block;
position: relative;
- top: 1px;
+ top: 2px;
margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index e82d052f45a..b2fab387e17 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -51,7 +51,7 @@
padding: 10px 15px;
}
-.select2-drop{
+.select2-drop {
color: #7f8fa4;
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 18189e985c4..1d49249dd80 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -144,7 +144,7 @@
}
a {
- padding: 7px 15px;
+ padding: 7px 12px;
font-size: $gl-font-size;
line-height: 24px;
color: $gray;
@@ -169,10 +169,12 @@
}
.count {
- float: right;
- background: #eee;
- padding: 0 8px;
- @include border-radius(6px);
+ &:before {
+ content: '(';
+ }
+ &:after {
+ content: ')';
+ }
}
&.back-link i {
@@ -191,6 +193,27 @@
}
}
+.expand-nav a {
+ color: $gl-icon-color;
+ width: 60px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ font-size: 20px;
+ background: #fff;
+ height: 59px;
+ text-align: center;
+ line-height: 59px;
+ border-bottom: 1px solid #eee;
+ transition-duration: .3s;
+ outline: none;
+ z-index: 100;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+
.collapse-nav a {
width: $sidebar_width;
position: fixed;
@@ -210,55 +233,12 @@
}
.page-sidebar-collapsed {
- padding-left: $sidebar_collapsed_width;
-
.sidebar-wrapper {
- width: $sidebar_collapsed_width;
-
- .header-logo {
- width: $sidebar_collapsed_width;
-
- a {
- padding-left: ($sidebar_collapsed_width - 36) / 2;
-
- .gitlab-text-container {
- display: none;
- }
- }
- }
-
- .nav-sidebar {
- width: $sidebar_collapsed_width;
-
- li {
- width: auto;
-
- a {
- span {
- display: none;
- }
- }
- }
- }
-
- .collapse-nav a {
- width: $sidebar_collapsed_width;
- }
-
- .sidebar-user {
- padding-left: ($sidebar_collapsed_width - 36) / 2;
- width: $sidebar_collapsed_width;
-
- .username {
- display: none;
- }
- }
+ display: none;
}
}
.page-sidebar-expanded {
- padding-left: $sidebar_collapsed_width;
-
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
@@ -309,3 +289,48 @@
padding-right: $sidebar_collapsed_width;
}
}
+
+.complex-sidebar {
+ display: inline-block;
+
+ .nav-primary {
+ width: 61px;
+ float: left;
+ height: 100vh;
+
+ .nav-sidebar {
+ width: 60px;
+
+ li a {
+ width: 60px;
+
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ .nav-secondary {
+ $nav-secondary-width: 168px;
+
+ float: left;
+ width: $nav-secondary-width;
+
+ .nav-sidebar {
+ width: $nav-secondary-width;
+
+ li {
+ width: $nav-secondary-width;
+
+ a {
+ width: $nav-secondary-width;
+
+ i {
+ display: none;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index f63ac033234..c72af5dad0a 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -56,8 +56,8 @@ $component-active-bg: $brand-info;
//##
$input-color: $text-color;
-$input-border: #e7e9ed;
-$input-border-focus: #7f8fa4;
+$input-border: $border-color;
+$input-border-focus: $focus-border-color;
$legend-color: $text-color;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index b1886fbe67b..c3c7bc9fdbe 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -138,6 +138,12 @@
}
}
+ a.no-attachment-icon {
+ &:before {
+ display: none;
+ }
+ }
+
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index bb49ae396c7..98fe794d362 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -11,6 +11,7 @@ $gutter_inner_width: 258px;
* UI elements
*/
$border-color: #efeff1;
+$focus-border-color: #3aabf0;
$table-border-color: #eef0f2;
$background-color: #faf9f9;
@@ -26,6 +27,7 @@ $gl-text-orange: #d90;
$gl-link-color: #3084bb;
$gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f;
+$gl-icon-color: $gl-placeholder-color;
$gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color;
@@ -178,7 +180,7 @@ $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555;
-$dropdown-input-focus-border: rgb(58, 171, 240);
+$dropdown-input-focus-border: $focus-border-color;
$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6);
@@ -207,3 +209,11 @@ $location-badge-bg: $gray-normal;
$location-icon-color: #e7e9ed;
$location-active-color: $gl-text-color;
$location-active-bg: $search-input-border-color;
+
+/*
+ * Notes
+ */
+$notes-light-color: #8e8e8e;
+$notes-action-color: #c3c3c3;
+$notes-role-color: #8e8e8e;
+$notes-role-border-color: #e4e4e4;
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 28994e60baa..37bf38fa65d 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -37,7 +37,7 @@
height: 300px;
overflow-y: scroll;
- input.emoji-search{
+ input.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index 2a7b5cfc7fd..67a9d7d2cf7 100644
--- a/app/assets/stylesheets/pages/ci_projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -42,7 +42,7 @@
}
}
- .loading{
+ .loading {
font-size: 20px;
}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 971656feb42..082911bd118 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -1,15 +1,15 @@
-.commit-title{
+.commit-title {
display: block;
}
-.commit-author, .commit-committer{
+.commit-author, .commit-committer {
display: block;
color: #999;
font-weight: normal;
font-style: italic;
}
-.commit-author strong, .commit-committer strong{
+.commit-author strong, .commit-committer strong {
font-weight: bold;
font-style: normal;
}
@@ -74,7 +74,7 @@
color: $gl-text-red;
}
}
- .edit-file{
+ .edit-file {
a {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index b6011fe7679..8272615768d 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -1,4 +1,4 @@
-.commits-compare-switch{
+.commits-compare-switch {
@include btn-default;
@include btn-white;
background: image-url("switch_icon.png") no-repeat center center;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 43be5e38ba8..0f0592a0ab8 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -1,5 +1,5 @@
.file-editor {
- #editor{
+ #editor {
border: none;
@include border-radius(0);
height: 500px;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 84eefd01cfe..c66efe978cd 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -43,10 +43,6 @@
.md {
color: #7f8fa4;
font-size: $gl-font-size;
-
- iframe.twitter-share-button {
- vertical-align: bottom;
- }
}
pre {
diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss
index 6d2bd33b28b..6926448519e 100644
--- a/app/assets/stylesheets/pages/lint.scss
+++ b/app/assets/stylesheets/pages/lint.scss
@@ -1,9 +1,9 @@
.ci-body {
- .incorrect-syntax{
+ .incorrect-syntax {
font-size: 19px;
color: red;
}
- .correct-syntax{
+ .correct-syntax {
font-size: 19px;
color: #47a447;
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 777bcbca5c3..403171d4532 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -36,7 +36,7 @@
}
}
- .login-box{
+ .login-box {
background: #fafafa;
border-radius: 10px;
box-shadow: 0 0 2px #ccc;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4bd2016bdcf..92fcaaeeacf 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -22,7 +22,7 @@ ul.notes {
margin-left: 55px;
}
- .note_created_ago, .note-updated-at {
+ .note-created-ago, .note-updated-at {
white-space: nowrap;
}
@@ -39,53 +39,6 @@ ul.notes {
}
}
- .discussion-header,
- .note-header {
- @extend .cgray;
-
- a:hover {
- text-decoration: none;
- }
-
- .avatar {
- float: left;
- margin-right: 10px;
- }
-
- .discussion-last-update,
- .note-last-update {
- &:before {
- content: "\00b7";
- }
-
- a {
- color: $gl-gray;
-
- &:hover {
- text-decoration: underline;
- }
- }
- }
- .author {
- color: #4c4e54;
- margin-right: 3px;
-
- &:hover {
- color: $gl-link-color;
- }
- }
- .author-username {
- }
-
- .note-role {
- float: right;
- margin-top: 1px;
- border: 1px solid #bbb;
- background-color: transparent;
- color: $gl-gray;
- }
- }
-
.discussion-body {
padding-top: 15px;
}
@@ -198,40 +151,88 @@ ul.notes {
border-width: 1px 0;
padding-top: 0;
vertical-align: top;
- &.parallel{
+ &.parallel {
border-width: 1px;
}
}
}
}
+.discussion-header,
+.note-header {
+ a {
+ color: inherit;
+
+ &:hover {
+ color: $gl-link-color;
+ text-decoration: none;
+ }
+ }
+
+ .author_link {
+ font-weight: 600;
+ }
+}
+
+.note-headline-light,
+.discussion-headline-light {
+ color: $notes-light-color;
+}
+
/**
* Actions for Discussions/Notes
*/
-.discussion,
-.note {
- .discussion-actions,
- .note-actions {
- float: right;
- margin-left: 10px;
+.discussion-actions,
+.note-actions {
+ float: right;
+ margin-left: 10px;
+ color: $notes-action-color;
+}
- a {
- margin-left: 5px;
- color: $gl-gray;
+.note-action-button,
+.discussion-action-button {
+ display: inline-block;
+ margin-left: 10px;
+ line-height: 24px;
- i.fa {
- font-size: 16px;
- line-height: 16px;
- }
+ .fa {
+ position: relative;
+ top: 1px;
+ font-size: 17px;
+ }
- &:hover {
- @extend .cgray;
- &.danger { @extend .cred; }
- }
- }
+ .fa-trash-o {
+ top: 0;
+ font-size: 16px;
}
}
+
+.discussion-toggle-button {
+ line-height: 20px;
+ font-size: 13px;
+
+ .fa {
+ margin-right: 3px;
+ font-size: 10px;
+ line-height: 18px;
+ vertical-align: top;
+ }
+}
+
+.note-role {
+ position: relative;
+ top: -2px;
+ display: inline-block;
+ padding-left: 4px;
+ padding-right: 4px;
+ color: $notes-role-color;
+ font-size: 12px;
+ line-height: 20px;
+ border: 1px solid $notes-role-border-color;
+ border-radius: $border-radius-base;
+}
+
.diff-file .note .note-actions {
right: 0;
top: 0;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index e909e15f74c..e83fa9e3d52 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,7 +6,7 @@
.navbar-nav {
li {
.badge.todos-pending-count {
- background-color: #7f8fa4;
+ background-color: $gl-icon-color;
margin-top: -5px;
font-weight: normal;
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index ed9f6031389..f010436bd36 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication,
:two_factor_grace_period,
:gravatar_enabled,
- :twitter_sharing_enabled,
:sign_in_text,
:help_page_text,
:home_page_url,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 62451ac73a9..49064f5d505 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -224,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
- ci_service = @merge_request.source_project.ci_service
- status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch)
+ ci_commit = @merge_request.ci_commit
+ if ci_commit
+ status = ci_commit.status
+ coverage = ci_commit.try(:coverage)
+ else
+ ci_service = @merge_request.source_project.ci_service
+ status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
- if ci_service.respond_to?(:commit_coverage)
- coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
+ if ci_service.respond_to?(:commit_coverage)
+ coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
+ end
end
response = {
+ title: merge_request.title,
+ sha: merge_request.last_commit_short_sha,
status: status,
coverage: coverage
}
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 62f53664db3..8c3a74c8236 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -71,7 +71,7 @@ class ProjectsController < Projects::ApplicationController
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
- if @project.unlink_fork
+ if ::Projects::UnlinkForkService.new(@project, current_user).execute
flash[:notice] = 'The fork relationship has been removed.'
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 23693629a4c..60a0ff32c9c 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -3,10 +3,6 @@ module ApplicationSettingsHelper
current_application_settings.gravatar_enabled?
end
- def twitter_sharing_enabled?
- current_application_settings.twitter_sharing_enabled?
- end
-
def signup_enabled?
current_application_settings.signup_enabled?
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index a36b13a7db5..592bad8ba24 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -216,7 +216,7 @@ module EventsHelper
end
def event_row_class(event)
- if event.body? || event.created_project?
+ if event.body?
"event-block"
else
"event-inline"
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c4879598c4e..052cd874733 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -12,7 +12,6 @@
# updated_at :datetime
# home_page_url :string(255)
# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
@@ -140,7 +139,6 @@ class ApplicationSetting < ActiveRecord::Base
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
- twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d0dbe009d0d..d09876a07d9 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -74,14 +74,14 @@ class Commit
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit>\h{7,40})
}x
end
def self.link_reference_pattern
- super("commit", /(?<commit>\h{7,40})/)
+ @link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
end
def to_reference(from_project = nil)
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 289dbc57287..51673897d98 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -43,14 +43,14 @@ class CommitRange
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{STRICT_PATTERN})
}x
end
def self.link_reference_pattern
- super("compare", /(?<commit_range>#{PATTERN})/)
+ @link_reference_pattern ||= super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index cf5b2c71675..afa2ca039ae 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -19,6 +19,7 @@ module Issuable
has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
+ has_many :todos, as: :target, dependent: :destroy
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
@@ -41,7 +42,7 @@ module Issuable
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
- scope :non_archived, -> { join_project.merge(Project.non_archived.only(:where)) }
+ scope :non_archived, -> { join_project.where(projects: { archived: false }) }
delegate :name,
:email,
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 2ca79df0a29..b8585d4e577 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -31,7 +31,7 @@ class ExternalIssue
# Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern
- %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
+ @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
def to_reference(_from_project = nil)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index ed960cb39f4..e064b0f8b95 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -73,14 +73,14 @@ class Issue < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<issue>\d+)
}x
end
def self.link_reference_pattern
- super("issues", /(?<issue>\d+)/)
+ @link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil)
diff --git a/app/models/label.rb b/app/models/label.rb
index 500d5a35521..55c01cae762 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -56,7 +56,7 @@ class Label < ActiveRecord::Base
# This pattern supports cross-project references.
#
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?:
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1245cc16d6a..bf185cb5dd8 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -135,6 +135,7 @@ class MergeRequest < ActiveRecord::Base
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
+ scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
@@ -149,14 +150,14 @@ class MergeRequest < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
}x
end
def self.link_reference_pattern
- super("merge_requests", /(?<merge_request>\d+)/)
+ @link_reference_pattern ||= super("merge_requests", /(?<merge_request>\d+)/)
end
# Returns all the merge requests from an ActiveRecord:Relation.
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index bbd59eab9ae..986184dd301 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -79,7 +79,7 @@ class Milestone < ActiveRecord::Base
end
def self.link_reference_pattern
- super("milestones", /(?<milestone>\d+)/)
+ @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end
def self.upcoming
@@ -89,7 +89,7 @@ class Milestone < ActiveRecord::Base
def to_reference(from_project = nil)
escaped_title = self.title.gsub("]", "\\]")
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
"[#{escaped_title}](#{url})"
diff --git a/app/models/note.rb b/app/models/note.rb
index b0c33f2eec5..87ced65c650 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -311,7 +311,7 @@ class Note < ActiveRecord::Base
for_merge_request? && for_diff_line?
end
- def for_project_snippet?
+ def for_snippet?
noteable_type == "Snippet"
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2285063ab50..3e1f04b4158 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -206,6 +206,8 @@ class Project < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
# Scopes
+ default_scope { where(pending_delete: false) }
+
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
@@ -469,7 +471,7 @@ class Project < ActiveRecord::Base
end
def web_url
- Gitlab::Application.routes.url_helpers.namespace_project_url(self.namespace, self)
+ Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
end
def web_url_without_protocol
@@ -590,7 +592,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
- Gitlab::Application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
+ Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
@@ -929,16 +931,6 @@ class Project < ActiveRecord::Base
self.builds_enabled = true
end
- def unlink_fork
- if forked?
- forked_from_project.lfs_objects.find_each do |lfs_object|
- lfs_object.projects << self
- end
-
- forked_project_link.destroy
- end
- end
-
def any_runners?(&block)
if runners.active.any?(&block)
return true
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 05436cd0f79..eaa5654b9c6 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -20,7 +20,7 @@
#
class GitlabIssueTrackerService < IssueTrackerService
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index aba37921c09..1ed42c4f3e7 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -21,7 +21,7 @@
class JiraService < IssueTrackerService
include HTTParty
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
DEFAULT_API_VERSION = 2
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ff24b75dcd1..a8e826c9cbf 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -72,7 +72,7 @@ class Repository
return @has_visible_content unless @has_visible_content.nil?
@has_visible_content = cache.fetch(:has_visible_content?) do
- raw_repository.branch_count > 0
+ branch_count > 0
end
end
@@ -173,7 +173,7 @@ class Repository
end
def branch_names
- cache.fetch(:branch_names) { raw_repository.branch_names }
+ cache.fetch(:branch_names) { branches.map(&:name) }
end
def tag_names
@@ -191,7 +191,7 @@ class Repository
end
def branch_count
- @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
+ @branch_count ||= cache.fetch(:branch_count) { branches.size }
end
def tag_count
@@ -239,7 +239,7 @@ class Repository
def expire_branches_cache
cache.expire(:branch_names)
- @branches = nil
+ @local_branches = nil
end
def expire_cache(branch_name = nil, revision = nil)
@@ -364,6 +364,11 @@ class Repository
expire_tag_count_cache
end
+ def before_import
+ expire_emptiness_caches
+ expire_exists_cache
+ end
+
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
@@ -614,10 +619,14 @@ class Repository
refs_contains_sha('tag', sha)
end
- def branches
- @branches ||= raw_repository.branches
+ def local_branches
+ @local_branches ||= rugged.branches.each(:local).map do |branch|
+ Gitlab::Git::Branch.new(branch.name, branch.target)
+ end
end
+ alias_method :branches, :local_branches
+
def tags
@tags ||= raw_repository.tags
end
@@ -820,7 +829,7 @@ class Repository
end
def fetch_ref(source_path, source_ref, target_ref)
- args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
+ args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b9e835a4486..b96e3937281 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -56,14 +56,14 @@ class Snippet < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<snippet>\d+)
}x
end
def self.link_reference_pattern
- super("snippets", /(?<snippet>\d+)/)
+ @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil)
diff --git a/app/models/user.rb b/app/models/user.rb
index 128ddc2a694..2b0bee2099f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -408,6 +408,8 @@ class User < ActiveRecord::Base
end
def owns_notification_email
+ return if self.temp_oauth_email?
+
self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
end
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index a5efb21fab6..82e7090f1ea 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -43,7 +43,7 @@ module Issues
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
project: @new_project, author: @old_issue.author,
- description: unfold_references(@old_issue.description) }
+ description: rewrite_content(@old_issue.description) }
new_params = @old_issue.serializable_hash.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
@@ -53,7 +53,7 @@ module Issues
@old_issue.notes.find_each do |note|
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
- note: unfold_references(new_note.note),
+ note: rewrite_content(new_note.note),
created_at: note.created_at,
updated_at: note.updated_at }
@@ -61,6 +61,18 @@ module Issues
end
end
+ def rewrite_content(content)
+ return unless content
+
+ rewriters = [Gitlab::Gfm::ReferenceRewriter,
+ Gitlab::Gfm::UploadsRewriter]
+
+ rewriters.inject(content) do |text, klass|
+ rewriter = klass.new(text, @old_project, @current_user)
+ rewriter.rewrite(@new_project)
+ end
+ end
+
def close_issue
close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false)
@@ -78,20 +90,12 @@ module Issues
direction: :to)
end
- def unfold_references(content)
- return unless content
-
- rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
- @current_user)
- rewriter.rewrite(@new_project)
+ def mark_as_moved
+ @old_issue.update(moved_to: @new_issue)
end
def notify_participants
notification_service.issue_moved(@old_issue, @new_issue, @current_user)
end
-
- def mark_as_moved
- @old_issue.update(moved_to: @new_issue)
- end
end
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 2015897dd19..ef15ef6a473 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -46,6 +46,8 @@ module Projects
def import_data
return unless has_importer?
+ project.repository.before_import
+
unless importer.execute
raise Error, 'The remote data could not be imported.'
end
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
new file mode 100644
index 00000000000..315c3e16292
--- /dev/null
+++ b/app/services/projects/unlink_fork_service.rb
@@ -0,0 +1,19 @@
+module Projects
+ class UnlinkForkService < BaseService
+ def execute
+ return unless @project.forked?
+
+ @project.forked_from_project.lfs_objects.find_each do |lfs_object|
+ lfs_object.projects << @project
+ end
+
+ merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project)
+
+ merge_requests.each do |mr|
+ MergeRequests::CloseService.new(@project, @current_user).execute(mr)
+ end
+
+ @project.forked_project_link.destroy
+ end
+ end
+end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index ea2b26ccb52..f0615ec7420 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -95,17 +95,19 @@ class SystemHooksService
end
def project_member_data(model)
+ project = model.project || Project.unscoped.find(model.source_id)
+
{
- project_name: model.project.name,
- project_path: model.project.path,
- project_path_with_namespace: model.project.path_with_namespace,
- project_id: model.project.id,
- user_username: model.user.username,
- user_name: model.user.name,
- user_email: model.user.email,
- user_id: model.user.id,
- access_level: model.human_access,
- project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
+ project_name: project.name,
+ project_path: project.path,
+ project_path_with_namespace: project.path_with_namespace,
+ project_id: project.id,
+ user_username: model.user.username,
+ user_name: model.user.name,
+ user_email: model.user.email,
+ user_id: model.user.id,
+ access_level: model.human_access,
+ project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase
}
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index e022a046c48..658b086496f 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -224,7 +224,7 @@ class SystemNoteService
#
# "Started branch `issue-branch-button-201`"
def self.new_issue_branch(issue, project, author, branch)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "Started branch [`#{branch}`](#{link})"
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index f2662922e90..42c5bca90fd 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -123,7 +123,7 @@ class TodoService
def handle_note(note, author)
# Skip system notes, and notes on project snippet
- return if note.system? || note.for_project_snippet?
+ return if note.system? || note.for_snippet?
project = note.project
target = note.noteable
@@ -170,14 +170,30 @@ class TodoService
end
def filter_mentioned_users(project, target, author)
- mentioned_users = target.mentioned_users.select do |user|
- user.can?(:read_project, project)
- end
-
+ mentioned_users = target.mentioned_users
+ mentioned_users = reject_users_without_access(mentioned_users, project, target)
mentioned_users.delete(author)
mentioned_users.uniq
end
+ def reject_users_without_access(users, project, target)
+ if target.is_a?(Note) && target.for_issue?
+ target = target.noteable
+ end
+
+ if target.is_a?(Issue)
+ select_users(users, :read_issue, target)
+ else
+ select_users(users, :read_project, project)
+ end
+ end
+
+ def select_users(users, ability, subject)
+ users.select do |user|
+ user.can?(ability.to_sym, subject)
+ end
+ end
+
def pending_todos(user, criteria = {})
valid_keys = [:project_id, :target_id, :target_type, :commit_id]
user.todos.pending.where(criteria.slice(*valid_keys))
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 86d24469e05..1af9e9b0edb 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -1,14 +1,15 @@
# encoding: utf-8
class FileUploader < CarrierWave::Uploader::Base
include UploaderHelper
+ MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
storage :file
attr_accessor :project, :secret
- def initialize(project, secret = self.class.generate_secret)
+ def initialize(project, secret = nil)
@project = project
- @secret = secret
+ @secret = secret || self.class.generate_secret
end
def base_dir
@@ -23,14 +24,14 @@ class FileUploader < CarrierWave::Uploader::Base
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
- def self.generate_secret
- SecureRandom.hex
- end
-
def secure_url
File.join("/uploads", @secret, file.filename)
end
+ def to_markdown
+ to_h[:markdown]
+ end
+
def to_h
filename = image? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
@@ -45,4 +46,8 @@ class FileUploader < CarrierWave::Uploader::Base
markdown: markdown
}
end
+
+ def self.generate_secret
+ SecureRandom.hex
+ end
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 0350995d03d..de86dacbb12 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -77,13 +77,6 @@
= f.check_box :gravatar_enabled
Gravatar enabled
.form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :twitter_sharing_enabled do
- = f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block'
- Twitter enabled
- %span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter
- .form-group
= f.label :default_projects_limit, class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :default_projects_limit, class: 'form-control'
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
index 588ad767426..3571eefd570 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -15,7 +15,7 @@
%td
- if project
- = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace"
+ = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 3274ba5377b..6dd2fef395d 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,4 +1,4 @@
-.admin-dashboard
+.admin-dashboard.prepend-top-default
.row
.col-md-4
%h4 Statistics
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 41c43899978..149593e7f46 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -1,5 +1,5 @@
- page_title "Deploy Keys"
-.panel.panel-default
+.panel.panel-default.prepend-top-default
.panel-heading
Public deploy keys (#{@deploy_keys.count})
.controls
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
new file mode 100644
index 00000000000..9025aaac097
--- /dev/null
+++ b/app/views/admin/groups/_group.html.haml
@@ -0,0 +1,28 @@
+- css_class = '' unless local_assigns[:css_class]
+- css_class += ' no-description' if group.description.blank?
+
+%li.group-row{ class: css_class }
+ .controls.hidden-xs
+ = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm'
+ = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove'
+
+ .stats
+ %span
+ = icon('bookmark')
+ = number_with_delimiter(group.projects.count)
+
+ %span
+ = icon('users')
+ = number_with_delimiter(group.users.count)
+
+ %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
+ = visibility_level_icon(group.visibility_level, fw: false)
+
+ = image_tag group_icon(group), class: 'avatar s40 hidden-xs'
+ .title
+ = link_to [:admin, group], class: 'group-name' do
+ = group.name
+
+ - if group.description.present?
+ .description
+ = markdown(group.description, pipeline: :description)
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 6bdc885a312..775072a7441 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,20 +1,19 @@
- page_title "Groups"
%h3.page-title
Groups (#{number_with_delimiter(@groups.total_count)})
- = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%p.light
Group allows you to keep projects organized.
Use groups for uniting related projects.
-%hr
-= form_tag admin_groups_path, method: :get, class: 'form-inline' do
- = hidden_field_tag :sort, @sort
- .form-group
- = text_field_tag :name, params[:name], class: "form-control"
- = button_tag "Search", class: "btn submit btn-primary"
+.top-area
+ .nav-search
+ = form_tag admin_groups_path, method: :get, class: 'form-inline' do
+ = hidden_field_tag :sort, @sort
+ = text_field_tag :name, params[:name], class: "form-control"
+ = button_tag "Search", class: "btn submit btn-primary"
- .pull-right
+ .nav-controls
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light
@@ -33,34 +32,10 @@
= sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
-%hr
-
-%ul.bordered-list
+%ul.content-list
- @groups.each do |group|
- %li
- .clearfix
- .pull-right.prepend-top-10
- = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-sm"
- = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-sm btn-remove"
-
- %h4
- = link_to [:admin, group] do
- %span{ class: visibility_level_color(group.visibility_level) }
- = visibility_level_icon(group.visibility_level)
-
- %i.fa.fa-folder
- = group.name
-
- &rarr;
- %span.monospace
- %strong #{group.path}/
- .clearfix
- %p
- = truncate group.description, length: 150
- .clearfix
- %p.light
- #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
-
+ = render 'group', group: group
= paginate @groups, theme: "gitlab"
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
index 3c57e3dc174..05d6b9ed238 100644
--- a/app/views/admin/labels/index.html.haml
+++ b/app/views/admin/labels/index.html.haml
@@ -1,8 +1,10 @@
- page_title "Labels"
-= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
- New label
-%h3.page-title
- Labels
+
+%div
+ = link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
+ New label
+ %h3.page-title
+ Labels
%hr
.labels
@@ -13,4 +15,4 @@
- else
.light-well
.nothing-here-block There are no labels yet
-
+
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index c407972cd08..2dad64b8d0f 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,4 +1,4 @@
-%p.lead
+%p.lead.prepend-top-default
%span
To register a new runner you should enter the following registration token.
With this token the runner will request a unique runner token and use that for future communication.
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 25d2b64e381..aa0aff86d4d 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -10,7 +10,10 @@
(removed)
%span.todo-label
= todo_action_name(todo)
- = todo_target_link(todo)
+ - if todo.target
+ = todo_target_link(todo)
+ - else
+ (removed)
&middot; #{time_ago_with_tooltip(todo.created_at)}
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index 8cf36c711b4..5a2a469ba62 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -7,21 +7,3 @@
= link_to_project event.project
- else
= event.project_name
-
-- if !event.project.private? && twitter_sharing_enabled?
- .event-body{"data-user-is" => event.author_id}
- .event-note
- .md
- %p
- Congratulations! Why not share your accomplishment with the world?
-
- %a.twitter-share-button{ |
- href: "https://twitter.com/share", |
- "data-url" => event.project.web_url, |
- "data-text" => "I just #{event.action_name} a new project on GitLab! GitLab is version control on your server.", |
- "data-size" => "medium", |
- "data-related" => "gitlab", |
- "data-hashtags" => "gitlab", |
- "data-count" => "none"}
- Tweet
- %script{src: "//platform.twitter.com/widgets.js"}
diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml
deleted file mode 100644
index 2ed51d87ca1..00000000000
--- a/app/views/layouts/_collapse_button.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- if nav_menu_collapsed?
- = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
-- else
- = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c799e9c588d..9be36273c7d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,5 +1,7 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast"
+ .expand-nav
+ = link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
%a#logo
@@ -8,15 +10,19 @@
.gitlab-text-container
%h3 GitLab
- - if defined?(sidebar) && sidebar
- = render "layouts/nav/#{sidebar}"
- - elsif current_user
- = render 'layouts/nav/dashboard'
+ - primary_sidebar = current_user ? 'dashboard' : 'explore'
+
+ - if defined?(sidebar) && sidebar && sidebar != primary_sidebar
+ .complex-sidebar
+ .nav-primary
+ = render "layouts/nav/#{primary_sidebar}"
+ .nav-secondary
+ = render "layouts/nav/#{sidebar}"
- else
- = render 'layouts/nav/explore'
+ = render "layouts/nav/#{primary_sidebar}"
.collapse-nav
- = render partial: 'layouts/collapse_button'
+ = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar"
- if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 280a1b93729..22d1d4d8597 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -95,7 +95,7 @@
Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
- = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
+ = nav_link(controller: :application_settings) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 4a0069f18f8..d1a180e4299 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -15,12 +15,12 @@
= icon('dashboard fw')
%span
Activity
- = nav_link(controller: :groups) do
+ = nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do
= link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
- = nav_link(controller: :milestones) do
+ = nav_link(path: 'dashboard#milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
@@ -48,7 +48,6 @@
%span
Help
- %li.separate-item
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 55940741dc0..0b7de9633ec 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,12 +1,4 @@
%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
-
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
= icon('group fw')
@@ -42,7 +34,7 @@
%span
Members
- if can?(current_user, :admin_group, @group)
- = nav_link(html_options: { class: "separate-item" }) do
+ = nav_link do
= link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw')
%span
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 3b9d31a6fc5..cc119fd64e6 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,12 +1,4 @@
%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
-
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 86b46e8c75e..d0f82b5f57f 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,19 +1,4 @@
%ul.nav.nav-sidebar
- - if @project.group
- = nav_link do
- = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to group
- - else
- = 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
-
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('bookmark fw')
@@ -113,7 +98,7 @@
Snippets
- if project_nav_tab? :settings
- = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
+ = nav_link(html_options: {class: "#{project_tab_class}"}) do
= link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 5d342ef58e5..69fc81cb45c 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -8,8 +8,6 @@
Increase your account's security by enabling two-factor authentication (2FA).
.col-lg-9
%p
- Status: #{current_user.two_factor_enabled? ? 'enabled' : 'disabled'}
- %p
Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code.
More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
.row.append-bottom-10
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index e7c85edff96..1e4c46fca2f 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -3,25 +3,32 @@
%a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- - if can?(current_user, :create_issue, @project)
+ - can_create_issue = can?(current_user, :create_issue, @project)
+ - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+ - can_create_snippet = can?(current_user, :create_snippet, @project)
+
+ - if can_create_issue
%li
= link_to url_for_new_issue(@project, only_path: true) do
= icon('exclamation-circle fw')
New issue
- - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+
- if merge_project
%li
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do
= icon('tasks fw')
New merge request
- - if can?(current_user, :create_snippet, @project)
+
+ - if can_create_snippet
%li
= link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw')
New snippet
- - if can?(current_user, :push_code, @project)
+ - if can_create_issue || merge_project || can_create_snippet
%li.divider
+
+ - if can?(current_user, :push_code, @project)
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
@@ -35,13 +42,11 @@
= icon('tags fw')
New tag
- elsif current_user && current_user.already_forked?(@project)
- %li.divider
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
New file
- elsif can?(current_user, :fork_project, @project)
- %li.divider
%li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index d22d1da8402..2cf9115e4dd 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -39,7 +39,7 @@
%td
= build.name
- .pull-right
+ .label-container
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 8367112a9cb..2731219ccad 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,7 +1,10 @@
- diff = diff_file.diff
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
-- old_commit_id = diff_refs.first.id
-- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+// diff_refs will be nil for orphaned commits (e.g. first commit in repo)
+- if diff_refs
+ - old_commit_id = diff_refs.first.id
+ - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index b05ab869215..2ec0d20a879 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,15 +1,17 @@
- if @ci_commit
.mr-widget-heading
- .ci_widget{class: "ci-#{@ci_commit.status}"}
- = ci_status_icon(@ci_commit)
- %span
- Build
- = ci_status_label(@ci_commit)
- for
- = succeed "." do
- = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
- %span.ci-coverage
- = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
+ - %w[success skipped canceled failed running pending].each do |status|
+ .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @ci_commit.status == status) }
+ = ci_icon_for_status(status)
+ %span
+ CI build
+ = ci_label_for_status(status)
+ for
+ - commit = @merge_request.last_commit
+ = succeed "." do
+ = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
+ %span.ci-coverage
+ = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
- elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
@@ -43,5 +45,5 @@
:javascript
$(function() {
- merge_request_widget.getCiStatus();
+ merge_request_widget.getCIStatus(false);
});
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index a489d4f9b24..2be06aebe6c 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -9,12 +9,17 @@
:javascript
var merge_request_widget;
-
- merge_request_widget = new MergeRequestWidget({
- url_to_automerge_check: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ var opts = {
+ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
- url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
+ ci_status: "",
+ ci_message: "Build {{status}} for \"{{title}}\"",
ci_enable: #{@project.ci_service ? "true" : "false"},
- current_status: "#{@merge_request.gitlab_merge_status}",
- });
+ builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
+ };
+ if(typeof merge_request_widget === 'undefined') {
+ merge_request_widget = new MergeRequestWidget(opts);
+ }
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 2cf32e6093d..34fe1743f4b 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -5,28 +5,21 @@
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
.timeline-content
.note-header
+ = link_to_member(note.project, note.author, avatar: false)
+ .inline.note-headline-light
+ = "#{note.author.to_reference} commented"
+ %a{ href: "##{dom_id(note)}" }
+ = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- if note_editable?(note)
.note-actions
- = link_to '#', title: 'Edit comment', class: 'js-note-edit' do
+ - access = note.project.team.human_max_access(note.author.id)
+ - if access
+ %span.note-role
+ = access
+ = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil-square-o')
-
- = 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: 'js-note-delete danger' do
+ = 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')
-
- - unless note.system
- - access = note.project.team.human_max_access(note.author.id)
- - if access
- %span.note-role.label
- = access
-
- = link_to_member(note.project, note.author, avatar: false)
-
- %span.author-username
- = '@' + note.author.username
-
- %span.note-last-update
- %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
- = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index 4f15a99d061..cd8a5f0bd02 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -1,22 +1,20 @@
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{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
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- started a discussion
- = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
- %strong on the diff
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
-
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{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 8d0a1af787c..46f2ba4bbcf 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -3,21 +3,20 @@
- commit_description = commit ? 'commit' : 'a deleted commit'
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{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')
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- 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')
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{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
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index 218b0da3977..f8e000b424f 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -1,19 +1,18 @@
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{note.author.to_reference} started a discussion"
+ on the outdated diff
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- started a discussion on the
- %strong outdated diff
- %div
+ .last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{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/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 5fcba2b7e93..9544e3d3e17 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,24 +1,20 @@
- project = note.project
+- note_url = Gitlab::UrlBuilder.new(:note).build(note.id)
+- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
.search-result-row
%h5.note-search-caption.str-truncated
%i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
commented on
+ = link_to project.name_with_namespace, project
+ &middot;
- if note.for_commit?
- = link_to project do
- = project.name_with_namespace
- &middot;
- = link_to namespace_project_commit_path(project.namespace, project, note.commit_id, anchor: dom_id(note)) do
- Commit #{truncate_sha(note.commit_id)}
+ = link_to "Commit #{truncate_sha(note.commit_id)}", note_url
- else
- = link_to project do
- = project.name_with_namespace
- &middot;
- %span #{note.noteable_type.titleize} ##{note.noteable.iid}
+ %span #{note.noteable_type.titleize} ##{noteable_identifier}
&middot;
- = link_to [project.namespace.becomes(Namespace), project, note.noteable, anchor: dom_id(note)] do
- = note.noteable.title
+ = link_to note.noteable.title, note_url
.note-search-result
.term
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index c99da92be9f..921eaefd79a 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -7,13 +7,13 @@
class: "check_all_issues left"
.issues-other-filters
.filter-item.inline
- - if params[:author_id]
+ - if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- - if params[:assignee_id]
+ - if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id])
= dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 006a34a11e3..fd5e58c1f1f 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,4 +1,4 @@
-- if params[:label_name]
+- if params[:label_name].present?
= hidden_field_tag(:label_name, params[:label_name])
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-filter-submit{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"}}
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index e52d2e39e6b..2fcf40ece99 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -1,4 +1,4 @@
-- if params[:milestone_title]
+- if params[:milestone_title].present?
= hidden_field_tag(:milestone_title, params[:milestone_title])
= dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index bca816f22cb..0c4b6a5618b 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -87,7 +87,7 @@
%div{ class: container_class }
.tab-content
#activity.tab-pane
- .gray-content-block.white.second-block
+ .gray-content-block.calender-block.white.second-block.hidden-xs
%div{ class: container_class }
.user-calendar{data: {href: user_calendar_path}}
%h4.center.light
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 02647229776..8ffcdc4a327 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -15,12 +15,14 @@
- if current_user
:javascript
+ var get_emojis_url = "#{emojis_path}";
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};
window.awards_handler = new AwardsHandler(
+ get_emojis_url,
post_emoji_url,
noteable_type,
noteable_id,
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
index d06e4480292..b51c6a266c9 100644
--- a/app/workers/project_destroy_worker.rb
+++ b/app/workers/project_destroy_worker.rb
@@ -5,7 +5,7 @@ class ProjectDestroyWorker
def perform(project_id, user_id, params)
begin
- project = Project.find(project_id)
+ project = Project.unscoped.find(project_id)
rescue ActiveRecord::RecordNotFound
return
end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 626268d7648..2b989015279 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -174,7 +174,6 @@ end
Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
-Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
diff --git a/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb b/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb
new file mode 100644
index 00000000000..1fff9759d1e
--- /dev/null
+++ b/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb
@@ -0,0 +1,17 @@
+class RemoveTodosForDeletedIssues < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM todos
+ WHERE todos.target_type = 'Issue'
+ AND NOT EXISTS (
+ SELECT *
+ FROM issues
+ WHERE issues.id = todos.target_id
+ AND issues.deleted_at IS NULL
+ )
+ SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb b/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb
new file mode 100644
index 00000000000..275554e736e
--- /dev/null
+++ b/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb
@@ -0,0 +1,6 @@
+class AddIndexOnPendingDeleteProjects < ActiveRecord::Migration
+ def change
+ add_index :projects, :pending_delete
+ end
+end
+
diff --git a/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb b/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb
new file mode 100644
index 00000000000..54cea964ff2
--- /dev/null
+++ b/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb
@@ -0,0 +1,17 @@
+class RemoveTodosForDeletedMergeRequests < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM todos
+ WHERE todos.target_type = 'MergeRequest'
+ AND NOT EXISTS (
+ SELECT *
+ FROM merge_requests
+ WHERE merge_requests.id = todos.target_id
+ AND merge_requests.deleted_at IS NULL
+ )
+ SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb b/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb
new file mode 100644
index 00000000000..0d736e323b6
--- /dev/null
+++ b/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb
@@ -0,0 +1,5 @@
+class RemoveTwitterSharingEnabledFromApplicationSettings < ActiveRecord::Migration
+ def change
+ remove_column :application_settings, :twitter_sharing_enabled, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index dce2bfe62ca..e63e22ce864 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160320204112) do
+ActiveRecord::Schema.define(version: 20160331133914) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -418,7 +418,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do
t.integer "iid"
t.integer "updated_by_id"
t.integer "moved_to_id"
- t.boolean "confidential", default: false
+ t.boolean "confidential", default: false
t.datetime "deleted_at"
end
@@ -745,6 +745,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
+ add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 3703f4b327a..3a909a2bc87 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -491,6 +491,172 @@ Parameters:
- `id` (required) - The ID of the project to be forked
+### Archive a project
+
+Archives the project if the user is either admin or the project owner of this project. This action is
+idempotent, thus archiving an already archived project will not change the project.
+
+Status code 201 with the project as body is given when successful, in case the user doesn't
+have the proper access rights, code 403 is returned. Status 404 is returned if the project
+doesn't exist, or is hidden to the user.
+
+```
+POST /projects/:id/archive
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
+```
+
+Example response:
+
+```json
+{
+ "id": 3,
+ "description": null,
+ "default_branch": "master",
+ "public": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "tag_list": [
+ "example",
+ "disapora project"
+ ],
+ "owner": {
+ "id": 3,
+ "name": "Diaspora",
+ "created_at": "2013-09-30T13: 46: 02Z"
+ },
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "builds_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
+ "namespace": {
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "description": "",
+ "id": 3,
+ "name": "Diaspora",
+ "owner_id": 1,
+ "path": "diaspora",
+ "updated_at": "2013-09-30T13: 46: 02Z"
+ },
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": true,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+}
+```
+
+### Unarchive a project
+
+Unarchives the project if the user is either admin or the project owner of this project. This action is
+idempotent, thus unarchiving an non-archived project will not change the project.
+
+Status code 201 with the project as body is given when successful, in case the user doesn't
+have the proper access rights, code 403 is returned. Status 404 is returned if the project
+doesn't exist, or is hidden to the user.
+
+```
+POST /projects/:id/archive
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
+```
+
+Example response:
+
+```json
+{
+ "id": 3,
+ "description": null,
+ "default_branch": "master",
+ "public": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "tag_list": [
+ "example",
+ "disapora project"
+ ],
+ "owner": {
+ "id": 3,
+ "name": "Diaspora",
+ "created_at": "2013-09-30T13: 46: 02Z"
+ },
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "builds_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
+ "namespace": {
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "description": "",
+ "id": 3,
+ "name": "Diaspora",
+ "owner_id": 1,
+ "path": "diaspora",
+ "updated_at": "2013-09-30T13: 46: 02Z"
+ },
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+}
+```
+
### Remove project
Removes a project including all associated resources (issues, merge requests etc.)
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 001de76c7af..1e745115dc8 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -26,7 +26,6 @@ Example response:
"default_branch_protection" : 2,
"restricted_visibility_levels" : [],
"signin_enabled" : true,
- "twitter_sharing_enabled" : true,
"after_sign_out_path" : null,
"max_attachment_size" : 10,
"user_oauth_applications" : true,
@@ -57,7 +56,6 @@ PUT /application/settings
| `sign_in_text` | string | no | Text on login page |
| `home_page_url` | string | no | Redirect to this URL when not logged in |
| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. |
-| `twitter_sharing_enabled` | boolean | no | Allow users to share project creation on Twitter |
| `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. |
| `max_attachment_size` | integer | no | Limit attachment size in MB |
| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
@@ -85,7 +83,6 @@ Example response:
"updated_at": "2015-06-30T13:22:42.210Z",
"home_page_url": "",
"default_branch_protection": 2,
- "twitter_sharing_enabled": true,
"restricted_visibility_levels": [],
"max_attachment_size": 10,
"session_expire_delay": 10080,
diff --git a/doc/install/installation.md b/doc/install/installation.md
index bffbc776500..e0a16df09c1 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -227,9 +227,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-6-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-7-stable gitlab
-**Note:** You can change `8-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-7-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
new file mode 100644
index 00000000000..76eee147c72
--- /dev/null
+++ b/doc/update/8.6-to-8.7.md
@@ -0,0 +1,146 @@
+# From 8.6 to 8.7
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-7-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v2.7.0
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.7.1
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 7. Update configuration files
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-6-stable:lib/support/nginx/gitlab-ssl origin/8-7-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-6-stable:lib/support/nginx/gitlab origin/8-7-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-7-stable/lib/support/init.d/gitlab.default.example#L37
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.6)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.5 to 8.6](8.5-to-8.6.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index c3b3577c449..db73309804c 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -6,6 +6,7 @@ Feature: Dashboard
And project "Shop" has push event
And project "Shop" has CI enabled
And project "Shop" has CI build
+ And project "Shop" has labels: "bug", "feature", "enhancement"
And I visit dashboard page
Scenario: I should see projects list
@@ -51,6 +52,13 @@ Feature: Dashboard
Then The list should be sorted by "Oldest updated"
@javascript
+ Scenario: Filtering Issues by label
+ Given project "Shop" has issue "Bugfix1" with label "feature"
+ When I visit dashboard issues page
+ And I filter the list by label "feature"
+ Then I should see "Bugfix1" in issues list
+
+ @javascript
Scenario: Visiting Project's issues after sorting
Given I visit dashboard issues page
And I sort the list by "Oldest updated"
diff --git a/features/groups.feature b/features/groups.feature
index 419a5d3963d..49e939807b5 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -7,10 +7,6 @@ Feature: Groups
When I visit group "NonExistentGroup" page
Then page status code should be 404
- Scenario: I should have back to group button
- When I visit group "Owned" page
- Then I should see back to dashboard button
-
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
diff --git a/features/project/project.feature b/features/project/project.feature
index f1f3ed26065..aa22401c88e 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -18,15 +18,6 @@ Feature: Project
Then I should see the default project avatar
And I should not see the "Remove avatar" button
- Scenario: I should have back to group button
- And project "Shop" belongs to group
- And I visit project "Shop" page
- Then I should see back to group button
-
- Scenario: I should have back to group button
- And I visit project "Shop" page
- Then I should see back to dashboard button
-
Scenario: I should have readme on page
And I visit project "Shop" page
Then I should see project "Shop" README
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 5062e348844..b5980b35102 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -87,4 +87,23 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
step 'I should see 1 project at group list' do
expect(find('span.last_activity/span')).to have_content('1')
end
+
+ step 'I filter the list by label "feature"' do
+ page.within ".labels-filter" do
+ find('.dropdown').click
+ click_link "feature"
+ end
+ end
+
+ step 'I should see "Bugfix1" in issues list' do
+ page.within "ul.content-list" do
+ expect(page).to have_content "Bugfix1"
+ end
+ end
+
+ step 'project "Shop" has issue "Bugfix1" with label "feature"' do
+ project = Project.find_by(name: "Shop")
+ issue = create(:issue, title: "Bugfix1", project: project, assignee: current_user)
+ issue.labels << project.labels.find_by(title: 'feature')
+ end
end
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index a167d259837..b6ce5bc9cec 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include SharedUser
step 'I click on group milestones' do
- click_link 'Milestones'
+ page.within '.nav-secondary' do
+ click_link("Milestones")
+ end
end
step 'I should see group milestones index page has no milestones' do
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index e5b7db4c5e3..483370f41c6 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedGroup
include SharedUser
- step 'I should see back to dashboard button' do
- expect(page).to have_content 'Go to dashboard'
- end
-
step 'I should see group "Owned"' do
expect(page).to have_content '@owned'
end
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 19d81453d8c..4584fc4d754 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -82,7 +82,9 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Issues
step 'I click the "Milestones" tab' do
- click_link('Milestones')
+ page.within '.nav-secondary' do
+ click_link('Milestones')
+ end
end
step 'I click the "Labels" tab' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 527f7853da9..d9b16afa9b8 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I goto the Merge Requests page' do
- page.within '.page-sidebar-expanded' do
+ page.within '.nav-secondary' do
click_link "Merge Requests"
end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 91fe19dd477..a4f02b590ea 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -326,7 +326,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on diff' do
page.within(".notes .discussion") do
- page.should have_content "#{current_user.name} started a discussion"
+ page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
end
@@ -334,7 +334,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion by user "John Doe" has started on diff' do
page.within(".notes .discussion") do
- page.should have_content "#{user_exists("John Doe").name} started a discussion"
+ page.should have_content "#{user_exists("John Doe").name} #{user_exists("John Doe").to_reference} started a discussion"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
end
@@ -350,7 +350,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on commit diff' do
page.within(".notes .discussion") do
- page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
end
@@ -358,7 +358,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on commit' do
page.within(".notes .discussion") do
- page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content "One comment to rule them all"
end
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index ef185861e00..8f1d4a223a9 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -114,7 +114,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should not see "Snippets" button' do
- expect(page).not_to have_link 'Snippets'
+ page.within '.nav-secondary' do
+ expect(page).not_to have_link 'Snippets'
+ end
end
step 'project "Shop" belongs to group' do
@@ -123,14 +125,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps
@project.save!
end
- step 'I should see back to dashboard button' do
- expect(page).to have_content 'Go to dashboard'
- end
-
- step 'I should see back to group button' do
- expect(page).to have_content 'Go to group'
- end
-
step 'I click notifications drop down button' do
click_link 'notifications-button'
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 223b7277b51..9f6aed1c5b9 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -85,7 +85,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I have an existing Wiki page with images linked on page' do
- wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![image](image.jpg)", :markdown, "first commit")
+ wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![alt text](image.jpg)", :markdown, "first commit")
@wiki_page = wiki.find_page("pictures")
end
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 4fc2ece79ff..fa7d24ce611 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -41,7 +41,7 @@ module SharedProjectTab
end
step 'the active main tab should be Settings' do
- page.within '.nav-sidebar' do
+ page.within '.nav-secondary' do
expect(page).to have_content('Go to project')
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index f686c568bee..340fc5452ab 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -8,7 +8,7 @@ module API
expose :id, :state, :avatar_url
expose :web_url do |user, options|
- Gitlab::Application.routes.url_helpers.user_url(user)
+ Gitlab::Routing.url_helpers.user_url(user)
end
end
@@ -89,7 +89,7 @@ module API
expose :avatar_url
expose :web_url do |group, options|
- Gitlab::Application.routes.url_helpers.group_url(group)
+ Gitlab::Routing.url_helpers.group_url(group)
end
end
@@ -334,7 +334,6 @@ module API
expose :updated_at
expose :home_page_url
expose :default_branch_protection
- expose :twitter_sharing_enabled
expose :restricted_visibility_levels
expose :max_attachment_size
expose :session_expire_delay
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 6fcb5261e40..24b31005475 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -244,6 +244,34 @@ module API
end
end
+ # Archive project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # PUT /projects/:id/archive
+ post ':id/archive' do
+ authorize!(:archive_project, user_project)
+
+ user_project.archive!
+
+ present user_project, with: Entities::Project
+ end
+
+ # Unarchive project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # PUT /projects/:id/unarchive
+ post ':id/unarchive' do
+ authorize!(:archive_project, user_project)
+
+ user_project.unarchive!
+
+ present user_project, with: Entities::Project
+ end
+
# Remove project
#
# Parameters:
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 34c38913474..f21dbef216c 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -11,15 +11,19 @@ module Banzai
end
def self.object_name
- object_class.name.underscore
+ @object_name ||= object_class.name.underscore
end
def self.object_sym
- object_name.to_sym
+ @object_sym ||= object_name.to_sym
end
def self.data_reference
- "data-#{object_name.dasherize}"
+ @data_reference ||= "data-#{object_name.dasherize}"
+ end
+
+ def self.object_class_title
+ @object_title ||= object_class.name.titleize
end
# Public: Find references in text (like `!123` for merge requests)
@@ -53,6 +57,10 @@ module Banzai
self.class.object_sym
end
+ def object_class_title
+ self.class.object_class_title
+ end
+
def references_in(*args, &block)
self.class.references_in(*args, &block)
end
@@ -62,36 +70,81 @@ module Banzai
# Example: project.merge_requests.find
end
+ def find_object_cached(project, id)
+ if RequestStore.active?
+ cache = find_objects_cache[object_class][project.id]
+
+ get_or_set_cache(cache, id) { find_object(project, id) }
+ else
+ find_object(project, id)
+ end
+ end
+
+ def project_from_ref_cache(ref)
+ if RequestStore.active?
+ cache = project_refs_cache
+
+ get_or_set_cache(cache, ref) { project_from_ref(ref) }
+ else
+ project_from_ref(ref)
+ end
+ end
+
def url_for_object(object, project)
# Implement in child class
# Example: project_merge_request_url
end
- def call
- if object_class.reference_pattern
- # `#123`
- replace_text_nodes_matching(object_class.reference_pattern) do |content|
- object_link_filter(content, object_class.reference_pattern)
- end
+ def url_for_object_cached(object, project)
+ if RequestStore.active?
+ cache = url_for_object_cache[object_class][project.id]
- # `[Issue](#123)`, which is turned into
- # `<a href="#123">Issue</a>`
- replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
- object_link_filter(link, object_class.reference_pattern, link_text: text)
- end
+ get_or_set_cache(cache, object) { url_for_object(object, project) }
+ else
+ url_for_object(object, project)
end
+ end
- if object_class.link_reference_pattern
- # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
- # `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
- replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
- object_link_filter(text, object_class.link_reference_pattern)
- end
+ def call
+ return doc if project.nil?
+
+ ref_pattern = object_class.reference_pattern
+ link_pattern = object_class.link_reference_pattern
- # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
- # `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
- replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
- object_link_filter(link, object_class.link_reference_pattern, link_text: text)
+ each_node do |node|
+ if text_node?(node) && ref_pattern
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ object_link_filter(content, ref_pattern)
+ end
+
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, text|
+ if ref_pattern && link =~ /\A#{ref_pattern}/
+ replace_link_node_with_href(node, link) do
+ object_link_filter(link, ref_pattern, link_text: text)
+ end
+
+ next
+ end
+
+ next unless link_pattern
+
+ if link == text && text =~ /\A#{link_pattern}/
+ replace_link_node_with_text(node, link) do
+ object_link_filter(text, link_pattern)
+ end
+
+ next
+ end
+
+ if link =~ /\A#{link_pattern}\z/
+ replace_link_node_with_href(node, link) do
+ object_link_filter(link, link_pattern, link_text: text)
+ end
+
+ next
+ end
+ end
end
end
@@ -109,9 +162,9 @@ module Banzai
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text, pattern, link_text: nil)
references_in(text, pattern) do |match, id, project_ref, matches|
- project = project_from_ref(project_ref)
+ project = project_from_ref_cache(project_ref)
- if project && object = find_object(project, id)
+ if project && object = find_object_cached(project, id)
title = object_link_title(object)
klass = reference_class(object_sym)
@@ -121,8 +174,11 @@ module Banzai
object_sym => object.id
)
- url = matches[:url] if matches.names.include?("url")
- url ||= url_for_object(object, project)
+ if matches.names.include?("url") && matches[:url]
+ url = matches[:url]
+ else
+ url = url_for_object_cached(object, project)
+ end
text = link_text || object_link_text(object, matches)
@@ -146,7 +202,7 @@ module Banzai
end
def object_link_title(object)
- "#{object_class.name.titleize}: #{object.title}"
+ "#{object_class_title}: #{object.title}"
end
def object_link_text(object, matches)
@@ -157,6 +213,32 @@ module Banzai
text
end
+
+ private
+
+ def project_refs_cache
+ RequestStore[:banzai_project_refs] ||= {}
+ end
+
+ def find_objects_cache
+ RequestStore[:banzai_find_objects_cache] ||= Hash.new do |hash, key|
+ hash[key] = Hash.new { |h, k| h[k] = {} }
+ end
+ end
+
+ def url_for_object_cache
+ RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key|
+ hash[key] = Hash.new { |h, k| h[k] = {} }
+ end
+ end
+
+ def get_or_set_cache(cache, key)
+ if cache.key?(key)
+ cache[key]
+ else
+ cache[key] = yield
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index 470727ee312..b469ea0f626 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -43,7 +43,7 @@ module Banzai
end
def url_for_object(range, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_compare_url(project.namespace, project,
range.to_param.merge(only_path: context[:only_path]))
end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 713a56ba949..bd88207326c 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -37,7 +37,7 @@ module Banzai
end
def url_for_object(commit, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index edc26386903..37344b90576 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -35,15 +35,29 @@ module Banzai
def call
# Early return if the project isn't using an external tracker
- return doc if project.nil? || project.default_issues_tracker?
+ return doc if project.nil? || default_issues_tracker?
- replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
- issue_link_filter(content)
- end
+ ref_pattern = ExternalIssue.reference_pattern
+ ref_start_pattern = /\A#{ref_pattern}\z/
+
+ each_node do |node|
+ if text_node?(node)
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ issue_link_filter(content)
+ end
- replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
- issue_link_filter(link, link_text: text)
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, text|
+ if link =~ ref_start_pattern
+ replace_link_node_with_href(node, link) do
+ issue_link_filter(link, link_text: text)
+ end
+ end
+ end
+ end
end
+
+ doc
end
# Replace `JIRA-123` issue references in text with links to the referenced
@@ -76,6 +90,21 @@ module Banzai
def url_for_issue(*args)
IssuesHelper.url_for_issue(*args)
end
+
+ def default_issues_tracker?
+ if RequestStore.active?
+ default_issues_tracker_cache[project.id] ||=
+ project.default_issues_tracker?
+ else
+ project.default_issues_tracker?
+ end
+ end
+
+ private
+
+ def default_issues_tracker_cache
+ RequestStore[:banzai_default_issues_tracker_cache] ||= {}
+ end
end
end
end
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
new file mode 100644
index 00000000000..ccd106860bd
--- /dev/null
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -0,0 +1,27 @@
+module Banzai
+ module Filter
+ # HTML filter that wraps links around inline images.
+ class ImageLinkFilter < HTML::Pipeline::Filter
+
+ # Find every image that isn't already wrapped in an `a` tag, create
+ # a new node (a link to the image source), copy the image as a child
+ # of the anchor, and then replace the img with the link-wrapped version.
+ def call
+ doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
+
+ link = doc.document.create_element(
+ 'a',
+ class: 'no-attachment-icon',
+ href: img['src'],
+ target: '_blank'
+ )
+
+ link.children = img.clone
+ img.replace(link)
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 8147e5ed3c7..a2987850d03 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -31,7 +31,7 @@ module Banzai
end
def url_for_object(label, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 57c71708992..cad38a51851 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -14,7 +14,7 @@ module Banzai
end
def url_for_object(mr, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 8f710a92bdc..4cb82178024 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -11,7 +11,7 @@ module Banzai
end
def url_for_object(issue, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_milestone_url(project.namespace, project, milestone,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index a3326ae042c..31386cf851c 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -52,18 +52,13 @@ module Banzai
html.html_safe? ? html : ERB::Util.html_escape_once(html)
end
- def ignore_parents
- @ignore_parents ||= begin
- # Don't look for references in text nodes that are children of these
- # elements.
+ def ignore_ancestor_query
+ @ignore_ancestor_query ||= begin
parents = %w(pre code a style)
parents << 'blockquote' if context[:ignore_blockquotes]
- parents.to_set
- end
- end
- def ignored_ancestry?(node)
- has_ancestor?(node, ignore_parents)
+ parents.map { |n| "ancestor::#{n}" }.join(' or ')
+ end
end
def project
@@ -74,119 +69,66 @@ module Banzai
"gfm gfm-#{type}"
end
- # Iterate through the document's text nodes, yielding the current node's
- # content if:
- #
- # * The `project` context value is present AND
- # * The node's content matches `pattern` AND
- # * The node is not an ancestor of an ignored node type
- #
- # pattern - Regex pattern against which to match the node's content
- #
- # Yields the current node's String contents. The result of the block will
- # replace the node's existing content and update the current document.
+ # Ensure that a :project key exists in context
#
- # Returns the updated Nokogiri::HTML::DocumentFragment object.
- def replace_text_nodes_matching(pattern)
- return doc if project.nil?
-
- search_text_nodes(doc).each do |node|
- next if ignored_ancestry?(node)
- next unless node.text =~ pattern
-
- content = node.to_html
-
- html = yield content
-
- next if html == content
-
- node.replace(html)
- end
-
- doc
+ # Note that while the key might exist, its value could be nil!
+ def validate
+ needs :project
end
- # Iterate through the document's link nodes, yielding the current node's
- # content if:
- #
- # * The `project` context value is present AND
- # * The node's content matches `pattern`
- #
- # pattern - Regex pattern against which to match the node's content
- #
- # Yields the current node's String contents. The result of the block will
- # replace the node and update the current document.
+ # Iterates over all <a> and text() nodes in a document.
#
- # Returns the updated Nokogiri::HTML::DocumentFragment object.
- def replace_link_nodes_with_text(pattern)
- return doc if project.nil?
+ # Nodes are skipped whenever their ancestor is one of the nodes returned
+ # by `ignore_ancestor_query`. Link tags are not processed if they have a
+ # "gfm" class or the "href" attribute is empty.
+ def each_node
+ query = %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})]
+ | descendant-or-self::a[
+ not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "")
+ ]}
- doc.xpath('descendant-or-self::a').each do |node|
- klass = node.attr('class')
- next if klass && klass.include?('gfm')
-
- link = node.attr('href')
- text = node.text
-
- next unless link && text
-
- link = CGI.unescape(link)
- next unless link.force_encoding('UTF-8').valid_encoding?
- # Ignore ending punctionation like periods or commas
- next unless link == text && text =~ /\A#{pattern}/
-
- html = yield text
+ doc.xpath(query).each do |node|
+ yield node
+ end
+ end
- next if html == text
+ # Yields the link's URL and text whenever the node is a valid <a> tag.
+ def yield_valid_link(node)
+ link = CGI.unescape(node.attr('href').to_s)
+ text = node.text
- node.replace(html)
- end
+ return unless link.force_encoding('UTF-8').valid_encoding?
- doc
+ yield link, text
end
- # Iterate through the document's link nodes, yielding the current node's
- # content if:
- #
- # * The `project` context value is present AND
- # * The node's HREF matches `pattern`
- #
- # pattern - Regex pattern against which to match the node's HREF
- #
- # Yields the current node's String HREF and String content.
- # The result of the block will replace the node and update the current document.
- #
- # Returns the updated Nokogiri::HTML::DocumentFragment object.
- def replace_link_nodes_with_href(pattern)
- return doc if project.nil?
+ def replace_text_when_pattern_matches(node, pattern)
+ return unless node.text =~ pattern
- doc.xpath('descendant-or-self::a').each do |node|
- klass = node.attr('class')
- next if klass && klass.include?('gfm')
+ content = node.to_html
+ html = yield content
- link = node.attr('href')
- text = node.text
+ node.replace(html) unless content == html
+ end
- next unless link && text
- link = CGI.unescape(link)
- next unless link.force_encoding('UTF-8').valid_encoding?
- next unless link && link =~ /\A#{pattern}\z/
+ def replace_link_node_with_text(node, link)
+ html = yield
- html = yield link, text
+ node.replace(html) unless html == node.text
+ end
- next if html == link
+ def replace_link_node_with_href(node, link)
+ html = yield
- node.replace(html)
- end
+ node.replace(html) unless html == link
+ end
- doc
+ def text_node?(node)
+ node.is_a?(Nokogiri::XML::Text)
end
- # Ensure that a :project key exists in context
- #
- # Note that while the key might exist, its value could be nil!
- def validate
- needs :project
+ def element_node?(node)
+ node.is_a?(Nokogiri::XML::Element)
end
end
end
diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
index c870a42f741..d507eb5ebe1 100644
--- a/lib/banzai/filter/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -14,7 +14,7 @@ module Banzai
end
def url_for_object(snippet, project)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
h.namespace_project_snippet_url(project.namespace, project, snippet,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 24f16f8b547..eea3af842b6 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -59,13 +59,28 @@ module Banzai
end
def call
- replace_text_nodes_matching(User.reference_pattern) do |content|
- user_link_filter(content)
+ return doc if project.nil?
+
+ ref_pattern = User.reference_pattern
+ ref_pattern_start = /\A#{ref_pattern}\z/
+
+ each_node do |node|
+ if text_node?(node)
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ user_link_filter(content)
+ end
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, text|
+ if link =~ ref_pattern_start
+ replace_link_node_with_href(node, link) do
+ user_link_filter(link, link_text: text)
+ end
+ end
+ end
+ end
end
- replace_link_nodes_with_href(User.reference_pattern) do |link, text|
- user_link_filter(link, link_text: text)
- end
+ doc
end
# Replace `@user` user references in text with links to the referenced
@@ -90,7 +105,7 @@ module Banzai
private
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
def link_class
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 8cd4b50e65a..ed3cfd6b023 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
Filter::SanitizationFilter,
Filter::UploadLinkFilter,
+ Filter::ImageLinkFilter,
Filter::EmojiFilter,
Filter::TableOfContentsFilter,
Filter::AutolinkFilter,
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 761b63e98f6..1acc22fe5bf 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -21,7 +21,6 @@ module Gitlab
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
- twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index 41f0edcaf7e..8f9be6cd9a3 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -5,7 +5,7 @@ module Gitlab
attr_accessor :recipient
attr_reader :author_id, :ref, :action
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
delegate :namespace, :name_with_namespace, to: :project, prefix: :project
delegate :name, to: :author, prefix: :author
diff --git a/lib/gitlab/fogbugz_import/client.rb b/lib/gitlab/fogbugz_import/client.rb
index 431d50882fd..2152182b37f 100644
--- a/lib/gitlab/fogbugz_import/client.rb
+++ b/lib/gitlab/fogbugz_import/client.rb
@@ -26,7 +26,7 @@ module Gitlab
def user_map
users = {}
res = @api.command(:listPeople)
- res['people']['person'].each do |user|
+ [res['people']['person']].flatten.each do |user|
users[user['ixPerson']] = { name: user['sFullName'], email: user['sEmail'] }
end
users
diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb
index a1c6ee7bd69..78d7a4f27cf 100644
--- a/lib/gitlab/gfm/reference_rewriter.rb
+++ b/lib/gitlab/gfm/reference_rewriter.rb
@@ -34,16 +34,21 @@ module Gitlab
@source_project = source_project
@current_user = current_user
@original_html = markdown(text)
+ @pattern = Gitlab::ReferenceExtractor.references_pattern
end
def rewrite(target_project)
- pattern = Gitlab::ReferenceExtractor.references_pattern
+ return @text unless needs_rewrite?
- @text.gsub(pattern) do |reference|
+ @text.gsub(@pattern) do |reference|
unfold_reference(reference, Regexp.last_match, target_project)
end
end
+ def needs_rewrite?
+ @text =~ @pattern
+ end
+
private
def unfold_reference(reference, match, target_project)
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
new file mode 100644
index 00000000000..abc8c8c55e6
--- /dev/null
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -0,0 +1,51 @@
+module Gitlab
+ module Gfm
+ ##
+ # Class that rewrites markdown links for uploads
+ #
+ # Using a pattern defined in `FileUploader` it copies files to a new
+ # project and rewrites all links to uploads in in a given text.
+ #
+ #
+ class UploadsRewriter
+ def initialize(text, source_project, _current_user)
+ @text = text
+ @source_project = source_project
+ @pattern = FileUploader::MARKDOWN_PATTERN
+ end
+
+ def rewrite(target_project)
+ return @text unless needs_rewrite?
+
+ @text.gsub(@pattern) do |markdown|
+ file = find_file(@source_project, $~[:secret], $~[:file])
+ return markdown unless file.try(:exists?)
+
+ new_uploader = FileUploader.new(target_project)
+ new_uploader.store!(file)
+ new_uploader.to_markdown
+ end
+ end
+
+ def needs_rewrite?
+ files.any?
+ end
+
+ def files
+ referenced_files = @text.scan(@pattern).map do
+ find_file(@source_project, $~[:secret], $~[:file])
+ end
+
+ referenced_files.compact.select(&:exists?)
+ end
+
+ private
+
+ def find_file(project, secret, file)
+ uploader = FileUploader.new(project, secret)
+ uploader.retrieve_from_store!(file)
+ uploader.file
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb
index 71cf6a0d886..18523e0aefe 100644
--- a/lib/gitlab/note_data_builder.rb
+++ b/lib/gitlab/note_data_builder.rb
@@ -41,7 +41,7 @@ module Gitlab
data[:issue] = note.noteable.hook_attrs
elsif note.for_merge_request?
data[:merge_request] = note.noteable.hook_attrs
- elsif note.for_project_snippet?
+ elsif note.for_snippet?
data[:snippet] = note.noteable.hook_attrs
end
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
new file mode 100644
index 00000000000..5132177de51
--- /dev/null
+++ b/lib/gitlab/routing.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module Routing
+ # Returns the URL helpers Module.
+ #
+ # This method caches the output as Rails' "url_helpers" method creates an
+ # anonymous module every time it's called.
+ #
+ # Returns a Module.
+ def self.url_helpers
+ @url_helpers ||= Gitlab::Application.routes.url_helpers
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 6f0d02cafd1..f301d42939d 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -1,7 +1,8 @@
module Gitlab
class UrlBuilder
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
include GitlabRoutingHelper
+ include ActionView::RecordIdentifier
def initialize(type)
@type = type
@@ -37,19 +38,16 @@ module Gitlab
namespace_project_commit_url(namespace_id: note.project.namespace,
id: note.commit_id,
project_id: note.project,
- anchor: "note_#{note.id}")
+ anchor: dom_id(note))
elsif note.for_issue?
issue = Issue.find(note.noteable_id)
- issue_url(issue,
- anchor: "note_#{note.id}")
+ issue_url(issue, anchor: dom_id(note))
elsif note.for_merge_request?
merge_request = MergeRequest.find(note.noteable_id)
- merge_request_url(merge_request,
- anchor: "note_#{note.id}")
- elsif note.for_project_snippet?
+ merge_request_url(merge_request, anchor: dom_id(note))
+ elsif note.for_snippet?
snippet = Snippet.find(note.noteable_id)
- project_snippet_url(snippet,
- anchor: "note_#{note.id}")
+ project_snippet_url(snippet, anchor: dom_id(note))
end
end
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 5b1f65d7aff..9ef8ba1b097 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -1,15 +1,14 @@
require 'spec_helper'
describe Admin::UsersController do
- let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
before do
- sign_in(admin)
+ sign_in(create(:admin))
end
describe 'DELETE #user with projects' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
+ let(:project) { create(:empty_project, namespace: user.namespace) }
before do
project.team << [user, :developer]
@@ -23,8 +22,6 @@ describe Admin::UsersController do
end
describe 'PUT block/:id' do
- let(:user) { create(:user) }
-
it 'blocks user' do
put :block, id: user.username
user.reload
@@ -50,8 +47,6 @@ describe Admin::UsersController do
end
context 'manually blocked users' do
- let(:user) { create(:user) }
-
before do
user.block
end
@@ -66,8 +61,6 @@ describe Admin::UsersController do
end
describe 'PUT unlock/:id' do
- let(:user) { create(:user) }
-
before do
request.env["HTTP_REFERER"] = "/"
user.lock_access!
@@ -95,8 +88,6 @@ describe Admin::UsersController do
end
describe 'PATCH disable_two_factor' do
- let(:user) { create(:user) }
-
it 'disables 2FA for the user' do
expect(user).to receive(:disable_two_factor!)
allow(subject).to receive(:user).and_return(user)
diff --git a/spec/factories/file_uploader.rb b/spec/factories/file_uploader.rb
new file mode 100644
index 00000000000..1b36e21f2b0
--- /dev/null
+++ b/spec/factories/file_uploader.rb
@@ -0,0 +1,20 @@
+FactoryGirl.define do
+ factory :file_uploader do
+ project
+ secret nil
+
+ transient do
+ fixture { 'rails_sample.jpg' }
+ path { File.join(Rails.root, 'spec/fixtures', fixture) }
+ file { Rack::Test::UploadedFile.new(path) }
+ end
+
+ after(:build) do |uploader, evaluator|
+ uploader.store!(evaluator.file)
+ end
+
+ initialize_with do
+ new(project, secret)
+ end
+ end
+end
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
index 252bf2747e1..19a54946fe0 100644
--- a/spec/factories/forked_project_links.rb
+++ b/spec/factories/forked_project_links.rb
@@ -13,5 +13,10 @@ FactoryGirl.define do
factory :forked_project_link do
association :forked_to_project, factory: :project
association :forked_from_project, factory: :project
+
+ after(:create) do |link|
+ link.forked_from_project.reload
+ link.forked_to_project.reload
+ end
end
end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 457859dedaf..62de081661d 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -1,9 +1,17 @@
require 'spec_helper'
-FactoryGirl.factories.map(&:name).each do |factory_name|
- describe "#{factory_name} factory" do
- it 'should be valid' do
- expect(build(factory_name)).to be_valid
+describe 'factories' do
+ FactoryGirl.factories.each do |factory|
+ describe "#{factory.name} factory" do
+ let(:entity) { build(factory.name) }
+
+ it 'does not raise error when created 'do
+ expect { entity }.to_not raise_error
+ end
+
+ it 'should be valid', if: factory.build_class < ActiveRecord::Base do
+ expect(entity).to be_valid
+ end
end
end
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index dc41be8246f..de6aed74fb4 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -61,7 +61,7 @@ describe "User Feed", feature: true do
end
it 'should have XHTML summaries in merge request descriptions' do
- expect(body).to match /Here is the fix: <img[^>]*\/>/
+ expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/
end
end
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
new file mode 100644
index 00000000000..fd02d584848
--- /dev/null
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+feature 'Create New Merge Request', feature: true, js: false do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ project.team << [user, :master]
+
+ login_as user
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ end
+
+ it 'generates a diff for an orphaned branch' do
+ click_link 'New Merge Request'
+ select "orphaned-branch", from: "merge_request_source_branch"
+ select "master", from: "merge_request_target_branch"
+ click_button "Compare branches"
+
+ expect(page).to have_content "README.md"
+ expect(page).to have_content "wm.png"
+
+ fill_in "merge_request_title", with: "Orphaned MR test"
+ click_button "Submit merge request"
+
+ expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 84c036e59c0..3e6289a46b1 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -1,19 +1,46 @@
require 'spec_helper'
describe "Search", feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
before do
- login_as :user
- @project = create(:project, namespace: @user.namespace)
- @project.team << [@user, :reporter]
+ login_with(user)
+ project.team << [user, :reporter]
visit search_path
+ end
- page.within '.search-holder' do
- fill_in "search", with: @project.name[0..3]
- click_button "Search"
+ describe 'searching for Projects' do
+ it 'finds a project' do
+ page.within '.search-holder' do
+ fill_in "search", with: project.name[0..3]
+ click_button "Search"
+ end
+
+ expect(page).to have_content project.name
end
end
- it "should show project in search results" do
- expect(page).to have_content @project.name
+ context 'search for comments' do
+ it 'finds a snippet' do
+ snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title')
+ note = create(:note,
+ noteable: snippet,
+ author: user,
+ note: 'Supercalifragilisticexpialidocious',
+ project: project)
+ # Must visit project dashboard since global search won't search
+ # everything (e.g. comments, snippets, etc.)
+ visit namespace_project_path(project.namespace, project)
+
+ page.within '.search' do
+ fill_in 'search', with: note.note
+ click_button 'Go'
+ end
+
+ click_link 'Comments'
+
+ expect(page).to have_link(snippet.title)
+ end
end
end
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
new file mode 100644
index 00000000000..dd5594750c8
--- /dev/null
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ImageLinkFilter, lib: true do
+ include FilterSpecHelper
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ it 'wraps the image with a link to the image src' do
+ doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
+ end
+
+ it 'does not wrap a duplicate link' do
+ exp = act = %q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'works with external images' do
+ doc = filter(image('https://i.imgur.com/DfssX9C.jpg'))
+ expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
+ end
+end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index f38fadda9ba..566035c60d0 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe ExtractsPath, lib: true do
include ExtractsPath
include RepoHelpers
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
let(:project) { double('project') }
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 844fd79c991..a1f51429a79 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -236,6 +236,6 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
end
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
end
diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb
new file mode 100644
index 00000000000..2dc71be0254
--- /dev/null
+++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::FogbugzImport::Client, lib: true do
+
+ let(:client) { described_class.new(uri: '', token: '') }
+ let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } }
+ let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } }
+
+ it 'retrieves user_map with one user' do
+ stub_api(one_user)
+
+ expect(client.user_map.count).to eq(1)
+ end
+
+ it 'retrieves user_map with two users' do
+ stub_api(two_users)
+
+ expect(client.user_map.count).to eq(2)
+ end
+
+ def stub_api(users)
+ allow_any_instance_of(::Fogbugz::Interface).to receive(:command).with(:listPeople).and_return(users)
+ end
+end
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
new file mode 100644
index 00000000000..eda956e6f0a
--- /dev/null
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::Gfm::UploadsRewriter do
+ let(:user) { create(:user) }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+ let(:rewriter) { described_class.new(text, old_project, user) }
+
+ context 'text contains links to uploads' do
+ let(:image_uploader) do
+ build(:file_uploader, project: old_project)
+ end
+
+ let(:zip_uploader) do
+ build(:file_uploader, project: old_project,
+ fixture: 'ci_build_artifacts.zip')
+ end
+
+ let(:text) do
+ "Text and #{image_uploader.to_markdown} and #{zip_uploader.to_markdown}"
+ end
+
+ describe '#rewrite' do
+ let!(:new_text) { rewriter.rewrite(new_project) }
+
+ let(:old_files) { [image_uploader, zip_uploader].map(&:file) }
+ let(:new_files) do
+ described_class.new(new_text, new_project, user).files
+ end
+
+ let(:old_paths) { old_files.map(&:path) }
+ let(:new_paths) { new_files.map(&:path) }
+
+ it 'rewrites content' do
+ expect(new_text).to_not eq text
+ expect(new_text.length).to eq text.length
+ end
+
+ it 'copies files' do
+ expect(new_files).to all(exist)
+ expect(old_paths).to_not match_array new_paths
+ expect(old_paths).to all(include(old_project.path_with_namespace))
+ expect(new_paths).to all(include(new_project.path_with_namespace))
+ end
+
+ it 'does not remove old files' do
+ expect(old_files).to all(exist)
+ end
+
+ it 'generates a new secret for each file' do
+ expect(new_paths).to_not include image_uploader.secret
+ expect(new_paths).to_not include zip_uploader.secret
+ end
+ end
+
+ describe '#needs_rewrite?' do
+ subject { rewriter.needs_rewrite? }
+ it { is_expected.to eq true }
+ end
+
+ describe '#files' do
+ subject { rewriter.files }
+ it { is_expected.to be_an(Array) }
+ end
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index b1764d7ac09..520cf1b75de 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -12,7 +12,6 @@
# updated_at :datetime
# home_page_url :string(255)
# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index be29b6d66ff..b16ccc6e305 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -9,6 +9,7 @@ describe Issue, "Issuable" do
it { is_expected.to belong_to(:author) }
it { is_expected.to belong_to(:assignee) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe "Validation" do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 55f1c665b86..f29c389e094 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -104,6 +104,15 @@ describe Project, models: true do
end
end
+ describe 'default_scope' do
+ it 'excludes projects pending deletion from the results' do
+ project = create(:empty_project)
+ create(:empty_project, pending_delete: true)
+
+ expect(Project.all).to eq [project]
+ end
+ end
+
describe 'project token' do
it 'should set an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: ''
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 9242a6f1739..f517f325c03 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -303,7 +303,7 @@ describe Repository, models: true do
describe 'when there are no branches' do
before do
- allow(repository.raw_repository).to receive(:branch_count).and_return(0)
+ allow(repository).to receive(:branch_count).and_return(0)
end
it { is_expected.to eq(false) }
@@ -311,13 +311,13 @@ describe Repository, models: true do
describe 'when there are branches' do
it 'returns true' do
- expect(repository.raw_repository).to receive(:branch_count).and_return(3)
+ expect(repository).to receive(:branch_count).and_return(3)
expect(subject).to eq(true)
end
it 'caches the output' do
- expect(repository.raw_repository).to receive(:branch_count).
+ expect(repository).to receive(:branch_count).
once.
and_return(3)
@@ -436,7 +436,7 @@ describe Repository, models: true do
it 'expires the visible content cache' do
repository.has_visible_content?
- expect(repository.raw_repository).to receive(:branch_count).
+ expect(repository).to receive(:branch_count).
once.
and_return(0)
@@ -612,6 +612,20 @@ describe Repository, models: true do
end
end
+ describe '#before_import' do
+ it 'flushes the emptiness cachess' do
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.before_import
+ end
+
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.before_import
+ end
+ end
+
describe '#after_import' do
it 'flushes the emptiness cachess' do
expect(repository).to receive(:expire_emptiness_caches)
@@ -865,4 +879,21 @@ describe Repository, models: true do
repository.build_cache
end
end
+
+ describe '#local_branches' do
+ it 'returns the local branches' do
+ masterrev = repository.find_branch('master').target
+ create_remote_branch('joe', 'remote_branch', masterrev)
+ repository.add_branch(user, 'local_branch', masterrev)
+
+ expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
+ expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
+ end
+ end
+
+ def create_remote_branch(remote_name, branch_name, target)
+ rugged = repository.rugged
+ rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
+ end
+
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0ab7fd88ce6..8b2fb77e28e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -173,6 +173,13 @@ describe User, models: true do
expect(user).to be_invalid
end
end
+
+ context 'owns_notification_email' do
+ it 'accepts temp_oauth_email emails' do
+ user = build(:user, email: "temp-email-for-oauth@example.com")
+ expect(user).to be_valid
+ end
+ end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a5d4985dc78..be2034e0f39 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -948,6 +948,78 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/archive' do
+ context 'on an unarchived project' do
+ it 'archives the project' do
+ post api("/projects/#{project.id}/archive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_truthy
+ end
+ end
+
+ context 'on an archived project' do
+ before do
+ project.archive!
+ end
+
+ it 'remains archived' do
+ post api("/projects/#{project.id}/archive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_truthy
+ end
+ end
+
+ context 'user without archiving rights to the project' do
+ before do
+ project.team << [user3, :developer]
+ end
+
+ it 'rejects the action' do
+ post api("/projects/#{project.id}/archive", user3)
+
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/unarchive' do
+ context 'on an unarchived project' do
+ it 'remains unarchived' do
+ post api("/projects/#{project.id}/unarchive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_falsey
+ end
+ end
+
+ context 'on an archived project' do
+ before do
+ project.archive!
+ end
+
+ it 'unarchives the project' do
+ post api("/projects/#{project.id}/unarchive", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response['archived']).to be_falsey
+ end
+ end
+
+ context 'user without archiving rights to the project' do
+ before do
+ project.team << [user3, :developer]
+ end
+
+ it 'rejects the action' do
+ post api("/projects/#{project.id}/unarchive", user3)
+
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
describe 'DELETE /projects/:id' do
context 'when authenticated as user' do
it 'should remove project' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 9b0c73aaf37..2a5e4ac3ec4 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -160,6 +160,20 @@ describe Issues::MoveService, services: true do
.to eq "Note with reference to merge request #{old_project.to_reference}!1"
end
end
+
+ context 'issue description with uploads' do
+ let(:uploader) { build(:file_uploader, project: old_project) }
+ let(:description) { "Text and #{uploader.to_markdown}" }
+
+ include_context 'issue move executed'
+
+ it 'rewrites uploads in description' do
+ expect(new_issue.description).to_not eq description
+ expect(new_issue.description)
+ .to match(/Text and #{FileUploader::MARKDOWN_PATTERN}/)
+ expect(new_issue.description).to_not include uploader.secret
+ end
+ end
end
describe 'rewritting references' do
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 04f474c736c..32bf3acf483 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -72,6 +72,23 @@ describe Projects::ImportService, services: true do
expect(result[:status]).to eq :success
end
+ it 'flushes various caches' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).
+ with(project.path_with_namespace, project.import_url).
+ and_return(true)
+
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).
+ and_return(true)
+
+ expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
+ and_call_original
+
+ expect_any_instance_of(Repository).to receive(:expire_exists_cache).
+ and_call_original
+
+ subject.execute
+ end
+
it 'fails if importer fails' do
expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
new file mode 100644
index 00000000000..23f5555d3e0
--- /dev/null
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Projects::UnlinkForkService, services: true do
+ subject { Projects::UnlinkForkService.new(fork_project, user) }
+
+ let(:fork_link) { create(:forked_project_link) }
+ let(:fork_project) { fork_link.forked_to_project }
+ let(:user) { create(:user) }
+
+ context 'with opened merge request on the source project' do
+ let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: fork_link.forked_from_project) }
+ let(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) }
+
+ before do
+ allow(MergeRequests::CloseService).to receive(:new).
+ with(fork_project, user).
+ and_return(mr_close_service)
+ end
+
+ it 'close all pending merge requests' do
+ expect(mr_close_service).to receive(:execute).with(merge_request)
+
+ subject.execute
+ end
+ end
+
+ it 'remove fork relation' do
+ expect(fork_project.forked_project_link).to receive(:destroy)
+
+ subject.execute
+ end
+end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index b4728807b8b..82b7fbfa816 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -2,22 +2,25 @@ require 'spec_helper'
describe TodoService, services: true do
let(:author) { create(:user) }
- let(:john_doe) { create(:user, username: 'john_doe') }
- let(:michael) { create(:user, username: 'michael') }
- let(:stranger) { create(:user, username: 'stranger') }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:john_doe) { create(:user) }
let(:project) { create(:project) }
- let(:mentions) { [author.to_reference, john_doe.to_reference, michael.to_reference, stranger.to_reference].join(' ') }
+ let(:mentions) { [author, assignee, john_doe, member, non_member, admin].map(&:to_reference).join(' ') }
let(:service) { described_class.new }
before do
project.team << [author, :developer]
+ project.team << [member, :developer]
project.team << [john_doe, :developer]
- project.team << [michael, :developer]
end
describe 'Issues' do
let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) }
let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
+ let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) }
describe '#new_issue' do
it 'creates a todo if assigned' do
@@ -37,10 +40,20 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.new_issue(issue, author)
- should_create_todo(user: michael, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
+ end
+
+ it 'does not create todo for non project members when issue is confidential' do
+ service.new_issue(confidential_issue, john_doe)
+
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED)
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
end
@@ -48,16 +61,26 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.update_issue(issue, author)
- should_create_todo(user: michael, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
it 'does not create a todo if user was already mentioned' do
- create(:todo, :mentioned, user: michael, project: project, target: issue, author: author)
+ create(:todo, :mentioned, user: member, project: project, target: issue, author: author)
- expect { service.update_issue(issue, author) }.not_to change(michael.todos, :count)
+ expect { service.update_issue(issue, author) }.not_to change(member.todos, :count)
+ end
+
+ it 'does not create todo for non project members when issue is confidential' do
+ service.update_issue(confidential_issue, john_doe)
+
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
end
@@ -109,8 +132,10 @@ describe TodoService, services: true do
describe '#new_note' do
let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
+ let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
+ let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') }
let(:system_note) { create(:system_note, project: project, noteable: issue) }
@@ -142,19 +167,29 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.new_note(note, john_doe)
- should_create_todo(user: michael, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
- should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ end
+
+ it 'does not create todo for non project members when leaving a note on a confidential issue' do
+ service.new_note(note_on_confidential_issue, john_doe)
+
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end
it 'creates a todo for each valid mentioned user when leaving a note on commit' do
service.new_note(note_on_commit, john_doe)
- should_create_todo(user: michael, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_not_create_todo(user: stranger, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end
it 'does not create todo when leaving a note on snippet' do
@@ -185,10 +220,10 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.new_merge_request(mr_assigned, author)
- should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
end
@@ -196,16 +231,16 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do
service.update_merge_request(mr_assigned, author)
- should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED)
+ should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
- should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
it 'does not create a todo if user was already mentioned' do
- create(:todo, :mentioned, user: michael, project: project, target: mr_assigned, author: author)
+ create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author)
- expect { service.update_merge_request(mr_assigned, author) }.not_to change(michael.todos, :count)
+ expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
end
end
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index ef5ea7d626e..e849a9633b9 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -78,6 +78,6 @@ module FilterSpecHelper
# Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index 73c6792b65f..b87cd6bbca2 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -106,7 +106,7 @@ class MarkdownFeature
end
def urls
- Gitlab::Application.routes.url_helpers
+ Gitlab::Routing.url_helpers
end
def raw_markdown
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 0d1bd030f3c..71664bb192e 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -15,6 +15,7 @@ module TestEnv
'lfs' => 'be93687',
'master' => '5937ac0',
"'test'" => 'e56497b',
+ 'orphaned-branch' => '45127a9',
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index b11c5de94e3..1abd87d7d33 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -22,6 +22,8 @@ describe MergeWorker do
merge_request.reload
expect(merge_request).to be_merged
+
+ source_project.repository.expire_branches_cache
expect(source_project.repository.branch_names).not_to include('markdown')
end
end