summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/gl_form.js13
-rw-r--r--app/assets/javascripts/notes.js47
-rw-r--r--app/assets/stylesheets/pages/notes.scss6
-rw-r--r--app/services/issuable_base_service.rb1
-rw-r--r--app/views/shared/notes/_edit.html.haml2
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml2
-rw-r--r--changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml4
-rw-r--r--changelogs/unreleased/sh-fix-container-registry-s3-redirects.yml4
-rw-r--r--db/post_migrate/20170516181025_add_constraints_to_issue_assignees_table.rb8
-rw-r--r--doc/api/award_emoji.md2
-rw-r--r--doc/api/boards.md2
-rw-r--r--doc/api/branches.md2
-rw-r--r--doc/api/broadcast_messages.md2
-rw-r--r--doc/api/build_variables.md2
-rw-r--r--doc/api/ci/lint.md2
-rw-r--r--doc/api/ci/runners.md2
-rw-r--r--doc/api/deploy_key_multiple_projects.md2
-rw-r--r--doc/api/deploy_keys.md2
-rw-r--r--doc/api/milestones.md2
-rw-r--r--doc/api/namespaces.md2
-rw-r--r--doc/api/notes.md2
-rw-r--r--doc/api/pipeline_triggers.md2
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/api/repository_files.md2
-rw-r--r--doc/api/services.md2
-rw-r--r--doc/api/session.md2
-rw-r--r--doc/api/sidekiq_metrics.md2
-rw-r--r--doc/api/system_hooks.md2
-rw-r--r--doc/api/tags.md2
-rw-r--r--doc/api/templates/gitignores.md2
-rw-r--r--doc/api/templates/gitlab_ci_ymls.md2
-rw-r--r--doc/api/templates/licenses.md2
-rw-r--r--doc/api/v3_to_v4.md2
-rw-r--r--doc/ci/README.md3
-rw-r--r--doc/ci/api/README.md2
-rw-r--r--doc/ci/api/builds.md2
-rw-r--r--doc/ci/api/runners.md2
-rw-r--r--doc/ci/img/pipeline_schedules_list.pngbin67555 -> 0 bytes
-rw-r--r--doc/ci/pipeline_schedules.md44
-rw-r--r--doc/ci/triggers/README.md20
-rw-r--r--doc/development/what_requires_downtime.md2
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_list.pngbin0 -> 14665 bytes
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_new_form.png (renamed from doc/ci/img/pipeline_schedules_new_form.png)bin49873 -> 49873 bytes
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_ownership.pngbin0 -> 12043 bytes
-rw-r--r--doc/user/project/pipelines/schedules.md62
-rw-r--r--lib/container_registry/client.rb14
-rw-r--r--spec/features/issues/note_polling_spec.rb101
-rw-r--r--spec/javascripts/notes_spec.js60
-rw-r--r--spec/lib/container_registry/blob_spec.rb2
-rw-r--r--spec/lib/container_registry/client_spec.rb39
52 files changed, 330 insertions, 163 deletions
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 51822f21e66..dc9f114af99 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -7,9 +7,10 @@ import GfmAutoComplete from './gfm_auto_complete';
window.gl = window.gl || {};
-function GLForm(form) {
+function GLForm(form, enableGFM = false) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
+ this.enableGFM = enableGFM;
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
@@ -32,8 +33,14 @@ GLForm.prototype.setupForm = function() {
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
-
- new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'));
+ new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'), {
+ emojis: true,
+ members: this.enableGFM,
+ issues: this.enableGFM,
+ milestones: this.enableGFM,
+ mergeRequests: this.enableGFM,
+ labels: this.enableGFM,
+ });
new DropzoneInput(this.form);
autosize(this.textarea);
}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 91d1afba7b6..e960f1e8018 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -27,7 +27,7 @@ const normalizeNewlines = function(str) {
Notes.interval = null;
- function Notes(notes_url, note_ids, last_fetched_at, view) {
+ function Notes(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
this.visibilityChange = this.visibilityChange.bind(this);
@@ -50,6 +50,7 @@ const normalizeNewlines = function(str) {
this.notes_url = notes_url;
this.note_ids = note_ids;
+ this.enableGFM = enableGFM;
// Used to keep track of updated notes while people are editing things
this.updatedNotesTrackingMap = {};
this.last_fetched_at = last_fetched_at;
@@ -306,7 +307,7 @@ const normalizeNewlines = function(str) {
}
const $note = $notesList.find(`#note_${noteEntity.id}`);
- if (this.isNewNote(noteEntity)) {
+ if (Notes.isNewNote(noteEntity, this.note_ids)) {
this.note_ids.push(noteEntity.id);
const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
@@ -319,7 +320,7 @@ const normalizeNewlines = function(str) {
return this.updateNotesCount(1);
}
// The server can send the same update multiple times so we need to make sure to only update once per actual update.
- else if (this.isUpdatedNote(noteEntity, $note)) {
+ else if (Notes.isUpdatedNote(noteEntity, $note)) {
const isEditing = $note.hasClass('is-editing');
const initialContent = normalizeNewlines(
$note.find('.original-note-content').text().trim()
@@ -347,23 +348,6 @@ const normalizeNewlines = function(str) {
}
};
- /*
- Check if note does not exists on page
- */
-
- Notes.prototype.isNewNote = function(noteEntity) {
- return $.inArray(noteEntity.id, this.note_ids) === -1;
- };
-
- Notes.prototype.isUpdatedNote = function(noteEntity, $note) {
- // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
- const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
- const currentNoteText = normalizeNewlines(
- $note.find('.original-note-content').text().trim()
- );
- return sanitizedNoteNote !== currentNoteText;
- };
-
Notes.prototype.isParallelView = function() {
return Cookies.get('diff_view') === 'parallel';
};
@@ -376,7 +360,7 @@ const normalizeNewlines = function(str) {
Notes.prototype.renderDiscussionNote = function(noteEntity, $form) {
var discussionContainer, form, row, lineType, diffAvatarContainer;
- if (!this.isNewNote(noteEntity)) {
+ if (!Notes.isNewNote(noteEntity, this.note_ids)) {
return;
}
this.note_ids.push(noteEntity.id);
@@ -523,7 +507,7 @@ const normalizeNewlines = function(str) {
Notes.prototype.setupNoteForm = function(form) {
var textarea, key;
- new gl.GLForm(form);
+ new gl.GLForm(form, this.enableGFM);
textarea = form.find(".js-note-text");
key = [
"Note",
@@ -1137,6 +1121,25 @@ const normalizeNewlines = function(str) {
return $form;
};
+ /**
+ * Check if note does not exists on page
+ */
+ Notes.isNewNote = function(noteEntity, noteIds) {
+ return $.inArray(noteEntity.id, noteIds) === -1;
+ };
+
+ /**
+ * Check if $note already contains the `noteEntity` content
+ */
+ Notes.isUpdatedNote = function(noteEntity, $note) {
+ // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
+ const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
+ const currentNoteText = normalizeNewlines(
+ $note.find('.original-note-content').text().trim()
+ );
+ return sanitizedNoteEntityText !== currentNoteText;
+ };
+
Notes.checkMergeRequestStatus = function() {
if (gl.utils.getPagePath(1) === 'merge_requests') {
gl.mrWidget.checkStatus();
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 5b6aa9d74f6..4b15fc2bd82 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -669,7 +669,7 @@ ul.notes {
.line-resolve-btn {
position: relative;
- top: 2px;
+ top: 0;
padding: 0;
background-color: transparent;
border: none;
@@ -690,8 +690,8 @@ ul.notes {
svg {
fill: $gray-darkest;
- height: 15px;
- width: 15px;
+ height: 16px;
+ width: 16px;
}
.loading {
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 5ad4b2a9adf..e94ab3e64db 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -178,7 +178,6 @@ class IssuableBaseService < BaseService
after_create(issuable)
issuable.create_cross_references!(current_user)
execute_hooks(issuable)
- issuable.assignees.each(&:invalidate_cache_counts)
invalidate_cache_counts(issuable.assignees, issuable)
end
diff --git a/app/views/shared/notes/_edit.html.haml b/app/views/shared/notes/_edit.html.haml
index 4a020865828..f4b3aac29b4 100644
--- a/app/views/shared/notes/_edit.html.haml
+++ b/app/views/shared/notes/_edit.html.haml
@@ -1,3 +1 @@
-.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
- #{note.note}
%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: note_url(note) } }= note.note
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 87aae793966..53d0e837aa0 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -43,6 +43,8 @@
.note-text.md
= note.redacted_note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago')
+ .original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
+ #{note.note}
- if note_editable
= render 'shared/notes/edit', note: note
.note-awards
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index 9930cbd96d7..05bb1970e21 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -23,4 +23,4 @@
to post a comment
:javascript
- var notes = new Notes("#{notes_url}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
+ var notes = new Notes("#{notes_url}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}", false)
diff --git a/changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml b/changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml
new file mode 100644
index 00000000000..0fd248e0400
--- /dev/null
+++ b/changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml
@@ -0,0 +1,4 @@
+---
+title: Disable reference prefixes in notes for Snippets
+merge_request: 11278
+author:
diff --git a/changelogs/unreleased/sh-fix-container-registry-s3-redirects.yml b/changelogs/unreleased/sh-fix-container-registry-s3-redirects.yml
new file mode 100644
index 00000000000..1e783811b66
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-container-registry-s3-redirects.yml
@@ -0,0 +1,4 @@
+---
+title: Properly handle container registry redirects to fix metadata stored on a S3 backend
+merge_request:
+author:
diff --git a/db/post_migrate/20170516181025_add_constraints_to_issue_assignees_table.rb b/db/post_migrate/20170516181025_add_constraints_to_issue_assignees_table.rb
index 2aab1f4d14f..6fa573c5b49 100644
--- a/db/post_migrate/20170516181025_add_constraints_to_issue_assignees_table.rb
+++ b/db/post_migrate/20170516181025_add_constraints_to_issue_assignees_table.rb
@@ -26,12 +26,12 @@ class AddConstraintsToIssueAssigneesTable < ActiveRecord::Migration
# disable_ddl_transaction!
def up
- change_column :issue_assignees, :issue_id, :integer, null: false
- change_column :issue_assignees, :user_id, :integer, null: false
+ change_column_null :issue_assignees, :issue_id, false
+ change_column_null :issue_assignees, :user_id, false
end
def down
- change_column :issue_assignees, :issue_id, :integer
- change_column :issue_assignees, :user_id, :integer
+ change_column_null :issue_assignees, :issue_id, true
+ change_column_null :issue_assignees, :user_id, true
end
end
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 5f3adcc397a..d6924741ee4 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -1,4 +1,4 @@
-# Award Emoji
+# Award Emoji API
> [Introduced][ce-4575] in GitLab 8.9, Snippet support in 8.12
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 17d2be0ee16..69c47abc806 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -1,4 +1,4 @@
-# Boards
+# Issue Boards API
Every API call to boards must be authenticated.
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 5717215deb6..325d0ea4ce3 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -1,4 +1,4 @@
-# Branches
+# Branches API
## List repository branches
diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md
index ad254e3515e..a8a248a17f4 100644
--- a/doc/api/broadcast_messages.md
+++ b/doc/api/broadcast_messages.md
@@ -1,4 +1,4 @@
-# Broadcast Messages
+# Broadcast Messages API
> **Note:** This feature was introduced in GitLab 8.12.
diff --git a/doc/api/build_variables.md b/doc/api/build_variables.md
index 9218902e84a..2aaf1c93705 100644
--- a/doc/api/build_variables.md
+++ b/doc/api/build_variables.md
@@ -1,4 +1,4 @@
-# Build Variables
+# Build Variables API
## List project variables
diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md
index 74def207816..6a4dca92cfe 100644
--- a/doc/api/ci/lint.md
+++ b/doc/api/ci/lint.md
@@ -1,4 +1,4 @@
-# Validate the .gitlab-ci.yml
+# Validate the .gitlab-ci.yml (API)
> [Introduced][ce-5953] in GitLab 8.12.
diff --git a/doc/api/ci/runners.md b/doc/api/ci/runners.md
index 16028d1f124..342c039dad8 100644
--- a/doc/api/ci/runners.md
+++ b/doc/api/ci/runners.md
@@ -1,4 +1,4 @@
-# Runners API
+# Register and Delete Runners API
API used by Runners to register and delete themselves.
diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md
index f94dbfa4059..127f9a196de 100644
--- a/doc/api/deploy_key_multiple_projects.md
+++ b/doc/api/deploy_key_multiple_projects.md
@@ -1,4 +1,4 @@
-# Adding deploy keys to multiple projects
+# Adding deploy keys to multiple projects via API
If you want to easily add the same deploy key to multiple projects in the same
group, this can be achieved quite easily with the API.
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index c3fe7f84ef2..4fa800ecb9c 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -1,4 +1,4 @@
-# Deploy Keys
+# Deploy Keys API
## List all deploy keys
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 7640eeb8d00..a082d548499 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -1,4 +1,4 @@
-# Milestones
+# Milestones API
## List project milestones
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index eef06d5f324..4ad6071a0ed 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -1,4 +1,4 @@
-# Namespaces
+# Namespaces API
Usernames and groupnames fall under a special category called namespaces.
diff --git a/doc/api/notes.md b/doc/api/notes.md
index b71fea5fc9f..388e6989df2 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -1,4 +1,4 @@
-# Notes
+# Notes API
Notes are comments on snippets, issues or merge requests.
diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md
index d639e8a0991..9030ae32d17 100644
--- a/doc/api/pipeline_triggers.md
+++ b/doc/api/pipeline_triggers.md
@@ -1,4 +1,4 @@
-# Pipeline triggers
+# Pipeline triggers API
You can read more about [triggering pipelines through the API](../ci/triggers/README.md).
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 673cf02705d..6b919f71792 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1,6 +1,5 @@
# Projects API
-
### Project visibility level
Project in GitLab has be either private, internal or public.
@@ -17,8 +16,6 @@ Constants for project visibility levels are next:
* `public`:
The project can be cloned without any authentication.
-
-
## List projects
Get a list of visible projects for authenticated user. When being accessed without authentication, all public projects are returned.
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 859cbd63831..bccef924375 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -1,4 +1,4 @@
-# Repositories
+# Repositories API
## List repository tree
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index aec91abd390..0b5782a8cc4 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -1,4 +1,4 @@
-# Repository files
+# Repository files API
**CRUD for repository files**
diff --git a/doc/api/services.md b/doc/api/services.md
index f77d15c2ea1..49b87a4228c 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -1,4 +1,4 @@
-# Services
+# Services API
## Asana
diff --git a/doc/api/session.md b/doc/api/session.md
index 056cc32597c..7dd504b67c5 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -1,4 +1,4 @@
-# Session
+# Session API
## Deprecation Notice
diff --git a/doc/api/sidekiq_metrics.md b/doc/api/sidekiq_metrics.md
index ea10a26bcd0..b9500916cf2 100644
--- a/doc/api/sidekiq_metrics.md
+++ b/doc/api/sidekiq_metrics.md
@@ -1,4 +1,4 @@
-# Sidekiq Metrics
+# Sidekiq Metrics API
>**Note:** This endpoint is only available on GitLab 8.9 and above.
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index bad380794c1..9750475f0a6 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -1,4 +1,4 @@
-# System hooks
+# System hooks API
All methods require administrator authorization.
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 0f6c4e6794e..54f092d1d30 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -1,4 +1,4 @@
-# Tags
+# Tags API
## List project repository tags
diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md
index 3f2f4ed54e0..d3f5c88ca90 100644
--- a/doc/api/templates/gitignores.md
+++ b/doc/api/templates/gitignores.md
@@ -1,4 +1,4 @@
-# Gitignores
+# Gitignores API
## List gitignore templates
diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md
index 27e8973da58..bdb128fc336 100644
--- a/doc/api/templates/gitlab_ci_ymls.md
+++ b/doc/api/templates/gitlab_ci_ymls.md
@@ -1,4 +1,4 @@
-# GitLab CI YMLs
+# GitLab CI YMLs API
## List GitLab CI YML templates
diff --git a/doc/api/templates/licenses.md b/doc/api/templates/licenses.md
index 33018f0c53f..8d1006e08c5 100644
--- a/doc/api/templates/licenses.md
+++ b/doc/api/templates/licenses.md
@@ -1,4 +1,4 @@
-# Licenses
+# Licenses API
## List license templates
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 8e002fe0022..15a5b7a5290 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -1,4 +1,4 @@
-# V3 to V4 version
+# API V3 to API V4
Since GitLab 9.0, API V4 is the preferred version to be used.
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 86de0ee9043..4f17e7062ab 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -66,7 +66,8 @@ learn how to leverage its potential even more.
submodules are involved
- [Auto deploy](autodeploy/index.md)
- [Use SSH keys in your build environment](ssh_keys/README.md)
-- [Trigger jobs through the GitLab API](triggers/README.md)
+- [Trigger pipelines through the GitLab API](triggers/README.md)
+- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md)
## Review Apps
diff --git a/doc/ci/api/README.md b/doc/ci/api/README.md
index 4ca8d92d7cc..98f37935427 100644
--- a/doc/ci/api/README.md
+++ b/doc/ci/api/README.md
@@ -1,3 +1 @@
-# GitLab CI API
-
This document was moved to a [new location](../../api/ci/README.md).
diff --git a/doc/ci/api/builds.md b/doc/ci/api/builds.md
index f5bd3181c02..0563a367609 100644
--- a/doc/ci/api/builds.md
+++ b/doc/ci/api/builds.md
@@ -1,3 +1 @@
-# Builds API
-
This document was moved to a [new location](../../api/ci/builds.md).
diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md
index b14ea99db76..1027363851c 100644
--- a/doc/ci/api/runners.md
+++ b/doc/ci/api/runners.md
@@ -1,3 +1 @@
-# Runners API
-
This document was moved to a [new location](../../api/ci/runners.md).
diff --git a/doc/ci/img/pipeline_schedules_list.png b/doc/ci/img/pipeline_schedules_list.png
deleted file mode 100644
index 9388fac98eb..00000000000
--- a/doc/ci/img/pipeline_schedules_list.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/pipeline_schedules.md b/doc/ci/pipeline_schedules.md
deleted file mode 100644
index 73451da6c0c..00000000000
--- a/doc/ci/pipeline_schedules.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Pipeline Schedules
-
-> **Note**:
-- This feature was introduced in 9.1 as [Trigger Schedule][ce-105533]
-- In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853]
-
-Pipeline schedules can be used to run pipelines only once, or for example every
-month on the 22nd for a certain branch.
-
-## Using Pipeline Schedules
-
-In order to schedule pipelines, navigate to your their pages **Pipelines ➔ Schedules**
-and click the **New Schedule** button.
-
-![New Schedule Form](img/pipeline_schedules_new_form.png)
-
-After entering the form, hit **Save Schedule** for the changes to have effect.
-You can check a next execution date of the scheduled trigger, which is automatically calculated by a server.
-
-## Taking ownership
-
-![Schedules list](img/pipeline_schedules_list.png)
-
-Pipelines are executed as a user, which owns a schedule. This influences what
-projects and other resources the pipeline has access to. If a user does not own
-a pipeline, you can take ownership by clicking the **Take ownership** button.
-The next time a pipeline is scheduled, your credentials will be used.
-
-> **Notes**:
-- Those pipelines won't be executed precicely. Because schedules are handled by
-Sidekiq, which runs according to its interval. For exmaple, if you set a schedule to
-create a pipeline every minute (`* * * * *`) and the Sidekiq worker performs 00:00
-and 12:00 o'clock every day (`0 */12 * * *`), only 2 pipelines will be created per day.
-To change the Sidekiq worker's frequency, you have to edit the `trigger_schedule_worker_cron`
-value in your `gitlab.rb` and restart GitLab. The Sidekiq worker's configuration
-on GiLab.com is able to be looked up at [here](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example#L185).
-- Cron notation is parsed by [Rufus-Scheduler](https://github.com/jmettraux/rufus-scheduler).
-- When the owner of the schedule does not have the ability to create pipelines
-anymore, due to e.g. being blocked or removed from the project, the schedule is
-deactivated. Another user can take ownership and activate it, so the schedule is
-run again.
-
-[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533
-[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 1251313cd14..27cdaa9978b 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -1,6 +1,6 @@
-# Triggering jobs through the API
+# Triggering pipelines through the API
-> **Note**:
+> **Notes**:
- [Introduced][ci-229] in GitLab CE 7.14.
- GitLab 8.12 has a completely redesigned job permissions system. Read all
about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers).
@@ -208,7 +208,7 @@ curl --request POST \
https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
```
-### Using webhook to trigger job
+### Using a webhook to trigger a pipeline
You can add the following webhook to another project in order to trigger a job:
@@ -216,4 +216,18 @@ You can add the following webhook to another project in order to trigger a job:
https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/pipeline?token=TOKEN&variables[UPLOAD_TO_S3]=true
```
+### Using cron to trigger nightly pipelines
+
+>**Note:**
+The following behavior can also be achieved through GitLab's UI with
+[pipeline schedules](../../user/project/pipelines/schedules.md).
+
+Whether you craft a script or just run cURL directly, you can trigger jobs
+in conjunction with cron. The example below triggers a job on the `master`
+branch of project with ID `9` every night at `00:30`:
+
+```bash
+30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
+```
+
[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index 8da6ad684f5..c4830322fa8 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -139,6 +139,8 @@ Adding or removing a NOT NULL clause (or another constraint) can typically be
done without requiring downtime. However, this does require that any application
changes are deployed _first_. Thus, changing the constraints of a column should
happen in a post-deployment migration.
+NOTE: Avoid using `change_column` as it produces inefficient query because it re-defines
+the whole column type. For example, to add a NOT NULL constraint, prefer `change_column_null `
## Changing Column Types
diff --git a/doc/user/project/pipelines/img/pipeline_schedules_list.png b/doc/user/project/pipelines/img/pipeline_schedules_list.png
new file mode 100644
index 00000000000..50d9d184b05
--- /dev/null
+++ b/doc/user/project/pipelines/img/pipeline_schedules_list.png
Binary files differ
diff --git a/doc/ci/img/pipeline_schedules_new_form.png b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
index ea5394fa8a6..ea5394fa8a6 100644
--- a/doc/ci/img/pipeline_schedules_new_form.png
+++ b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipeline_schedules_ownership.png b/doc/user/project/pipelines/img/pipeline_schedules_ownership.png
new file mode 100644
index 00000000000..31ed83abb4d
--- /dev/null
+++ b/doc/user/project/pipelines/img/pipeline_schedules_ownership.png
Binary files differ
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
new file mode 100644
index 00000000000..641876f948f
--- /dev/null
+++ b/doc/user/project/pipelines/schedules.md
@@ -0,0 +1,62 @@
+# Pipeline Schedules
+
+> **Notes**:
+- This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
+- In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853].
+- Cron notation is parsed by [Rufus-Scheduler](https://github.com/jmettraux/rufus-scheduler).
+
+Pipeline schedules can be used to run pipelines only once, or for example every
+month on the 22nd for a certain branch.
+
+## Using Pipeline schedules
+
+In order to schedule a pipeline:
+
+1. Navigate to your project's **Pipelines ➔ Schedules** and click the
+ **New Schedule** button.
+1. Fill in the form
+1. Hit **Save pipeline schedule** for the changes to take effect.
+
+![New Schedule Form](img/pipeline_schedules_new_form.png)
+
+>**Attention:**
+The pipelines won't be executed precisely, because schedules are handled by
+Sidekiq, which runs according to its interval.
+See [advanced admin configuration](#advanced-admin-configuration) for more
+information.
+
+In the **Schedules** index page you can see a list of the pipelines that are
+scheduled to run. The next run is automatically calculated by the server GitLab
+is installed on.
+
+![Schedules list](img/pipeline_schedules_list.png)
+
+## Taking ownership
+
+Pipelines are executed as a user, who owns a schedule. This influences what
+projects and other resources the pipeline has access to. If a user does not own
+a pipeline, you can take ownership by clicking the **Take ownership** button.
+The next time a pipeline is scheduled, your credentials will be used.
+
+![Schedules list](img/pipeline_schedules_ownership.png)
+
+>**Note:**
+When the owner of the schedule doesn't have the ability to create pipelines
+anymore, due to e.g., being blocked or removed from the project, the schedule
+is deactivated. Another user can take ownership and activate it, so the
+schedule can be run again.
+
+## Advanced admin configuration
+
+The pipelines won't be executed precisely, because schedules are handled by
+Sidekiq, which runs according to its interval. For example, if you set a
+schedule to create a pipeline every minute (`* * * * *`) and the Sidekiq worker
+runs on 00:00 and 12:00 every day (`0 */12 * * *`), only 2 pipelines will be
+created per day. To change the Sidekiq worker's frequency, you have to edit the
+`trigger_schedule_worker_cron` value in your `gitlab.rb` and restart GitLab.
+For GitLab.com, you can check the [dedicated settings page][settings]. If you
+don't have admin access to the server, ask your administrator.
+
+[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533
+[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853
+[settings]: https://about.gitlab.com/gitlab-com/settings/#cron-jobs
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index 7f5f6d9ddb6..c7263f302ab 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -75,10 +75,7 @@ module ContainerRegistry
def redirect_response(location)
return unless location
- # We explicitly remove authorization token
- faraday_blob.get(location) do |req|
- req['Authorization'] = ''
- end
+ faraday_redirect.get(location)
end
def faraday
@@ -93,5 +90,14 @@ module ContainerRegistry
initialize_connection(conn, @options)
end
end
+
+ # Create a new request to make sure the Authorization header is not inserted
+ # via the Faraday middleware
+ def faraday_redirect
+ @faraday_redirect ||= Faraday.new(@base_uri) do |conn|
+ conn.request :json
+ conn.adapter :net_http
+ end
+ end
end
end
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 58b3215f14c..da81fa4e367 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -18,58 +18,93 @@ feature 'Issue notes polling', :feature, :js do
end
describe 'updates' do
- let(:user) { create(:user) }
- let(:note_text) { "Hello World" }
- let(:updated_text) { "Bye World" }
- let!(:existing_note) { create(:note, noteable: issue, project: project, author: user, note: note_text) }
+ context 'when from own user' do
+ let(:user) { create(:user) }
+ let(:note_text) { "Hello World" }
+ let(:updated_text) { "Bye World" }
+ let!(:existing_note) { create(:note, noteable: issue, project: project, author: user, note: note_text) }
- before do
- login_as(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
- end
+ before do
+ login_as(user)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
- it 'displays the updated content' do
- expect(page).to have_selector("#note_#{existing_note.id}", text: note_text)
+ it 'has .original-note-content to compare against' do
+ expect(page).to have_selector("#note_#{existing_note.id}", text: note_text)
+ expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
- update_note(existing_note, updated_text)
+ update_note(existing_note, updated_text)
- expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
- end
+ expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
+ expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
+ end
- it 'when editing but have not changed anything, and an update comes in, show the updated content in the textarea' do
- find("#note_#{existing_note.id} .js-note-edit").click
+ it 'displays the updated content' do
+ expect(page).to have_selector("#note_#{existing_note.id}", text: note_text)
- expect(page).to have_field("note[note]", with: note_text)
+ update_note(existing_note, updated_text)
- update_note(existing_note, updated_text)
+ expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
+ end
- expect(page).to have_field("note[note]", with: updated_text)
- end
+ it 'when editing but have not changed anything, and an update comes in, show the updated content in the textarea' do
+ find("#note_#{existing_note.id} .js-note-edit").click
- it 'when editing but you changed some things, and an update comes in, show a warning' do
- find("#note_#{existing_note.id} .js-note-edit").click
+ expect(page).to have_field("note[note]", with: note_text)
- expect(page).to have_field("note[note]", with: note_text)
+ update_note(existing_note, updated_text)
- find("#note_#{existing_note.id} .js-note-text").set('something random')
+ expect(page).to have_field("note[note]", with: updated_text)
+ end
- update_note(existing_note, updated_text)
+ it 'when editing but you changed some things, and an update comes in, show a warning' do
+ find("#note_#{existing_note.id} .js-note-edit").click
- expect(page).to have_selector(".alert")
- end
+ expect(page).to have_field("note[note]", with: note_text)
+
+ find("#note_#{existing_note.id} .js-note-text").set('something random')
+
+ update_note(existing_note, updated_text)
- it 'when editing but you changed some things, an update comes in, and you press cancel, show the updated content' do
- find("#note_#{existing_note.id} .js-note-edit").click
+ expect(page).to have_selector(".alert")
+ end
+
+ it 'when editing but you changed some things, an update comes in, and you press cancel, show the updated content' do
+ find("#note_#{existing_note.id} .js-note-edit").click
+
+ expect(page).to have_field("note[note]", with: note_text)
+
+ find("#note_#{existing_note.id} .js-note-text").set('something random')
+
+ update_note(existing_note, updated_text)
+
+ find("#note_#{existing_note.id} .note-edit-cancel").click
+
+ expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
+ end
+ end
- expect(page).to have_field("note[note]", with: note_text)
+ context 'when from another user' do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:note_text) { "Hello World" }
+ let(:updated_text) { "Bye World" }
+ let!(:existing_note) { create(:note, noteable: issue, project: project, author: user1, note: note_text) }
- find("#note_#{existing_note.id} .js-note-text").set('something random')
+ before do
+ login_as(user2)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
- update_note(existing_note, updated_text)
+ it 'has .original-note-content to compare against' do
+ expect(page).to have_selector("#note_#{existing_note.id}", text: note_text)
+ expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
- find("#note_#{existing_note.id} .note-edit-cancel").click
+ update_note(existing_note, updated_text)
- expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
+ expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
+ expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
+ end
end
end
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 87745ea9817..7f12dea5277 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -99,8 +99,6 @@ import '~/notes';
notes = jasmine.createSpyObj('notes', [
'refresh',
- 'isNewNote',
- 'isUpdatedNote',
'collapseLongCommitList',
'updateNotesCount',
'putConflictEditWarningInPlace'
@@ -110,13 +108,15 @@ import '~/notes';
notes.updatedNotesTrackingMap = {};
spyOn(gl.utils, 'localTimeAgo');
+ spyOn(Notes, 'isNewNote').and.callThrough();
+ spyOn(Notes, 'isUpdatedNote').and.callThrough();
spyOn(Notes, 'animateAppendNote').and.callThrough();
spyOn(Notes, 'animateUpdateNote').and.callThrough();
});
describe('when adding note', () => {
it('should call .animateAppendNote', () => {
- notes.isNewNote.and.returnValue(true);
+ Notes.isNewNote.and.returnValue(true);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
@@ -125,7 +125,8 @@ import '~/notes';
describe('when note was edited', () => {
it('should call .animateUpdateNote', () => {
- notes.isUpdatedNote.and.returnValue(true);
+ Notes.isNewNote.and.returnValue(false);
+ Notes.isUpdatedNote.and.returnValue(true);
const $note = $('<div>');
$notesList.find.and.returnValue($note);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
@@ -135,7 +136,8 @@ import '~/notes';
describe('while editing', () => {
it('should update textarea if nothing has been touched', () => {
- notes.isUpdatedNote.and.returnValue(true);
+ Notes.isNewNote.and.returnValue(false);
+ Notes.isUpdatedNote.and.returnValue(true);
const $note = $(`<div class="is-editing">
<div class="original-note-content">initial</div>
<textarea class="js-note-text">initial</textarea>
@@ -147,7 +149,8 @@ import '~/notes';
});
it('should call .putConflictEditWarningInPlace', () => {
- notes.isUpdatedNote.and.returnValue(true);
+ Notes.isNewNote.and.returnValue(false);
+ Notes.isUpdatedNote.and.returnValue(true);
const $note = $(`<div class="is-editing">
<div class="original-note-content">initial</div>
<textarea class="js-note-text">different</textarea>
@@ -161,6 +164,47 @@ import '~/notes';
});
});
+ describe('isUpdatedNote', () => {
+ it('should consider same note text as the same', () => {
+ const result = Notes.isUpdatedNote(
+ {
+ note: 'initial'
+ },
+ $(`<div>
+ <div class="original-note-content">initial</div>
+ </div>`)
+ );
+
+ expect(result).toEqual(false);
+ });
+
+ it('should consider same note with trailing newline as the same', () => {
+ const result = Notes.isUpdatedNote(
+ {
+ note: 'initial\n'
+ },
+ $(`<div>
+ <div class="original-note-content">initial\n</div>
+ </div>`)
+ );
+
+ expect(result).toEqual(false);
+ });
+
+ it('should consider different notes as different', () => {
+ const result = Notes.isUpdatedNote(
+ {
+ note: 'foo'
+ },
+ $(`<div>
+ <div class="original-note-content">bar</div>
+ </div>`)
+ );
+
+ expect(result).toEqual(true);
+ });
+ });
+
describe('renderDiscussionNote', () => {
let discussionContainer;
let note;
@@ -180,15 +224,15 @@ import '~/notes';
row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']);
notes = jasmine.createSpyObj('notes', [
- 'isNewNote',
'isParallelView',
'updateNotesCount',
]);
notes.note_ids = [];
spyOn(gl.utils, 'localTimeAgo');
+ spyOn(Notes, 'isNewNote');
spyOn(Notes, 'animateAppendNote');
- notes.isNewNote.and.returnValue(true);
+ Notes.isNewNote.and.returnValue(true);
notes.isParallelView.and.returnValue(false);
row.prevAll.and.returnValue(row);
row.first.and.returnValue(row);
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index f06e5fd54a2..ab010c6dfeb 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -98,7 +98,7 @@ describe ContainerRegistry::Blob do
context 'for a valid address' do
before do
stub_request(:get, location).
- with(headers: { 'Authorization' => nil }).
+ with { |request| !request.headers.include?('Authorization') }.
to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
new file mode 100644
index 00000000000..ec03b533383
--- /dev/null
+++ b/spec/lib/container_registry/client_spec.rb
@@ -0,0 +1,39 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe ContainerRegistry::Client do
+ let(:token) { '12345' }
+ let(:options) { { token: token } }
+ let(:client) { described_class.new("http://container-registry", options) }
+
+ describe '#blob' do
+ it 'GET /v2/:name/blobs/:digest' do
+ stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345").
+ with(headers: {
+ 'Accept' => 'application/octet-stream',
+ 'Authorization' => "bearer #{token}"
+ }).
+ to_return(status: 200, body: "Blob")
+
+ expect(client.blob('group/test', 'sha256:0123456789012345')).to eq('Blob')
+ end
+
+ it 'follows 307 redirect for GET /v2/:name/blobs/:digest' do
+ stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345").
+ with(headers: {
+ 'Accept' => 'application/octet-stream',
+ 'Authorization' => "bearer #{token}"
+ }).
+ to_return(status: 307, body: "", headers: { Location: 'http://redirected' })
+ # We should probably use hash_excluding here, but that requires an update to WebMock:
+ # https://github.com/bblimke/webmock/blob/master/lib/webmock/matchers/hash_excluding_matcher.rb
+ stub_request(:get, "http://redirected/").
+ with { |request| !request.headers.include?('Authorization') }.
+ to_return(status: 200, body: "Successfully redirected")
+
+ response = client.blob('group/test', 'sha256:0123456789012345')
+
+ expect(response).to eq('Successfully redirected')
+ end
+ end
+end