summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG10
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock20
-rw-r--r--README.md2
-rw-r--r--app/assets/javascripts/markdown_area.js.coffee45
-rw-r--r--app/assets/javascripts/notes.js.coffee55
-rw-r--r--app/assets/stylesheets/generic/markdown_area.scss26
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss31
-rw-r--r--app/assets/stylesheets/sections/events.scss10
-rw-r--r--app/assets/stylesheets/sections/notes.scss10
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb10
-rw-r--r--app/controllers/projects/application_controller.rb27
-rw-r--r--app/controllers/projects/hooks_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb16
-rw-r--r--app/controllers/projects/merge_requests_controller.rb15
-rw-r--r--app/controllers/projects/notes_controller.rb4
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/controllers/users_controller.rb7
-rw-r--r--app/finders/issuable_finder.rb9
-rw-r--r--app/helpers/events_helper.rb22
-rw-r--r--app/helpers/issues_helper.rb15
-rw-r--r--app/helpers/oauth_helper.rb4
-rw-r--r--app/helpers/profile_helper.rb6
-rw-r--r--app/mailers/notify.rb8
-rw-r--r--app/models/hooks/web_hook.rb8
-rw-r--r--app/models/identity.rb5
-rw-r--r--app/models/merge_request.rb14
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/service.rb4
-rw-r--r--app/models/snippet.rb10
-rw-r--r--app/models/user.rb9
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/services/test_hook_service.rb3
-rw-r--r--app/uploaders/attachment_uploader.rb4
-rw-r--r--app/views/admin/users/show.html.haml2
-rw-r--r--app/views/dashboard/issues.atom.builder21
-rw-r--r--app/views/dashboard/show.atom.builder25
-rw-r--r--app/views/devise/sessions/_oauth_providers.html.haml2
-rw-r--r--app/views/groups/issues.atom.builder13
-rw-r--r--app/views/groups/show.atom.builder24
-rw-r--r--app/views/projects/_issuable_filter.html.haml72
-rw-r--r--app/views/projects/_issuable_form.html.haml25
-rw-r--r--app/views/projects/_md_preview.html.haml13
-rw-r--r--app/views/projects/issues/_issues.html.haml50
-rw-r--r--app/views/projects/issues/index.atom.builder13
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml13
-rw-r--r--app/views/projects/merge_requests/index.html.haml52
-rw-r--r--app/views/projects/merge_requests/show/_state_widget.html.haml13
-rw-r--r--app/views/projects/milestones/_form.html.haml11
-rw-r--r--app/views/projects/notes/_form.html.haml22
-rw-r--r--app/views/projects/notes/_note.html.haml3
-rw-r--r--app/views/users/show.atom.builder12
-rw-r--r--app/views/users/show.html.haml10
-rw-r--r--app/workers/project_service_worker.rb9
-rw-r--r--config/initializers/4_sidekiq.rb2
-rw-r--r--config/routes.rb10
-rw-r--r--config/unicorn.rb.example14
-rw-r--r--db/migrate/20141121161704_add_identity_table.rb38
-rw-r--r--db/migrate/20141205134006_add_locked_at_to_merge_request.rb5
-rw-r--r--db/schema.rb16
-rw-r--r--doc/development/rake_tasks.md6
-rw-r--r--doc/development/shell_commands.md63
-rw-r--r--doc/development/sidekiq_debugging.md (renamed from doc/sidekiq_debugging.md)0
-rw-r--r--doc/install/requirements.md37
-rw-r--r--doc/integration/gitlab_buttons_in_gmail.md17
-rw-r--r--doc/integration/twitter.md2
-rw-r--r--doc/raketasks/backup_restore.md25
-rw-r--r--doc/release/patch.md3
-rw-r--r--doc/release/security.md1
-rw-r--r--features/project/commits/comments.feature8
-rw-r--r--features/project/commits/diff_comments.feature6
-rw-r--r--features/project/issues/issues.feature34
-rw-r--r--features/project/merge_requests.feature31
-rw-r--r--features/steps/profile/profile.rb2
-rw-r--r--features/steps/project/issues/issues.rb1
-rw-r--r--features/steps/project/merge_requests.rb1
-rw-r--r--features/steps/shared/diff_note.rb26
-rw-r--r--features/steps/shared/issuable.rb15
-rw-r--r--features/steps/shared/markdown.rb45
-rw-r--r--features/steps/shared/note.rb26
-rw-r--r--lib/api/entities.rb8
-rw-r--r--lib/api/users.rb12
-rw-r--r--lib/gitlab/git_access.rb15
-rw-r--r--lib/gitlab/ldap/access.rb8
-rw-r--r--lib/gitlab/ldap/user.rb13
-rw-r--r--lib/gitlab/oauth/user.rb24
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb53
-rw-r--r--lib/tasks/gitlab/mail_google_schema_whitelisting.rake73
-rw-r--r--lib/tasks/gitlab/shell.rake23
-rw-r--r--spec/factories.rb26
-rw-r--r--spec/features/atom/users_spec.rb43
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb23
-rw-r--r--spec/finders/issues_finder_spec.rb81
-rw-r--r--spec/helpers/application_helper_spec.rb10
-rw-r--r--spec/helpers/oauth_helper_spec.rb20
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/authentication_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb8
-rw-r--r--spec/lib/gitlab/oauth/user_spec.rb7
-rw-r--r--spec/models/user_spec.rb16
-rw-r--r--spec/requests/api/users_spec.rb2
-rw-r--r--spec/routing/project_routing_spec.rb27
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb27
106 files changed, 1214 insertions, 583 deletions
diff --git a/CHANGELOG b/CHANGELOG
index ae8de1df277..2061237fb42 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,7 @@ v 7.6.0
- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
-
-
+ - Monokai highlighting style now more faithful to original design (Mark Riedesel)
- Create project with repository in synchrony
- Added ability to create empty repo or import existing one if project does not have repository
-
@@ -15,11 +16,12 @@ v 7.6.0
- Mobile UI improvements
-
- Change maximum avatar file size from 100KB to 200KB
- -
- -
+ - Strict validation for snippet file names
+ - Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
- In the docker directory is a container template based on the Omnibus packages.
- -
- -
+ - Update Sidekiq to version 2.17.8
+ - Add author filter to project issues and merge requests pages
+ - Atom feed for user activity
v 7.5.2
- Don't log Sidekiq arguments by default
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9da89cc2107..2195ea6e739 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -37,7 +37,7 @@ Please send a merge request with a tested solution or a merge request with a fai
**[Search the issues](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post):
1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
-1. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab development virtual machine with vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) (start your issue with: `vagrant destroy && vagrant up && vagrant ssh`)
+1. **Steps to reproduce:** How can we reproduce the issue
1. **Expected behavior:** Describe your issue in detail
1. **Observed behavior**
1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
@@ -75,6 +75,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submission
1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
+1. Also have a look at the [shell command guidelines](doc/development/shell_commands.md) if your code reads or opens files, or handles paths to files on disk.
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
diff --git a/Gemfile b/Gemfile
index 613ef11cf4d..b4ca5969277 100644
--- a/Gemfile
+++ b/Gemfile
@@ -112,7 +112,7 @@ gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
-gem 'sidekiq', '2.17.0'
+gem 'sidekiq', '2.17.8'
# HTTP requests
gem "httparty"
diff --git a/Gemfile.lock b/Gemfile.lock
index 7871f49d0bf..4bcb1eb0de5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -78,7 +78,7 @@ GEM
coffee-script-source (1.6.3)
colored (1.2)
colorize (0.5.8)
- connection_pool (1.2.0)
+ connection_pool (2.1.0)
coveralls (0.7.0)
multi_json (~> 1.3)
rest-client
@@ -402,7 +402,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.1.2)
- redis (3.0.6)
+ redis (3.1.0)
redis-actionpack (4.0.0)
actionpack (~> 4)
redis-rack (~> 1.5.0)
@@ -410,8 +410,8 @@ GEM
redis-activesupport (4.0.0)
activesupport (~> 4)
redis-store (~> 1.1.0)
- redis-namespace (1.4.1)
- redis (~> 3.0.4)
+ redis-namespace (1.5.1)
+ redis (~> 3.0, >= 3.0.4)
redis-rack (1.5.0)
rack (~> 1.5)
redis-store (~> 1.1.0)
@@ -470,12 +470,12 @@ GEM
sexp_processor (4.4.0)
shoulda-matchers (2.1.0)
activesupport (>= 3.0.0)
- sidekiq (2.17.0)
- celluloid (>= 0.15.2)
- connection_pool (>= 1.0.0)
+ sidekiq (2.17.8)
+ celluloid (= 0.15.2)
+ connection_pool (~> 2.0)
json
- redis (>= 3.0.4)
- redis-namespace (>= 1.3.1)
+ redis (~> 3.1)
+ redis-namespace (~> 1.3)
simple_oauth (0.1.9)
simplecov (0.9.0)
docile (~> 1.1.0)
@@ -684,7 +684,7 @@ DEPENDENCIES
semantic-ui-sass (~> 0.16.1.0)
settingslogic
shoulda-matchers (~> 2.1.0)
- sidekiq (= 2.17.0)
+ sidekiq (= 2.17.8)
simplecov
sinatra
six
diff --git a/README.md b/README.md
index 63fa5e3da86..f303e8e7383 100644
--- a/README.md
+++ b/README.md
@@ -131,4 +131,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
-[These people](https://twitter.com/gitlabhq/favorites) seem to like it.
+[These people](https://twitter.com/gitlab/favorites) seem to like it.
diff --git a/app/assets/javascripts/markdown_area.js.coffee b/app/assets/javascripts/markdown_area.js.coffee
index a0ebfc98ce6..0ca7070dc8b 100644
--- a/app/assets/javascripts/markdown_area.js.coffee
+++ b/app/assets/javascripts/markdown_area.js.coffee
@@ -24,6 +24,51 @@ $(document).ready ->
"opacity": 0
"display": "none"
+ # Preview button
+ $(document).off "click", ".js-md-preview-button"
+ $(document).on "click", ".js-md-preview-button", (e) ->
+ ###
+ Shows the Markdown preview.
+
+ Lets the server render GFM into Html and displays it.
+ ###
+ e.preventDefault()
+ form = $(this).closest("form")
+ # toggle tabs
+ form.find(".js-md-write-button").parent().removeClass "active"
+ form.find(".js-md-preview-button").parent().addClass "active"
+
+ # toggle content
+ form.find(".md-write-holder").hide()
+ form.find(".md-preview-holder").show()
+
+ preview = form.find(".js-md-preview")
+ mdText = form.find(".markdown-area").val()
+ if mdText.trim().length is 0
+ preview.text "Nothing to preview."
+ else
+ preview.text "Loading..."
+ $.get($(this).data("url"),
+ md_text: mdText
+ ).success (previewData) ->
+ preview.html previewData
+
+ # Write button
+ $(document).off "click", ".js-md-write-button"
+ $(document).on "click", ".js-md-write-button", (e) ->
+ ###
+ Shows the Markdown textarea.
+ ###
+ e.preventDefault()
+ form = $(this).closest("form")
+ # toggle tabs
+ form.find(".js-md-write-button").parent().addClass "active"
+ form.find(".js-md-preview-button").parent().removeClass "active"
+
+ # toggle content
+ form.find(".md-write-holder").show()
+ form.find(".md-preview-holder").hide()
+
dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
dictDefaultMessage: ""
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 978f83dd442..30f8530dfda 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -36,12 +36,6 @@ class @Notes
# delete note attachment
$(document).on "click", ".js-note-attachment-delete", @removeAttachment
- # Preview button
- $(document).on "click", ".js-note-preview-button", @previewNote
-
- # Preview button
- $(document).on "click", ".js-note-write-button", @writeNote
-
# reset main target form after submit
$(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm
@@ -77,8 +71,6 @@ class @Notes
$(document).off "click", ".note-edit-cancel"
$(document).off "click", ".js-note-delete"
$(document).off "click", ".js-note-attachment-delete"
- $(document).off "click", ".js-note-preview-button"
- $(document).off "click", ".js-note-write-button"
$(document).off "ajax:complete", ".js-main-target-form"
$(document).off "click", ".js-choose-note-attachment-button"
$(document).off "click", ".js-discussion-reply-button"
@@ -166,47 +158,6 @@ class @Notes
@removeDiscussionNoteForm(form)
###
- Shows write note textarea.
- ###
- writeNote: (e) ->
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-note-write-button").parent().addClass "active"
- form.find(".js-note-preview-button").parent().removeClass "active"
-
- # toggle content
- form.find(".note-write-holder").show()
- form.find(".note-preview-holder").hide()
-
- ###
- Shows the note preview.
-
- Lets the server render GFM into Html and displays it.
- ###
- previewNote: (e) ->
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-note-write-button").parent().removeClass "active"
- form.find(".js-note-preview-button").parent().addClass "active"
-
- # toggle content
- form.find(".note-write-holder").hide()
- form.find(".note-preview-holder").show()
-
- preview = form.find(".js-note-preview")
- noteText = form.find(".js-note-text").val()
- if noteText.trim().length is 0
- preview.text "Nothing to preview."
- else
- preview.text "Loading..."
- $.post($(this).data("url"),
- note: noteText
- ).success (previewData) ->
- preview.html previewData
-
- ###
Called in response the main target form has been successfully submitted.
Removes any errors.
@@ -220,7 +171,7 @@ class @Notes
form.find(".js-errors").remove()
# reset text and preview
- form.find(".js-note-write-button").click()
+ form.find(".js-md-write-button").click()
form.find(".js-note-text").val("").trigger "input"
###
@@ -270,8 +221,8 @@ class @Notes
form.removeClass "js-new-note-form"
# setup preview buttons
- form.find(".js-note-write-button, .js-note-preview-button").tooltip placement: "left"
- previewButton = form.find(".js-note-preview-button")
+ form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left"
+ previewButton = form.find(".js-md-preview-button")
form.find(".js-note-text").on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index fbfa72c5e5e..4168e235cae 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -20,6 +20,7 @@
opacity: 0;
font-size: 50px;
transition: opacity 200ms ease-in-out;
+ pointer-events: none;
}
.div-dropzone-spinner {
@@ -50,3 +51,28 @@
margin-bottom: 0;
transition: opacity 200ms ease-in-out;
}
+
+.md-preview-holder {
+ background: #FFF;
+ border: 1px solid #ddd;
+ min-height: 100px;
+ padding: 5px;
+ font-size: 14px;
+ box-shadow: none;
+}
+
+.new_note,
+.edit_note,
+.issuable-description,
+.milestone-description,
+.merge-request-form {
+ .nav-tabs {
+ margin-bottom: 0;
+ border: none;
+
+ li a,
+ li.active a {
+ border: 1px solid #DDD;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 36bc5df2f44..dffa2dc9ed2 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -29,28 +29,30 @@
.hljs-tag,
.hljs-tag .hljs-title,
- .hljs-keyword,
- .hljs-literal,
.hljs-strong,
.hljs-change,
.hljs-winutils,
.hljs-flow,
.lisp .hljs-title,
.clojure .hljs-built_in,
+ .hljs-keyword,
.nginx .hljs-title,
.tex .hljs-special {
color: #F92672;
}
.hljs {
- color: #DDD;
+ color: #F8F8F2;
}
- .hljs .hljs-constant,
- .asciidoc .hljs-code {
+ .asciidoc .hljs-code,
+ .markdown .hljs-code,
+ .hljs-literal,
+ .hljs-function .hljs-keyword {
color: #66D9EF;
}
+
.hljs-code,
.hljs-class .hljs-title,
.hljs-header {
@@ -62,18 +64,27 @@
.hljs-symbol,
.hljs-symbol .hljs-string,
.hljs-value,
+ .hljs-constant,
+ .hljs-number,
.hljs-regexp {
- color: #BF79DB;
+ color: #AE81FF;
+ }
+
+ .hljs-string {
+ color: #E6DB74;
+ }
+
+ .hljs-params {
+ color: #fd971f;
}
.hljs-link_url,
.hljs-tag .hljs-value,
- .hljs-string,
.hljs-bullet,
.hljs-subst,
.hljs-title,
.hljs-emphasis,
- .haskell .hljs-type,
+ .hljs-type,
.hljs-preprocessor,
.hljs-pragma,
.ruby .hljs-class .hljs-parent,
@@ -99,12 +110,12 @@
}
.hljs-comment,
- .java .hljs-annotation,
+ .hljs-annotation,
.smartquote,
.hljs-blockquote,
.hljs-horizontal_rule,
- .python .hljs-decorator,
.hljs-template_comment,
+ .hljs-decorator,
.hljs-pi,
.hljs-doctype,
.hljs-deletion,
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss
index 485a9c46610..a766d6e77ab 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/sections/events.scss
@@ -47,7 +47,7 @@
.event-title {
@include str-truncated(72%);
color: #333;
- font-weight: normal;
+ font-weight: 500;
font-size: 14px;
.author_name {
color: #333;
@@ -56,12 +56,9 @@
.event-body {
margin-left: 35px;
margin-right: 100px;
+ color: #777;
- .event-info {
- color: #666;
- }
.event-note {
- color: #666;
margin-top: 5px;
.md {
@@ -72,7 +69,7 @@
border: none;
background: #f9f9f9;
border-radius: 0;
- color: #666;
+ color: #777;
margin: 0 20px;
}
@@ -120,7 +117,6 @@
padding: 3px;
padding-left: 0;
border: none;
- color: #666;
.commit-row-title {
font-size: 12px;
}
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index 783f6ae02d3..e1f9c0cb258 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -227,7 +227,6 @@ ul.notes {
margin-bottom: 0;
}
- .note-preview-holder,
.note_text {
background: #FFF;
border: 1px solid #ddd;
@@ -246,15 +245,6 @@ ul.notes {
.note_text {
width: 100%;
}
- .nav-tabs {
- margin-bottom: 0;
- border: none;
-
- li a,
- li.active a {
- border: 1px solid #DDD;
- }
- }
}
/* loading indicator */
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index bd4b310fcbf..3e984e5007a 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -42,10 +42,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth
if current_user
- # Change a logged-in user's authentication method:
- current_user.extern_uid = oauth['uid']
- current_user.provider = oauth['provider']
- current_user.save
+ # Add new authentication method
+ current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
redirect_to profile_path
else
@user = Gitlab::OAuth::User.new(oauth)
@@ -67,8 +65,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
- rescue StandardError
- flash[:notice] = "There's no such user!"
+ rescue ForbiddenAction => e
+ flash[:notice] = e.message
redirect_to new_user_session_path
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 7e4580017dd..6b7fe06d59f 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -29,4 +29,31 @@ class Projects::ApplicationController < ApplicationController
redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch"
end
end
+
+ def set_filter_variables(collection)
+ params[:sort] ||= 'newest'
+ params[:scope] = 'all' if params[:scope].blank?
+ params[:state] = 'opened' if params[:state].blank?
+
+ @sort = params[:sort].humanize
+
+ assignee_id = params[:assignee_id]
+ author_id = params[:author_id]
+ milestone_id = params[:milestone_id]
+
+ if assignee_id.present? && !assignee_id.to_i.zero?
+ @assignee = @project.team.find(assignee_id)
+ end
+
+ if author_id.present? && !author_id.to_i.zero?
+ @author = @project.team.find(assignee_id)
+ end
+
+ if milestone_id.present? && !milestone_id.to_i.zero?
+ @milestone = @project.milestones.find(milestone_id)
+ end
+
+ @assignees = User.where(id: collection.pluck(:assignee_id))
+ @authors = User.where(id: collection.pluck(:author_id))
+ end
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index cab8fd76e6c..2d6c3111192 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
def test
if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user)
+
if status
flash[:notice] = 'Hook successfully executed.'
else
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c6d526f05c5..22235123826 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -18,18 +18,12 @@ class Projects::IssuesController < Projects::ApplicationController
def index
terms = params['issue_search']
+ set_filter_variables(@project.issues)
- @issues = issues_filtered
+ @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
@issues = @issues.full_search(terms) if terms.present?
@issues = @issues.page(params[:page]).per(20)
- assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
- @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
- @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
- sort_param = params[:sort] || 'newest'
- @sort = sort_param.humanize unless sort_param.empty?
- @assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
-
respond_to do |format|
format.html
format.atom { render layout: false }
@@ -127,12 +121,6 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.issues_enabled
end
- def issues_filtered
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
- @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
- end
-
# Since iids are implemented only in 6.1
# user may navigate to issue page using old global ids.
#
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 20a733b10e1..4d6f41e9de5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -17,18 +17,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index
- params[:sort] ||= 'newest'
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
+ set_filter_variables(@project.merge_requests)
@merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
@merge_requests = @merge_requests.page(params[:page]).per(20)
-
- @sort = params[:sort].humanize
- assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
- @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
- @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
- @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id))
end
def show
@@ -225,6 +217,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@allowed_to_merge = allowed_to_merge?
@show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
@source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
+
+ if @merge_request.locked_long_ago?
+ @merge_request.unlock_mr
+ @merge_request.close
+ end
end
def allowed_to_merge?
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 7b08b79d236..2f1d631c14a 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -61,10 +61,6 @@ class Projects::NotesController < Projects::ApplicationController
end
end
- def preview
- render text: view_context.markdown(params[:note])
- end
-
private
def note
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 9d5dd8a95cc..25c887deafa 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -68,7 +68,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet.content,
type: 'text/plain; charset=utf-8',
disposition: 'inline',
- filename: @snippet.file_name
+ filename: @snippet.sanitized_file_name
)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index fbd9e5f2a5b..fcff6952d38 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -147,6 +147,10 @@ class ProjectsController < ApplicationController
render json: { star_count: @project.star_count }
end
+ def markdown_preview
+ render text: view_context.markdown(params[:md_text])
+ end
+
private
def upload_path
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index bf3312fedc8..312e561b522 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -79,7 +79,7 @@ class SnippetsController < ApplicationController
@snippet.content,
type: 'text/plain; charset=utf-8',
disposition: 'inline',
- filename: @snippet.file_name
+ filename: @snippet.sanitized_file_name
)
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 0b442f5383a..67af1801bda 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -20,9 +20,14 @@ class UsersController < ApplicationController
# Get user activity feed for projects common for both users
@events = @user.recent_events.
- where(project_id: authorized_projects_ids).limit(20)
+ where(project_id: authorized_projects_ids).limit(30)
@title = @user.name
+
+ respond_to do |format|
+ format.html
+ format.atom { render layout: false }
+ end
end
def determine_layout
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index d0574240511..e1477510065 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -33,6 +33,7 @@ class IssuableFinder
items = by_search(items)
items = by_milestone(items)
items = by_assignee(items)
+ items = by_author(items)
items = by_label(items)
items = sort(items)
end
@@ -125,6 +126,14 @@ class IssuableFinder
items
end
+ def by_author(items)
+ if params[:author_id].present?
+ items = items.where(author_id: (params[:author_id] == '0' ? nil : params[:author_id]))
+ end
+
+ items
+ end
+
def by_label(items)
if params[:label_name].present?
label_names = params[:label_name].split(",")
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 71f97fbb8c8..a3136926b38 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -145,4 +145,26 @@ module EventsHelper
rescue
"--broken encoding"
end
+
+ def event_to_atom(xml, event)
+ if event.proper?
+ xml.entry do
+ event_link = event_feed_url(event)
+ event_title = event_feed_title(event)
+ event_summary = event_feed_summary(event)
+
+ xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
+ xml.link href: event_link
+ xml.title truncate(event_title, length: 80)
+ xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
+ xml.author do |author|
+ xml.name event.author_name
+ xml.email event.author_email
+ end
+
+ xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
+ end
+ end
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index d513e0ba58e..a5b393c1e3c 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -113,4 +113,19 @@ module IssuesHelper
'issue-box-open'
end
end
+
+ def issue_to_atom(xml, issue)
+ xml.entry do
+ xml.id project_issue_url(issue.project, issue)
+ xml.link href: project_issue_url(issue.project, issue)
+ xml.title truncate(issue.title, length: 80)
+ xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email)
+ xml.author do |author|
+ xml.name issue.author_name
+ xml.email issue.author_email
+ end
+ xml.summary issue.title
+ end
+ end
end
diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb
index 7024483b8b3..df18db71c84 100644
--- a/app/helpers/oauth_helper.rb
+++ b/app/helpers/oauth_helper.rb
@@ -16,4 +16,8 @@ module OauthHelper
[:twitter, :github, :google_oauth2].include?(name.to_sym)
end
end
+
+ def additional_providers
+ enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
+ end
end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
index 0b375558305..6480fd3886f 100644
--- a/app/helpers/profile_helper.rb
+++ b/app/helpers/profile_helper.rb
@@ -1,6 +1,6 @@
module ProfileHelper
def oauth_active_class(provider)
- if current_user.provider == provider.to_s
+ if current_user.identities.exists?(provider: provider.to_s)
'active'
end
end
@@ -10,10 +10,10 @@ module ProfileHelper
end
def show_profile_social_tab?
- enabled_social_providers.any? && !current_user.ldap_user?
+ enabled_social_providers.any?
end
def show_profile_remove_tab?
- gitlab_config.signup_enabled && !current_user.ldap_user?
+ gitlab_config.signup_enabled
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0ee19836627..6d671e6e0bd 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -26,6 +26,14 @@ class Notify < ActionMailer::Base
delay_for(2.seconds)
end
+ def test_email(recepient_email, subject, body)
+ mail(to: recepient_email,
+ subject: subject,
+ body: body.html_safe,
+ content_type: 'text/html'
+ )
+ end
+
private
# The default email address to send emails from
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 23fa01e0b70..8479d4aecf6 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -32,7 +32,10 @@ class WebHook < ActiveRecord::Base
def execute(data)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
- WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
+ WebHook.post(url,
+ body: data.to_json,
+ headers: { "Content-Type" => "application/json" },
+ verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
@@ -45,6 +48,9 @@ class WebHook < ActiveRecord::Base
verify: false,
basic_auth: auth)
end
+ rescue SocketError, Errno::ECONNREFUSED => e
+ logger.error("WebHook Error => #{e}")
+ false
end
def async_execute(data)
diff --git a/app/models/identity.rb b/app/models/identity.rb
new file mode 100644
index 00000000000..5fb1850c30b
--- /dev/null
+++ b/app/models/identity.rb
@@ -0,0 +1,5 @@
+class Identity < ActiveRecord::Base
+ belongs_to :user
+
+ validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
+end \ No newline at end of file
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7c525b02f48..2cc427d35c2 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -70,6 +70,16 @@ class MergeRequest < ActiveRecord::Base
transition locked: :reopened
end
+ after_transition any => :locked do |merge_request, transition|
+ merge_request.locked_at = Time.now
+ merge_request.save
+ end
+
+ after_transition :locked => (any - :locked) do |merge_request, transition|
+ merge_request.locked_at = nil
+ merge_request.save
+ end
+
state :opened
state :reopened
state :closed
@@ -336,4 +346,8 @@ class MergeRequest < ActiveRecord::Base
source_project.repository.branch_names
end
end
+
+ def locked_long_ago?
+ locked_at && locked_at < (Time.now - 1.day)
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index daf4bdd0aad..32b0145ca24 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -390,14 +390,8 @@ class Project < ActiveRecord::Base
end
def execute_services(data)
- services.each do |service|
-
- # Call service hook only if it is active
- begin
- service.execute(data) if service.active
- rescue => e
- logger.error(e)
- end
+ services.select(&:active).each do |service|
+ service.async_execute(data)
end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index c489c1e96e1..71c8aa39e45 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -82,4 +82,8 @@ class Service < ActiveRecord::Base
}
end
end
+
+ def async_execute(data)
+ Sidekiq::Client.enqueue(ProjectServiceWorker, id, data)
+ end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index a47fbca3260..9aba42a0622 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -29,7 +29,9 @@ class Snippet < ActiveRecord::Base
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
- validates :file_name, presence: true, length: { within: 0..255 }
+ validates :file_name, presence: true, length: { within: 0..255 },
+ format: { with: Gitlab::Regex.path_regex,
+ message: Gitlab::Regex.path_regex_message }
validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
@@ -62,6 +64,10 @@ class Snippet < ActiveRecord::Base
file_name
end
+ def sanitized_file_name
+ file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '')
+ end
+
def mode
nil
end
@@ -72,7 +78,7 @@ class Snippet < ActiveRecord::Base
def visibility_level_field
visibility_level
- end
+ end
class << self
def search(query)
diff --git a/app/models/user.rb b/app/models/user.rb
index 1cddd85ada0..7faeef1b5b0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -79,6 +79,7 @@ class User < ActiveRecord::Base
# Profile
has_many :keys, dependent: :destroy
has_many :emails, dependent: :destroy
+ has_many :identities, dependent: :destroy
# Groups
has_many :members, dependent: :destroy
@@ -113,7 +114,6 @@ class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true, email: {strict_mode: true}, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true
- validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
validates :username, presence: true, uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path },
@@ -178,7 +178,6 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
- scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
@@ -407,7 +406,11 @@ class User < ActiveRecord::Base
end
def ldap_user?
- extern_uid && provider.start_with?('ldap')
+ identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
+ end
+
+ def ldap_identity
+ @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end
def accessible_deploy_keys
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 2b6217e2e29..d1aadd741e1 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -107,7 +107,7 @@ class NotificationService
# Notify new user with email after creation
def new_user(user, token = nil)
# Don't email omniauth created users
- mailer.new_user_email(user.id, token) unless user.extern_uid?
+ mailer.new_user_email(user.id, token) unless user.identities.any?
end
# Notify users on new note in system
diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb
index b6b1ef29b51..17d86a7a274 100644
--- a/app/services/test_hook_service.rb
+++ b/app/services/test_hook_service.rb
@@ -2,8 +2,5 @@ class TestHookService
def execute(hook, current_user)
data = GitPushService.new.sample_data(hook.project, current_user)
hook.execute(data)
- true
- rescue SocketError
- false
end
end
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index 29a55b36ca5..b122b6c8658 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -26,10 +26,6 @@ class AttachmentUploader < CarrierWave::Uploader::Base
Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
end
- def url
- Gitlab.config.gitlab.relative_url_root + super unless super.nil?
- end
-
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 211d77d5185..29717aedd80 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -95,7 +95,7 @@
%li
%span.light LDAP uid:
%strong
- = @user.extern_uid
+ = @user.ldap_identity.extern_uid
- if @user.created_by
%li
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index f5413557783..66381310221 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,24 +1,13 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
- xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml"
- xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html"
- xml.id issues_dashboard_url(:private_token => current_user.private_token)
+ xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html"
+ xml.id issues_dashboard_url(private_token: current_user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(issue.project, issue)
- xml.link :href => project_issue_url(issue.project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder
index f4cf24ccd99..70ac66f8016 100644
--- a/app/views/dashboard/show.atom.builder
+++ b/app/views/dashboard/show.atom.builder
@@ -1,29 +1,12 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
- xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html"
+ xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml"
+ xml.link href: dashboard_url, rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
- if event.proper?
- xml.entry do
- event_link = event_feed_url(event)
- event_title = event_feed_title(event)
- event_summary = event_feed_summary(event)
-
- xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
- xml.link :href => event_link
- xml.title truncate(event_title, :length => 80)
- xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
- xml.author do |author|
- xml.name event.author_name
- xml.email event.author_email
- end
- xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? }
- end
- end
+ event_to_atom(xml, event)
end
end
diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml
index 15048a78063..8d6aaefb9ff 100644
--- a/app/views/devise/sessions/_oauth_providers.html.haml
+++ b/app/views/devise/sessions/_oauth_providers.html.haml
@@ -1,4 +1,4 @@
-- providers = (enabled_oauth_providers - [:ldap])
+- providers = additional_providers
- if providers.present?
.bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'}
%span Sign in with: &nbsp;
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index f2005193f83..240001967f3 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(issue.project, issue)
- xml.link :href => project_issue_url(issue.project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index e07bb7d2fb7..e765ea8338d 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -1,28 +1,12 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Group feed - #{@group.name}"
- xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html"
+ xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml"
+ xml.link href: group_path(@group), rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
- if event.proper?
- xml.entry do
- event_link = event_feed_url(event)
- event_title = event_feed_title(event)
-
- xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
- xml.link :href => event_link
- xml.title truncate(event_title, :length => 80)
- xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
- xml.author do |author|
- xml.name event.author_name
- xml.email event.author_email
- end
- xml.summary event_title
- end
- end
+ event_to_atom(xml, event)
end
end
diff --git a/app/views/projects/_issuable_filter.html.haml b/app/views/projects/_issuable_filter.html.haml
new file mode 100644
index 00000000000..b3e5efd938f
--- /dev/null
+++ b/app/views/projects/_issuable_filter.html.haml
@@ -0,0 +1,72 @@
+.issues-filters
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light assignee:
+ - if @assignee.present?
+ %strong= @assignee.name
+ - elsif params[:assignee_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(assignee_id: nil) do
+ Any
+ = link_to project_filter_path(assignee_id: 0) do
+ Unassigned
+ - @assignees.sort_by(&:name).each do |user|
+ %li
+ = link_to project_filter_path(assignee_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light author:
+ - if @author.present?
+ %strong= @author.name
+ - elsif params[:author_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(author_id: nil) do
+ Any
+ = link_to project_filter_path(author_id: 0) do
+ Unassigned
+ - @authors.sort_by(&:name).each do |user|
+ %li
+ = link_to project_filter_path(author_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-clock-o
+ %span.light milestone:
+ - if @milestone.present?
+ %strong= @milestone.title
+ - elsif params[:milestone_id] == "0"
+ None (backlog)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(milestone_id: nil) do
+ Any
+ = link_to project_filter_path(milestone_id: 0) do
+ None (backlog)
+ - project_active_milestones.each do |milestone|
+ %li
+ = link_to project_filter_path(milestone_id: milestone.id) do
+ %strong= milestone.title
+ %small.light= milestone.expires_at
+
+ .pull-right
+ = render 'shared/sort_dropdown'
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index dd40a719561..b02f52a5aff 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -14,17 +14,20 @@
.form-group.issuable-description
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
- = render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
- .col-sm-12.hint
- .pull-left
- Parsed with
- #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
- .pull-right
- Attach images (JPG, PNG, GIF) by dragging &amp; dropping
- or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
- .clearfix
- .error-alert
+
+ = render layout: 'projects/md_preview' do
+ = render 'projects/zen', f: f, attr: :description,
+ classes: 'description form-control'
+ .col-sm-12.hint
+ .pull-left
+ Parsed with
+ #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .pull-right
+ Attach images (JPG, PNG, GIF) by dragging &amp; dropping
+ or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
+
+ .clearfix
+ .error-alert
%hr
.form-group
.issue-assignee
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
new file mode 100644
index 00000000000..cb75149434f
--- /dev/null
+++ b/app/views/projects/_md_preview.html.haml
@@ -0,0 +1,13 @@
+%ul.nav.nav-tabs
+ %li.active
+ = link_to '#md-write-holder', class: 'js-md-write-button' do
+ Write
+ %li
+ = link_to '#md-preview-holder', class: 'js-md-preview-button',
+ data: { url: markdown_preview_project_path(@project) } do
+ Preview
+%div
+ .md-write-holder
+ = yield
+ .md-preview-holder.hide
+ .js-md-preview
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 0bff8bdbead..15c84c7ced2 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,55 +1,7 @@
.append-bottom-10
.check-all-holder
= check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
- .issues-filters
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
+ = render 'projects/issuable_filter'
.clearfix
.issues_bulk_update.hide
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 012ba235951..61e651da932 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -7,17 +7,6 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(@project, issue)
- xml.link :href => project_issue_url(@project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index d4666eacd7e..76813e688b5 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -21,12 +21,13 @@
.form-group
.light
= f.label :description, "Description"
- = render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
- .clearfix.hint
- .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
- .error-alert
+ = render layout: 'projects/md_preview' do
+ = render 'projects/zen', f: f, attr: :description,
+ classes: 'description form-control'
+ .clearfix.hint
+ .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
+ .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ .error-alert
.form-group
.issue-assignee
= f.label :assignee_id do
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index a6d90a68b11..b93e0f9da3e 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -5,56 +5,8 @@
= render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
labels: true, redirect: 'merge_requests', entity: 'merge_request'
.col-md-9
- .mr-filters.append-bottom-10
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
-
+ .append-bottom-10
+ = render 'projects/issuable_filter'
.panel.panel-default
%ul.well-list.mr-list
= render @merge_requests
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
index 87dad6140be..a4f2a890969 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -11,14 +11,18 @@
- if @merge_request.closed?
%h4
- Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
+ Closed
+ - if @merge_request.closed_event
+ by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
%p Changes were not merged into target branch
- if @merge_request.merged?
%h4
- Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
+ Merged
+ - if @merge_request.merge_event
+ by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
- if @merge_request.locked?
@@ -44,4 +48,3 @@
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
= succeed '.' do
!= gfm(issues_sentence(@closes_issues))
-
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 5fb01a11cc5..0f51a347f01 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -18,13 +18,14 @@
.col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control"
%p.hint Required
- .form-group
+ .form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
- .hint
- .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ = render layout: 'projects/md_preview' do
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ .hint
+ .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
+ .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
.clearfix
.error-alert
.col-md-6
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 5bc0e60bbe8..47ffe1fd2f3 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -5,23 +5,13 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
- %ul.nav.nav-tabs
- %li.active
- = link_to '#note-write-holder', class: 'js-note-write-button' do
- Write
- %li
- = link_to '#note-preview-holder', class: 'js-note-preview-button', data: { url: preview_project_notes_path(@project) } do
- Preview
- %div
- .note-write-holder
- = render 'projects/zen', f: f, attr: :note,
- classes: 'note_text js-note-text'
- .light.clearfix
- .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
- .pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+ = render layout: 'projects/md_preview' do
+ = render 'projects/zen', f: f, attr: :note,
+ classes: 'note_text js-note-text'
+ .light.clearfix
+ .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
+ .pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
- .note-preview-holder.hide
- .js-note-preview
.note-form-actions
.buttons
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index b2abdf0035d..354afd3e2c9 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -40,7 +40,8 @@
.note-edit-form
= form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f|
- = f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on'
+ = render layout: 'projects/md_preview' do
+ = f.text_area :note, class: 'note_text js-note-text markdown-area js-gfm-input turn-on'
.form-actions.clearfix
= f.submit 'Save changes', class: "btn btn-primary btn-save js-comment-button"
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
new file mode 100644
index 00000000000..b7216a88765
--- /dev/null
+++ b/app/views/users/show.atom.builder
@@ -0,0 +1,12 @@
+xml.instruct!
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
+ xml.title "Activity feed for #{@user.name}"
+ xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
+ xml.link href: user_url(@user), rel: "alternate", type: "text/html"
+ xml.id projects_url
+ xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+
+ @events.each do |event|
+ event_to_atom(xml, event)
+ end
+end
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index cb49c030af2..54f2666ce5d 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -18,7 +18,15 @@
%h4 Groups:
= render 'groups', groups: @groups
%hr
- %h4 User Activity:
+ %h4
+ User Activity:
+
+ - if current_user
+ %span.rss-icon.pull-right
+ = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do
+ %strong
+ %i.fa.fa-rss
+
= render @events
.col-md-4
= render 'profile', user: @user
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
new file mode 100644
index 00000000000..cc0a7f25664
--- /dev/null
+++ b/app/workers/project_service_worker.rb
@@ -0,0 +1,9 @@
+class ProjectServiceWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :project_web_hook
+
+ def perform(hook_id, data)
+ Service.find(hook_id).execute(data)
+ end
+end
diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb
index 75c543c0f47..e856499732e 100644
--- a/config/initializers/4_sidekiq.rb
+++ b/config/initializers/4_sidekiq.rb
@@ -15,7 +15,7 @@ Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
- chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MAX_RSS']
+ chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 723104daf13..533e044ca4c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -137,7 +137,8 @@ Gitlab::Application.routes.draw do
end
end
- match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get
+ match "/u/:username" => "users#show", as: :user,
+ constraints: {username: /(?:[^.]|\.(?!atom$))+/, format: /atom/}, via: :get
#
# Dashboard Area
@@ -185,11 +186,12 @@ Gitlab::Application.routes.draw do
post :unarchive
post :upload_image
post :toggle_star
+ get :markdown_preview
get :autocomplete_sources
end
scope module: :projects do
- resources :blob, only: [:show, :destroy], constraints: { id: /.+/ } do
+ resources :blob, only: [:show, :destroy], constraints: { id: /.+/, format: false } do
get :diff, on: :member
end
resources :raw, only: [:show], constraints: {id: /.+/}
@@ -328,10 +330,6 @@ Gitlab::Application.routes.draw do
member do
delete :delete_attachment
end
-
- collection do
- post :preview
- end
end
end
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index ea22744fd90..d8b4f5c7c32 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -13,9 +13,9 @@
#
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
-# Use at least one worker per core if you're on a dedicated server,
-# more will usually help for _short_ waits on databases/caches.
-# The minimum is 2
+# Read about unicorn workers here:
+# http://doc.gitlab.com/ee/install/requirements.html#unicorn-workers
+#
worker_processes 2
# Since Unicorn is never exposed to outside clients, it does not need to
@@ -37,10 +37,10 @@ listen "127.0.0.1:8080", :tcp_nopush => true
# nuke workers after 30 seconds instead of 60 seconds (the default)
#
-# NOTICE: git push over http depends on this value.
-# If you want be able to push huge amount of data to git repository over http
-# you will have to increase this value too.
-#
+# NOTICE: git push over http depends on this value.
+# If you want be able to push huge amount of data to git repository over http
+# you will have to increase this value too.
+#
# Example of output if you try to push 1GB repo to GitLab over http.
# -> git push http://gitlab.... master
#
diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb
new file mode 100644
index 00000000000..6fe63637dfe
--- /dev/null
+++ b/db/migrate/20141121161704_add_identity_table.rb
@@ -0,0 +1,38 @@
+class AddIdentityTable < ActiveRecord::Migration
+ def up
+ create_table :identities do |t|
+ t.string :extern_uid
+ t.string :provider
+ t.references :user
+ end
+
+ add_index :identities, :user_id
+
+ execute <<eos
+INSERT INTO identities (provider, extern_uid, user_id)
+SELECT provider, extern_uid, id FROM users
+WHERE provider IS NOT NULL
+eos
+
+ remove_column :users, :extern_uid
+ remove_column :users, :provider
+ end
+
+ def down
+ add_column :users, :extern_uid, :string
+ add_column :users, :provider, :string
+
+ if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
+ execute <<eos
+UPDATE users u
+SET provider = i.provider, extern_uid = i.extern_uid
+FROM identities i
+WHERE i.user_id = u.id
+eos
+ else
+ execute "UPDATE users u, identities i SET u.provider = i.provider, u.extern_uid = i.extern_uid WHERE u.id = i.user_id"
+ end
+
+ drop_table :identities
+ end
+end
diff --git a/db/migrate/20141205134006_add_locked_at_to_merge_request.rb b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb
new file mode 100644
index 00000000000..49651c44a82
--- /dev/null
+++ b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb
@@ -0,0 +1,5 @@
+class AddLockedAtToMergeRequest < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :locked_at, :datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 68d1080b6ee..b8335c5841b 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: 20141121133009) do
+ActiveRecord::Schema.define(version: 20141205134006) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -74,6 +74,14 @@ ActiveRecord::Schema.define(version: 20141121133009) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
+ create_table "identities", force: true do |t|
+ t.string "extern_uid"
+ t.string "provider"
+ t.integer "user_id"
+ end
+
+ add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
+
create_table "issues", force: true do |t|
t.string "title"
t.integer "assignee_id"
@@ -173,6 +181,7 @@ ActiveRecord::Schema.define(version: 20141121133009) do
t.integer "iid"
t.text "description"
t.integer "position", default: 0
+ t.datetime "locked_at"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -350,8 +359,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do
t.string "bio"
t.integer "failed_attempts", default: 0
t.datetime "locked_at"
- t.string "extern_uid"
- t.string "provider"
t.string "username"
t.boolean "can_create_group", default: true, null: false
t.boolean "can_create_team", default: true, null: false
@@ -360,6 +367,7 @@ ActiveRecord::Schema.define(version: 20141121133009) do
t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
+ t.datetime "last_credential_check_at"
t.string "avatar"
t.string "confirmation_token"
t.datetime "confirmed_at"
@@ -367,7 +375,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do
t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false
- t.datetime "last_credential_check_at"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -375,7 +382,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
- add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 6d9ac161e91..53f8095cb13 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -1,6 +1,6 @@
# Rake tasks for developers
-## Setup db with developer seeds:
+## Setup db with developer seeds
Note that if your db user does not have advanced privileges you must create the db manually before running this command.
@@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the
bundle exec rake setup
```
+The `setup` task is a alias for `gitlab:setup`.
+This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database.
+Note: `db:setup` calls `db:seed` but this does nothing.
+
## Run tests
This runs all test suites present in GitLab.
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index 23c8365c340..1e51ad73e32 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -1,5 +1,8 @@
# Guidelines for shell commands in the GitLab codebase
+This document contains guidelines for working with processes and files in the GitLab codebase.
+These guidelines are meant to make your code more reliable _and_ secure.
+
## References
- [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide)
@@ -109,3 +112,63 @@ logs = IO.popen(%W(git log), chdir: repo_dir).read
```
Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error.
+
+## Avoid user input at the start of path strings
+
+Various methods for opening and reading files in Ruby can be used to read the
+standard output of a process instead of a file. The following two commands do
+roughly the same:
+
+```
+`touch /tmp/pawned-by-backticks`
+File.read('|touch /tmp/pawned-by-file-read')
+```
+
+The key is to open a 'file' whose name starts with a `|`.
+Affected methods include Kernel#open, File::read, File::open, IO::open and IO::read.
+
+You can protect against this behavior of 'open' and 'read' by ensuring that an
+attacker cannot control the start of the filename string you are opening. For
+instance, the following is sufficient to protect against accidentally starting
+a shell command with `|`:
+
+```
+# we assume repo_path is not controlled by the attacker (user)
+path = File.join(repo_path, user_input)
+# path cannot start with '|' now.
+File.read(path)
+```
+
+## Guard against path traversal
+
+Path traversal is a security where the program (GitLab) tries to restrict user
+access to a certain directory on disk, but the user manages to open a file
+outside that directory by taking advantage of the `../` path notation.
+
+```
+# Suppose the user gave us a path and they are trying to trick us
+user_input = '../other-repo.git/other-file'
+
+# We look up the repo path somewhere
+repo_path = 'repositories/user-repo.git'
+
+# The intention of the code below is to open a file under repo_path, but
+# because the user used '..' she can 'break out' into
+# 'repositories/other-repo.git'
+full_path = File.join(repo_path, user_input)
+File.open(full_path) do # Oops!
+```
+
+A good way to protect against this is to compare the full path with its
+'absolute path' according to Ruby's `File.absolute_path`.
+
+```
+full_path = File.join(repo_path, user_input)
+if full_path != File.absolute_path(full_path)
+ raise "Invalid path: #{full_path.inspect}"
+end
+
+File.open(full_path) do # Etc.
+```
+
+A check like this could have avoided CVE-2013-4583.
diff --git a/doc/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md
index cea11e5f126..cea11e5f126 100644
--- a/doc/sidekiq_debugging.md
+++ b/doc/development/sidekiq_debugging.md
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 2b60c3560a5..8eabb219b1b 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -38,6 +38,16 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
## Hardware requirements
+### Storage
+
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+
+If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
+
+Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
+
+If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+
### CPU
- 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
@@ -50,12 +60,10 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Memory
-- 512MB is the absolute minimum but we do not recommend this amount of memory.
-You will need to configure minimum 1.5GB of swap space.
-With 1.5GB of swap space you must configure only one unicorn worker.
-With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
-If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow.
-Consider installing GitLab on Ubuntu as installation on CentOS could be unsuccessful with this amount of memory.
+You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
+With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
+
+- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise.
- 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users
@@ -66,15 +74,16 @@ Consider installing GitLab on Ubuntu as installation on CentOS could be unsucces
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application.
-### Storage
+## Unicorn Workers
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+It's possible to increase the amount of unicorn workers and tis will usually help for to reduce the response time of the applications.
+For most instances we recommend using: CPU cores + 1 = unicorn workers.
+So for a machine with 2 cores, 3 unicorn workers is ideal.
-If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
-
-Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
-
-If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+For all machines that have 1GB and up we recommend a minimum of two unicorn workers.
+If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping.
+With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
+If you have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping.
## Database
@@ -91,7 +100,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
## Supported web browsers
- Chrome (Latest stable version)
-- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
+- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
- IE 10+
diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md
index 5cfea5a90f8..0816509c557 100644
--- a/doc/integration/gitlab_buttons_in_gmail.md
+++ b/doc/integration/gitlab_buttons_in_gmail.md
@@ -9,3 +9,20 @@ If correctly setup, emails that require an action will be marked in Gmail.
To get this functioning, you need to be registered with Google.
[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
+To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server.
+
+To check what would be sent to the google email address, run the rake task:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production
+```
+
+**This will not send the email but give you the output of how the mail will look.**
+
+Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate".
+
+If you receive "No errors detected" message from the tester you can send the email using:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true
+``
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index d1b52927d30..b9e501c5ec1 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -13,7 +13,7 @@ To enable the Twitter OmniAuth provider you must register your application with
something else descriptive.
- Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- - Callback URL: 'https://gitlab.example.com/users/auth/github/callback'
+ - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Rules of the Road."
![Twitter App Details](twitter_app_details.png)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 68e8a14f52f..f9d2f5dc4eb 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -137,7 +137,7 @@ with the name of your bucket:
Please be informed that a backup does not store your configuration files.
If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
-If you have a manual installation please consider backing up your gitlab.yml file and any SSL keys and certificates.
+If you have a manual installation please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
## Restore a previously created backup
@@ -208,3 +208,26 @@ Add the following lines at the bottom:
The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
This is recommended to reduce cron spam.
+
+## Alternative backup strategies
+
+If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
+In this case you can consider using filesystem snapshots as part of your backup strategy.
+
+Example: Amazone EBS
+
+> A GitLab server using omnibus-gitlab hosted on Amazon AWS.
+> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`.
+> In this case you could make an application backup by taking an EBS snapshot.
+> The backup includes all repositories, uploads and Postgres data.
+
+Example: LVM snapshots + Rsync
+
+> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`.
+> Replicating the `/var/opt/gitlab` directory usign Rsync would not be reliable because too many files would change while Rsync is running.
+> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`.
+> Now we can have a longer running Rsync job which will create a consistent replica on the remote server.
+> The replica includes all repositories, uploads and Postgres data.
+
+If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server.
+It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use.
diff --git a/doc/release/patch.md b/doc/release/patch.md
index ce5c2170302..2bd34b7d822 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -18,13 +18,14 @@ Otherwise include it in the monthly release and note there was a regression fix
1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
1. Fix the issue on a feature branch, do this on the private GitLab development server
1. If it is a security issue, then assign it to the release manager and apply a 'security' label
+1. Build the package for GitLab.com and do a deploy
1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
1. Make sure that the build has passed and all tests are passing
1. In a separate commit in the stable branch update the CHANGELOG
1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
-### Bump version
+### Bump version
Get release tools
diff --git a/doc/release/security.md b/doc/release/security.md
index c24a394ef4a..b67e0f37a04 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -17,6 +17,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Inform the release manager that there needs to be a security release
1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server"
1. The MR with the security fix should get a 'security' label and be assigned to the release manager
+1. Build the package for GitLab.com and do a deploy
1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge and publish the blog posts
1. Send tweets about the release from `@gitlabhq`
diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature
index e176752cfbf..a45245917e3 100644
--- a/features/project/commits/comments.feature
+++ b/features/project/commits/comments.feature
@@ -16,12 +16,12 @@ Feature: Project Commits Comments
@javascript
Scenario: I can't preview without text
Given I haven't written any comment text
- Then I should not see the comment preview button
+ Then The comment preview tab should say there is nothing to do
@javascript
Scenario: I can preview with text
- Given I write a comment like "Nice"
- Then I should see the comment preview button
+ Given I write a comment like ":+1: Nice"
+ Then The comment preview tab should be display rendered Markdown
@javascript
Scenario: I preview a comment
@@ -32,7 +32,7 @@ Feature: Project Commits Comments
@javascript
Scenario: I can edit after preview
Given I preview a comment text like "Bug fixed :smile:"
- Then I should see the comment edit button
+ Then I should see the comment write tab
@javascript
Scenario: I have a reset form after posting from preview
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index a145ec84b78..9c4cc723d1b 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -58,13 +58,13 @@ Feature: Project Commits Diff Comments
Scenario: I can't preview without text
Given I open a diff comment form
And I haven't written any diff comment text
- Then I should not see the diff comment preview button
+ Then The diff comment preview tab should say there is nothing to do
@javascript
Scenario: I can preview with text
Given I open a diff comment form
And I write a diff comment like ":-1: I don't like this"
- Then I should see the diff comment preview button
+ Then The diff comment preview tab should display rendered Markdown
@javascript
Scenario: I preview a diff comment
@@ -75,7 +75,7 @@ Feature: Project Commits Diff Comments
@javascript
Scenario: I can edit after preview
Given I preview a diff comment text like "Should fix it :smile:"
- Then I should see the diff comment edit button
+ Then I should see the diff comment write tab
@javascript
Scenario: The form gets removed after posting
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 4db8551559b..28ea44530fe 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -159,3 +159,37 @@ Feature: Project Issues
Given project "Shop" has "Tasks-closed" closed issue with task markdown
When I visit issue page "Tasks-closed"
Then Task checkboxes should be disabled
+
+ # Issue description preview
+
+ @javascript
+ Scenario: I can't preview without text
+ Given I click link "New Issue"
+ And I haven't written any description text
+ Then The Markdown preview tab should say there is nothing to do
+
+ @javascript
+ Scenario: I can preview with text
+ Given I click link "New Issue"
+ And I write a description like ":+1: Nice"
+ Then The Markdown preview tab should display rendered Markdown
+
+ @javascript
+ Scenario: I preview an issue description
+ Given I click link "New Issue"
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown preview
+ And I should not see the Markdown text field
+
+ @javascript
+ Scenario: I can edit after preview
+ Given I click link "New Issue"
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
+
+ @javascript
+ Scenario: I can preview when editing an existing issue
+ Given I click link "Release 0.4"
+ And I click link "Edit" for the issue
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index d20358a7dc6..7c029f05d75 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -187,3 +187,34 @@ Feature: Project Merge Requests
And I visit merge request page "MR-task-open"
And I click link "Close"
Then Task checkboxes should be disabled
+
+ # Description preview
+
+ @javascript
+ Scenario: I can't preview without text
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I haven't written any description text
+ Then The Markdown preview tab should say there is nothing to do
+
+ @javascript
+ Scenario: I can preview with text
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I write a description like ":+1: Nice"
+ Then The Markdown preview tab should display rendered Markdown
+
+ @javascript
+ Scenario: I preview a merge request description
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown preview
+ And I should not see the Markdown text field
+
+ @javascript
+ Scenario: I can edit after preview
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 6d747b65bae..38aaadcd28d 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -170,7 +170,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step "I am not an ldap user" do
- current_user.update_attributes(extern_uid: nil, provider: '')
+ current_user.identities.delete
current_user.ldap_user?.should be_false
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 640603562dd..c0ae5208541 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -1,5 +1,6 @@
class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
include SharedAuthentication
+ include SharedIssuable
include SharedProject
include SharedNote
include SharedPaths
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index fae0cec53a6..d5e060bdbe8 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -1,5 +1,6 @@
class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
include SharedAuthentication
+ include SharedIssuable
include SharedProject
include SharedNote
include SharedPaths
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 10f3ed90b56..28964d54a8f 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -32,7 +32,7 @@ module SharedDiffNote
click_diff_line(sample_commit.line_code)
within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
@@ -41,7 +41,7 @@ module SharedDiffNote
within("#{diff_file_selector} form[rel$='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
@@ -71,9 +71,10 @@ module SharedDiffNote
end
end
- step 'I should not see the diff comment preview button' do
+ step 'The diff comment preview tab should say there is nothing to do' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview-button", visible: false)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
@@ -131,27 +132,28 @@ module SharedDiffNote
step 'I should see the diff comment preview' do
within("#{diff_file_selector} form") do
- page.should have_css(".js-note-preview", visible: false)
+ expect(page).to have_css('.js-md-preview', visible: true)
end
end
- step 'I should see the diff comment edit button' do
+ step 'I should see the diff comment write tab' do
within(diff_file_selector) do
- page.should have_css(".js-note-write-button", visible: true)
+ expect(page).to have_css('.js-md-write-button', visible: true)
end
end
- step 'I should see the diff comment preview button' do
+ step 'The diff comment preview tab should display rendered Markdown' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview-button", visible: true)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
step 'I should see two separate previews' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview", visible: true, count: 2)
- page.should have_content("Should fix it")
- page.should have_content("DRY this up")
+ expect(page).to have_css('.js-md-preview', visible: true, count: 2)
+ expect(page).to have_content('Should fix it')
+ expect(page).to have_content('DRY this up')
end
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
new file mode 100644
index 00000000000..a0150e90380
--- /dev/null
+++ b/features/steps/shared/issuable.rb
@@ -0,0 +1,15 @@
+module SharedIssuable
+ include Spinach::DSL
+
+ def edit_issuable
+ find('.issue-btn-group').click_link 'Edit'
+ end
+
+ step 'I click link "Edit" for the merge request' do
+ edit_issuable
+ end
+
+ step 'I click link "Edit" for the issue' do
+ edit_issuable
+ end
+end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index 8bf138065b0..e71700880cd 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -54,4 +54,49 @@ EOT
'div.description li.task-list-item input[type="checkbox"]:disabled'
)
end
+
+ step 'I should not see the Markdown preview' do
+ expect(find('.gfm-form .js-md-preview')).not_to be_visible
+ end
+
+ step 'The Markdown preview tab should say there is nothing to do' do
+ within('.gfm-form') do
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_content('Nothing to preview.')
+ end
+ end
+
+ step 'I should not see the Markdown text field' do
+ expect(find('.gfm-form textarea')).not_to be_visible
+ end
+
+ step 'I should see the Markdown write tab' do
+ expect(find('.gfm-form')).to have_css('.js-md-write-button', visible: true)
+ end
+
+ step 'I should see the Markdown preview' do
+ expect(find('.gfm-form')).to have_css('.js-md-preview', visible: true)
+ end
+
+ step 'The Markdown preview tab should display rendered Markdown' do
+ within('.gfm-form') do
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
+ end
+ end
+
+ step 'I write a description like ":+1: Nice"' do
+ find('.gfm-form').fill_in 'Description', with: ':+1: Nice'
+ end
+
+ step 'I preview a description text like "Bug fixed :smile:"' do
+ within('.gfm-form') do
+ fill_in 'Description', with: 'Bug fixed :smile:'
+ find('.js-md-preview-button').click
+ end
+ end
+
+ step 'I haven\'t written any description text' do
+ find('.gfm-form').fill_in 'Description', with: ''
+ end
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 2b2cb47a715..17adec3eda1 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -23,7 +23,7 @@ module SharedNote
step 'I preview a comment text like "Bug fixed :smile:"' do
within(".js-main-target-form") do
fill_in "note[note]", with: "Bug fixed :smile:"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
@@ -33,9 +33,9 @@ module SharedNote
end
end
- step 'I write a comment like "Nice"' do
+ step 'I write a comment like ":+1: Nice"' do
within(".js-main-target-form") do
- fill_in "note[note]", with: "Nice"
+ fill_in 'note[note]', with: ':+1: Nice'
end
end
@@ -51,13 +51,14 @@ module SharedNote
step 'I should not see the comment preview' do
within(".js-main-target-form") do
- page.should have_css(".js-note-preview", visible: false)
+ expect(find('.js-md-preview')).not_to be_visible
end
end
- step 'I should not see the comment preview button' do
+ step 'The comment preview tab should say there is nothing to do' do
within(".js-main-target-form") do
- page.should have_css(".js-note-preview-button", visible: false)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
@@ -79,21 +80,22 @@ module SharedNote
end
end
- step 'I should see the comment edit button' do
+ step 'I should see the comment write tab' do
within(".js-main-target-form") do
- page.should have_css(".js-note-write-button", visible: true)
+ expect(page).to have_css('.js-md-write-button', visible: true)
end
end
- step 'I should see the comment preview' do
+ step 'The comment preview tab should be display rendered Markdown' do
within(".js-main-target-form") do
- page.should have_css(".js-note-preview", visible: true)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
- step 'I should see the comment preview button' do
+ step 'I should see the comment preview' do
within(".js-main-target-form") do
- page.should have_css(".js-note-preview-button", visible: true)
+ expect(page).to have_css('.js-md-preview', visible: true)
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 42e4442365d..2fea151aeb3 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -14,10 +14,14 @@ module API
expose :bio, :skype, :linkedin, :twitter, :website_url
end
+ class Identity < Grape::Entity
+ expose :provider, :extern_uid
+ end
+
class UserFull < User
expose :email
- expose :theme_id, :color_scheme_id, :extern_uid, :provider, \
- :projects_limit
+ expose :theme_id, :color_scheme_id, :projects_limit
+ expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index d07815a8a97..37b36ddcf94 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -59,10 +59,16 @@ module API
post do
authenticated_as_admin!
required_attributes! [:email, :password, :name, :username]
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.build_user(attrs)
admin = attrs.delete(:admin)
user.admin = admin unless admin.nil?
+
+ identity_attrs = attributes_for_keys [:provider, :extern_uid]
+ if identity_attrs.any?
+ user.identities.build(identity_attrs)
+ end
+
if user.save
present user, with: Entities::UserFull
else
@@ -89,8 +95,6 @@ module API
# twitter - Twitter account
# website_url - Website url
# projects_limit - Limit projects each user can create
- # extern_uid - External authentication provider UID
- # provider - External provider
# bio - Bio
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
@@ -99,7 +103,7 @@ module API
put ":id" do
authenticated_as_admin!
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.find(params[:id])
not_found!('User') unless user
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 8b4729896b5..875f8d8b3a3 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -49,8 +49,17 @@ module Gitlab
end
def push_access_check(user, project, changes)
- return build_status_object(false, "You don't have access") unless user && user_allowed?(user)
- return build_status_object(true) if changes.blank?
+ unless user && user_allowed?(user)
+ return build_status_object(false, "You don't have access")
+ end
+
+ if changes.blank?
+ return build_status_object(true)
+ end
+
+ unless project.repository.exists?
+ return build_status_object(false, "Repository does not exist")
+ end
changes = changes.lines if changes.kind_of?(String)
@@ -79,7 +88,7 @@ module Gitlab
else
:push_code_to_protected_branches
end
- elsif project.repository && project.repository.tag_names.include?(tag_name(ref))
+ elsif project.repository.tag_names.include?(tag_name(ref))
# Prevent any changes to existing git tag unless user has permissions
:admin_project
else
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index eb2c4e48ff2..0c85acf7e69 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -8,7 +8,7 @@ module Gitlab
attr_reader :adapter, :provider, :user
def self.open(user, &block)
- Gitlab::LDAP::Adapter.open(user.provider) do |adapter|
+ Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
block.call(self.new(user, adapter))
end
end
@@ -28,13 +28,13 @@ module Gitlab
def initialize(user, adapter=nil)
@adapter = adapter
@user = user
- @provider = user.provider
+ @provider = user.ldap_identity.provider
end
def allowed?
- if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
+ if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
return true unless ldap_config.active_directory
- !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter)
+ !Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
else
false
end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 3176e9790a7..3ef494ba137 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -12,9 +12,10 @@ module Gitlab
class << self
def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive
- ::User.
+ identity = ::Identity.
where(provider: [provider, :ldap]).
where('lower(extern_uid) = ?', uid.downcase).last
+ identity && identity.user
end
end
@@ -34,15 +35,13 @@ module Gitlab
end
def find_by_email
- model.find_by(email: auth_hash.email)
+ ::User.find_by(email: auth_hash.email)
end
def update_user_attributes
- gl_user.attributes = {
- extern_uid: auth_hash.uid,
- provider: auth_hash.provider,
- email: auth_hash.email
- }
+ gl_user.email = auth_hash.email
+ gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid)
+ gl_user
end
def changed?
diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb
index 47f62153a50..6861427864e 100644
--- a/lib/gitlab/oauth/user.rb
+++ b/lib/gitlab/oauth/user.rb
@@ -5,6 +5,8 @@
#
module Gitlab
module OAuth
+ class ForbiddenAction < StandardError; end
+
class User
attr_accessor :auth_hash, :gl_user
@@ -70,24 +72,24 @@ module Gitlab
end
def find_by_uid_and_provider
- model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
+ identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
+ identity && identity.user
end
def build_new_user
- model.new(user_attributes).tap do |user|
- user.skip_confirmation!
- end
+ user = ::User.new(user_attributes)
+ user.skip_confirmation!
+ user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider)
+ user
end
def user_attributes
{
- extern_uid: auth_hash.uid,
- provider: auth_hash.provider,
name: auth_hash.name,
username: auth_hash.username,
email: auth_hash.email,
password: auth_hash.password,
- password_confirmation: auth_hash.password,
+ password_confirmation: auth_hash.password
}
end
@@ -95,12 +97,8 @@ module Gitlab
Gitlab::AppLogger
end
- def model
- ::User
- end
-
- def raise_unauthorized_to_create
- raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
+ def unauthorized_to_create
+ raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 0fb09d3f228..0f2db50e98c 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -1,26 +1,41 @@
module Gitlab
module SidekiqMiddleware
class MemoryKiller
+ # Default the RSS limit to 0, meaning the MemoryKiller is disabled
+ MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
+ # Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
+ GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
# Wait 30 seconds for running jobs to finish during graceful shutdown
- GRACEFUL_SHUTDOWN_WAIT = 30
+ SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
+
+ # Create a mutex used to ensure there will be only one thread waiting to
+ # shut Sidekiq down
+ MUTEX = Mutex.new
def call(worker, job, queue)
yield
current_rss = get_rss
- return unless max_rss > 0 && current_rss > max_rss
-
- Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
- "#{max_rss}"
- Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
- # SIGUSR1 tells Sidekiq to stop accepting new jobs
- Process.kill('SIGUSR1', Process.pid)
-
- Sidekiq.logger.warn "spawning thread that will send SIGTERM to PID "\
- "#{Process.pid} in #{graceful_shutdown_wait} seconds"
- # Send the final shutdown signal to Sidekiq from a separate thread so
- # that the current job can finish
+
+ return unless MAX_RSS > 0 && current_rss > MAX_RSS
+
Thread.new do
- sleep(graceful_shutdown_wait)
+ # Return if another thread is already waiting to shut Sidekiq down
+ return unless MUTEX.try_lock
+
+ Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
+ "#{MAX_RSS}"
+ Sidekiq.logger.warn "spawned thread that will shut down PID "\
+ "#{Process.pid} in #{GRACE_TIME} seconds"
+ sleep(GRACE_TIME)
+
+ Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
+ Process.kill('SIGUSR1', Process.pid)
+
+ Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
+ "SIGTERM to PID #{Process.pid}"
+ sleep(SHUTDOWN_WAIT)
+
+ Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
Process.kill('SIGTERM', Process.pid)
end
end
@@ -33,16 +48,6 @@ module Gitlab
output.to_i
end
-
- def max_rss
- @max_rss ||= ENV['SIDEKIQ_MAX_RSS'].to_s.to_i
- end
-
- def graceful_shutdown_wait
- @graceful_shutdown_wait ||= (
- ENV['SIDEKIQ_GRACEFUL_SHUTDOWN_WAIT'] || GRACEFUL_SHUTDOWN_WAIT
- ).to_i
- end
end
end
end
diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
new file mode 100644
index 00000000000..f40bba24da8
--- /dev/null
+++ b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
@@ -0,0 +1,73 @@
+require "#{Rails.root}/app/helpers/emails_helper"
+require 'action_view/helpers'
+extend ActionView::Helpers
+
+include ActionView::Context
+include EmailsHelper
+
+namespace :gitlab do
+ desc "Email google whitelisting email with example email for actions in inbox"
+ task mail_google_schema_whitelisting: :environment do
+ subject = "Rails | Implemented feature"
+ url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}"
+ schema = email_action(url)
+ body = email_template(schema, url)
+ mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe)
+ if send_now
+ mail.deliver
+ else
+ puts "WOULD SEND:"
+ end
+ puts mail
+ end
+
+ def email_template(schema, url)
+ "<html lang='en'>
+ <head>
+ <meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
+ <title>
+ GitLab
+ </title>
+ </meta>
+ </head>
+ <style>
+ img {
+ max-width: 100%;
+ height: auto;
+ }
+ p.details {
+ font-style:italic;
+ color:#777
+ }
+ .footer p {
+ font-size:small;
+ color:#777
+ }
+ </style>
+ <body>
+ <div class='content'>
+ <div>
+ <p>I like it :+1: </p>
+ </div>
+ </div>
+
+ <div class='footer' style='margin-top: 10px;'>
+ <p>
+ <br>
+ You're receiving this notification because you are a member of the Base / Rails Project project team.
+ <a href=\"#{url}\">View it on GitLab</a>
+ #{schema}
+ </p>
+ </div>
+ </body>
+ </html>"
+ end
+
+ def send_now
+ if ENV['SEND'] == "true"
+ true
+ else
+ false
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 202e55c89ad..9af93300e08 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -17,15 +17,19 @@ namespace :gitlab do
# Clone if needed
unless File.directory?(target_dir)
- sh(*%W(git clone #{args.repo} #{target_dir}))
+ system(*%W(git clone -- #{args.repo} #{target_dir}))
end
# Make sure we're on the right tag
Dir.chdir(target_dir) do
# First try to checkout without fetching
# to avoid stalling tests if the Internet is down.
- reset = "git reset --hard $(git describe #{args.tag} || git describe origin/#{args.tag})"
- sh "#{reset} || git fetch origin && #{reset}"
+ reseted = reset_to_commit(args)
+
+ unless reseted
+ system(*%W(git fetch origin))
+ reset_to_commit(args)
+ end
config = {
user: user,
@@ -54,7 +58,7 @@ namespace :gitlab do
File.open("config.yml", "w+") {|f| f.puts config.to_yaml}
# Launch installation process
- sh "bin/install"
+ system(*%W(bin/install))
end
# Required for debian packaging with PKGR: Setup .ssh/environment with
@@ -118,5 +122,16 @@ namespace :gitlab do
puts "Quitting...".red
exit 1
end
+
+ def reset_to_commit(args)
+ tag, status = Gitlab::Popen.popen(%W(git describe -- #{args.tag}))
+
+ unless status.zero?
+ tag, status = Gitlab::Popen.popen(%W(git describe -- origin/#{args.tag}))
+ end
+
+ tag = tag.strip
+ system(*%W(git reset --hard #{tag}))
+ end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index 15899d8c3c4..50580cd1334 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -5,10 +5,14 @@ FactoryGirl.define do
Faker::Lorem.sentence
end
- sequence :name, aliases: [:file_name] do
+ sequence :name do
Faker::Name.name
end
+ sequence :file_name do
+ Faker::Internet.user_name
+ end
+
sequence(:url) { Faker::Internet.uri('http') }
factory :user, aliases: [:author, :assignee, :owner, :creator] do
@@ -24,9 +28,18 @@ FactoryGirl.define do
admin true
end
- trait :ldap do
- provider 'ldapmain'
- extern_uid 'my-ldap-id'
+ factory :omniauth_user do
+ ignore do
+ extern_uid '123456'
+ provider 'ldapmain'
+ end
+
+ after(:create) do |user, evaluator|
+ user.identities << create(:identity,
+ provider: evaluator.provider,
+ extern_uid: evaluator.extern_uid
+ )
+ end
end
factory :admin, traits: [:admin]
@@ -182,4 +195,9 @@ FactoryGirl.define do
deploy_key
project
end
+
+ factory :identity do
+ provider 'ldapmain'
+ extern_uid 'my-ldap-id'
+ end
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
new file mode 100644
index 00000000000..746b6fc1ac9
--- /dev/null
+++ b/spec/features/atom/users_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe "User Feed", feature: true do
+ describe "GET /" do
+ let!(:user) { create(:user) }
+
+ context "user atom feed via private token" do
+ it "should render user atom feed" do
+ visit user_path(user, :atom, private_token: user.private_token)
+ body.should have_selector("feed title")
+ end
+ end
+
+ context 'feed content' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project, author: user, description: '') }
+ let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) }
+
+ before do
+ project.team << [user, :master]
+ issue_event(issue, user)
+ note_event(note, user)
+ visit user_path(user, :atom, private_token: user.private_token)
+ end
+
+ it "should have issue opened event" do
+ body.should have_content("#{user.name} opened issue ##{issue.iid}")
+ end
+
+ it "should have issue comment event" do
+ body.should have_content("#{user.name} commented on issue ##{issue.iid}")
+ end
+ end
+ end
+
+ def issue_event(issue, user)
+ EventCreateService.new.open_issue(issue, user)
+ end
+
+ def note_event(note, user)
+ EventCreateService.new.leave_note(note, user)
+ end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 92f3a6c0929..cac409b9139 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -19,8 +19,9 @@ describe 'Comments' do
it 'should be valid' do
should have_css(".js-main-target-form", visible: true, count: 1)
find(".js-main-target-form input[type=submit]").value.should == "Add Comment"
- within(".js-main-target-form") { should_not have_link("Cancel") }
- within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) }
+ within('.js-main-target-form') do
+ expect(page).not_to have_link('Cancel')
+ end
end
describe "with text" do
@@ -31,8 +32,10 @@ describe 'Comments' do
end
it 'should have enable submit button and preview button' do
- within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") }
- within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) }
+ within('.js-main-target-form') do
+ expect(page).not_to have_css('.js-comment-button[disabled]')
+ expect(page).to have_css('.js-md-preview-button', visible: true)
+ end
end
end
end
@@ -41,15 +44,17 @@ describe 'Comments' do
before do
within(".js-main-target-form") do
fill_in "note[note]", with: "This is awsome!"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
click_button "Add Comment"
end
end
it 'should be added and form reset' do
should have_content("This is awsome!")
- within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") }
- within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) }
+ within('.js-main-target-form') do
+ expect(page).to have_no_field('note[note]', with: 'This is awesome!')
+ expect(page).to have_css('.js-md-preview', visible: :hidden)
+ end
within(".js-main-target-form") { should have_css(".js-note-text", visible: true) }
end
end
@@ -172,11 +177,11 @@ describe 'Comments' do
# add two separate texts and trigger previews on both
within("tr[id='#{line_code}'] + .js-temp-notes-holder") do
fill_in "note[note]", with: "One comment on line 7"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
fill_in "note[note]", with: "Another comment on line 10"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 7489e56f423..06e247aea61 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -5,9 +5,10 @@ describe IssuesFinder do
let(:user2) { create :user }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
- let(:issue1) { create(:issue, assignee: user, project: project1) }
- let(:issue2) { create(:issue, assignee: user, project: project2) }
- let(:issue3) { create(:issue, assignee: user2, project: project2) }
+ let(:milestone) { create(:milestone, project: project1) }
+ let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
+ let(:issue2) { create(:issue, author: user, assignee: user, project: project2) }
+ let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) }
before do
project1.team << [user, :master]
@@ -22,37 +23,59 @@ describe IssuesFinder do
issue3
end
- it 'should filter by all' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 3
- end
+ context 'scope: all' do
+ it 'should filter by all' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 3
+ end
- it 'should filter by assignee' do
- params = { scope: "assigned-to-me", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 2
- end
+ it 'should filter by assignee id' do
+ params = { scope: "all", assignee_id: user.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 2
+ end
- it 'should filter by project' do
- params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 1
- end
+ it 'should filter by author id' do
+ params = { scope: "all", author_id: user2.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.should == [issue3]
+ end
- it 'should be empty for unauthorized user' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(nil, params)
- issues.size.should be_zero
+ it 'should filter by milestone id' do
+ params = { scope: "all", milestone_id: milestone.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.should == [issue1]
+ end
+
+ it 'should be empty for unauthorized user' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(nil, params)
+ issues.size.should be_zero
+ end
+
+ it 'should not include unauthorized issues' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(user2, params)
+ issues.size.should == 2
+ issues.should_not include(issue1)
+ issues.should include(issue2)
+ issues.should include(issue3)
+ end
end
- it 'should not include unauthorized issues' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user2, params)
- issues.size.should == 2
- issues.should_not include(issue1)
- issues.should include(issue2)
- issues.should include(issue3)
+ context 'personal scope' do
+ it 'should filter by assignee' do
+ params = { scope: "assigned-to-me", state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 2
+ end
+
+ it 'should filter by project' do
+ params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 1
+ end
end
end
end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 2db67cfdf95..07dd33b211b 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -66,6 +66,16 @@ describe ApplicationHelper do
avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png")
end
+ it "should return an url for the avatar with relative url" do
+ Gitlab.config.gitlab.stub(relative_url_root: "/gitlab")
+ Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
+
+ user = create(:user)
+ user.avatar = File.open(avatar_file_path)
+ user.save!
+ avatar_icon(user.email).to_s.should match("/gitlab//uploads/user/avatar/#{ user.id }/gitlab_logo.png")
+ end
+
it "should call gravatar_icon when no avatar is present" do
user = create(:user, email: 'test@example.com')
user.save!
diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb
new file mode 100644
index 00000000000..453699136e9
--- /dev/null
+++ b/spec/helpers/oauth_helper_spec.rb
@@ -0,0 +1,20 @@
+require "spec_helper"
+
+describe OauthHelper do
+ describe "additional_providers" do
+ it 'returns all enabled providers' do
+ allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] }
+ helper.additional_providers.should include(*[:twitter, :github])
+ end
+
+ it 'does not return ldap provider' do
+ allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] }
+ helper.additional_providers.should include(:twitter)
+ end
+
+ it 'returns empty array' do
+ allow(helper).to receive(:enabled_oauth_providers) { [] }
+ helper.additional_providers.should == []
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index f4d5a927396..4573b8696c4 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Access do
let(:access) { Gitlab::LDAP::Access.new user }
- let(:user) { create(:user, :ldap) }
+ let(:user) { create(:omniauth_user) }
describe :allowed? do
subject { access.allowed? }
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
index 0eb7c443b8b..11fdf108756 100644
--- a/spec/lib/gitlab/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Authentication do
let(:klass) { Gitlab::LDAP::Authentication }
- let(:user) { create(:user, :ldap, extern_uid: dn) }
+ let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' }
let(:password) { 'password' }
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 726c9764e3d..f73884e6441 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -15,18 +15,18 @@ describe Gitlab::LDAP::User do
describe :find_or_create do
it "finds the user if already existing" do
- existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain')
+ existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
expect{ gl_user.save }.to_not change{ User.count }
end
it "connects to existing non-ldap user if the email matches" do
- existing_user = create(:user, email: 'john@example.com')
+ existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
expect{ gl_user.save }.to_not change{ User.count }
existing_user.reload
- expect(existing_user.extern_uid).to eql 'my-uid'
- expect(existing_user.provider).to eql 'ldapmain'
+ expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+ expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
end
it "creates a new user if not found" do
diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb
index 8a83a1b2588..88307515789 100644
--- a/spec/lib/gitlab/oauth/user_spec.rb
+++ b/spec/lib/gitlab/oauth/user_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do
end
describe :persisted? do
- let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') }
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
@@ -39,8 +39,9 @@ describe Gitlab::OAuth::User do
oauth_user.save
expect(gl_user).to be_valid
- expect(gl_user.extern_uid).to eql uid
- expect(gl_user.provider).to eql 'twitter'
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'twitter'
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6d865cfc691..8be7f733a5b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -62,6 +62,7 @@ describe User do
it { should have_many(:assigned_issues).dependent(:destroy) }
it { should have_many(:merge_requests).dependent(:destroy) }
it { should have_many(:assigned_merge_requests).dependent(:destroy) }
+ it { should have_many(:identities).dependent(:destroy) }
end
describe "Mass assignment" do
@@ -361,24 +362,29 @@ describe User do
end
describe :ldap_user? do
- let(:user) { build(:user, :ldap) }
-
it "is true if provider name starts with ldap" do
- user.provider = 'ldapmain'
+ user = create(:omniauth_user, provider: 'ldapmain')
expect( user.ldap_user? ).to be_true
end
it "is false for other providers" do
- user.provider = 'other-provider'
+ user = create(:omniauth_user, provider: 'other-provider')
expect( user.ldap_user? ).to be_false
end
it "is false if no extern_uid is provided" do
- user.extern_uid = nil
+ user = create(:omniauth_user, extern_uid: nil)
expect( user.ldap_user? ).to be_false
end
end
+ describe :ldap_identity do
+ it "returns ldap identity" do
+ user = create :omniauth_user
+ user.ldap_identity.provider.should_not be_empty
+ end
+ end
+
describe '#full_website_url' do
let(:user) { create(:user) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 113a39b870e..1ecc79ea7ef 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -33,7 +33,7 @@ describe API::API, api: true do
response.status.should == 200
json_response.should be_an Array
json_response.first.keys.should include 'email'
- json_response.first.keys.should include 'extern_uid'
+ json_response.first.keys.should include 'identities'
json_response.first.keys.should include 'can_create_project'
end
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index ea584c9802d..e6505040317 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -53,13 +53,14 @@ shared_examples "RESTful project resources" do
end
end
-# projects POST /projects(.:format) projects#create
-# new_project GET /projects/new(.:format) projects#new
-# files_project GET /:id/files(.:format) projects#files
-# edit_project GET /:id/edit(.:format) projects#edit
-# project GET /:id(.:format) projects#show
-# PUT /:id(.:format) projects#update
-# DELETE /:id(.:format) projects#destroy
+# projects POST /projects(.:format) projects#create
+# new_project GET /projects/new(.:format) projects#new
+# files_project GET /:id/files(.:format) projects#files
+# edit_project GET /:id/edit(.:format) projects#edit
+# project GET /:id(.:format) projects#show
+# PUT /:id(.:format) projects#update
+# DELETE /:id(.:format) projects#destroy
+# markdown_preview_project GET /:id/markdown_preview(.:format) projects#markdown_preview
describe ProjectsController, "routing" do
it "to #create" do
post("/projects").should route_to('projects#create')
@@ -88,6 +89,12 @@ describe ProjectsController, "routing" do
it "to #destroy" do
delete("/gitlab/gitlabhq").should route_to('projects#destroy', id: 'gitlab/gitlabhq')
end
+
+ it 'to #markdown_preview' do
+ get('/gitlab/gitlabhq/markdown_preview').should(
+ route_to('projects#markdown_preview', id: 'gitlab/gitlabhq')
+ )
+ end
end
# pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages
@@ -387,15 +394,10 @@ describe Projects::IssuesController, "routing" do
end
end
-# preview_project_notes POST /:project_id/notes/preview(.:format) notes#preview
# project_notes GET /:project_id/notes(.:format) notes#index
# POST /:project_id/notes(.:format) notes#create
# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
describe Projects::NotesController, "routing" do
- it "to #preview" do
- post("/gitlab/gitlabhq/notes/preview").should route_to('projects/notes#preview', project_id: 'gitlab/gitlabhq')
- end
-
it_behaves_like "RESTful project resources" do
let(:actions) { [:index, :create, :destroy] }
let(:controller) { 'notes' }
@@ -415,6 +417,7 @@ describe Projects::BlobController, "routing" do
it "to #show" do
get("/gitlab/gitlabhq/blob/master/app/models/project.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
get("/gitlab/gitlabhq/blob/master/app/models/compare.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb')
+ get("/gitlab/gitlabhq/blob/master/app/models/diff.js").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/diff.js')
get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
end
end
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
new file mode 100644
index 00000000000..45aaf0fc90b
--- /dev/null
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'rake'
+
+describe 'gitlab:mail_google_schema_whitelisting rake task' do
+ before :all do
+ Rake.application.rake_require "tasks/gitlab/task_helpers"
+ Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ describe 'call' do
+ before do
+ # avoid writing task output to spec progress
+ $stdout.stub :write
+ end
+
+ let :run_rake_task do
+ Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
+ Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
+ end
+
+ it 'should run the task without errors' do
+ expect { run_rake_task }.to_not raise_error
+ end
+ end
+end