summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2017-05-16 10:46:52 +0100
committerPhil Hughes <me@iamphill.com>2017-05-16 10:46:52 +0100
commit468afcaaff7fa0dd75f52890a410b018d7c06b0f (patch)
treefb723b33cdc76aa6163210f7c90eb8a3c1673a75
parentc4fea61f152bc8ea73d912af3467a35b34e74aaf (diff)
parent5f2b142b66ed8f355b36b91907ca9bea17c070f2 (diff)
downloadgitlab-ce-468afcaaff7fa0dd75f52890a410b018d7c06b0f.tar.gz
Merge branch 'issue-edit-inline' into issue-edit-inline-confidential
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js10
-rw-r--r--app/assets/javascripts/filtered_search/stores/recent_searches_store.js1
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue43
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue34
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue36
-rw-r--r--app/assets/javascripts/issue_show/services/index.js6
-rw-r--r--app/assets/javascripts/notes.js8
-rw-r--r--app/assets/stylesheets/framework/nav.scss22
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss1
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss12
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss23
-rw-r--r--app/assets/stylesheets/pages/notes.scss9
-rw-r--r--app/controllers/projects/issues_controller.rb6
-rw-r--r--[-rwxr-xr-x]app/controllers/projects/merge_requests_controller.rb0
-rw-r--r--app/finders/users_finder.rb74
-rw-r--r--app/helpers/submodule_helper.rb4
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/views/projects/builds/_sidebar.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml70
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--changelogs/unreleased/31106-tabs-alignment.yml4
-rw-r--r--changelogs/unreleased/31886-remover-comment-load-spinner.yml4
-rw-r--r--changelogs/unreleased/31902-namespace-recent-searches-to-project.yml4
-rw-r--r--changelogs/unreleased/adam-influxdb-hostname.yml4
-rw-r--r--changelogs/unreleased/add-index-for-auto_canceled_by_id-mysql.yml4
-rw-r--r--changelogs/unreleased/enable-auto-cancelling-by-default.yml4
-rw-r--r--changelogs/unreleased/fix-github-import.yml4
-rw-r--r--changelogs/unreleased/omega-submodules.yml4
-rw-r--r--config/environments/production.rb2
-rw-r--r--config/initializers/8_gitaly.rb6
-rw-r--r--config/routes/project.rb2
-rw-r--r--db/migrate/20170502065653_make_auto_cancel_pending_pipelines_on_by_default.rb13
-rw-r--r--db/migrate/20170502135553_create_index_ci_pipelines_auto_canceled_by_id.rb21
-rw-r--r--db/migrate/20170502140503_create_index_ci_builds_auto_canceled_by_id.rb21
-rw-r--r--db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb15
-rw-r--r--db/schema.rb4
-rw-r--r--doc/articles/how_to_install_git/index.md66
-rw-r--r--doc/articles/index.md4
-rw-r--r--doc/ci/quick_start/README.md6
-rw-r--r--doc/install/kubernetes/gitlab_chart.md4
-rw-r--r--doc/topics/git/index.md1
-rw-r--r--lib/api/users.rb11
-rw-r--r--lib/github/import.rb5
-rw-r--r--lib/gitlab/database/migration_helpers.rb3
-rw-r--r--lib/gitlab/dependency_linker/base_linker.rb10
-rw-r--r--lib/gitlab/dependency_linker/gemfile_linker.rb4
-rw-r--r--lib/gitlab/etag_caching/router.rb6
-rw-r--r--lib/gitlab/file_detector.rb1
-rw-r--r--lib/gitlab/git/repository.rb20
-rw-r--r--lib/gitlab/gitaly_client.rb64
-rw-r--r--lib/gitlab/gitaly_client/commit.rb4
-rw-r--r--lib/gitlab/gitaly_client/notifications.rb2
-rw-r--r--lib/gitlab/gitaly_client/ref.rb2
-rw-r--r--lib/gitlab/metrics.rb3
-rw-r--r--lib/gitlab/workhorse.rb2
-rw-r--r--spec/features/auto_deploy_spec.rb9
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/recent_searches_spec.rb54
-rw-r--r--spec/finders/users_finder_spec.rb66
-rw-r--r--spec/helpers/submodule_helper_spec.rb13
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js2
-rw-r--r--spec/javascripts/notes_spec.js46
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb6
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb21
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/support/filtered_search_helpers.rb6
-rwxr-xr-xspec/support/generate-seed-repo-rb162
-rw-r--r--spec/support/seed_repo.rb11
-rw-r--r--spec/support/test_env.rb5
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb1
-rw-r--r--spec/workers/post_receive_spec.rb7
74 files changed, 854 insertions, 264 deletions
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 9fea563370f..57d247e11a9 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -16,10 +16,14 @@ class FilteredSearchManager {
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
});
- let recentSearchesKey = 'issue-recent-searches';
+ const searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
+ const projectPath = searchHistoryDropdownElement ?
+ searchHistoryDropdownElement.dataset.projectFullPath : 'project';
+ let recentSearchesPagePrefix = 'issue-recent-searches';
if (page === 'merge_requests') {
- recentSearchesKey = 'merge-request-recent-searches';
+ recentSearchesPagePrefix = 'merge-request-recent-searches';
}
+ const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`;
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
// Fetch recent searches from localStorage
@@ -47,7 +51,7 @@ class FilteredSearchManager {
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
this.recentSearchesService,
- document.querySelector('.js-filtered-search-history-dropdown'),
+ searchHistoryDropdownElement,
);
this.recentSearchesRoot.init();
diff --git a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
index 066be69766a..35fc15e4c87 100644
--- a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
+++ b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
@@ -3,6 +3,7 @@ import _ from 'underscore';
class RecentSearchesStore {
constructor(initialState = {}) {
this.state = Object.assign({
+ isLocalStorageAvailable: true,
recentSearches: [],
}, initialState);
}
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 854c7513ee6..a4d517ddded 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -7,8 +7,7 @@ import Service from '../services/index';
import Store from '../stores';
import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
-import confidentialCheckbox from './fields/confidential_checkbox.vue';
-import editActions from './edit_actions.vue';
+import formComponent from './form.vue';
export default {
props: {
@@ -61,9 +60,6 @@ export default {
};
},
computed: {
- elementType() {
- return this.showForm ? 'form' : 'div';
- },
formState() {
return this.store.formState;
},
@@ -71,8 +67,7 @@ export default {
components: {
descriptionComponent,
titleComponent,
- editActions,
- confidentialCheckbox,
+ formComponent,
},
methods: {
openForm() {
@@ -156,25 +151,23 @@ export default {
</script>
<template>
- <div :is="elementType">
- <title-component
- :form-state="formState"
- :show-form="showForm"
- :issuable-ref="issuableRef"
- :title-html="state.titleHtml"
- :title-text="state.titleText" />
- <description-component
- v-if="state.descriptionHtml"
- :can-update="canUpdate"
- :description-html="state.descriptionHtml"
- :description-text="state.descriptionText"
- :updated-at="state.updatedAt"
- :task-status="state.taskStatus" />
- <confidential-checkbox
- v-if="showForm"
- :form-state="formState" />
- <edit-actions
+ <div>
+ <form-component
v-if="canUpdate && showForm"
+ :form-state="formState"
:can-destroy="canDestroy" />
+ <div v-else>
+ <title-component
+ :issuable-ref="issuableRef"
+ :title-html="state.titleHtml"
+ :title-text="state.titleText" />
+ <description-component
+ v-if="state.descriptionHtml"
+ :can-update="canUpdate"
+ :description-html="state.descriptionHtml"
+ :description-text="state.descriptionText"
+ :updated-at="state.updatedAt"
+ :task-status="state.taskStatus" />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
new file mode 100644
index 00000000000..862558562e5
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -0,0 +1,34 @@
+<script>
+ import titleField from './fields/title.vue';
+ import editActions from './edit_actions.vue';
+ import confidentialCheckbox from './fields/confidential_checkbox.vue';
+
+ export default {
+ props: {
+ canDestroy: {
+ type: Boolean,
+ required: true,
+ },
+ formState: {
+ type: Object,
+ required: true,
+ },
+ },
+ components: {
+ titleField,
+ editActions,
+ confidentialCheckbox,
+ },
+ };
+</script>
+
+<template>
+ <form>
+ <title-field
+ :form-state="formState" />
+ <confidential-checkbox
+ :form-state="formState" />
+ <edit-actions
+ :can-destroy="canDestroy" />
+ </form>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue
index d410c61b655..a9dabd4cff1 100644
--- a/app/assets/javascripts/issue_show/components/title.vue
+++ b/app/assets/javascripts/issue_show/components/title.vue
@@ -1,12 +1,8 @@
<script>
import animateMixin from '../mixins/animate';
- import titleField from './fields/title.vue';
export default {
mixins: [animateMixin],
- components: {
- titleField,
- },
data() {
return {
preAnimation: false,
@@ -27,14 +23,6 @@
type: String,
required: true,
},
- formState: {
- type: Object,
- required: true,
- },
- showForm: {
- type: Boolean,
- required: true,
- },
},
watch: {
titleHtml() {
@@ -53,19 +41,13 @@
</script>
<template>
- <div>
- <title-field
- v-if="showForm"
- :form-state="formState" />
- <h2
- v-else
- class="title"
- :class="{
- 'issue-realtime-pre-pulse': preAnimation,
- 'issue-realtime-trigger-pulse': pulseAnimation
- }"
- v-html="titleHtml"
- >
- </h2>
- </div>
+ <h2
+ class="title"
+ :class="{
+ 'issue-realtime-pre-pulse': preAnimation,
+ 'issue-realtime-trigger-pulse': pulseAnimation
+ }"
+ v-html="titleHtml"
+ >
+ </h2>
</template>
diff --git a/app/assets/javascripts/issue_show/services/index.js b/app/assets/javascripts/issue_show/services/index.js
index 5da15882c25..0ceff34cf8b 100644
--- a/app/assets/javascripts/issue_show/services/index.js
+++ b/app/assets/javascripts/issue_show/services/index.js
@@ -8,15 +8,15 @@ export default class Service {
this.endpoint = endpoint;
this.resource = Vue.resource(this.endpoint, {}, {
- rendered_title: {
+ realtimeChanges: {
method: 'GET',
- url: `${this.endpoint}/rendered_title`,
+ url: `${this.endpoint}/realtime_changes`,
},
});
}
getData() {
- return this.resource.rendered_title();
+ return this.resource.realtimeChanges();
}
deleteIssuable() {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 7ba44835741..f143bfbfc29 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -24,7 +24,7 @@ const normalizeNewlines = function(str) {
(function() {
this.Notes = (function() {
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
- const REGEX_SLASH_COMMANDS = /^\/\w+/gm;
+ const REGEX_SLASH_COMMANDS = /^\/\w+.*$/gm;
Notes.interval = null;
@@ -1170,6 +1170,7 @@ const normalizeNewlines = function(str) {
*/
Notes.prototype.createPlaceholderNote = function({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname }) {
const discussionClass = isDiscussionNote ? 'discussion' : '';
+ const escapedFormContent = _.escape(formContent);
const $tempNote = $(
`<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
<div class="timeline-entry-inner">
@@ -1183,14 +1184,11 @@ const normalizeNewlines = function(str) {
<span class="hidden-xs">${currentUserFullname}</span>
<span class="note-headline-light">@${currentUsername}</span>
</a>
- <span class="note-headline-light">
- <i class="fa fa-spinner fa-spin" aria-label="Comment is being posted" aria-hidden="true"></i>
- </span>
</div>
</div>
<div class="note-body">
<div class="note-text">
- <p>${formContent}</p>
+ <p>${escapedFormContent}</p>
</div>
</div>
</div>
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 64e6ab391b6..d2f6a227128 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -24,10 +24,10 @@
}
@mixin scrolling-links() {
- white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
+ display: flex;
&::-webkit-scrollbar {
display: none;
@@ -35,6 +35,7 @@
}
.nav-links {
+ display: flex;
padding: 0;
margin: 0;
list-style: none;
@@ -42,17 +43,16 @@
border-bottom: 1px solid $border-color;
li {
- display: inline-block;
+ display: flex;
a {
- display: inline-block;
padding: $gl-btn-padding;
padding-bottom: 11px;
- margin-bottom: -1px;
font-size: 14px;
line-height: 28px;
color: $gl-text-color-secondary;
border-bottom: 2px solid transparent;
+ white-space: nowrap;
&:hover,
&:active,
@@ -85,10 +85,10 @@
.container-fluid {
background-color: $gray-normal;
margin-bottom: 0;
+ display: flex;
}
li {
-
&.active a {
border-bottom: none;
color: $link-underline-blue;
@@ -137,9 +137,9 @@
}
.nav-links {
- display: inline-block;
margin-bottom: 0;
border-bottom: none;
+ float: left;
&.wide {
width: 100%;
@@ -337,6 +337,10 @@
border-bottom: none;
height: 51px;
+ @media (min-width: $screen-sm-min) {
+ justify-content: center;
+ }
+
li {
a {
padding-top: 10px;
@@ -347,6 +351,7 @@
.scrolling-tabs-container {
position: relative;
+ overflow: hidden;
.nav-links {
@include scrolling-links();
@@ -484,10 +489,7 @@
.inner-page-scroll-tabs {
position: relative;
-
- .nav-links {
- padding-bottom: 1px;
- }
+ overflow: hidden;
.fade-right {
@include fade(left, $white-light);
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 2b5ab539955..018f61ca3a8 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -53,6 +53,7 @@
.right-sidebar-expanded {
padding-right: 0;
+ z-index: 300;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
&:not(.wiki-sidebar):not(.build-sidebar) .content-wrapper {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 724b4080ee0..14a62b6cbf0 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -378,7 +378,7 @@
background-color: $row-hover;
}
- .fa-spinner {
+ .fa-refresh {
font-size: 13px;
margin-left: 3px;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index c4210ffd823..0184208ab82 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -23,16 +23,6 @@
.merge-manually {
@extend .fixed-width-container;
}
-
- .merge-request-tabs-holder {
- &.affix {
- border-bottom: 1px solid $border-color;
-
- .nav-links {
- border: 0;
- }
- }
- }
}
.merge-request-details {
@@ -206,7 +196,7 @@
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
- z-index: 2;
+ z-index: 200;
&.right-sidebar-expanded {
width: $gutter_width;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 0173a05b403..d208e54207e 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -121,6 +121,7 @@
.dropdown-menu {
margin-top: 11px;
+ z-index: 200;
}
.ci-action-icon-wrapper {
@@ -690,8 +691,9 @@
.merge-request-tabs-holder {
top: $header-height;
- z-index: 10;
+ z-index: 100;
background-color: $white-light;
+ border-bottom: 1px solid $border-color;
@media(min-width: $screen-sm-min) {
position: sticky;
@@ -711,6 +713,16 @@
padding-right: $gl-padding;
}
}
+
+ .nav-links {
+ border: 0;
+ }
+}
+
+.merge-request-tabs {
+ display: flex;
+ margin-bottom: 0;
+ padding: 0;
}
.limit-container-width {
@@ -721,6 +733,15 @@
}
}
+.merge-request-tabs-container {
+ display: flex;
+ justify-content: space-between;
+
+ @media (max-width: $screen-xs-max) {
+ flex-direction: column-reverse;
+ }
+}
+
.limit-container-width:not(.container-limited) {
.merge-request-tabs-holder:not(.affix) {
.merge-request-tabs-container {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 0600bb1cb1a..5b6aa9d74f6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -609,6 +609,15 @@ ul.notes {
}
.line-resolve-all-container {
+ @media (min-width: $screen-sm-min) {
+ margin-right: 0;
+ padding-left: $gl-padding;
+ }
+
+ > div {
+ white-space: nowrap;
+ }
+
.btn-group {
margin-left: -4px;
}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 760ba246e3e..46438e68d54 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -11,10 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
- :related_branches, :can_create_branch, :rendered_title, :create_merge_request]
+ :related_branches, :can_create_branch, :realtime_changes, :create_merge_request]
# Allow read any issue
- before_action :authorize_read_issue!, only: [:show, :rendered_title]
+ before_action :authorize_read_issue!, only: [:show, :realtime_changes]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
@@ -199,7 +199,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
- def rendered_title
+ def realtime_changes
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: {
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index b99ccd453b8..b99ccd453b8 100755..100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
new file mode 100644
index 00000000000..dbd50d1db7c
--- /dev/null
+++ b/app/finders/users_finder.rb
@@ -0,0 +1,74 @@
+# UsersFinder
+#
+# Used to filter users by set of params
+#
+# Arguments:
+# current_user - which user use
+# params:
+# username: string
+# extern_uid: string
+# provider: string
+# search: string
+# active: boolean
+# blocked: boolean
+# external: boolean
+#
+class UsersFinder
+ attr_accessor :current_user, :params
+
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute
+ users = User.all
+ users = by_username(users)
+ users = by_search(users)
+ users = by_blocked(users)
+ users = by_active(users)
+ users = by_external_identity(users)
+ users = by_external(users)
+
+ users
+ end
+
+ private
+
+ def by_username(users)
+ return users unless params[:username]
+
+ users.where(username: params[:username])
+ end
+
+ def by_search(users)
+ return users unless params[:search].present?
+
+ users.search(params[:search])
+ end
+
+ def by_blocked(users)
+ return users unless params[:blocked]
+
+ users.blocked
+ end
+
+ def by_active(users)
+ return users unless params[:active]
+
+ users.active
+ end
+
+ def by_external_identity(users)
+ return users unless current_user.admin? && params[:extern_uid] && params[:provider]
+
+ users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
+ end
+
+ def by_external(users)
+ return users = users.where.not(external: true) unless current_user.admin?
+ return users unless params[:external]
+
+ users.external
+ end
+end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index b739554a7a4..09b73eee8cf 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -7,6 +7,10 @@ module SubmoduleHelper
def submodule_links(submodule_item, ref = nil, repository = @repository)
url = repository.submodule_url_for(ref, submodule_item.path)
+ if url == '.' || url == './'
+ url = File.join(Gitlab.config.gitlab.url, @project.full_path)
+ end
+
if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
namespace, project = $1, $2
project.sub!(/\.git\z/, '')
diff --git a/app/models/issue.rb b/app/models/issue.rb
index ecfc33ec1a1..a88dbb3e065 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -292,7 +292,7 @@ class Issue < ActiveRecord::Base
end
def expire_etag_cache
- key = Gitlab::Routing.url_helpers.rendered_title_namespace_project_issue_path(
+ key = Gitlab::Routing.url_helpers.realtime_changes_namespace_project_issue_path(
project.namespace,
project,
self
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b1563bfba8b..9153e5ae5a8 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1163,8 +1163,6 @@ class Repository
@project.repository_storage_path
end
- delegate :gitaly_channel, :gitaly_repository, to: :raw_repository
-
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, path_with_namespace + '.git')
end
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 8a5c8e2429c..8032d81cd91 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -136,7 +136,7 @@
- else
= build.id
- if build.retried?
- %i.fa.fa-spinner.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
+ %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
:javascript
new Sidebar();
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 25b8567b78f..b7515e1d91f 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -27,40 +27,42 @@
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
- .merge-request-tabs-container.scrolling-tabs-container.inner-page-scroll-tabs
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- %ul.merge-request-tabs.nav-links.scrolling-tabs
- %li.notes-tab
- = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
- Discussion
- %span.badge= @merge_request.related_notes.user.count
- - if @merge_request.source_project
- %li.commits-tab
- = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
- Commits
- %span.badge= @commits_count
- - if @pipelines.any?
- %li.pipelines-tab
- = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
- Pipelines
- %span.badge= @pipelines.size
- %li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
- Changes
- %span.badge= @merge_request.diff_size
- %li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true }
- %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
- %div
- .line-resolve-all{ "v-show" => "discussionCount > 0",
- ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
- %span.line-resolve-btn.is-disabled{ type: "button",
- ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
- = render "shared/icons/icon_status_success.svg"
- %span.line-resolve-text
- {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
- = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
- = render "discussions/jump_to_next"
+ .merge-request-tabs-container
+ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
+ .fade-left= icon('angle-left')
+ .fade-right= icon('angle-right')
+ .nav-links.scrolling-tabs
+ %ul.merge-request-tabs
+ %li.notes-tab
+ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
+ Discussion
+ %span.badge= @merge_request.related_notes.user.count
+ - if @merge_request.source_project
+ %li.commits-tab
+ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+ Commits
+ %span.badge= @commits_count
+ - if @pipelines.any?
+ %li.pipelines-tab
+ = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
+ Pipelines
+ %span.badge= @pipelines.size
+ %li.diffs-tab
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
+ Changes
+ %span.badge= @merge_request.diff_size
+ #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
+ %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
+ %div
+ .line-resolve-all{ "v-show" => "discussionCount > 0",
+ ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
+ %span.line-resolve-btn.is-disabled{ type: "button",
+ ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
+ = render "shared/icons/icon_status_success.svg"
+ %span.line-resolve-text
+ {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
+ = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
+ = render "discussions/jump_to_next"
.tab-content#diff-notes-app
#notes.notes.tab-pane.voting_notes
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 622e2f33eea..0e535117353 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -19,7 +19,7 @@
dropdown_class: "filtered-search-history-dropdown",
content_class: "filtered-search-history-dropdown-content",
title: "Recent searches" }) do
- .js-filtered-search-history-dropdown
+ .js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } }
.filtered-search-box-input-container
.scroll-container
%ul.tokens-container.list-unstyled
diff --git a/changelogs/unreleased/31106-tabs-alignment.yml b/changelogs/unreleased/31106-tabs-alignment.yml
new file mode 100644
index 00000000000..53da08cc32d
--- /dev/null
+++ b/changelogs/unreleased/31106-tabs-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: prevent nav tabs from wrapping to new line
+merge_request:
+author:
diff --git a/changelogs/unreleased/31886-remover-comment-load-spinner.yml b/changelogs/unreleased/31886-remover-comment-load-spinner.yml
new file mode 100644
index 00000000000..4b36538064a
--- /dev/null
+++ b/changelogs/unreleased/31886-remover-comment-load-spinner.yml
@@ -0,0 +1,4 @@
+---
+title: Remove spinner from loading comment
+merge_request:
+author:
diff --git a/changelogs/unreleased/31902-namespace-recent-searches-to-project.yml b/changelogs/unreleased/31902-namespace-recent-searches-to-project.yml
new file mode 100644
index 00000000000..e00eb6d8f72
--- /dev/null
+++ b/changelogs/unreleased/31902-namespace-recent-searches-to-project.yml
@@ -0,0 +1,4 @@
+---
+title: Scope issue/merge request recent searches to project
+merge_request:
+author:
diff --git a/changelogs/unreleased/adam-influxdb-hostname.yml b/changelogs/unreleased/adam-influxdb-hostname.yml
new file mode 100644
index 00000000000..ab201ae7894
--- /dev/null
+++ b/changelogs/unreleased/adam-influxdb-hostname.yml
@@ -0,0 +1,4 @@
+---
+title: Allow GitLab instance to start when InfluxDB hostname cannot be resolved
+merge_request: 11356
+author:
diff --git a/changelogs/unreleased/add-index-for-auto_canceled_by_id-mysql.yml b/changelogs/unreleased/add-index-for-auto_canceled_by_id-mysql.yml
new file mode 100644
index 00000000000..eac78e9ee1f
--- /dev/null
+++ b/changelogs/unreleased/add-index-for-auto_canceled_by_id-mysql.yml
@@ -0,0 +1,4 @@
+---
+title: Add indices for auto_canceled_by_id for ci_pipelines and ci_builds on PostgreSQL
+merge_request: 11034
+author:
diff --git a/changelogs/unreleased/enable-auto-cancelling-by-default.yml b/changelogs/unreleased/enable-auto-cancelling-by-default.yml
new file mode 100644
index 00000000000..8b1659bf38b
--- /dev/null
+++ b/changelogs/unreleased/enable-auto-cancelling-by-default.yml
@@ -0,0 +1,4 @@
+---
+title: Enable cancelling non-HEAD pending pipelines by default for all projects
+merge_request: 11023
+author:
diff --git a/changelogs/unreleased/fix-github-import.yml b/changelogs/unreleased/fix-github-import.yml
new file mode 100644
index 00000000000..3a57152f7a8
--- /dev/null
+++ b/changelogs/unreleased/fix-github-import.yml
@@ -0,0 +1,4 @@
+---
+title: Fix token interpolation when setting the Github remote
+merge_request:
+author:
diff --git a/changelogs/unreleased/omega-submodules.yml b/changelogs/unreleased/omega-submodules.yml
new file mode 100644
index 00000000000..1488eb72174
--- /dev/null
+++ b/changelogs/unreleased/omega-submodules.yml
@@ -0,0 +1,4 @@
+---
+title: 'Repository browser: handle in-repository submodule urls'
+merge_request:
+author: David Turner
diff --git a/config/environments/production.rb b/config/environments/production.rb
index a9d8ac4b6d4..82a19085b1d 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -16,7 +16,7 @@ Rails.application.configure do
# config.assets.css_compressor = :sass
# Don't fallback to assets pipeline if a precompiled asset is missed
- config.assets.compile = true
+ config.assets.compile = false
# Generate digests for assets URLs
config.assets.digest = true
diff --git a/config/initializers/8_gitaly.rb b/config/initializers/8_gitaly.rb
index 42ec7240b0f..31c7c91d78f 100644
--- a/config/initializers/8_gitaly.rb
+++ b/config/initializers/8_gitaly.rb
@@ -1,6 +1,8 @@
require 'uri'
-# Make sure we initialize our Gitaly channels before Sidekiq starts multi-threaded execution.
if Gitlab.config.gitaly.enabled || Rails.env.test?
- Gitlab::GitalyClient.configure_channels
+ Gitlab.config.repositories.storages.keys.each do |storage|
+ # Force validation of each address
+ Gitlab::GitalyClient.address(storage)
+ end
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index a6c104c2d3f..c786cbdee1e 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -244,7 +244,7 @@ constraints(ProjectUrlConstrainer.new) do
get :referenced_merge_requests
get :related_branches
get :can_create_branch
- get :rendered_title
+ get :realtime_changes
post :create_merge_request
end
collection do
diff --git a/db/migrate/20170502065653_make_auto_cancel_pending_pipelines_on_by_default.rb b/db/migrate/20170502065653_make_auto_cancel_pending_pipelines_on_by_default.rb
new file mode 100644
index 00000000000..03bf626a08a
--- /dev/null
+++ b/db/migrate/20170502065653_make_auto_cancel_pending_pipelines_on_by_default.rb
@@ -0,0 +1,13 @@
+class MakeAutoCancelPendingPipelinesOnByDefault < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ change_column_default(:projects, :auto_cancel_pending_pipelines, 1)
+ end
+
+ def down
+ change_column_default(:projects, :auto_cancel_pending_pipelines, 0)
+ end
+end
diff --git a/db/migrate/20170502135553_create_index_ci_pipelines_auto_canceled_by_id.rb b/db/migrate/20170502135553_create_index_ci_pipelines_auto_canceled_by_id.rb
new file mode 100644
index 00000000000..b64d7e0e3f6
--- /dev/null
+++ b/db/migrate/20170502135553_create_index_ci_pipelines_auto_canceled_by_id.rb
@@ -0,0 +1,21 @@
+class CreateIndexCiPipelinesAutoCanceledById < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # MySQL would already have the index
+ unless index_exists?(:ci_pipelines, :auto_canceled_by_id)
+ add_concurrent_index(:ci_pipelines, :auto_canceled_by_id)
+ end
+ end
+
+ def down
+ # We cannot remove index for MySQL because it's needed for foreign key
+ if Gitlab::Database.postgresql?
+ remove_concurrent_index(:ci_pipelines, :auto_canceled_by_id)
+ end
+ end
+end
diff --git a/db/migrate/20170502140503_create_index_ci_builds_auto_canceled_by_id.rb b/db/migrate/20170502140503_create_index_ci_builds_auto_canceled_by_id.rb
new file mode 100644
index 00000000000..0a8d2c8ff61
--- /dev/null
+++ b/db/migrate/20170502140503_create_index_ci_builds_auto_canceled_by_id.rb
@@ -0,0 +1,21 @@
+class CreateIndexCiBuildsAutoCanceledById < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # MySQL would already have the index
+ unless index_exists?(:ci_builds, :auto_canceled_by_id)
+ add_concurrent_index(:ci_builds, :auto_canceled_by_id)
+ end
+ end
+
+ def down
+ # We cannot remove index for MySQL because it's needed for foreign key
+ if Gitlab::Database.postgresql?
+ remove_concurrent_index(:ci_builds, :auto_canceled_by_id)
+ end
+ end
+end
diff --git a/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb b/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
new file mode 100644
index 00000000000..a19b73fc114
--- /dev/null
+++ b/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
@@ -0,0 +1,15 @@
+class EnableAutoCancelPendingPipelinesForAll < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ update_column_in_batches(:projects, :auto_cancel_pending_pipelines, 1)
+ end
+
+ def down
+ # Nothing we can do!
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 65eaccf766a..a683521c84b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -235,6 +235,7 @@ ActiveRecord::Schema.define(version: 20170508190732) do
t.boolean "retried"
end
+ add_index "ci_builds", ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
@@ -284,6 +285,7 @@ ActiveRecord::Schema.define(version: 20170508190732) do
t.integer "pipeline_schedule_id"
end
+ add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
add_index "ci_pipelines", ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
add_index "ci_pipelines", ["project_id", "ref", "status"], name: "index_ci_pipelines_on_project_id_and_ref_and_status", using: :btree
add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree
@@ -986,7 +988,7 @@ ActiveRecord::Schema.define(version: 20170508190732) do
t.text "description_html"
t.boolean "only_allow_merge_if_all_discussions_are_resolved"
t.boolean "printing_merge_request_link_enabled", default: true, null: false
- t.integer "auto_cancel_pending_pipelines", default: 0, null: false
+ t.integer "auto_cancel_pending_pipelines", default: 1, null: false
t.string "import_jid"
t.integer "cached_markdown_version"
t.datetime "last_repository_updated_at"
diff --git a/doc/articles/how_to_install_git/index.md b/doc/articles/how_to_install_git/index.md
new file mode 100644
index 00000000000..66d866b2d09
--- /dev/null
+++ b/doc/articles/how_to_install_git/index.md
@@ -0,0 +1,66 @@
+# Installing Git
+
+> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** user guide ||
+> **Level:** beginner ||
+> **Author:** [Sean Packham](https://gitlab.com/SeanPackham) ||
+> **Publication date:** 2017/05/15
+
+To begin contributing to GitLab projects
+you will need to install the Git client on your computer.
+This article will show you how to install Git on macOS, Ubuntu Linux and Windows.
+
+## Install Git on macOS using the Homebrew package manager
+
+Although it is easy to use the version of Git shipped with macOS
+or install the latest version of Git on macOS by downloading it from the project website,
+we recommend installing it via Homebrew to get access to
+an extensive selection of dependancy managed libraries and applications.
+
+If you are sure you don't need access to any additional development libraries
+or don't have approximately 15gb of available disk space for Xcode and Homebrew
+use one of the the aforementioned methods.
+
+### Installing Xcode
+
+Xcode is needed by Homebrew to build dependencies.
+You can install [XCode](https://developer.apple.com/xcode/)
+through the macOS App Store.
+
+### Installing Homebrew
+
+Once Xcode is installed browse to the [Homebrew website](http://brew.sh/index.html)
+for the official Homebrew installation instructions.
+
+### Installing Git via Homebrew
+
+With Homebrew installed you are now ready to install Git.
+Open a Terminal and enter in the following command:
+
+```bash
+brew install git
+```
+
+Congratulations you should now have Git installed via Homebrew.
+Next read our article on [adding an SSH key to GitLab](../../ssh/README.md).
+
+## Install Git on Ubuntu Linux
+
+On Ubuntu and other Linux operating systems
+it is recommended to use the built in package manager to install Git.
+
+Open a Terminal and enter in the following commands
+to install the latest Git from the official Git maintained package archives:
+
+```bash
+sudo apt-add-repository ppa:git-core/ppa
+sudo apt-get update
+sudo apt-get install git
+```
+
+Congratulations you should now have Git installed via the Ubuntu package manager.
+Next read our article on [adding an SSH key to GitLab](../../ssh/README.md).
+
+## Installing Git on Windows from the Git website
+
+Browse to the [Git website](https://git-scm.com/) and download and install Git for Windows.
+Next read our article on [adding an SSH key to GitLab](../../ssh/README.md).
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 49db64134f5..342fa88e80f 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -12,6 +12,10 @@ They are written by members of the GitLab Team and by
- **LDAP**
- [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md)
+## Git
+
+- [How to install Git](how_to_install_git/index.md)
+
## GitLab Pages
- **GitLab Pages from A to Z**
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 30f209f80eb..41cae58782d 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -155,7 +155,7 @@ Find more information about different Runners in the
[Runners](../runners/README.md) documentation.
You can find whether any Runners are assigned to your project by going to
-**Settings ➔ Runners**. Setting up a Runner is easy and straightforward. The
+**Settings ➔ CI/CD Pipelines**. Setting up a Runner is easy and straightforward. The
official Runner supported by GitLab is written in Go and its documentation
can be found at <https://docs.gitlab.com/runner/>.
@@ -168,7 +168,7 @@ Follow the links above to set up your own Runner or use a Shared Runner as
described in the next section.
Once the Runner has been set up, you should see it on the Runners page of your
-project, following **Settings ➔ Runners**.
+project, following **Settings ➔ CI/CD Pipelines**.
![Activated runners](img/runners_activated.png)
@@ -181,7 +181,7 @@ These are special virtual machines that run on GitLab's infrastructure and can
build any project.
To enable the **Shared Runners** you have to go to your project's
-**Settings ➔ Runners** and click **Enable shared runners**.
+**Settings ➔ CI/CD Pipelines** and click **Enable shared runners**.
[Read more on Shared Runners](../runners/README.md).
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 35d395af024..2d7edbe16e4 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -392,7 +392,7 @@ Once you [have configured](#configuration) GitLab in your `values.yml` file,
run the following:
```bash
-helm install --namepace <NAMEPACE> --name gitlab -f <CONFIG_VALUES_FILE> gitlab/gitlab
+helm install --namespace <NAMESPACE> --name gitlab -f <CONFIG_VALUES_FILE> gitlab/gitlab
```
where:
@@ -407,7 +407,7 @@ Once your GitLab Chart is installed, configuration changes and chart updates
should we done using `helm upgrade`
```bash
-helm upgrade --namepace <NAMEPACE> -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab
+helm upgrade --namespace <NAMESPACE> -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab
```
where:
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index d13066c9015..604f9375714 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -22,6 +22,7 @@ We've gathered some resources to help you to get the best from Git with GitLab.
- [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
- [Squashing commits](../../workflow/gitlab_flow.md#squashing-commits-with-rebase)
- **Articles:**
+ - [How to install Git](../../articles/how_to_install_git/index.md)
- [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/)
- [Eight Tips to help you work better with Git](https://about.gitlab.com/2015/02/19/8-tips-to-help-you-work-better-with-git/)
- **Presentations:**
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 40acaebf670..3d83720b7b9 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -56,16 +56,7 @@ module API
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
- users = User.all
- users = User.where(username: params[:username]) if params[:username]
- users = users.active if params[:active]
- users = users.search(params[:search]) if params[:search].present?
- users = users.blocked if params[:blocked]
-
- if current_user.admin?
- users = users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid])) if params[:extern_uid] && params[:provider]
- users = users.external if params[:external]
- end
+ users = UsersFinder.new(current_user, params).execute
entity = current_user.admin? ? Entities::UserPublic : Entities::UserBasic
present paginate(users), with: entity
diff --git a/lib/github/import.rb b/lib/github/import.rb
index 06beb607a3e..9c7eb965f93 100644
--- a/lib/github/import.rb
+++ b/lib/github/import.rb
@@ -1,4 +1,5 @@
require_relative 'error'
+
module Github
class Import
include Gitlab::ShellAdapter
@@ -6,6 +7,7 @@ module Github
class MergeRequest < ::MergeRequest
self.table_name = 'merge_requests'
+ self.reset_callbacks :create
self.reset_callbacks :save
self.reset_callbacks :commit
self.reset_callbacks :update
@@ -16,6 +18,7 @@ module Github
self.table_name = 'issues'
self.reset_callbacks :save
+ self.reset_callbacks :create
self.reset_callbacks :commit
self.reset_callbacks :update
self.reset_callbacks :validate
@@ -79,7 +82,7 @@ module Github
def fetch_repository
begin
project.create_repository unless project.repository.exists?
- project.repository.add_remote('github', "https://{options.fetch(:token)}@github.com/#{repo}.git")
+ project.repository.add_remote('github', "https://#{options.fetch(:token)}@github.com/#{repo}.git")
project.repository.set_remote_as_mirror('github')
project.repository.fetch_remote('github', forced: true)
rescue Gitlab::Shell::Error => e
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index f3476dadec8..e76c9abbe04 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -283,7 +283,6 @@ module Gitlab
add_column(table, new, new_type,
limit: old_col.limit,
- null: old_col.null,
precision: old_col.precision,
scale: old_col.scale)
@@ -307,6 +306,8 @@ module Gitlab
update_column_in_batches(table, new, Arel::Table.new(table)[old])
+ change_column_null(table, new, false) unless old_col.null
+
copy_indexes(table, old, new)
copy_foreign_keys(table, old, new)
end
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 40a4ad11372..5f4027e7e81 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -1,8 +1,14 @@
module Gitlab
module DependencyLinker
class BaseLinker
- def self.link(plain_text, highlighted_text)
- new(plain_text, highlighted_text).link
+ class_attribute :file_type
+
+ def self.support?(blob_name)
+ Gitlab::FileDetector.type_of(blob_name) == file_type
+ end
+
+ def self.link(*args)
+ new(*args).link
end
attr_accessor :plain_text, :highlighted_text
diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb
index 45be760d89e..9b82e126528 100644
--- a/lib/gitlab/dependency_linker/gemfile_linker.rb
+++ b/lib/gitlab/dependency_linker/gemfile_linker.rb
@@ -1,9 +1,7 @@
module Gitlab
module DependencyLinker
class GemfileLinker < BaseLinker
- def self.support?(blob_name)
- blob_name == 'Gemfile' || blob_name == 'gems.rb'
- end
+ self.file_type = :gemfile
private
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index 31a5b9d108b..ba31041d0c1 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -7,8 +7,8 @@ module Gitlab
# - Don't contain a reserved word (expect for the words used in the
# regex itself)
# - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route
- # - Ending in `issues/id`/rendered_title` for the `issue_title` route
- USED_IN_ROUTES = %w[noteable issue notes issues rendered_title
+ # - Ending in `issues/id`/realtime_changes` for the `issue_title` route
+ USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
commit pipelines merge_requests new].freeze
RESERVED_WORDS = DynamicPathValidator::WILDCARD_ROUTES - USED_IN_ROUTES
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS)
@@ -18,7 +18,7 @@ module Gitlab
'issue_notes'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/issues/\d+/rendered_title\z),
+ %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/issues/\d+/realtime_changes\z),
'issue_title'
),
Gitlab::EtagCaching::Router::Route.new(
diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb
index f8b3d0b4965..c6a89597b23 100644
--- a/lib/gitlab/file_detector.rb
+++ b/lib/gitlab/file_detector.rb
@@ -12,6 +12,7 @@ module Gitlab
version: 'version',
gitignore: '.gitignore',
koding: '.koding.yml',
+ gemfile: /\A(Gemfile|gems\.rb)\z/,
gitlab_ci: '.gitlab-ci.yml',
avatar: /\Alogo\.(png|jpg|gif)\z/,
route_map: 'route-map.yml'
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 256318cb833..d380c5021ee 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -27,13 +27,15 @@ module Gitlab
# Rugged repo object
attr_reader :rugged
+ attr_reader :storage
+
# 'path' must be the path to a _bare_ git repository, e.g.
# /path/to/my-repo.git
- def initialize(repository_storage, relative_path)
- @repository_storage = repository_storage
+ def initialize(storage, relative_path)
+ @storage = storage
@relative_path = relative_path
- storage_path = Gitlab.config.repositories.storages[@repository_storage]['path']
+ storage_path = Gitlab.config.repositories.storages[@storage]['path']
@path = File.join(storage_path, @relative_path)
@name = @relative_path.split("/").last
@attributes = Gitlab::Git::Attributes.new(path)
@@ -114,7 +116,7 @@ module Gitlab
# Returns the number of valid branches
def branch_count
- Gitlab::GitalyClient.migrate(:branch_names) do |is_enabled|
+ gitaly_migrate(:branch_names) do |is_enabled|
if is_enabled
gitaly_ref_client.count_branch_names
else
@@ -133,7 +135,7 @@ module Gitlab
# Returns the number of valid tags
def tag_count
- Gitlab::GitalyClient.migrate(:tag_names) do |is_enabled|
+ gitaly_migrate(:tag_names) do |is_enabled|
if is_enabled
gitaly_ref_client.count_tag_names
else
@@ -471,7 +473,7 @@ module Gitlab
def ref_name_for_sha(ref_path, sha)
# NOTE: This feature is intentionally disabled until
# https://gitlab.com/gitlab-org/gitaly/issues/180 is resolved
- # Gitlab::GitalyClient.migrate(:find_ref_name) do |is_enabled|
+ # gitaly_migrate(:find_ref_name) do |is_enabled|
# if is_enabled
# gitaly_ref_client.find_ref_name(sha, ref_path)
# else
@@ -965,11 +967,7 @@ module Gitlab
end
def gitaly_repository
- Gitlab::GitalyClient::Util.repository(@repository_storage, @relative_path)
- end
-
- def gitaly_channel
- Gitlab::GitalyClient.get_channel(@repository_storage)
+ Gitlab::GitalyClient::Util.repository(@storage, @relative_path)
end
private
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index c69676a1dac..72466700c05 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -4,56 +4,42 @@ module Gitlab
module GitalyClient
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'.freeze
- # This function is not thread-safe because it updates Hashes in instance variables.
- def self.configure_channels
- @addresses = {}
- @channels = {}
- Gitlab.config.repositories.storages.each do |name, params|
- address = params['gitaly_address']
- unless address.present?
- raise "storage #{name.inspect} is missing a gitaly_address"
- end
+ MUTEX = Mutex.new
+ private_constant :MUTEX
- unless URI(address).scheme.in?(%w(tcp unix))
- raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'"
+ def self.stub(name, storage)
+ MUTEX.synchronize do
+ @stubs ||= {}
+ @stubs[storage] ||= {}
+ @stubs[storage][name] ||= begin
+ klass = Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
+ addr = address(storage)
+ addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
+ klass.new(addr, :this_channel_is_insecure)
end
-
- @addresses[name] = address
- @channels[name] = new_channel(address)
end
end
- def self.new_channel(address)
- address = address.sub(%r{^tcp://}, '') if URI(address).scheme == 'tcp'
- # NOTE: When Gitaly runs on a Unix socket, permissions are
- # handled using the file system and no additional authentication is
- # required (therefore the :this_channel_is_insecure flag)
- # TODO: Add authentication support when Gitaly is running on a TCP socket.
- GRPC::Core::Channel.new(address, {}, :this_channel_is_insecure)
+ def self.clear_stubs!
+ MUTEX.synchronize do
+ @stubs = nil
+ end
end
- def self.get_channel(storage)
- if !Rails.env.production? && @channels.nil?
- # In development mode the Rails auto-loader may reset the instance
- # variables of this class. What we do here is not thread-safe. In normal
- # circumstances (including production) these instance variables have
- # been initialized from config/initializers.
- configure_channels
- end
+ def self.address(storage)
+ params = Gitlab.config.repositories.storages[storage]
+ raise "storage not found: #{storage.inspect}" if params.nil?
- @channels[storage]
- end
+ address = params['gitaly_address']
+ unless address.present?
+ raise "storage #{storage.inspect} is missing a gitaly_address"
+ end
- def self.get_address(storage)
- if !Rails.env.production? && @addresses.nil?
- # In development mode the Rails auto-loader may reset the instance
- # variables of this class. What we do here is not thread-safe. In normal
- # circumstances (including development) these instance variables have
- # been initialized from config/initializers.
- configure_channels
+ unless URI(address).scheme.in?(%w(tcp unix))
+ raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'"
end
- @addresses[storage]
+ address
end
def self.enabled?
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
index 15c57420fb2..4491903d788 100644
--- a/lib/gitlab/gitaly_client/commit.rb
+++ b/lib/gitlab/gitaly_client/commit.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def is_ancestor(ancestor_id, child_id)
- stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: @repository.gitaly_channel)
+ stub = GitalyClient.stub(:commit, @repository.storage)
request = Gitaly::CommitIsAncestorRequest.new(
repository: @gitaly_repo,
ancestor_id: ancestor_id,
@@ -52,7 +52,7 @@ module Gitlab
end
def diff_service_stub
- Gitaly::Diff::Stub.new(nil, nil, channel_override: @repository.gitaly_channel)
+ GitalyClient.stub(:diff, @repository.storage)
end
end
end
diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notifications.rb
index a94a54883db..719554eac52 100644
--- a/lib/gitlab/gitaly_client/notifications.rb
+++ b/lib/gitlab/gitaly_client/notifications.rb
@@ -6,7 +6,7 @@ module Gitlab
# 'repository' is a Gitlab::Git::Repository
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
- @stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
+ @stub = GitalyClient.stub(:notifications, repository.storage)
end
def post_receive
diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb
index f6c77ef1a3e..bf04e1fa50b 100644
--- a/lib/gitlab/gitaly_client/ref.rb
+++ b/lib/gitlab/gitaly_client/ref.rb
@@ -6,7 +6,7 @@ module Gitlab
# 'repository' is a Gitlab::Git::Repository
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
- @stub = Gitaly::Ref::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
+ @stub = GitalyClient.stub(:ref, repository.storage)
end
def default_branch_name
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index c6dfa4ad9bd..cb8db2f1e9f 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -49,6 +49,9 @@ module Gitlab
end
end
end
+ rescue Errno::EADDRNOTAVAIL, SocketError => ex
+ Gitlab::EnvironmentLogger.error('Cannot resolve InfluxDB address. GitLab Performance Monitoring will not work.')
+ Gitlab::EnvironmentLogger.error(ex)
end
def self.prepare_metrics(metrics)
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 351e2b10595..fe37e4da94f 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -26,7 +26,7 @@ module Gitlab
}
if Gitlab.config.gitaly.enabled
- address = Gitlab::GitalyClient.get_address(project.repository_storage)
+ address = Gitlab::GitalyClient.address(project.repository_storage)
params[:Repository] = repository.gitaly_repository.to_h
feature_enabled = case action.to_s
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index eba1bca83a8..6c7423e4922 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -5,14 +5,7 @@ describe 'Auto deploy' do
let(:project) { create(:project, :repository) }
before do
- project.create_kubernetes_service(
- active: true,
- properties: {
- namespace: project.path,
- api_url: 'https://kubernetes.example.com',
- token: 'a' * 40
- }
- )
+ create :kubernetes_service, project: project
project.team << [user, :master]
login_as user
end
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index dc13cab2cd1..24e2419b5ce 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -14,7 +14,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
end
it 'shows a button to resolve all discussions by creating a new issue' do
- within('li#resolve-count-app') do
+ within('#resolve-count-app') do
expect(page).to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index 08fe3b4553b..09f228bcf49 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
describe 'Recent searches', js: true, feature: true do
include FilteredSearchHelpers
- let!(:group) { create(:group) }
- let!(:project) { create(:project, group: group) }
- let!(:user) { create(:user) }
+ let(:project_1) { create(:empty_project, :public) }
+ let(:project_2) { create(:empty_project, :public) }
+ let(:project_1_local_storage_key) { "#{project_1.full_path}-issue-recent-searches" }
before do
Capybara.ignore_hidden_elements = false
- project.add_master(user)
- group.add_developer(user)
- create(:issue, project: project)
- login_as(user)
+ create(:issue, project: project_1)
+ create(:issue, project: project_2)
+ # Visit any fast-loading page so we can clear local storage without a DOM exception
+ visit '/404'
remove_recent_searches
end
@@ -22,7 +22,7 @@ describe 'Recent searches', js: true, feature: true do
end
it 'searching adds to recent searches' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_issues_path(project_1.namespace, project_1)
input_filtered_search('foo', submit: true)
input_filtered_search('bar', submit: true)
@@ -35,8 +35,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'visiting URL with search params adds to recent searches' do
- visit namespace_project_issues_path(project.namespace, project, label_name: 'foo', search: 'bar')
- visit namespace_project_issues_path(project.namespace, project, label_name: 'qux', search: 'garply')
+ visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'foo', search: 'bar')
+ visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'qux', search: 'garply')
items = all('.filtered-search-history-dropdown-item', visible: false)
@@ -46,9 +46,9 @@ describe 'Recent searches', js: true, feature: true do
end
it 'saved recent searches are restored last on the list' do
- set_recent_searches('["saved1", "saved2"]')
+ set_recent_searches(project_1_local_storage_key, '["saved1", "saved2"]')
- visit namespace_project_issues_path(project.namespace, project, search: 'foo')
+ visit namespace_project_issues_path(project_1.namespace, project_1, search: 'foo')
items = all('.filtered-search-history-dropdown-item', visible: false)
@@ -58,9 +58,27 @@ describe 'Recent searches', js: true, feature: true do
expect(items[2].text).to eq('saved2')
end
+ it 'searches are scoped to projects' do
+ visit namespace_project_issues_path(project_1.namespace, project_1)
+
+ input_filtered_search('foo', submit: true)
+ input_filtered_search('bar', submit: true)
+
+ visit namespace_project_issues_path(project_2.namespace, project_2)
+
+ input_filtered_search('more', submit: true)
+ input_filtered_search('things', submit: true)
+
+ items = all('.filtered-search-history-dropdown-item', visible: false)
+
+ expect(items.count).to eq(2)
+ expect(items[0].text).to eq('things')
+ expect(items[1].text).to eq('more')
+ end
+
it 'clicking item fills search input' do
- set_recent_searches('["foo", "bar"]')
- visit namespace_project_issues_path(project.namespace, project)
+ set_recent_searches(project_1_local_storage_key, '["foo", "bar"]')
+ visit namespace_project_issues_path(project_1.namespace, project_1)
all('.filtered-search-history-dropdown-item', visible: false)[0].trigger('click')
wait_for_filtered_search('foo')
@@ -69,8 +87,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'clear recent searches button, clears recent searches' do
- set_recent_searches('["foo"]')
- visit namespace_project_issues_path(project.namespace, project)
+ set_recent_searches(project_1_local_storage_key, '["foo"]')
+ visit namespace_project_issues_path(project_1.namespace, project_1)
items_before = all('.filtered-search-history-dropdown-item', visible: false)
@@ -83,8 +101,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'shows flash error when failed to parse saved history' do
- set_recent_searches('fail')
- visit namespace_project_issues_path(project.namespace, project)
+ set_recent_searches(project_1_local_storage_key, 'fail')
+ visit namespace_project_issues_path(project_1.namespace, project_1)
expect(find('.flash-alert')).to have_text('An error occured while parsing recent searches')
end
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
new file mode 100644
index 00000000000..780b309b45e
--- /dev/null
+++ b/spec/finders/users_finder_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe UsersFinder do
+ describe '#execute' do
+ let!(:user1) { create(:user, username: 'johndoe') }
+ let!(:user2) { create(:user, :blocked, username: 'notsorandom') }
+ let!(:external_user) { create(:user, :external) }
+ let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+
+ context 'with a normal user' do
+ let(:user) { create(:user) }
+
+ it 'returns all users' do
+ users = described_class.new(user).execute
+
+ expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ end
+
+ it 'filters by username' do
+ users = described_class.new(user, username: 'johndoe').execute
+
+ expect(users).to contain_exactly(user1)
+ end
+
+ it 'filters by search' do
+ users = described_class.new(user, search: 'orando').execute
+
+ expect(users).to contain_exactly(user2)
+ end
+
+ it 'filters by blocked users' do
+ users = described_class.new(user, blocked: true).execute
+
+ expect(users).to contain_exactly(user2)
+ end
+
+ it 'filters by active users' do
+ users = described_class.new(user, active: true).execute
+
+ expect(users).to contain_exactly(user, user1, omniauth_user)
+ end
+
+ it 'returns no external users' do
+ users = described_class.new(user, external: true).execute
+
+ expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ end
+ end
+
+ context 'with an admin user' do
+ let(:admin) { create(:admin) }
+
+ it 'filters by external users' do
+ users = described_class.new(admin, external: true).execute
+
+ expect(users).to contain_exactly(external_user)
+ end
+
+ it 'returns all users' do
+ users = described_class.new(admin).execute
+
+ expect(users).to contain_exactly(admin, user1, user2, external_user, omniauth_user)
+ end
+ end
+ end
+end
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 9da33792659..18935be95c9 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -81,6 +81,19 @@ describe SubmoduleHelper do
end
end
+ context 'in-repository submodule' do
+ let(:group) { create(:group, name: "Master Project", path: "master-project") }
+ let(:project) { create(:empty_project, group: group) }
+ before do
+ self.instance_variable_set(:@project, project)
+ end
+
+ it 'in-repository' do
+ stub_url('./')
+ expect(submodule_links(submodule_item)).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"])
+ end
+ end
+
context 'submodule on gitlab.com' do
it 'detects ssh' do
stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git')
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index a310d5cc918..ce90ac0b4ac 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -29,7 +29,7 @@ describe('Issuable output', () => {
propsData: {
canUpdate: true,
canDestroy: true,
- endpoint: '/gitlab-org/gitlab-shell/issues/9/rendered_title',
+ endpoint: '/gitlab-org/gitlab-shell/issues/9/realtime_changes',
issuableRef: '#1',
initialTitle: '',
initialDescriptionHtml: '',
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index be4605a5b89..8243a9c991a 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -377,7 +377,7 @@ import '~/notes';
});
it('should return true when comment begins with a slash command', () => {
- const sampleComment = '/wip \n/milestone %1.0 \n/merge \n/unassign Merging this';
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
const hasSlashCommands = this.notes.hasSlashCommands(sampleComment);
expect(hasSlashCommands).toBeTruthy();
@@ -401,10 +401,18 @@ import '~/notes';
describe('stripSlashCommands', () => {
it('should strip slash commands from the comment which begins with a slash command', () => {
this.notes = new Notes();
- const sampleComment = '/wip \n/milestone %1.0 \n/merge \n/unassign Merging this';
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
const stripedComment = this.notes.stripSlashCommands(sampleComment);
- expect(stripedComment).not.toBe(sampleComment);
+ expect(stripedComment).toBe('');
+ });
+
+ it('should strip slash commands from the comment but leaves plain comment if it is present', () => {
+ this.notes = new Notes();
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
+ const stripedComment = this.notes.stripSlashCommands(sampleComment);
+
+ expect(stripedComment).toBe('Merging this');
});
it('should NOT strip string that has slashes within', () => {
@@ -424,6 +432,22 @@ import '~/notes';
beforeEach(() => {
this.notes = new Notes('', []);
+ spyOn(_, 'escape').and.callFake((comment) => {
+ const escapedString = comment.replace(/["&'<>]/g, (a) => {
+ const escapedToken = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#x27;',
+ '`': '&#x60;'
+ }[a];
+
+ return escapedToken;
+ });
+
+ return escapedString;
+ });
});
it('should return constructed placeholder element for regular note based on form contents', () => {
@@ -444,7 +468,21 @@ import '~/notes';
expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy();
expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual(currentUserFullname);
expect($tempNoteHeader.find('.note-headline-light').text().trim()).toEqual(`@${currentUsername}`);
- expect($tempNote.find('.note-body .note-text').text().trim()).toEqual(sampleComment);
+ expect($tempNote.find('.note-body .note-text p').text().trim()).toEqual(sampleComment);
+ });
+
+ it('should escape HTML characters from note based on form contents', () => {
+ const commentWithHtml = '<script>alert("Boom!");</script>';
+ const $tempNote = this.notes.createPlaceholderNote({
+ formContent: commentWithHtml,
+ uniqueId,
+ isDiscussionNote: false,
+ currentUsername,
+ currentUserFullname
+ });
+
+ expect(_.escape).toHaveBeenCalledWith(commentWithHtml);
+ expect($tempNote.find('.note-body .note-text p').html()).toEqual('&lt;script&gt;alert("Boom!");&lt;/script&gt;');
});
it('should return constructed placeholder element for discussion note based on form contents', () => {
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index d6535f97665..dfa3ae9142e 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -382,7 +382,6 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
expect(model).to receive(:add_column).
with(:users, :new, :integer,
limit: old_column.limit,
- null: old_column.null,
precision: old_column.precision,
scale: old_column.scale)
@@ -391,6 +390,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
expect(model).to receive(:update_column_in_batches)
+ expect(model).to receive(:change_column_null).with(:users, :new, false)
+
expect(model).to receive(:copy_indexes).with(:users, :old, :new)
expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
@@ -408,7 +409,6 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
expect(model).to receive(:add_column).
with(:users, :new, :integer,
limit: old_column.limit,
- null: old_column.null,
precision: old_column.precision,
scale: old_column.scale)
@@ -417,6 +417,8 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
expect(model).to receive(:update_column_in_batches)
+ expect(model).to receive(:change_column_null).with(:users, :new, false)
+
expect(model).to receive(:copy_indexes).with(:users, :old, :new)
expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index f3dacb4ef04..5ae4a19263c 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::EtagCaching::Router do
it 'matches issue title endpoint' do
env = build_env(
- '/my-group/my-project/issues/123/rendered_title'
+ '/my-group/my-project/issues/123/realtime_changes'
)
result = described_class.match(env)
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 55fcf91fb6e..08ee0dff6b2 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -1,14 +1,19 @@
require 'spec_helper'
describe Gitlab::GitalyClient, lib: true do
- describe '.new_channel' do
+ describe '.stub' do
+ before { described_class.clear_stubs! }
+
context 'when passed a UNIX socket address' do
- it 'passes the address as-is to GRPC::Core::Channel initializer' do
+ it 'passes the address as-is to GRPC' do
address = 'unix:/tmp/gitaly.sock'
+ allow(Gitlab.config.repositories).to receive(:storages).and_return({
+ 'default' => { 'gitaly_address' => address }
+ })
- expect(GRPC::Core::Channel).to receive(:new).with(address, any_args)
+ expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args)
- described_class.new_channel(address)
+ described_class.stub(:commit, 'default')
end
end
@@ -17,9 +22,13 @@ describe Gitlab::GitalyClient, lib: true do
address = 'localhost:9876'
prefixed_address = "tcp://#{address}"
- expect(GRPC::Core::Channel).to receive(:new).with(address, any_args)
+ allow(Gitlab.config.repositories).to receive(:storages).and_return({
+ 'default' => { 'gitaly_address' => prefixed_address }
+ })
+
+ expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args)
- described_class.new_channel(prefixed_address)
+ described_class.stub(:commit, 'default')
end
end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 67b759f7dcd..fdbb55fc874 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -202,7 +202,7 @@ describe Gitlab::Workhorse, lib: true do
context 'when Gitaly is enabled' do
let(:gitaly_params) do
{
- GitalyAddress: Gitlab::GitalyClient.get_address('default')
+ GitalyAddress: Gitlab::GitalyClient.address('default')
}
end
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
index 36be0bb6bf8..37cc308e613 100644
--- a/spec/support/filtered_search_helpers.rb
+++ b/spec/support/filtered_search_helpers.rb
@@ -73,11 +73,11 @@ module FilteredSearchHelpers
end
def remove_recent_searches
- execute_script('window.localStorage.removeItem(\'issue-recent-searches\');')
+ execute_script('window.localStorage.clear();')
end
- def set_recent_searches(input)
- execute_script("window.localStorage.setItem('issue-recent-searches', '#{input}');")
+ def set_recent_searches(key, input)
+ execute_script("window.localStorage.setItem('#{key}', '#{input}');")
end
def wait_for_filtered_search(text)
diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb
new file mode 100755
index 00000000000..7335f74c0e9
--- /dev/null
+++ b/spec/support/generate-seed-repo-rb
@@ -0,0 +1,162 @@
+#!/usr/bin/env ruby
+#
+# # generate-seed-repo-rb
+#
+# This script generates the seed_repo.rb file used by lib/gitlab/git
+# tests. The seed_repo.rb file needs to be updated anytime there is a
+# Git push to https://gitlab.com/gitlab-org/gitlab-git-test.
+#
+# Usage:
+#
+# ./spec/support/generate-seed-repo-rb > spec/support/seed_repo.rb
+#
+#
+
+require 'erb'
+require 'tempfile'
+
+SOURCE = 'https://gitlab.com/gitlab-org/gitlab-git-test.git'.freeze
+SCRIPT_NAME = 'generate-seed-repo-rb'.freeze
+REPO_NAME = 'gitlab-git-test.git'.freeze
+
+def main
+ Dir.mktmpdir do |dir|
+ unless system(*%W[git clone --bare #{SOURCE} #{REPO_NAME}], chdir: dir)
+ abort "git clone failed"
+ end
+ repo = File.join(dir, REPO_NAME)
+ erb = ERB.new(DATA.read)
+ erb.run(binding)
+ end
+end
+
+def capture!(cmd, dir)
+ output = IO.popen(cmd, 'r', chdir: dir) { |io| io.read }
+ raise "command failed with #{$?}: #{cmd.join(' ')}" unless $?.success?
+ output.chomp
+end
+
+main
+
+__END__
+# This file is generated by <%= SCRIPT_NAME %>. Do not edit this file manually.
+#
+# Seed repo:
+<%= capture!(%w{git log --format=#\ %H\ %s}, repo) %>
+
+module SeedRepo
+ module BigCommit
+ ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e".freeze
+ PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660".freeze
+ MESSAGE = "Files, encoding and much more".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES_COUNT = 2
+ end
+
+ module Commit
+ ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d".freeze
+ PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9".freeze
+ MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES = ["files/ruby/popen.rb", "files/ruby/regex.rb"].freeze
+ FILES_COUNT = 2
+ C_FILE_PATH = "files/ruby".freeze
+ C_FILES = ["popen.rb", "regex.rb", "version_info.rb"].freeze
+ BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}.freeze
+ BLOB_FILE_PATH = "app/views/keys/show.html.haml".freeze
+ end
+
+ module EmptyCommit
+ ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9".freeze
+ PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
+ MESSAGE = "Empty commit".freeze
+ AUTHOR_FULL_NAME = "Rémy Coutable".freeze
+ FILES = [].freeze
+ FILES_COUNT = FILES.count
+ end
+
+ module EncodingCommit
+ ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
+ PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8".freeze
+ MESSAGE = "Add ISO-8859-encoded file".freeze
+ AUTHOR_FULL_NAME = "Stan Hu".freeze
+ FILES = ["encoding/iso8859.txt"].freeze
+ FILES_COUNT = FILES.count
+ end
+
+ module FirstCommit
+ ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863".freeze
+ PARENT_ID = nil
+ MESSAGE = "Initial commit".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES = ["LICENSE", ".gitignore", "README.md"].freeze
+ FILES_COUNT = 3
+ end
+
+ module LastCommit
+ ID = <%= capture!(%w[git show -s --format=%H HEAD], repo).inspect %>.freeze
+ PARENT_ID = <%= capture!(%w[git show -s --format=%P HEAD], repo).split.last.inspect %>.freeze
+ MESSAGE = <%= capture!(%w[git show -s --format=%s HEAD], repo).inspect %>.freeze
+ AUTHOR_FULL_NAME = <%= capture!(%w[git show -s --format=%an HEAD], repo).inspect %>.freeze
+ FILES = <%=
+ parents = capture!(%w[git show -s --format=%P HEAD], repo).split
+ merge_base = parents.size > 1 ? capture!(%w[git merge-base] + parents, repo) : parents.first
+ capture!( %W[git diff --name-only #{merge_base}..HEAD --], repo).split("\n").inspect
+ %>.freeze
+ FILES_COUNT = FILES.count
+ end
+
+ module Repo
+ HEAD = "master".freeze
+ BRANCHES = %w[
+<%= capture!(%W[git for-each-ref --format=#{' ' * 3}%(refname:strip=2) refs/heads/], repo) %>
+ ].freeze
+ TAGS = %w[
+<%= capture!(%W[git for-each-ref --format=#{' ' * 3}%(refname:strip=2) refs/tags/], repo) %>
+ ].freeze
+ end
+
+ module RubyBlob
+ ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c".freeze
+ NAME = "popen.rb".freeze
+ CONTENT = <<-eos.freeze
+require 'fileutils'
+require 'open3'
+
+module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+end
+ eos
+ end
+end
diff --git a/spec/support/seed_repo.rb b/spec/support/seed_repo.rb
index 99a500bbbb1..cfe7fc980a8 100644
--- a/spec/support/seed_repo.rb
+++ b/spec/support/seed_repo.rb
@@ -1,4 +1,8 @@
+# This file is generated by generate-seed-repo-rb. Do not edit this file manually.
+#
# Seed repo:
+# 4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 Merge branch 'master' into 'master'
+# 0e1b353b348f8477bdbec1ef47087171c5032cd9 adds an executable with different permissions
# 0e50ec4d3c7ce42ab74dda1d422cb2cbffe1e326 Merge branch 'lfs_pointers' into 'master'
# 33bcff41c232a11727ac6d660bd4b0c2ba86d63d Add valid and invalid lfs pointers
# 732401c65e924df81435deb12891ef570167d2e2 Update year in license file
@@ -94,7 +98,12 @@ module SeedRepo
master
merge-test
].freeze
- TAGS = %w[v1.0.0 v1.1.0 v1.2.0 v1.2.1].freeze
+ TAGS = %w[
+ v1.0.0
+ v1.1.0
+ v1.2.0
+ v1.2.1
+ ].freeze
end
module RubyBlob
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 8e31c26591b..9bf9dc5d4b2 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -120,7 +120,7 @@ module TestEnv
end
def setup_gitaly
- socket_path = Gitlab::GitalyClient.get_address('default').sub(/\Aunix:/, '')
+ socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
unless File.directory?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
@@ -133,7 +133,8 @@ module TestEnv
def start_gitaly(gitaly_dir)
gitaly_exec = File.join(gitaly_dir, 'gitaly')
gitaly_config = File.join(gitaly_dir, 'config.toml')
- @gitaly_pid = spawn(gitaly_exec, gitaly_config, [:out, :err] => '/dev/null')
+ log_file = Rails.root.join('log/gitaly-test.log').to_s
+ @gitaly_pid = spawn(gitaly_exec, gitaly_config, [:out, :err] => log_file)
end
def stop_gitaly
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 4def113dd77..0ff1a988a9e 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -236,7 +236,6 @@ describe 'gitlab:app namespace rake task' do
'custom' => { 'path' => Settings.absolute('tmp/tests/custom_storage'), 'gitaly_address' => gitaly_address }
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
- Gitlab::GitalyClient.configure_channels
# Create the projects now, after mocking the settings but before doing the backup
project_a
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 3289c2df1fb..f4bc63bcc6a 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -4,13 +4,16 @@ describe PostReceive do
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
- let(:project) { create(:project, :repository) }
let(:project_identifier) { "project-#{project.id}" }
let(:key) { create(:key, user: project.owner) }
let(:key_id) { key.shell_id }
+ let(:project) do
+ create(:project, :repository, auto_cancel_pending_pipelines: 'disabled')
+ end
+
context "as a sidekiq worker" do
- it "reponds to #perform" do
+ it "responds to #perform" do
expect(described_class.new).to respond_to(:perform)
end
end