summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml226
-rw-r--r--app/assets/javascripts/gl_form.js13
-rw-r--r--app/assets/javascripts/merge_request_tabs.js6
-rw-r--r--app/assets/javascripts/notes.js70
-rw-r--r--app/assets/javascripts/users_select.js9
-rw-r--r--app/assets/stylesheets/pages/notes.scss6
-rw-r--r--app/models/blob.rb1
-rw-r--r--app/models/blob_viewer/auxiliary.rb6
-rw-r--r--app/models/blob_viewer/readme.rb14
-rw-r--r--app/models/repository.rb16
-rw-r--r--app/services/issuable_base_service.rb1
-rw-r--r--app/services/members/authorized_destroy_service.rb2
-rw-r--r--app/views/projects/blob/_auxiliary_viewer.html.haml5
-rw-r--r--app/views/projects/blob/_blob.html.haml5
-rw-r--r--app/views/projects/blob/viewers/_readme.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml15
-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--app/views/users/show.html.haml4
-rw-r--r--changelogs/unreleased/32086-atwho-is-still-enabled-for-personal-snippet-comments-form.yml4
-rw-r--r--changelogs/unreleased/gitaly-local-branches.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.md4
-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/examples/README.md1
-rw-r--r--doc/ci/examples/code_climate.md28
-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--lib/gitlab/git/branch.rb34
-rw-r--r--lib/gitlab/git/commit.rb9
-rw-r--r--lib/gitlab/git/repository.rb37
-rw-r--r--lib/gitlab/gitaly_client/ref.rb16
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--scripts/prepare_build.sh30
-rw-r--r--spec/factories/group_members.rb6
-rw-r--r--spec/factories/project_members.rb6
-rw-r--r--spec/factories/projects.rb12
-rw-r--r--spec/features/boards/sidebar_spec.rb1
-rw-r--r--spec/features/issues/form_spec.rb28
-rw-r--r--spec/features/issues/note_polling_spec.rb101
-rw-r--r--spec/features/issues_spec.rb4
-rw-r--r--spec/features/merge_requests/diffs_spec.rb28
-rw-r--r--spec/features/protected_branches_spec.rb1
-rw-r--r--spec/features/protected_tags_spec.rb1
-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
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb45
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb29
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_spec.rb30
-rw-r--r--spec/services/members/authorized_destroy_service_spec.rb21
-rw-r--r--spec/support/matchers/gitaly_matchers.rb6
-rw-r--r--spec/support/protected_branches/access_control_ce_shared_examples.rb (renamed from spec/features/protected_branches/access_control_ce_spec.rb)0
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb (renamed from spec/features/protected_tags/access_control_ce_spec.rb)0
-rw-r--r--spec/support/test_env.rb61
92 files changed, 867 insertions, 404 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1322843b592..45f1638f871 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -84,7 +84,7 @@ stages:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
- export CI_NODE_TOTAL=${JOB_NAME[-1]}
- - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_${JOB_NAME[1]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
@@ -115,7 +115,7 @@ stages:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
- export CI_NODE_TOTAL=${JOB_NAME[-1]}
- - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_${JOB_NAME[1]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
@@ -139,6 +139,13 @@ stages:
<<: *only-master-and-ee-or-mysql
<<: *except-docs
+.only-canonical-masters: &only-canonical-masters
+ only:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - master@gitlab/gitlabhq
+ - master@gitlab/gitlab-ee
+
# Trigger a package build on omnibus-gitlab repository
build-package:
@@ -168,17 +175,13 @@ knapsack:
update-knapsack:
<<: *knapsack-state
<<: *dedicated-runner
+ <<: *only-canonical-masters
stage: post-test
script:
- - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_pg_node_*.json
- - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach_pg_node_*.json
+ - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
+ - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
- '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - master@gitlab/gitlabhq
- - master@gitlab/gitlab-ee
setup-test-env:
<<: *use-pg
@@ -197,76 +200,75 @@ setup-test-env:
- public/assets
- tmp/tests
-rspec pg 0 20: *rspec-knapsack-pg
-rspec pg 1 20: *rspec-knapsack-pg
-rspec pg 2 20: *rspec-knapsack-pg
-rspec pg 3 20: *rspec-knapsack-pg
-rspec pg 4 20: *rspec-knapsack-pg
-rspec pg 5 20: *rspec-knapsack-pg
-rspec pg 6 20: *rspec-knapsack-pg
-rspec pg 7 20: *rspec-knapsack-pg
-rspec pg 8 20: *rspec-knapsack-pg
-rspec pg 9 20: *rspec-knapsack-pg
-rspec pg 10 20: *rspec-knapsack-pg
-rspec pg 11 20: *rspec-knapsack-pg
-rspec pg 12 20: *rspec-knapsack-pg
-rspec pg 13 20: *rspec-knapsack-pg
-rspec pg 14 20: *rspec-knapsack-pg
-rspec pg 15 20: *rspec-knapsack-pg
-rspec pg 16 20: *rspec-knapsack-pg
-rspec pg 17 20: *rspec-knapsack-pg
-rspec pg 18 20: *rspec-knapsack-pg
-rspec pg 19 20: *rspec-knapsack-pg
-
-rspec mysql 0 20: *rspec-knapsack-mysql
-rspec mysql 1 20: *rspec-knapsack-mysql
-rspec mysql 2 20: *rspec-knapsack-mysql
-rspec mysql 3 20: *rspec-knapsack-mysql
-rspec mysql 4 20: *rspec-knapsack-mysql
-rspec mysql 5 20: *rspec-knapsack-mysql
-rspec mysql 6 20: *rspec-knapsack-mysql
-rspec mysql 7 20: *rspec-knapsack-mysql
-rspec mysql 8 20: *rspec-knapsack-mysql
-rspec mysql 9 20: *rspec-knapsack-mysql
-rspec mysql 10 20: *rspec-knapsack-mysql
-rspec mysql 11 20: *rspec-knapsack-mysql
-rspec mysql 12 20: *rspec-knapsack-mysql
-rspec mysql 13 20: *rspec-knapsack-mysql
-rspec mysql 14 20: *rspec-knapsack-mysql
-rspec mysql 15 20: *rspec-knapsack-mysql
-rspec mysql 16 20: *rspec-knapsack-mysql
-rspec mysql 17 20: *rspec-knapsack-mysql
-rspec mysql 18 20: *rspec-knapsack-mysql
-rspec mysql 19 20: *rspec-knapsack-mysql
-
-spinach pg 0 10: *spinach-knapsack-pg
-spinach pg 1 10: *spinach-knapsack-pg
-spinach pg 2 10: *spinach-knapsack-pg
-spinach pg 3 10: *spinach-knapsack-pg
-spinach pg 4 10: *spinach-knapsack-pg
-spinach pg 5 10: *spinach-knapsack-pg
-spinach pg 6 10: *spinach-knapsack-pg
-spinach pg 7 10: *spinach-knapsack-pg
-spinach pg 8 10: *spinach-knapsack-pg
-spinach pg 9 10: *spinach-knapsack-pg
-
-spinach mysql 0 10: *spinach-knapsack-mysql
-spinach mysql 1 10: *spinach-knapsack-mysql
-spinach mysql 2 10: *spinach-knapsack-mysql
-spinach mysql 3 10: *spinach-knapsack-mysql
-spinach mysql 4 10: *spinach-knapsack-mysql
-spinach mysql 5 10: *spinach-knapsack-mysql
-spinach mysql 6 10: *spinach-knapsack-mysql
-spinach mysql 7 10: *spinach-knapsack-mysql
-spinach mysql 8 10: *spinach-knapsack-mysql
-spinach mysql 9 10: *spinach-knapsack-mysql
-
-# Other generic tests
+rspec-pg 0 20: *rspec-knapsack-pg
+rspec-pg 1 20: *rspec-knapsack-pg
+rspec-pg 2 20: *rspec-knapsack-pg
+rspec-pg 3 20: *rspec-knapsack-pg
+rspec-pg 4 20: *rspec-knapsack-pg
+rspec-pg 5 20: *rspec-knapsack-pg
+rspec-pg 6 20: *rspec-knapsack-pg
+rspec-pg 7 20: *rspec-knapsack-pg
+rspec-pg 8 20: *rspec-knapsack-pg
+rspec-pg 9 20: *rspec-knapsack-pg
+rspec-pg 10 20: *rspec-knapsack-pg
+rspec-pg 11 20: *rspec-knapsack-pg
+rspec-pg 12 20: *rspec-knapsack-pg
+rspec-pg 13 20: *rspec-knapsack-pg
+rspec-pg 14 20: *rspec-knapsack-pg
+rspec-pg 15 20: *rspec-knapsack-pg
+rspec-pg 16 20: *rspec-knapsack-pg
+rspec-pg 17 20: *rspec-knapsack-pg
+rspec-pg 18 20: *rspec-knapsack-pg
+rspec-pg 19 20: *rspec-knapsack-pg
+
+rspec-mysql 0 20: *rspec-knapsack-mysql
+rspec-mysql 1 20: *rspec-knapsack-mysql
+rspec-mysql 2 20: *rspec-knapsack-mysql
+rspec-mysql 3 20: *rspec-knapsack-mysql
+rspec-mysql 4 20: *rspec-knapsack-mysql
+rspec-mysql 5 20: *rspec-knapsack-mysql
+rspec-mysql 6 20: *rspec-knapsack-mysql
+rspec-mysql 7 20: *rspec-knapsack-mysql
+rspec-mysql 8 20: *rspec-knapsack-mysql
+rspec-mysql 9 20: *rspec-knapsack-mysql
+rspec-mysql 10 20: *rspec-knapsack-mysql
+rspec-mysql 11 20: *rspec-knapsack-mysql
+rspec-mysql 12 20: *rspec-knapsack-mysql
+rspec-mysql 13 20: *rspec-knapsack-mysql
+rspec-mysql 14 20: *rspec-knapsack-mysql
+rspec-mysql 15 20: *rspec-knapsack-mysql
+rspec-mysql 16 20: *rspec-knapsack-mysql
+rspec-mysql 17 20: *rspec-knapsack-mysql
+rspec-mysql 18 20: *rspec-knapsack-mysql
+rspec-mysql 19 20: *rspec-knapsack-mysql
+
+spinach-pg 0 10: *spinach-knapsack-pg
+spinach-pg 1 10: *spinach-knapsack-pg
+spinach-pg 2 10: *spinach-knapsack-pg
+spinach-pg 3 10: *spinach-knapsack-pg
+spinach-pg 4 10: *spinach-knapsack-pg
+spinach-pg 5 10: *spinach-knapsack-pg
+spinach-pg 6 10: *spinach-knapsack-pg
+spinach-pg 7 10: *spinach-knapsack-pg
+spinach-pg 8 10: *spinach-knapsack-pg
+spinach-pg 9 10: *spinach-knapsack-pg
+
+spinach-mysql 0 10: *spinach-knapsack-mysql
+spinach-mysql 1 10: *spinach-knapsack-mysql
+spinach-mysql 2 10: *spinach-knapsack-mysql
+spinach-mysql 3 10: *spinach-knapsack-mysql
+spinach-mysql 4 10: *spinach-knapsack-mysql
+spinach-mysql 5 10: *spinach-knapsack-mysql
+spinach-mysql 6 10: *spinach-knapsack-mysql
+spinach-mysql 7 10: *spinach-knapsack-mysql
+spinach-mysql 8 10: *spinach-knapsack-mysql
+spinach-mysql 9 10: *spinach-knapsack-mysql
+
+# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
variables:
SIMPLECOV: "false"
SETUP_DB: "false"
- USE_BUNDLE_INSTALL: "true"
.rake-exec: &rake-exec
<<: *ruby-static-analysis
@@ -331,6 +333,7 @@ ee_compat_check:
paths:
- ee_compat_check/patches/*.patch
+# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
stage: test
<<: *dedicated-runner
@@ -338,14 +341,38 @@ ee_compat_check:
script:
- bundle exec rake db:migrate:reset
-rake pg db:migrate:reset:
+db:migrate:reset-pg:
<<: *db-migrate-reset
<<: *use-pg
-rake mysql db:migrate:reset:
+db:migrate:reset-mysql:
<<: *db-migrate-reset
<<: *use-mysql
+.migration-paths: &migration-paths
+ stage: test
+ <<: *dedicated-runner
+ variables:
+ SETUP_DB: "false"
+ <<: *only-canonical-masters
+ script:
+ - git fetch origin v8.14.10
+ - git checkout -f FETCH_HEAD
+ - bundle install $BUNDLE_INSTALL_FLAGS
+ - bundle exec rake db:drop db:create db:schema:load db:seed_fu
+ - git checkout $CI_COMMIT_SHA
+ - bundle install $BUNDLE_INSTALL_FLAGS
+ - . scripts/prepare_build.sh
+ - bundle exec rake db:migrate
+
+migration:path-pg:
+ <<: *migration-paths
+ <<: *use-pg
+
+migration:path-mysql:
+ <<: *migration-paths
+ <<: *use-mysql
+
.db-rollback: &db-rollback
stage: test
<<: *dedicated-runner
@@ -354,11 +381,11 @@ rake mysql db:migrate:reset:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
-rake pg db:rollback:
+db:rollback-pg:
<<: *db-rollback
<<: *use-pg
-rake mysql db:rollback:
+db:rollback-mysql:
<<: *db-rollback
<<: *use-mysql
@@ -380,15 +407,16 @@ rake mysql db:rollback:
paths:
- log/development.log
-rake pg db:seed_fu:
+db:seed_fu-pg:
<<: *db-seed_fu
<<: *use-pg
-rake mysql db:seed_fu:
+db:seed_fu-mysql:
<<: *db-seed_fu
<<: *use-mysql
-rake gitlab:assets:compile:
+# Frontend-related jobs
+gitlab:assets:compile:
stage: test
<<: *dedicated-runner
<<: *except-docs
@@ -409,7 +437,7 @@ rake gitlab:assets:compile:
paths:
- webpack-report/
-rake karma:
+karma:
stage: test
<<: *use-pg
<<: *dedicated-runner
@@ -425,34 +453,6 @@ rake karma:
paths:
- coverage-javascript/
-.migration-paths: &migration-paths
- stage: test
- <<: *dedicated-runner
- variables:
- SETUP_DB: "false"
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - master@gitlab/gitlabhq
- - master@gitlab/gitlab-ee
- script:
- - git fetch origin v8.14.10
- - git checkout -f FETCH_HEAD
- - bundle install $BUNDLE_INSTALL_FLAGS
- - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- - git checkout $CI_COMMIT_SHA
- - bundle install $BUNDLE_INSTALL_FLAGS
- - . scripts/prepare_build.sh
- - bundle exec rake db:migrate
-
-migration pg paths:
- <<: *migration-paths
- <<: *use-pg
-
-migration mysql paths:
- <<: *migration-paths
- <<: *use-mysql
-
coverage:
stage: post-test
services: []
@@ -510,8 +510,8 @@ pages:
<<: *dedicated-runner
dependencies:
- coverage
- - rake karma
- - rake gitlab:assets:compile
+ - karma
+ - gitlab:assets:compile
- lint:javascript:report
script:
- mv public/ .public/
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/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 37822dac064..22032d0f914 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -288,7 +288,11 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
if (anchor) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
- notes.addDiffNote(anchor, lineType, false);
+ notes.toggleDiffNote({
+ target: anchor,
+ lineType,
+ forceShow: true,
+ });
anchor[0].scrollIntoView();
// We have multiple elements on the page with `#note_xxx`
// (discussion and diff tabs) and `:target` only applies to the first
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 91d1afba7b6..67df1c0c4d7 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",
@@ -876,10 +860,19 @@ const normalizeNewlines = function(str) {
e.preventDefault();
const $link = $(e.currentTarget || e.target);
const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
- this.addDiffNote($link, $link.data('lineType'), showReplyInput);
+ this.toggleDiffNote({
+ target: $link,
+ lineType: $link.data('lineType'),
+ showReplyInput
+ });
};
- Notes.prototype.addDiffNote = function(target, lineType, showReplyInput) {
+ Notes.prototype.toggleDiffNote = function({
+ target,
+ lineType,
+ forceShow,
+ showReplyInput = false,
+ }) {
var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
$link = $(target);
row = $link.closest("tr");
@@ -924,12 +917,12 @@ const normalizeNewlines = function(str) {
notesContent = targetRow.find(notesContentSelector);
addForm = true;
} else {
- targetRow.show();
- notesContent.toggle(!notesContent.is(':visible'));
+ const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
+ const isForced = forceShow === true || forceShow === false;
+ const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
- if (!targetRow.find('.content:not(:empty)').is(':visible')) {
- targetRow.hide();
- }
+ targetRow.toggle(showNow);
+ notesContent.toggle(showNow);
}
if (addForm) {
@@ -1137,6 +1130,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/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 1724ca86b61..aea3592c6ba 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -421,6 +421,15 @@ function UsersSelect(currentUser, els) {
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
return assignTo(selected);
}
+
+ // Automatically close dropdown after assignee is selected
+ // since CE has no multiple assignees
+ // EE does not have a max-select
+ if ($dropdown.data('max-select') &&
+ getSelected().length === $dropdown.data('max-select')) {
+ // Close the dropdown
+ $dropdown.dropdown('toggle');
+ }
},
id: function (user) {
return user.id;
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/models/blob.rb b/app/models/blob.rb
index 5ae35d3ab08..8e25ba590c7 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -40,6 +40,7 @@ class Blob < SimpleDelegator
BlobViewer::GitlabCiYml,
BlobViewer::RouteMap,
+ BlobViewer::Readme,
BlobViewer::License,
BlobViewer::Contributing,
BlobViewer::Changelog
diff --git a/app/models/blob_viewer/auxiliary.rb b/app/models/blob_viewer/auxiliary.rb
index cd6e596ed60..07a207730cf 100644
--- a/app/models/blob_viewer/auxiliary.rb
+++ b/app/models/blob_viewer/auxiliary.rb
@@ -2,11 +2,17 @@ module BlobViewer
module Auxiliary
extend ActiveSupport::Concern
+ include Gitlab::Allowable
+
included do
self.loading_partial_name = 'loading_auxiliary'
self.type = :auxiliary
self.overridable_max_size = 100.kilobytes
self.max_size = 100.kilobytes
end
+
+ def visible_to?(current_user)
+ true
+ end
end
end
diff --git a/app/models/blob_viewer/readme.rb b/app/models/blob_viewer/readme.rb
new file mode 100644
index 00000000000..75c373a03bb
--- /dev/null
+++ b/app/models/blob_viewer/readme.rb
@@ -0,0 +1,14 @@
+module BlobViewer
+ class Readme < Base
+ include Auxiliary
+ include Static
+
+ self.partial_name = 'readme'
+ self.file_types = %i(readme)
+ self.binary = false
+
+ def visible_to?(current_user)
+ can?(current_user, :read_wiki, project)
+ end
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 9153e5ae5a8..07e0b3bae4f 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -649,22 +649,8 @@ class Repository
"#{name}-#{highest_branch_id + 1}"
end
- # Remove archives older than 2 hours
def branches_sorted_by(value)
- case value
- when 'name'
- branches.sort_by(&:name)
- when 'updated_desc'
- branches.sort do |a, b|
- commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date
- end
- when 'updated_asc'
- branches.sort do |a, b|
- commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date
- end
- else
- branches
- end
+ raw_repository.local_branches(sort_by: value)
end
def tags_sorted_by(value)
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/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb
index 0f94504625a..f846d72498f 100644
--- a/app/services/members/authorized_destroy_service.rb
+++ b/app/services/members/authorized_destroy_service.rb
@@ -10,7 +10,7 @@ module Members
return false if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
Member.transaction do
- unassign_issues_and_merge_requests(member)
+ unassign_issues_and_merge_requests(member) unless member.invite?
member.destroy
end
diff --git a/app/views/projects/blob/_auxiliary_viewer.html.haml b/app/views/projects/blob/_auxiliary_viewer.html.haml
new file mode 100644
index 00000000000..9749afdc580
--- /dev/null
+++ b/app/views/projects/blob/_auxiliary_viewer.html.haml
@@ -0,0 +1,5 @@
+- blob = local_assigns.fetch(:blob)
+- auxiliary_viewer = blob.auxiliary_viewer
+- if auxiliary_viewer && auxiliary_viewer.render_error.nil? && auxiliary_viewer.visible_to?(current_user)
+ .well-segment.blob-auxiliary-viewer
+ = render 'projects/blob/viewer', viewer: auxiliary_viewer
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index c90a7440480..8bd336269ff 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -5,10 +5,7 @@
%ul.blob-commit-info
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref
- - auxiliary_viewer = blob.auxiliary_viewer
- - if auxiliary_viewer && !auxiliary_viewer.render_error
- .well-segment.blob-auxiliary-viewer
- = render 'projects/blob/viewer', viewer: auxiliary_viewer
+ = render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
%article.file-holder
diff --git a/app/views/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml
new file mode 100644
index 00000000000..334b33faf48
--- /dev/null
+++ b/app/views/projects/blob/viewers/_readme.html.haml
@@ -0,0 +1,4 @@
+= icon('info-circle fw')
+= succeed '.' do
+ To learn more about this project, read
+ = link_to "the wiki", namespace_project_wikis_path(viewer.project.namespace, viewer.project)
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index f3372c7657f..766cb272bec 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -49,7 +49,7 @@
%strong Tip:
= succeed '.' do
You can also checkout merge requests locally by
- = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer'
+ = link_to 'following these guidelines', help_page_path('user/project/merge_requests/index.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer'
:javascript
$(function(){
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 2c2f64283f5..de57cd4ba00 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,8 +1,9 @@
-%article.file-holder.readme-holder
- .js-file-title.file-title
- = blob_icon readme.mode, readme.name
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do
- %strong
- = readme.name
+- if readme.rich_viewer
+ %article.file-holder.readme-holder
+ .js-file-title.file-title
+ = blob_icon readme.mode, readme.name
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do
+ %strong
+ = readme.name
- = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
+ = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
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/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 8e8b84e0408..2b70d70e360 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -10,7 +10,7 @@
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile
- .cover-block.user-cover-block
+ .cover-block.user-cover-block.layout-nav
.cover-controls
- if @user == current_user
= link_to profile_path, class: 'btn btn-gray has-tooltip', title: 'Edit profile', 'aria-label': 'Edit profile' do
@@ -82,7 +82,7 @@
.scrolling-tabs-container
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
- %ul.nav-links.center.user-profile-nav.scrolling-tabs
+ %ul.nav-links.user-profile-nav.scrolling-tabs
%li.js-activity-tab
= link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do
Activity
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/gitaly-local-branches.yml b/changelogs/unreleased/gitaly-local-branches.yml
new file mode 100644
index 00000000000..adcc0fa6280
--- /dev/null
+++ b/changelogs/unreleased/gitaly-local-branches.yml
@@ -0,0 +1,4 @@
+---
+title: Add suport for find_local_branches GRPC from Gitaly
+merge_request: 10059
+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 c4f9a3cb573..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
@@ -108,6 +109,7 @@ Here is an collection of tutorials and guides on setting up your CI pipeline.
- [Scala](examples/test-scala-application.md)
- [Phoenix](examples/test-phoenix-application.md)
- [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md)
+ - [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
- **Blog posts**
- [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
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/examples/README.md b/doc/ci/examples/README.md
index 33c27b39a8a..2458cb959ab 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -55,6 +55,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
- [Using `dpl` as deployment tool](deployment/README.md)
- [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+- [Analyze code quality with the Code Climate CLI](code_climate.md)
- **Articles:**
- [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md
new file mode 100644
index 00000000000..bd53f80ce14
--- /dev/null
+++ b/doc/ci/examples/code_climate.md
@@ -0,0 +1,28 @@
+# Analyze project code quality with Code Climate CLI
+
+This example shows how to run [Code Climate CLI][cli] on your code by using\
+GitLab CI and Docker.
+
+First, you need GitLab Runner with [docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
+
+Once you setup the Runner add new job to `.gitlab-ci.yml`:
+
+```yaml
+codeclimate:
+ image: docker:latest
+ variables:
+ DOCKER_DRIVER: overlay
+ services:
+ - docker:dind
+ script:
+ - docker pull codeclimate/codeclimate
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate init
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json
+ artifacts:
+ paths: [codeclimate.json]
+```
+
+This will create a `codeclimate` job in your CI pipeline and will allow you to
+download and analyze the report artifact in JSON format.
+
+[cli]: https://github.com/codeclimate/codeclimate
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/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb
index 586380da94a..124526e4b59 100644
--- a/lib/gitlab/git/branch.rb
+++ b/lib/gitlab/git/branch.rb
@@ -1,6 +1,40 @@
module Gitlab
module Git
class Branch < Ref
+ def initialize(repository, name, target)
+ if target.is_a?(Gitaly::FindLocalBranchResponse)
+ target = target_from_gitaly_local_branches_response(target)
+ end
+
+ super(repository, name, target)
+ end
+
+ def target_from_gitaly_local_branches_response(response)
+ # Git messages have no encoding enforcements. However, in the UI we only
+ # handle UTF-8, so basically we cross our fingers that the message force
+ # encoded to UTF-8 is readable.
+ message = response.commit_subject.dup.force_encoding('UTF-8')
+
+ # NOTE: For ease of parsing in Gitaly, we have only the subject of
+ # the commit and not the full message. This is ok, since all the
+ # code that uses `local_branches` only cares at most about the
+ # commit message.
+ # TODO: Once gitaly "takes over" Rugged consider separating the
+ # subject from the message to make it clearer when there's one
+ # available but not the other.
+ hash = {
+ id: response.commit_id,
+ message: message,
+ authored_date: Time.at(response.commit_author.date.seconds),
+ author_name: response.commit_author.name,
+ author_email: response.commit_author.email,
+ committed_date: Time.at(response.commit_committer.date.seconds),
+ committer_name: response.commit_committer.name,
+ committer_email: response.commit_committer.email
+ }
+
+ Gitlab::Git::Commit.decorate(hash)
+ end
end
end
end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index f9a9b767ef4..297531db4cc 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -19,13 +19,7 @@ module Gitlab
def ==(other)
return false unless other.is_a?(Gitlab::Git::Commit)
- methods = [:message, :parent_ids, :authored_date, :author_name,
- :author_email, :committed_date, :committer_name,
- :committer_email]
-
- methods.all? do |method|
- send(method) == other.send(method)
- end
+ id && id == other.id
end
class << self
@@ -55,6 +49,7 @@ module Gitlab
# Commit.find(repo, 'master')
#
def find(repo, commit_id = "HEAD")
+ return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
return decorate(commit_id) if commit_id.is_a?(Rugged::Commit)
obj = if commit_id.is_a?(String)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index a2c01ec4432..b9f1ac144b6 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -80,14 +80,16 @@ module Gitlab
end
# Returns an Array of Branches
- def branches
- rugged.branches.map do |rugged_ref|
+ def branches(filter: nil, sort_by: nil)
+ branches = rugged.branches.each(filter).map do |rugged_ref|
begin
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target)
rescue Rugged::ReferenceError
# Omit invalid branch
end
- end.compact.sort_by(&:name)
+ end.compact
+
+ sort_branches(branches, sort_by)
end
def reload_rugged
@@ -108,9 +110,15 @@ module Gitlab
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref
end
- def local_branches
- rugged.branches.each(:local).map do |branch|
- Gitlab::Git::Branch.new(self, branch.name, branch.target)
+ def local_branches(sort_by: nil)
+ gitaly_migrate(:local_branches) do |is_enabled|
+ if is_enabled
+ gitaly_ref_client.local_branches(sort_by: sort_by).map do |gitaly_branch|
+ Gitlab::Git::Branch.new(self, gitaly_branch.name, gitaly_branch)
+ end
+ else
+ branches(filter: :local, sort_by: sort_by)
+ end
end
end
@@ -1202,6 +1210,23 @@ module Gitlab
diff.each_patch
end
+ def sort_branches(branches, sort_by)
+ case sort_by
+ when 'name'
+ branches.sort_by(&:name)
+ when 'updated_desc'
+ branches.sort do |a, b|
+ b.dereferenced_target.committed_date <=> a.dereferenced_target.committed_date
+ end
+ when 'updated_asc'
+ branches.sort do |a, b|
+ a.dereferenced_target.committed_date <=> b.dereferenced_target.committed_date
+ end
+ else
+ branches
+ end
+ end
+
def gitaly_ref_client
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self)
end
diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb
index 53c43e28df8..227fe45642e 100644
--- a/lib/gitlab/gitaly_client/ref.rb
+++ b/lib/gitlab/gitaly_client/ref.rb
@@ -44,6 +44,12 @@ module Gitlab
branch_names.count
end
+ def local_branches(sort_by: nil)
+ request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
+ request.sort_by = sort_by_param(sort_by) if sort_by
+ consume_branches_response(stub.find_local_branches(request))
+ end
+
private
def consume_refs_response(response, prefix:)
@@ -51,6 +57,16 @@ module Gitlab
r.names.map { |name| name.sub(/\A#{Regexp.escape(prefix)}/, '') }
end
end
+
+ def sort_by_param(sort_by)
+ enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym)
+ raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value
+ enum_value
+ end
+
+ def consume_branches_response(response)
+ response.flat_map { |r| r.branches }
+ end
end
end
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 26473f99bc3..6200bd460ea 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -17,6 +17,7 @@ module Gitlab
gon.current_user_id = current_user.id
gon.current_username = current_user.username
gon.current_user_fullname = current_user.name
+ gon.current_user_avatar_url = current_user.avatar_url
end
end
end
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index c727a0e2d88..03de59f27ad 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -4,9 +4,22 @@ export SETUP_DB=${SETUP_DB:-true}
export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
export BUNDLE_INSTALL_FLAGS="--without production --jobs $(nproc) --path vendor --retry 3 --quiet"
+if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
+ bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
+fi
+
+# Only install knapsack after bundle install! Otherwise oddly some native
+# gems could not be found under some circumstance. No idea why, hours wasted.
+retry gem install knapsack fog-aws mime-types
+
+cp config/resque.yml.example config/resque.yml
+sed -i 's/localhost/redis/g' config/resque.yml
+
+cp config/gitlab.yml.example config/gitlab.yml
+
# Determine the database by looking at the job name.
-# For example, we'll get pg if the job is `rspec pg 19 20`
-export GITLAB_DATABASE=$(echo $CI_JOB_NAME | cut -f2 -d' ')
+# For example, we'll get pg if the job is `rspec-pg 19 20`
+export GITLAB_DATABASE=$(echo $CI_JOB_NAME | cut -f1 -d' ' | cut -f2 -d-)
# This would make the default database postgresql, and we could also use
# pg to mean postgresql.
@@ -24,19 +37,6 @@ else # Assume it's mysql
sed -i 's/# host:.*/host: mysql/g' config/database.yml
fi
-cp config/resque.yml.example config/resque.yml
-sed -i 's/localhost/redis/g' config/resque.yml
-
-cp config/gitlab.yml.example config/gitlab.yml
-
-if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
- bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
-fi
-
-# Only install knapsack after bundle install! Otherwise oddly some native
-# gems could not be found under some circumstance. No idea why, hours wasted.
-retry gem install knapsack fog-aws mime-types
-
if [ "$SETUP_DB" != "false" ]; then
bundle exec rake db:drop db:create db:schema:load db:migrate
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 080b2e75ea1..32cbfe28a60 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -10,5 +10,11 @@ FactoryGirl.define do
trait(:master) { access_level GroupMember::MASTER }
trait(:owner) { access_level GroupMember::OWNER }
trait(:access_request) { requested_at Time.now }
+
+ trait(:invited) do
+ user_id nil
+ invite_token 'xxx'
+ invite_email 'email@email.com'
+ end
end
end
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index d62799a5a47..fe4518caadf 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -9,5 +9,11 @@ FactoryGirl.define do
trait(:developer) { access_level ProjectMember::DEVELOPER }
trait(:master) { access_level ProjectMember::MASTER }
trait(:access_request) { requested_at Time.now }
+
+ trait(:invited) do
+ user_id nil
+ invite_token 'xxx'
+ invite_email 'email@email.com'
+ end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 3580752a805..7a76f5f8afc 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -60,7 +60,9 @@ FactoryGirl.define do
trait :test_repo do
after :create do |project|
- TestEnv.copy_repo(project)
+ TestEnv.copy_repo(project,
+ bare_repo: TestEnv.factory_repo_path_bare,
+ refs: TestEnv::BRANCH_SHA)
end
end
@@ -139,7 +141,9 @@ FactoryGirl.define do
end
after :create do |project, evaluator|
- TestEnv.copy_repo(project)
+ TestEnv.copy_repo(project,
+ bare_repo: TestEnv.factory_repo_path_bare,
+ refs: TestEnv::BRANCH_SHA)
if evaluator.create_template
args = evaluator.create_template
@@ -172,7 +176,9 @@ FactoryGirl.define do
path { 'forked-gitlabhq' }
after :create do |project|
- TestEnv.copy_forked_repo_with_submodules(project)
+ TestEnv.copy_repo(project,
+ bare_repo: TestEnv.forked_repo_path_bare,
+ refs: TestEnv::FORKED_BRANCH_SHA)
end
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 1238647d3f3..4667be49fe6 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -115,7 +115,6 @@ describe 'Issue Boards', feature: true, js: true do
click_link 'Unassigned'
end
- find('.dropdown-menu-toggle').click
wait_for_vue_resource
expect(page).to have_content('No assignee')
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 095cbb65c16..5c0907e26df 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -24,10 +24,10 @@ describe 'New/edit issue', :feature, :js do
visit new_namespace_project_issue_path(project.namespace, project)
end
- describe 'multiple assignees' do
+ describe 'single assignee' do
before do
click_button 'Unassigned'
-
+
wait_for_ajax
end
@@ -36,14 +36,12 @@ describe 'New/edit issue', :feature, :js do
click_link user2.name
end
+ click_button user2.name
+
page.within '.dropdown-menu-user' do
click_link 'Unassigned'
end
- page.within '.js-assignee-search' do
- expect(page).to have_content 'Unassigned'
- end
-
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match('0')
end
@@ -54,11 +52,13 @@ describe 'New/edit issue', :feature, :js do
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
- page.within '.dropdown-menu-user' do
+ click_button user.name
+
+ page.within('.dropdown-menu-user') do
click_link user.name
end
- expect(find('a', text: 'Assign to me')).to be_visible
+ expect(page.find('.dropdown-menu-user', visible: false)).not_to be_visible
end
end
@@ -154,25 +154,21 @@ describe 'New/edit issue', :feature, :js do
it 'correctly updates the selected user when changing assignee' do
click_button 'Unassigned'
-
+
wait_for_ajax
page.within '.dropdown-menu-user' do
click_link user.name
end
- expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
- expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user.id.to_s)
- # check the ::before pseudo element to ensure checkmark icon is present
- expect(before_for_selector('.dropdown-menu-selectable a.is-active')).not_to eq('')
- expect(before_for_selector('.dropdown-menu-selectable a:not(.is-active)')).to eq('')
+ expect(find('.js-assignee-search')).to have_content(user.name)
+ click_button user.name
page.within '.dropdown-menu-user' do
click_link user2.name
end
- expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
- expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user2.id.to_s)
+ expect(find('.js-assignee-search')).to have_content(user2.name)
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/features/issues_spec.rb b/spec/features/issues_spec.rb
index fdd78600a1d..da369a6f111 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -465,8 +465,6 @@ describe 'Issues', feature: true do
click_link 'Edit'
click_link @user.name
- find('.dropdown-menu-toggle').click
-
page.within '.value .author' do
expect(page).to have_content @user.name
end
@@ -474,8 +472,6 @@ describe 'Issues', feature: true do
click_link 'Edit'
click_link @user.name
- find('.dropdown-menu-toggle').click
-
page.within '.value .assign-yourself' do
expect(page).to have_content "No assignee"
end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index 7dee3b852ca..4860a2a7498 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -20,6 +20,34 @@ feature 'Diffs URL', js: true, feature: true do
end
end
+ context 'when linking to note' do
+ describe 'with unresolved note' do
+ let(:note) { create :diff_note_on_merge_request, project: project, noteable: merge_request }
+ let(:fragment) { "#note_#{note.id}" }
+
+ before do
+ visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+ end
+
+ it 'shows expanded note' do
+ expect(page).to have_selector(fragment, visible: true)
+ end
+ end
+
+ describe 'with resolved note' do
+ let(:note) { create :diff_note_on_merge_request, :resolved, project: project, noteable: merge_request }
+ let(:fragment) { "#note_#{note.id}" }
+
+ before do
+ visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+ end
+
+ it 'shows expanded note' do
+ expect(page).to have_selector(fragment, visible: true)
+ end
+ end
+ end
+
context 'when merge request has overflow' do
it 'displays warning' do
allow(Commit).to receive(:max_diff_options).and_return(max_files: 3)
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index fc9b293c393..884d1bbb10c 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-Dir["./spec/features/protected_branches/*.rb"].sort.each { |f| require f }
feature 'Projected Branches', feature: true, js: true do
let(:user) { create(:user, :admin) }
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index e68448467b0..66236dbc7fc 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-Dir["./spec/features/protected_tags/*.rb"].sort.each { |f| require f }
feature 'Projected Tags', feature: true, js: true do
let(:user) { create(:user, :admin) }
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
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index cdf1b8beee3..9eac7660cd1 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -7,6 +7,51 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { is_expected.to be_kind_of Array }
+ describe 'initialize' do
+ let(:commit_id) { 'f00' }
+ let(:commit_subject) { "My commit".force_encoding('ASCII-8BIT') }
+ let(:committer) do
+ Gitaly::FindLocalBranchCommitAuthor.new(
+ name: generate(:name),
+ email: generate(:email),
+ date: Google::Protobuf::Timestamp.new(seconds: 123)
+ )
+ end
+ let(:author) do
+ Gitaly::FindLocalBranchCommitAuthor.new(
+ name: generate(:name),
+ email: generate(:email),
+ date: Google::Protobuf::Timestamp.new(seconds: 456)
+ )
+ end
+ let(:gitaly_branch) do
+ Gitaly::FindLocalBranchResponse.new(
+ name: 'foo', commit_id: commit_id, commit_subject: commit_subject,
+ commit_author: author, commit_committer: committer
+ )
+ end
+ let(:attributes) do
+ {
+ id: commit_id,
+ message: commit_subject,
+ authored_date: Time.at(author.date.seconds),
+ author_name: author.name,
+ author_email: author.email,
+ committed_date: Time.at(committer.date.seconds),
+ committer_name: committer.name,
+ committer_email: committer.email
+ }
+ end
+ let(:branch) { described_class.new(repository, 'foo', gitaly_branch) }
+
+ it 'parses Gitaly::FindLocalBranchResponse correctly' do
+ expect(Gitlab::Git::Commit).to receive(:decorate).
+ with(hash_including(attributes)).and_call_original
+
+ expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8)
+ end
+ end
+
describe '#size' do
subject { super().size }
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 53d492b8f74..cb107c6d1f9 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1105,7 +1105,9 @@ describe Gitlab::Git::Repository, seed_helper: true do
ref = double()
allow(ref).to receive(:name) { 'bad-branch' }
allow(ref).to receive(:target) { raise Rugged::ReferenceError }
- allow(repository.rugged).to receive(:branches) { [ref] }
+ branches = double()
+ allow(branches).to receive(:each) { [ref].each }
+ allow(repository.rugged).to receive(:branches) { branches }
end
it 'should return empty branches' do
@@ -1289,7 +1291,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#local_branches' do
before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH)
+ @repo = Gitlab::Git::Repository.new('default', File.join(TEST_MUTABLE_REPO_PATH, '.git'))
end
after(:all) do
@@ -1304,6 +1306,29 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end
+
+ context 'with gitaly enabled' do
+ before { stub_gitaly }
+ after { Gitlab::GitalyClient.clear_stubs! }
+
+ it 'gets the branches from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
+ and_return([])
+ @repo.local_branches
+ end
+
+ it 'wraps GRPC not found' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
+ and_raise(GRPC::NotFound)
+ expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
+ end
+
+ it 'wraps GRPC exceptions' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
+ and_raise(GRPC::Unknown)
+ expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
+ end
+ end
end
def create_remote_branch(remote_name, branch_name, source_branch_name)
diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb
index 255f23e6270..d8cd2dcbd2a 100644
--- a/spec/lib/gitlab/gitaly_client/ref_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb
@@ -9,6 +9,13 @@ describe Gitlab::GitalyClient::Ref do
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
end
+ after do
+ # When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created,
+ # and because GitalyClient shares stubs these will get passed from example to
+ # example, which will cause an error, so we clean the stubs after each example.
+ Gitlab::GitalyClient.clear_stubs!
+ end
+
describe '#branch_names' do
it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::Ref::Stub).
@@ -38,4 +45,27 @@ describe Gitlab::GitalyClient::Ref do
client.default_branch_name
end
end
+
+ describe '#local_branches' do
+ it 'sends a find_local_branches message' do
+ expect_any_instance_of(Gitaly::Ref::Stub).
+ to receive(:find_local_branches).with(gitaly_request_with_repo_path(repo_path)).
+ and_return([])
+
+ client.local_branches
+ end
+
+ it 'parses and sends the sort parameter' do
+ expect_any_instance_of(Gitaly::Ref::Stub).
+ to receive(:find_local_branches).
+ with(gitaly_request_with_params(sort_by: :UPDATED_DESC)).
+ and_return([])
+
+ client.local_branches(sort_by: 'updated_desc')
+ end
+
+ it 'raises an argument error if an invalid sort_by parameter is passed' do
+ expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
+ end
+ end
end
diff --git a/spec/services/members/authorized_destroy_service_spec.rb b/spec/services/members/authorized_destroy_service_spec.rb
index ab440d18e9f..8a6732faa19 100644
--- a/spec/services/members/authorized_destroy_service_spec.rb
+++ b/spec/services/members/authorized_destroy_service_spec.rb
@@ -10,6 +10,27 @@ describe Members::AuthorizedDestroyService, services: true do
Issue.assigned_to(user).count + MergeRequest.assigned_to(user).count
end
+ context 'Invited users' do
+ # Regression spec for issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/32504
+ it 'destroys invited project member' do
+ project.team << [member_user, :developer]
+
+ member = create :project_member, :invited, project: project
+
+ expect { described_class.new(member, member_user).execute }
+ .to change { Member.count }.from(2).to(1)
+ end
+
+ it 'destroys invited group member' do
+ group.add_developer(member_user)
+
+ member = create :group_member, :invited, group: group
+
+ expect { described_class.new(member, member_user).execute }
+ .to change { Member.count }.from(2).to(1)
+ end
+ end
+
context 'Group member' do
it "unassigns issues and merge requests" do
group.add_developer(member_user)
diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb
index 65dbc01f6e4..ed14bcec9f2 100644
--- a/spec/support/matchers/gitaly_matchers.rb
+++ b/spec/support/matchers/gitaly_matchers.rb
@@ -1,3 +1,9 @@
RSpec::Matchers.define :gitaly_request_with_repo_path do |path|
match { |actual| actual.repository.path == path }
end
+
+RSpec::Matchers.define :gitaly_request_with_params do |params|
+ match do |actual|
+ params.reduce(true) { |r, (key, val)| r && actual.send(key) == val }
+ end
+end
diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/support/protected_branches/access_control_ce_shared_examples.rb
index 7fda4ade665..7fda4ade665 100644
--- a/spec/features/protected_branches/access_control_ce_spec.rb
+++ b/spec/support/protected_branches/access_control_ce_shared_examples.rb
diff --git a/spec/features/protected_tags/access_control_ce_spec.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 12622cd548a..12622cd548a 100644
--- a/spec/features/protected_tags/access_control_ce_spec.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 9bf9dc5d4b2..b168098edea 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -40,8 +40,8 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
- 'add-ipython-files' => '6d85bb69',
- 'add-pdf-file' => 'e774ebd3'
+ 'add-ipython-files' => '6d85bb6',
+ 'add-pdf-file' => 'e774ebd'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
@@ -155,14 +155,14 @@ module TestEnv
FORKED_BRANCH_SHA)
end
- def setup_repo(repo_path, repo_path_bare, repo_name, branch_sha)
+ def setup_repo(repo_path, repo_path_bare, repo_name, refs)
clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git"
unless File.directory?(repo_path)
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end
- set_repo_refs(repo_path, branch_sha)
+ set_repo_refs(repo_path, refs)
unless File.directory?(repo_path_bare)
# We must copy bare repositories because we will push to them.
@@ -170,13 +170,12 @@ module TestEnv
end
end
- def copy_repo(project)
- base_repo_path = File.expand_path(factory_repo_path_bare)
+ def copy_repo(project, bare_repo:, refs:)
target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
FileUtils.mkdir_p(target_repo_path)
- FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
+ FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
- set_repo_refs(target_repo_path, BRANCH_SHA)
+ set_repo_refs(target_repo_path, refs)
end
def repos_path
@@ -191,15 +190,6 @@ module TestEnv
Gitlab.config.pages.path
end
- def copy_forked_repo_with_submodules(project)
- base_repo_path = File.expand_path(forked_repo_path_bare)
- target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
- FileUtils.mkdir_p(target_repo_path)
- FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
- FileUtils.chmod_R 0755, target_repo_path
- set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
- end
-
# When no cached assets exist, manually hit the root path to create them
#
# Otherwise they'd be created by the first test, often timing out and
@@ -211,16 +201,20 @@ module TestEnv
Capybara.current_session.visit '/'
end
+ def factory_repo_path_bare
+ "#{factory_repo_path}_bare"
+ end
+
+ def forked_repo_path_bare
+ "#{forked_repo_path}_bare"
+ end
+
private
def factory_repo_path
@factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
end
- def factory_repo_path_bare
- "#{factory_repo_path}_bare"
- end
-
def factory_repo_name
'gitlab-test'
end
@@ -229,10 +223,6 @@ module TestEnv
@forked_repo_path ||= Rails.root.join('tmp', 'tests', forked_repo_name)
end
- def forked_repo_path_bare
- "#{forked_repo_path}_bare"
- end
-
def forked_repo_name
'gitlab-test-fork'
end
@@ -244,19 +234,22 @@ module TestEnv
end
def set_repo_refs(repo_path, branch_sha)
- instructions = branch_sha.map {|branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00"
+ instructions = branch_sha.map { |branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00"
update_refs = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
reset = proc do
- IO.popen(update_refs, "w") {|io| io.write(instructions) }
- $?.success?
+ Dir.chdir(repo_path) do
+ IO.popen(update_refs, "w") { |io| io.write(instructions) }
+ $?.success?
+ end
end
- Dir.chdir(repo_path) do
- # Try to reset without fetching to avoid using the network.
- unless reset.call
- raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
- raise 'The fetched test seed does not contain the required revision.' unless reset.call
- end
+ # Try to reset without fetching to avoid using the network.
+ unless reset.call
+ raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin))
+
+ # Before we used Git clone's --mirror option, bare repos could end up
+ # with missing refs, clearing them and retrying should fix the issue.
+ cleanup && init unless reset.call
end
end
end