Commit f2867fe6 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream

parents 41964f02 539ed0a6
...@@ -266,7 +266,7 @@ setup-test-env: ...@@ -266,7 +266,7 @@ setup-test-env:
<<: *default-cache <<: *default-cache
script: script:
- node --version - node --version
- yarn install --pure-lockfile --cache-folder .yarn-cache - yarn install --frozen-lockfile --cache-folder .yarn-cache
- bundle exec rake gettext:po_to_json - bundle exec rake gettext:po_to_json
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
...@@ -495,7 +495,7 @@ gitlab:assets:compile: ...@@ -495,7 +495,7 @@ gitlab:assets:compile:
WEBPACK_REPORT: "true" WEBPACK_REPORT: "true"
NO_COMPRESSION: "true" NO_COMPRESSION: "true"
script: script:
- yarn install --pure-lockfile --production --cache-folder .yarn-cache - yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- bundle exec rake gettext:po_to_json - bundle exec rake gettext:po_to_json
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
artifacts: artifacts:
...@@ -509,7 +509,7 @@ karma: ...@@ -509,7 +509,7 @@ karma:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *pull-cache <<: *pull-cache
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-59.0-node-7.1-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-60.0-node-7.1-postgresql-9.6"
stage: test stage: test
variables: variables:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
......
...@@ -162,7 +162,7 @@ gem 'acts-as-taggable-on', '~> 4.0' ...@@ -162,7 +162,7 @@ gem 'acts-as-taggable-on', '~> 4.0'
gem 'sidekiq', '~> 5.0' gem 'sidekiq', '~> 5.0'
gem 'sidekiq-cron', '~> 0.6.0' gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.5.2' gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4' gem 'sidekiq-limit_fetch', '~> 3.4', require: false
# Cron Parser # Cron Parser
gem 'rufus-scheduler', '~> 3.4' gem 'rufus-scheduler', '~> 3.4'
...@@ -299,7 +299,7 @@ group :metrics do ...@@ -299,7 +299,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta11' gem 'prometheus-client-mmap', '~>0.7.0.beta12'
gem 'raindrops', '~> 0.18' gem 'raindrops', '~> 0.18'
end end
...@@ -417,7 +417,7 @@ group :ed25519 do ...@@ -417,7 +417,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.27.0' gem 'gitaly', '~> 0.29.0'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -299,7 +299,7 @@ GEM ...@@ -299,7 +299,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly (0.27.0) gitaly (0.29.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -648,7 +648,7 @@ GEM ...@@ -648,7 +648,7 @@ GEM
parser parser
unparser unparser
procto (0.0.3) procto (0.0.3)
prometheus-client-mmap (0.7.0.beta11) prometheus-client-mmap (0.7.0.beta12)
mmap2 (~> 2.2, >= 2.2.7) mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
...@@ -751,7 +751,7 @@ GEM ...@@ -751,7 +751,7 @@ GEM
retriable (1.4.1) retriable (1.4.1)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (2.1.0) rouge (2.2.0)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
...@@ -1054,7 +1054,7 @@ DEPENDENCIES ...@@ -1054,7 +1054,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.27.0) gitaly (~> 0.29.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
...@@ -1132,7 +1132,7 @@ DEPENDENCIES ...@@ -1132,7 +1132,7 @@ DEPENDENCIES
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta11) prometheus-client-mmap (~> 0.7.0.beta12)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
......
...@@ -97,7 +97,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({ ...@@ -97,7 +97,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return `Avatar for ${assignee.name}`; return `Avatar for ${assignee.name}`;
}, },
showLabel(label) { showLabel(label) {
if (!this.list || !label) return true; if (!label.id) return false;
return true; return true;
}, },
filterByLabel(label, e) { filterByLabel(label, e) {
......
...@@ -378,15 +378,15 @@ ...@@ -378,15 +378,15 @@
w.gl.utils.backOff = (fn, timeout = 60000) => { w.gl.utils.backOff = (fn, timeout = 60000) => {
const maxInterval = 32000; const maxInterval = 32000;
let nextInterval = 2000; let nextInterval = 2000;
let timeElapsed = 0;
const startTime = Date.now();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg)); const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
const next = () => { const next = () => {
if (Date.now() - startTime < timeout) { if (timeElapsed < timeout) {
setTimeout(fn.bind(null, next, stop), nextInterval); setTimeout(() => fn(next, stop), nextInterval);
timeElapsed += nextInterval;
nextInterval = Math.min(nextInterval + nextInterval, maxInterval); nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
} else { } else {
reject(new Error('BACKOFF_TIMEOUT')); reject(new Error('BACKOFF_TIMEOUT'));
......
...@@ -4,10 +4,10 @@ export default class ProjectSelectComboButton { ...@@ -4,10 +4,10 @@ export default class ProjectSelectComboButton {
constructor(select) { constructor(select) {
this.projectSelectInput = $(select); this.projectSelectInput = $(select);
this.newItemBtn = $('.new-project-item-link'); this.newItemBtn = $('.new-project-item-link');
this.newItemBtnBaseText = this.newItemBtn.data('label'); this.resourceType = this.newItemBtn.data('type');
this.itemType = this.deriveItemTypeFromLabel(); this.resourceLabel = this.newItemBtn.data('label');
this.formattedText = this.deriveTextVariants();
this.groupId = this.projectSelectInput.data('groupId'); this.groupId = this.projectSelectInput.data('groupId');
this.bindEvents(); this.bindEvents();
this.initLocalStorage(); this.initLocalStorage();
} }
...@@ -23,9 +23,7 @@ export default class ProjectSelectComboButton { ...@@ -23,9 +23,7 @@ export default class ProjectSelectComboButton {
const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe(); const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
if (localStorageIsSafe) { if (localStorageIsSafe) {
const itemTypeKebabed = this.newItemBtnBaseText.toLowerCase().split(' ').join('-'); this.localStorageKey = ['group', this.groupId, this.formattedText.localStorageItemType, 'recent-project'].join('-');
this.localStorageKey = ['group', this.groupId, itemTypeKebabed, 'recent-project'].join('-');
this.setBtnTextFromLocalStorage(); this.setBtnTextFromLocalStorage();
} }
} }
...@@ -57,19 +55,14 @@ export default class ProjectSelectComboButton { ...@@ -57,19 +55,14 @@ export default class ProjectSelectComboButton {
setNewItemBtnAttributes(project) { setNewItemBtnAttributes(project) {
if (project) { if (project) {
this.newItemBtn.attr('href', project.url); this.newItemBtn.attr('href', project.url);
this.newItemBtn.text(`${this.newItemBtnBaseText} in ${project.name}`); this.newItemBtn.text(`${this.formattedText.defaultTextPrefix} in ${project.name}`);
this.newItemBtn.enable(); this.newItemBtn.enable();
} else { } else {
this.newItemBtn.text(`Select project to create ${this.itemType}`); this.newItemBtn.text(`Select project to create ${this.formattedText.presetTextSuffix}`);
this.newItemBtn.disable(); this.newItemBtn.disable();
} }
} }
deriveItemTypeFromLabel() {
// label is either 'New issue' or 'New merge request'
return this.newItemBtnBaseText.split(' ').slice(1).join(' ');
}
getProjectFromLocalStorage() { getProjectFromLocalStorage() {
const projectString = localStorage.getItem(this.localStorageKey); const projectString = localStorage.getItem(this.localStorageKey);
...@@ -81,5 +74,19 @@ export default class ProjectSelectComboButton { ...@@ -81,5 +74,19 @@ export default class ProjectSelectComboButton {
localStorage.setItem(this.localStorageKey, projectString); localStorage.setItem(this.localStorageKey, projectString);
} }
deriveTextVariants() {
const defaultTextPrefix = this.resourceLabel;
// the trailing slice call depluralizes each of these strings (e.g. new-issues -> new-issue)
const localStorageItemType = `new-${this.resourceType.split('_').join('-').slice(0, -1)}`;
const presetTextSuffix = this.resourceType.split('_').join(' ').slice(0, -1);
return {
localStorageItemType, // new-issue / new-merge-request
defaultTextPrefix, // New issue / New merge request
presetTextSuffix, // issue / merge request
};
}
} }
...@@ -74,7 +74,8 @@ export default { ...@@ -74,7 +74,8 @@ export default {
<tbody> <tbody>
<repo-file-options <repo-file-options
:is-mini="isMini" :is-mini="isMini"
:project-name="projectName"/> :project-name="projectName"
/>
<repo-previous-directory <repo-previous-directory
v-if="isRoot" v-if="isRoot"
:prev-url="prevURL" :prev-url="prevURL"
...@@ -84,7 +85,8 @@ export default { ...@@ -84,7 +85,8 @@ export default {
:key="n" :key="n"
:loading="loading" :loading="loading"
:has-files="!!files.length" :has-files="!!files.length"
:is-mini="isMini"/> :is-mini="isMini"
/>
<repo-file <repo-file
v-for="file in files" v-for="file in files"
:key="file.id" :key="file.id"
...@@ -93,7 +95,8 @@ export default { ...@@ -93,7 +95,8 @@ export default {
@linkclicked="fileClicked(file)" @linkclicked="fileClicked(file)"
:is-tree="isTree" :is-tree="isTree"
:has-files="!!files.length" :has-files="!!files.length"
:active-file="activeFile"/> :active-file="activeFile"
/>
</tbody> </tbody>
</table> </table>
</div> </div>
......
...@@ -761,7 +761,11 @@ ...@@ -761,7 +761,11 @@
&:hover, &:hover,
&:active, &:active,
&:focus { &:focus {
<<<<<<< HEAD
background-color: $gray-darker; background-color: $gray-darker;
=======
background-color: $dropdown-item-hover-bg;
>>>>>>> 539ed0a6375d5bb6d734e688b801373e4b8006f9
color: $gl-text-color; color: $gl-text-color;
} }
......
...@@ -276,3 +276,41 @@ ...@@ -276,3 +276,41 @@
.ajax-users-dropdown { .ajax-users-dropdown {
min-width: 250px !important; min-width: 250px !important;
} }
// TODO: change global style
.ajax-project-dropdown {
&.select2-drop {
color: $gl-text-color;
}
.select2-results {
.select2-no-results,
.select2-searching,
.select2-ajax-error,
.select2-selection-limit {
background: transparent;
}
.select2-result {
padding: 0 1px;
.select2-match {
font-weight: bold;
text-decoration: none;
}
.select2-result-label {
padding: #{$gl-padding / 2} $gl-padding;
}
&.select2-highlighted {
background-color: transparent !important;
color: $gl-text-color;
.select2-result-label {
background-color: $dropdown-item-hover-bg;
}
}
}
}
}
...@@ -300,7 +300,7 @@ $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); ...@@ -300,7 +300,7 @@ $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, .6);
$dropdown-chevron-size: 10px; $dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%); $dropdown-toggle-active-border-color: darken($border-color, 14%);
$dropdown-item-hover-bg: $gray-darker;
/* /*
* Filtered Search * Filtered Search
......
...@@ -35,18 +35,18 @@ module EventsHelper ...@@ -35,18 +35,18 @@ module EventsHelper
[event.action_name, target].join(" ") [event.action_name, target].join(" ")
end end
def event_filter_link(key, tooltip) def event_filter_link(key, text, tooltip)
key = key.to_s key = key.to_s
active = 'active' if @event_filter.active?(key) active = 'active' if @event_filter.active?(key)
link_opts = { link_opts = {
class: "event-filter-link", class: "event-filter-link has-tooltip",
id: "#{key}_event_filter", id: "#{key}_event_filter",
title: "Filter by #{tooltip.downcase}" title: tooltip
} }
content_tag :li, class: active do content_tag :li, class: active do
link_to request.path, link_opts do link_to request.path, link_opts do
content_tag(:span, ' ' + tooltip) content_tag(:span, ' ' + text)
end end
end end
end end
...@@ -176,7 +176,7 @@ module EventsHelper ...@@ -176,7 +176,7 @@ module EventsHelper
sanitize( sanitize(
text, text,
tags: %w(a img gl-emoji b pre code p span), tags: %w(a img gl-emoji b pre code p span),
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-name', 'data-unicode-version'] attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
) )
end end
......
...@@ -9,7 +9,7 @@ module Ci ...@@ -9,7 +9,7 @@ module Ci
belongs_to :owner, class_name: 'User' belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline' has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
has_many :pipelines has_many :pipelines
has_many :variables, class_name: 'Ci::PipelineScheduleVariable' has_many :variables, class_name: 'Ci::PipelineScheduleVariable', validate: false
validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? } validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? }
validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? } validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
......
...@@ -4,5 +4,7 @@ module Ci ...@@ -4,5 +4,7 @@ module Ci
include HasVariable include HasVariable
belongs_to :pipeline_schedule belongs_to :pipeline_schedule
validates :key, uniqueness: { scope: :pipeline_schedule_id }
end end
end end
module Ci module Ci
class Stage < ActiveRecord::Base class Stage < ActiveRecord::Base
extend Ci::Model extend Ci::Model
include Importable
include HasStatus
include Gitlab::OptimisticLocking
enum status: HasStatus::STATUSES_ENUM
belongs_to :project belongs_to :project
belongs_to :pipeline belongs_to :pipeline
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :builds, foreign_key: :commit_id has_many :builds, foreign_key: :stage_id
validates :project, presence: true, unless: :importing?
validates :pipeline, presence: true, unless: :importing?
validates :name, presence: true, unless: :importing?
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
transition [:success, :failed, :canceled, :skipped] => :running
end
event :run do
transition any - [:running] => :running
end
event :skip do
transition any - [:skipped] => :skipped
end
event :drop do
transition any - [:failed] => :failed
end
event :succeed do
transition any - [:success] => :success
end
event :cancel do
transition any - [:canceled] => :canceled
end
event :block do
transition any - [:manual] => :manual
end
end
def update_status
retry_optimistic_lock(self) do
case statuses.latest.status
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
when 'failed' then drop
when 'canceled' then cancel
when 'manual' then block
when 'skipped' then skip
else skip
end
end
end
end end
end end
...@@ -39,14 +39,14 @@ class CommitStatus < ActiveRecord::Base ...@@ -39,14 +39,14 @@ class CommitStatus < ActiveRecord::Base
scope :after_stage, -> (index) { where('stage_idx > ?', index) } scope :after_stage, -> (index) { where('stage_idx > ?', index) }
state_machine :status do state_machine :status do
event :enqueue do
transition [:created, :skipped, :manual] => :pending
end
event :process do event :process do
transition [:skipped, :manual] => :created transition [:skipped, :manual] => :created
end end
event :enqueue do
transition [:created, :skipped, :manual] => :pending
end
event :run do event :run do
transition pending: :running transition pending: :running
end end
...@@ -91,6 +91,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -91,6 +91,7 @@ class CommitStatus < ActiveRecord::Base
end end
end end
StageUpdateWorker.perform_async(commit_status.stage_id)
ExpireJobCacheWorker.perform_async(commit_status.id) ExpireJobCacheWorker.perform_async(commit_status.id)
end end
end end
......
...@@ -8,6 +8,8 @@ module HasStatus ...@@ -8,6 +8,8 @@ module HasStatus
ACTIVE_STATUSES = %w[pending running].freeze ACTIVE_STATUSES = %w[pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
ORDERED_STATUSES = %w[failed pending running manual canceled success skipped created].freeze ORDERED_STATUSES = %w[failed pending running manual canceled success skipped created].freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
class_methods do class_methods do
def status_sql def status_sql
......
...@@ -89,6 +89,10 @@ class Event < ActiveRecord::Base ...@@ -89,6 +89,10 @@ class Event < ActiveRecord::Base
self.inheritance_column = 'action' self.inheritance_column = 'action'
class << self class << self
def model_name
ActiveModel::Name.new(self, nil, 'event')
end
def find_sti_class(action) def find_sti_class(action)
if action.to_i == PUSHED if action.to_i == PUSHED
PushEvent PushEvent
...@@ -444,6 +448,12 @@ class Event < ActiveRecord::Base ...@@ -444,6 +448,12 @@ class Event < ActiveRecord::Base
EventForMigration.create!(new_attributes) EventForMigration.create!(new_attributes)
end end
def to_partial_path
# We are intentionally using `Event` rather than `self.class` so that
# subclasses also use the `Event` implementation.
Event._to_partial_path
end
private private
def recent_update? def recent_update?
......
...@@ -12,9 +12,9 @@ class Issue < ActiveRecord::Base ...@@ -12,9 +12,9 @@ class Issue < ActiveRecord::Base
include Elastic::IssuesSearch include Elastic::IssuesSearch
include FasterCacheKeys include FasterCacheKeys
include RelativePositioning include RelativePositioning
include IgnorableColumn
include CreatedAtFilterable include CreatedAtFilterable
<<<<<<< HEAD
ignore_column :position ignore_column :position
WEIGHT_RANGE = 1..9 WEIGHT_RANGE = 1..9
...@@ -22,6 +22,8 @@ class Issue < ActiveRecord::Base ...@@ -22,6 +22,8 @@ class Issue < ActiveRecord::Base
WEIGHT_ANY = 'Any Weight'.freeze WEIGHT_ANY = 'Any Weight'.freeze
WEIGHT_NONE = 'No Weight'.freeze WEIGHT_NONE = 'No Weight'.freeze
=======
>>>>>>> 539ed0a6375d5bb6d734e688b801373e4b8006f9
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
......
...@@ -8,7 +8,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -8,7 +8,6 @@ class MergeRequest < ActiveRecord::Base
include IgnorableColumn include IgnorableColumn
include CreatedAtFilterable include CreatedAtFilterable
ignore_column :position
ignore_column :locked_at ignore_column :locked_at
include ::EE::MergeRequest include ::EE::MergeRequest
......
...@@ -856,7 +856,12 @@ class User < ActiveRecord::Base ...@@ -856,7 +856,12 @@ class User < ActiveRecord::Base
create_namespace!(path: username, name: username) unless namespace create_namespace!(path: username, name: username) unless namespace
if username_changed? if username_changed?
namespace.update_attributes(path: username, name: username) unless namespace.update_attributes(path: username, name: username)
namespace.errors.each do |attribute, message|
self.errors.add(:"namespace_#{attribute}", message)
end
raise ActiveRecord::RecordInvalid.new(namespace)
end
end end
end end
......
...@@ -181,9 +181,14 @@ module Ci ...@@ -181,9 +181,14 @@ module Ci
end end
def error(message, save: false) def error(message, save: false)
pipeline.errors.add(:base, message) pipeline.tap do
pipeline.drop if save pipeline.errors.add(:base, message)
pipeline
if save
pipeline.drop
update_merge_requests_head_pipeline
end
end
end end
def pipeline_created_counter def pipeline_created_counter
......
...@@ -3,6 +3,8 @@ module MergeRequests ...@@ -3,6 +3,8 @@ module MergeRequests
def execute def execute
return error('Invalid issue iid') unless issue_iid.present? && issue.present? return error('Invalid issue iid') unless issue_iid.present? && issue.present?
params[:label_ids] = issue.label_ids if issue.label_ids.any?
result = CreateBranchService.new(project, current_user).execute(branch_name, ref) result = CreateBranchService.new(project, current_user).execute(branch_name, ref)
return result if result[:status] == :error return result if result[:status] == :error
...@@ -43,7 +45,8 @@ module MergeRequests ...@@ -43,7 +45,8 @@ module MergeRequests
{ {
source_project_id: project.id, source_project_id: project.id,
source_branch: branch_name, source_branch: branch_name,
target_project_id: project.id target_project_id: project.id,
milestone_id: issue.milestone_id
} }
end end
......
...@@ -397,7 +397,9 @@ ...@@ -397,7 +397,9 @@
%fieldset %fieldset
%legend Background Jobs %legend Background Jobs
%p %p
These settings require a restart to take effect. These settings require a
= link_to 'restart', help_page_path('administration/restart_gitlab')
to take effect.
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
......
...@@ -8,14 +8,14 @@ ...@@ -8,14 +8,14 @@
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues' = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues' = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
= render 'shared/issues' = render 'shared/issues'
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
- if show_new_nav? - if show_new_nav?
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests' = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests' = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests' = render 'shared/merge_requests'
...@@ -4,13 +4,13 @@ ...@@ -4,13 +4,13 @@
- if show_new_nav? - if show_new_nav?
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.top-area .top-area
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.milestones .milestones
%ul.content-list %ul.content-list
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do = link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues
- if group_issues_exists - if group_issues_exists
.top-area .top-area
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
Subscribe Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if show_new_nav? && current_user - if show_new_nav? && current_user
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
- if @group_merge_requests.empty? - if @group_merge_requests.empty?
= render 'shared/empty_states/merge_requests', project_select_button: true = render 'shared/empty_states/merge_requests', project_select_button: true
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
- if current_user - if current_user
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
......
%div{ class: container_class } %div{ class: container_class }
.nav-block.activity-filter-block.activities .nav-block.activity-filter-block.activities
.controls .controls
= link_to project_path(@project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do
= icon('rss') = icon('rss')
= render 'shared/event_filter' = render 'shared/event_filter'
......
...@@ -3,16 +3,16 @@ ...@@ -3,16 +3,16 @@
.row-content-block.top-block.hidden-xs.white .row-content-block.top-block.hidden-xs.white
.event-last-push .event-last-push
.event-last-push-text .event-last-push-text
%span You pushed to %span= s_("LastPushEvent|You pushed to")
%strong %strong
= link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name' = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- if event.project != @project - if event.project != @project
%span at %span= s_("LastPushEvent|at")
%strong= link_to_project event.project %strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.pull-right .pull-right
= link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
#{ _('Create merge request') } #{ _('Create merge request') }
- @no_container = true - @no_container = true
- if show_new_nav? - if show_new_nav?
- add_to_breadcrumbs("Project", project_path(@project)) - add_to_breadcrumbs(_("Project"), project_path(@project))
- page_title "Activity" - page_title _("Activity")
= render "projects/head" = render "projects/head"
= render 'projects/last_push' = render 'projects/last_push'
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- notes = commit.notes - notes = commit.notes
- note_count = notes.user.count - note_count = notes.user.count
- cache_key = [project.full_path, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)] - cache_key = [project.full_path, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits), I18n.locale]
- cache_key.push(commit.status(ref)) if commit.status(ref) - cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do = cache(cache_key, expires_in: 1.day) do
......
%ul.nav-links.event-filter.scrolling-tabs %ul.nav-links.event-filter.scrolling-tabs
= event_filter_link EventFilter.all, 'All' = event_filter_link EventFilter.all, _('All'), s_('EventFilterBy|Filter by all')
- if event_filter_visible(:repository) - if event_filter_visible(:repository)
= event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.push, _('Push events'), s_('EventFilterBy|Filter by push events')
- if event_filter_visible(:merge_requests) - if event_filter_visible(:merge_requests)
= event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.merged, _('Merge events'), s_('EventFilterBy|Filter by merge events')
- if event_filter_visible(:issues) - if event_filter_visible(:issues)
= event_filter_link EventFilter.issue, 'Issue events' = event_filter_link EventFilter.issue, _('Issue events'), s_('EventFilterBy|Filter by issue events')
- if comments_visible? - if comments_visible?
= event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.comments, _('Comments'), s_('EventFilterBy|Filter by comments')
= event_filter_link EventFilter.team, 'Team' = event_filter_link EventFilter.team, _('Team'), s_('EventFilterBy|Filter by team')
- if any_projects?(@projects) - if any_projects?(@projects)
.project-item-select-holder.btn-group.pull-right .project-item-select-holder.btn-group.pull-right
%a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label] } } %a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } }
= icon('spinner spin') = icon('spinner spin')
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled] = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
%button.btn.btn-new.new-project-item-select-button %button.btn.btn-new.new-project-item-select-button
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
Issues can be bugs, tasks or ideas to be discussed. Issues can be bugs, tasks or ideas to be discussed.
Also, issues are searchable and filterable. Also, issues are searchable and filterable.
- if project_select_button - if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue' = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
- else - else
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
- else - else
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
%p %p
Interested parties can even contribute by pushing commits if they want to. Interested parties can even contribute by pushing commits if they want to.
- if project_select_button - if project_select_button
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: 'New merge request' = render 'shared/new_project_item_select', path: 'merge_requests/new', label: 'New merge request', type: :merge_requests
- else - else
= link_to 'New merge request', button_path, class: 'btn btn-new', title: 'New merge request', id: 'new_merge_request_link' = link_to 'New merge request', button_path, class: 'btn btn-new', title: 'New merge request', id: 'new_merge_request_link'
- else - else
......
class StageUpdateWorker
include Sidekiq::Worker
include PipelineQueue
def perform(stage_id)
Ci::Stage.find_by(id: stage_id).try do |stage|
stage.update_status
end
end
end
---
title: inherits milestone and labels when a merge request is created from issue
merge_request: 13461
author: haseebeqx
type: added
---
title: Commit rows would occasionally render with the wrong language
merge_request:
author:
type: fixed
---
title: Fix merge request pipeline status when pipeline has errors
merge_request: 13664
author:
type: fixed
---
title: Implement the Gitaly RefService::RefExists endpoint
merge_request: 13528
author: Andrew Newdigate
---
title: Make username update fail if the namespace update fails
merge_request: 13642
author:
type: fixed
---
title: Only require Sidekiq throttling library when enabled, to reduce cache misses
merge_request:
author:
type: fixed
---
title: Bump rouge to v2.2.0
merge_request: 13633
author:
type: other
---
title: Remove CI API v1
merge_request:
author:
type: removed
...@@ -514,7 +514,9 @@ unless Settings.repositories.storages['default'] ...@@ -514,7 +514,9 @@ unless Settings.repositories.storages['default']
Settings.repositories.storages['default']['path'] ||= Settings.gitlab['user_home'] + '/repositories/' Settings.repositories.storages['default']['path'] ||= Settings.gitlab['user_home'] + '/repositories/'
end end
Settings.repositories.storages.values.each do |storage| Settings.repositories.storages.each do |key, storage|
storage = Settingslogic.new(storage)
# Expand relative paths # Expand relative paths
storage['path'] = Settings.absolute(storage['path']) storage['path'] = Settings.absolute(storage['path'])
# Set failure defaults # Set failure defaults
...@@ -528,6 +530,8 @@ Settings.repositories.storages.values.each do |storage| ...@@ -528,6 +530,8 @@ Settings.repositories.storages.values.each do |storage|
storage['failure_reset_time'] = storage['failure_reset_time'].to_i storage['failure_reset_time'] = storage['failure_reset_time'].to_i
# We might want to have a timeout shorter than 1 second. # We might want to have a timeout shorter than 1 second.
storage['storage_timeout'] = storage['storage_timeout'].to_f storage['storage_timeout'] = storage['storage_timeout'].to_f
Settings.repositories.storages[key] = storage
end end
# #
......
namespace :ci do namespace :ci do
# CI API
Ci::API::API.logger Rails.logger
mount Ci::API::API => '/api'
resource :lint, only: [:show, :create] resource :lint, only: [:show, :create]
root to: redirect('/') root to: redirect('/')
......
...@@ -83,7 +83,6 @@ var config = { ...@@ -83,7 +83,6 @@ var config = {
terminal: './terminal/terminal_bundle.js', terminal: './terminal/terminal_bundle.js',
u2f: ['vendor/u2f'], u2f: ['vendor/u2f'],
ui_development_kit: './ui_development_kit.js', ui_development_kit: './ui_development_kit.js',
users: './users/index.js',
raven: './raven/index.js', raven: './raven/index.js',
vue_merge_request_widget: './vue_merge_request_widget/index.js', vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js', test: './test.js',
...@@ -288,14 +287,9 @@ if (IS_PRODUCTION) { ...@@ -288,14 +287,9 @@ if (IS_PRODUCTION) {
}) })
); );
// zopfli requires a lot of compute time and is disabled in CI // compression can require a lot of compute time and is disabled in CI
if (!NO_COMPRESSION) { if (!NO_COMPRESSION) {
// gracefully fall back to gzip if `node-zopfli` is unavailable (e.g. in CentOS 6) config.plugins.push(new CompressionPlugin());
try {
config.plugins.push(new CompressionPlugin({ algorithm: 'zopfli' }));
} catch(err) {
config.plugins.push(new CompressionPlugin({ algorithm: 'gzip' }));
}
} }
} }
......
class AddStatusToCiStages < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_stages, :status, :integer
end
end
class AddLockVersionToCiStages < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_stages, :lock_version, :integer
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CorrectProtectedTagsForeignKeys < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
remove_foreign_key_without_error(:protected_tag_create_access_levels,
column: :protected_tag_id)
execute <<-EOF
DELETE FROM protected_tag_create_access_levels
WHERE NOT EXISTS (
SELECT true
FROM protected_tags
WHERE protected_tag_create_access_levels.protected_tag_id = protected_tags.id
)
AND protected_tag_id IS NOT NULL
EOF
add_concurrent_foreign_key(:protected_tag_create_access_levels,
:protected_tags,
column: :protected_tag_id)
end
def down
# Previously there was a foreign key without a CASCADING DELETE, so we'll
# just leave the foreign key in place.
end
end
class MigrateStagesStatuses < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
BATCH_SIZE = 10000
RANGE_SIZE = 1000
MIGRATION = 'MigrateStageStatus'.freeze
class Stage < ActiveRecord::Base
self.table_name = 'ci_stages'
include ::EachBatch
end
def up
Stage.where(status: nil).each_batch(of: BATCH_SIZE) do |relation, index|
relation.each_batch(of: RANGE_SIZE) do |batch|
range = relation.pluck('MIN(id)', 'MAX(id)').first
schedule = index * 5.minutes
BackgroundMigrationWorker.perform_in(schedule, MIGRATION, range)
end
end
end
def down
disable_statement_timeout
update_column_in_batches(:ci_stages, :status, nil)
end
end
...@@ -11,7 +11,11 @@ ...@@ -11,7 +11,11 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
<<<<<<< HEAD
ActiveRecord::Schema.define(version: 20170818174141) do ActiveRecord::Schema.define(version: 20170818174141) do
=======
ActiveRecord::Schema.define(version: 20170820100558) do
>>>>>>> 539ed0a6375d5bb6d734e688b801373e4b8006f9
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -452,6 +456,8 @@ ActiveRecord::Schema.define(version: 20170818174141) do ...@@ -452,6 +456,8 @@ ActiveRecord::Schema.define(version: 20170818174141) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "name" t.string "name"
t.integer "status"
t.integer "lock_version"
end end
add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", using: :btree add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", using: :btree
...@@ -2101,7 +2107,7 @@ ActiveRecord::Schema.define(version: 20170818174141) do ...@@ -2101,7 +2107,7 @@ ActiveRecord::Schema.define(version: 20170818174141) do
add_foreign_key "protected_branch_push_access_levels", "users" add_foreign_key "protected_branch_push_access_levels", "users"
add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade
add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id" add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id"
add_foreign_key "protected_tag_create_access_levels", "protected_tags" add_foreign_key "protected_tag_create_access_levels", "protected_tags", name: "fk_f7dfda8c51", on_delete: :cascade
add_foreign_key "protected_tag_create_access_levels", "users" add_foreign_key "protected_tag_create_access_levels", "users"
add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade
add_foreign_key "push_rules", "projects", name: "fk_83b29894de", on_delete: :cascade add_foreign_key "push_rules", "projects", name: "fk_83b29894de", on_delete: :cascade
......
...@@ -56,15 +56,10 @@ following locations: ...@@ -56,15 +56,10 @@ following locations:
- [Tags](tags.md) - [Tags](tags.md)
- [Todos](todos.md) - [Todos](todos.md)
- [Users](users.md) - [Users](users.md)
- [Validate CI configuration](ci/lint.md) - [Validate CI configuration](lint.md)
- [V3 to V4](v3_to_v4.md) - [V3 to V4](v3_to_v4.md)
- [Version](version.md) - [Version](version.md)
The following documentation is for the [internal CI API](ci/README.md):
- [Builds](ci/builds.md)
- [Runners](ci/runners.md)
## Road to GraphQL ## Road to GraphQL
Going forward, we will start on moving to Going forward, we will start on moving to
......
# GitLab CI API
## Purpose
The main purpose of GitLab CI API is to provide the necessary data and context
for GitLab CI Runners.
All relevant information about the consumer API can be found in a
[separate document](../../api/README.md).
## API Prefix
The current CI API prefix is `/ci/api/v1`.
You need to prepend this prefix to all examples in this documentation, like:
```bash
GET /ci/api/v1/builds/:id/artifacts
```
## Resources
- [Builds](builds.md)
- [Runners](runners.md)
# Builds API
API used by runners to receive and update builds.
>**Note:**
This API is intended to be used only by Runners as their own
communication channel. For the consumer API see the
[Jobs API](../jobs.md).
## Authentication
This API uses two types of authentication:
1. Unique Runner's token which is the token assigned to the Runner after it
has been registered.
2. Using the build authorization token.
This is project's CI token that can be found under the **Builds** section of
a project's settings. The build authorization token can be passed as a
parameter or a value of `BUILD-TOKEN` header.
These two methods of authentication are interchangeable.
## Builds
### Runs oldest pending build by runner
```
POST /ci/api/v1/builds/register
```
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| `token` | string | yes | Unique runner token |
```
curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --form "token=t0k3n"
```
**Responses:**
| Status | Data |Description |
|--------|------|---------------------------------------------------------------------------|
| `201` | yes | When a build is scheduled for a runner |
| `204` | no | When no builds are scheduled for a runner (for GitLab Runner >= `v1.3.0`) |
| `403` | no | When invalid token is used or no token is sent |
| `404` | no | When no builds are scheduled for a runner (for GitLab Runner < `v1.3.0`) **or** when the runner is set to `paused` in GitLab runner's configuration page |
### Update details of an existing build
```
PUT /ci/api/v1/builds/:id
```
| Attribute | Type | Required | Description |
|-----------|---------|----------|----------------------|
| `id` | integer | yes | The ID of a project |
| `token` | string | yes | Unique runner token |
| `state` | string | no | The state of a build |
| `trace` | string | no | The trace of a build |
```
curl --request PUT "https://gitlab.example.com/ci/api/v1/builds/1234" --form "token=t0k3n" --form "state=running" --form "trace=Running git clone...\n"
```
### Incremental build trace update
Using this method you need to send trace content as a request body. You also need to provide the `Content-Range` header
with a range of sent trace part. Note that you need to send parts in the proper order, so the begining of the part
must start just after the end of the previous part. If you provide the wrong part, then GitLab CI API will return `416
Range Not Satisfiable` response with a header `Range: 0-X`, where `X` is the current trace length.
For example, if you receive `Range: 0-11` in the response, then your next part must contain a `Content-Range: 11-...`
header and a trace part covered by this range.
For a valid update API will return `202` response with:
* `Build-Status: {status}` header containing current status of the build,
* `Range: 0-{length}` header with the current trace length.
```
PATCH /ci/api/v1/builds/:id/trace.txt
```
Parameters:
| Attribute | Type | Required | Description |
|-----------|---------|----------|----------------------|
| `id` | integer | yes | The ID of a build |
Headers:
| Attribute | Type | Required | Description |
|-----------------|---------|----------|-----------------------------------|
| `BUILD-TOKEN` | string | yes | The build authorization token |
| `Content-Range` | string | yes | Bytes range of trace that is sent |
```
curl --request PATCH "https://gitlab.example.com/ci/api/v1/builds/1234/trace.txt" --header "BUILD-TOKEN=build_t0k3n" --header "Content-Range=0-21" --data "Running git clone...\n"
```
### Upload artifacts to build
```
POST /ci/api/v1/builds/:id/artifacts
```
| Attribute | Type | Required | Description |
|-----------|---------|----------|-------------------------------|
| `id` | integer | yes | The ID of a build |
| `token` | string | yes | The build authorization token |
| `file` | mixed | yes | Artifacts file |
```
curl --request POST "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n" --form "file=@/path/to/file"
```
### Download the artifacts file from build
```
GET /ci/api/v1/builds/:id/artifacts
```
| Attribute | Type | Required | Description |
|-----------|---------|----------|-------------------------------|
| `id` | integer | yes | The ID of a build |
| `token` | string | yes | The build authorization token |
```
curl "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n"
```
### Remove the artifacts file from build
```
DELETE /ci/api/v1/builds/:id/artifacts
```
| Attribute | Type | Required | Description |
|-----------|---------|----------|-------------------------------|
| ` id` | integer | yes | The ID of a build |
| `token` | string | yes | The build authorization token |
```
curl --request DELETE "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n"
```
# Register and Delete Runners API
API used by Runners to register and delete themselves.
>**Note:**
This API is intended to be used only by Runners as their own
communication channel. For the consumer API see the
[new Runners API](../runners.md).
## Authentication
This API uses two types of authentication:
1. Unique Runner's token, which is the token assigned to the Runner after it
has been registered. This token can be found on the Runner's edit page (go to
**Project > Runners**, select one of the Runners listed under **Runners activated for
this project**).
2. Using Runners' registration token.
This is a token that can be found in project's settings.
It can also be found in the **Admin > Runners** settings area.
There are two types of tokens you can pass: shared Runner registration
token or project specific registration token.
## Register a new runner
Used to make GitLab CI aware of available runners.
```sh
POST /ci/api/v1/runners/register
```
| Attribute | Type | Required | Description |
| --------- | ------- | --------- | ----------- |
| `token` | string | yes | Runner's registration token |
Example request:
```sh
curl --request POST "https://gitlab.example.com/ci/api/v1/runners/register" --form "token=t0k3n"
```
## Delete a Runner
Used to remove a Runner.
```sh
DELETE /ci/api/v1/runners/delete
```
| Attribute | Type | Required | Description |
| --------- | ------- | --------- | ----------- |
| `token` | string | yes | Unique Runner's token |
Example request:
```sh
curl --request DELETE "https://gitlab.example.com/ci/api/v1/runners/delete" --form "token=t0k3n"
```
# Group-level Variables API # Group-level Variables API
> [Introduced][ce-34519] in GitLab 9.5
## List group variables ## List group variables
Get list of a group's variables. Get list of a group's variables.
...@@ -123,3 +125,5 @@ DELETE /groups/:id/variables/:key ...@@ -123,3 +125,5 @@ DELETE /groups/:id/variables/:key
``` ```
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/VARIABLE_1" curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/VARIABLE_1"
``` ```
[ce-34519]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34519
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
Checks if your .gitlab-ci.yml file is valid. Checks if your .gitlab-ci.yml file is valid.
``` ```
POST ci/lint POST /lint
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
...@@ -49,3 +49,4 @@ Example responses: ...@@ -49,3 +49,4 @@ Example responses:
``` ```
[ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953 [ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953
...@@ -150,4 +150,4 @@ Example response: ...@@ -150,4 +150,4 @@ Example response:
} }
``` ```
[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508 [ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
This document was moved to a [new location](../../api/ci/README.md).
This document was moved to a [new location](../../api/ci/builds.md).
This document was moved to a [new location](../../api/ci/runners.md).
...@@ -13,13 +13,17 @@ ...@@ -13,13 +13,17 @@
![Project information](img/create_new_project_info.png) ![Project information](img/create_new_project_info.png)
1. Choose if you want start a blank project, or with one of the predefined
[Project Templates](https://gitlab.com/gitlab-org/project-templates):
this will kickstart your repository code and CI automatically.
Otherwise, if you have a project in a different repository, you can [import it] by
clicking an **Import project from** button provided this is enabled in
your GitLab instance. Ask your administrator if not.
1. Provide the following information: 1. Provide the following information:
- Enter the name of your project in the **Project name** field. You can't use - Enter the name of your project in the **Project name** field. You can't use
special characters, but you can use spaces, hyphens, underscores or even special characters, but you can use spaces, hyphens, underscores or even
emoji. emoji.
- If you have a project in a different repository, you can [import it] by
clicking an **Import project from** button provided this is enabled in
your GitLab instance. Ask your administrator if not.
- The **Project description (optional)** field enables you to enter a - The **Project description (optional)** field enables you to enter a
description for your project's dashboard, which will help others description for your project's dashboard, which will help others
understand what your project is about. Though it's not required, it's a good understand what your project is about. Though it's not required, it's a good
...@@ -29,12 +33,5 @@ ...@@ -29,12 +33,5 @@
1. Click **Create project**. 1. Click **Create project**.
## From a template
To kickstart your development GitLab projects can be started from a template.
For example, one of the templates included is Ruby on Rails. When filling out the
form for new projects, click the 'Ruby on Rails' button. During project creation,
this will import a Ruby on Rails template with GitLab CI preconfigured.
[import it]: ../workflow/importing/README.md [import it]: ../workflow/importing/README.md
[reserved]: ../user/reserved_names.md [reserved]: ../user/reserved_names.md
...@@ -37,7 +37,6 @@ This page gathers all the resources for the topic **Authentication** within GitL ...@@ -37,7 +37,6 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Private Tokens](../../api/README.md#private-tokens) - [Private Tokens](../../api/README.md#private-tokens)
- [Impersonation tokens](../../api/README.md#impersonation-tokens) - [Impersonation tokens](../../api/README.md#impersonation-tokens)
- [GitLab as an OAuth2 provider](../../api/oauth2.md#gitlab-as-an-oauth2-provider) - [GitLab as an OAuth2 provider](../../api/oauth2.md#gitlab-as-an-oauth2-provider)
- [GitLab Runner API - Authentication](../../api/ci/runners.md#authentication)
## Third-party resources ## Third-party resources
......
...@@ -55,6 +55,12 @@ By doing so: ...@@ -55,6 +55,12 @@ By doing so:
- John mentions everyone from his team with `@john-team` - John mentions everyone from his team with `@john-team`
- John mentions only his marketing team with `@john-team/marketing` - John mentions only his marketing team with `@john-team/marketing`
## Issues and merge requests within a group
Issues and merge requests are part of projects. For a given group, view all the
[issues](../project/issues/index.md#issues-per-group) and [merge requests](../project/merge_requests/index.md#merge-requests-per-group) across all the projects in that group,
together in a single list view.
## Create a new group ## Create a new group
> **Notes:** > **Notes:**
......
...@@ -126,6 +126,10 @@ are a tool for working faster and more effectively with your team, ...@@ -126,6 +126,10 @@ are a tool for working faster and more effectively with your team,
by listing all user or group mentions, as well as issues and merge by listing all user or group mentions, as well as issues and merge
requests you're assigned to. requests you're assigned to.
## Search
[Search and filter](search/index.md) through groups, projects, issues, merge requests, files, code, and more.
## Snippets ## Snippets
[Snippets](snippets.md) are code blocks that you want to store in GitLab, from which [Snippets](snippets.md) are code blocks that you want to store in GitLab, from which
......
# Migrating from ClearCase
[ClearCase](https://www-03.ibm.com/software/products/en/clearcase/) is a set of
tools developed by IBM which also include a centralized version control system
similar to Git.
A good read of ClearCase's basic concepts is can be found in this [StackOverflow
post](https://stackoverflow.com/a/645771/974710).
The following table illustrates the main differences between ClearCase and Git:
| Aspect | ClearCase | Git |
| ------ | --------- | --- |
| Repository model | Client-server | Distributed |
| Revision IDs | Branch + number | Global alphanumeric ID |
| Scope of Change | File | Directory tree snapshot |
| Concurrency model | Merge | Merge |
| Storage Method | Deltas | Full content |
| Client | CLI, Eclipse, CC Client | CLI, Eclipse, Git client/GUIs |
| Server | UNIX, Windows legacy systems | UNIX, macOS |
| License | Proprietary | GPL |
_Taken from the slides [ClearCase and the journey to Git](https://www.open.collab.net/media/pdfs/ClearCase-and-the-journey-to-Git.pdf) provided by collab.net_
## Why migrate
ClearCase can be difficult to manage both from a user and an admin perspective.
Migrating to Git/GitLab there is:
- **No licensing costs**, Git is GPL while ClearCase is proprietary.
- **Shorter learning curve**, Git has a big community and a vast number of
tutorials to get you started.
- **Integration with modern tools**, migrating to Git and GitLab you can have
an open source end-to-end software development platform with built-in version
control, issue tracking, code review, CI/CD, and more.
## How to migrate
While there doesn't exist a tool to fully migrate from ClearCase to Git, here
are some useful links to get you started:
- [Bridge for Git and ClearCase](https://github.com/charleso/git-cc)
- [Slides "ClearCase and the journey to Git"](https://www.open.collab.net/media/pdfs/ClearCase-and-the-journey-to-Git.pdf)
- [ClearCase to Git](https://therub.org/2013/07/19/clearcase-to-git/)
- [Dual syncing ClearCase to Git](https://therub.org/2013/10/22/dual-syncing-clearcase-and-git/)
- [Moving to Git from ClearCase](https://sateeshkumarb.wordpress.com/2011/01/15/moving-to-git-from-clearcase/)
- [ClearCase to Git webinar](https://www.brighttalk.com/webcast/11817/162473/clearcase-to-git)
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
1. [From FogBugz](fogbugz.md) 1. [From FogBugz](fogbugz.md)
1. [From Gitea](gitea.md) 1. [From Gitea](gitea.md)
1. [From SVN](svn.md) 1. [From SVN](svn.md)
1. [From ClearCase](clearcase.md)
In addition to the specific migration documentation above, you can import any In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the Git repository via HTTP from the New Project page. Be aware that if the
......
...@@ -7,7 +7,7 @@ of solving a problem. ...@@ -7,7 +7,7 @@ of solving a problem.
It allows you, your team, and your collaborators to share It allows you, your team, and your collaborators to share
and discuss proposals before and while implementing them. and discuss proposals before and while implementing them.
Issues and the GitLab Issue Tracker are available in all GitLab Issues and the GitLab Issue Tracker are available in all
[GitLab Products](https://about.gitlab.com/products/) as [GitLab Products](https://about.gitlab.com/products/) as
part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/). part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/).
...@@ -48,11 +48,27 @@ for feature proposals and another one for bug reports. ...@@ -48,11 +48,27 @@ for feature proposals and another one for bug reports.
## Issue Tracker ## Issue Tracker
The issue tracker is the collection of opened and closed issues created in a project. The Issue Tracker is the collection of opened and closed issues created in a project.
It is available for all projects, from the moment the project is created.
![Issue tracker](img/issue_tracker.png) Find the issue tracker by navigating to your **Project's homepage** > **Issues**.
Find the issue tracker by navigating to your **Project's Dashboard** > **Issues**. ### Issues per project
When you access your project's issues, GitLab will present them in a list,
and you can use the tabs available to quickly filter by open and closed issues.
![Project issues list view](img/project_issues_list_view.png)
You can also [search and filter](../../search/index.md#issues-and-merge-requests-per-project) the results more deeply with GitLab's search capacities.
### Issues per group
View all the issues in a group (that is, all the issues across all projects in that
group) by navigating to **Group > Issues**. This view also has the open and closed
issue tabs.
![Group Issues list view](img/group_issues_list_view.png)
## GitLab Issues Functionalities ## GitLab Issues Functionalities
...@@ -140,6 +156,12 @@ and projects. ...@@ -140,6 +156,12 @@ and projects.
Read more about [Related Issues](related_issues.md). Read more about [Related Issues](related_issues.md).
### External Issue Tracker
Alternatively to GitLab's built-in Issue Tracker, you can also use an [external
tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
or Bugzilla.
### Issue's API ### Issue's API
Read through the [API documentation](../../../api/issues.md). Read through the [API documentation](../../../api/issues.md).
...@@ -57,6 +57,23 @@ B. Consider you're a web developer writing a webpage for your company's website: ...@@ -57,6 +57,23 @@ B. Consider you're a web developer writing a webpage for your company's website:
1. Once approved, your merge request is [squashed and merged](#squash-and-merge), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) 1. Once approved, your merge request is [squashed and merged](#squash-and-merge), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production 1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production
## Merge requests per project
View all the merge requests within a project by navigating to **Project > Merge Requests**.
When you access your project's merge requests, GitLab will present them in a list,
and you can use the tabs available to quickly filter by open and closed. You can also [search and filter the results](../../search/index.md#issues-and-merge-requests-per-project).
![Project merge requests list view](img/project_merge_requests_list_view.png)
## Merge requests per group
View all the merge requests in a group (that is, all the merge requests across all projects in that
group) by navigating to **Group > Merge Requests**. This view also has the open, merged, and closed
merge request tabs, from which you can [search and filter the results](../../search/index.md#issues-and-merge-requests-per-group).
![Group Issues list view](img/group_merge_requests_list_view.png)
## Authorization for merge requests ## Authorization for merge requests
There are two main ways to have a merge request flow with GitLab: There are two main ways to have a merge request flow with GitLab:
...@@ -199,7 +216,6 @@ all your changes will be available to preview by anyone with the Review Apps lin ...@@ -199,7 +216,6 @@ all your changes will be available to preview by anyone with the Review Apps lin
[Read more about Review Apps.](../../../ci/review_apps/index.md) [Read more about Review Apps.](../../../ci/review_apps/index.md)
## Tips ## Tips
Here are some tips that will help you be more efficient with merge requests in Here are some tips that will help you be more efficient with merge requests in
...@@ -288,8 +304,12 @@ git checkout origin/merge-requests/1 ...@@ -288,8 +304,12 @@ git checkout origin/merge-requests/1
``` ```
[protected branches]: ../protected_branches.md [protected branches]: ../protected_branches.md
<<<<<<< HEAD
[products]: https://about.gitlab.com/products/ "GitLab products page" [products]: https://about.gitlab.com/products/ "GitLab products page"
[ci]: ../../../ci/README.md [ci]: ../../../ci/README.md
[cc]: https://codeclimate.com/ [cc]: https://codeclimate.com/
[cd]: https://hub.docker.com/r/codeclimate/codeclimate/ [cd]: https://hub.docker.com/r/codeclimate/codeclimate/
[ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition" [ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition"
=======
[ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition"
>>>>>>> 539ed0a6375d5bb6d734e688b801373e4b8006f9
...@@ -27,7 +27,7 @@ on the search field on the top-right of your screen: ...@@ -27,7 +27,7 @@ on the search field on the top-right of your screen:
![shortcut to your issues and mrs](img/issues_mrs_shortcut.png) ![shortcut to your issues and mrs](img/issues_mrs_shortcut.png)
## Issues and merge requests per project ### Issues and merge requests per project
If you want to search for issues present in a specific project, navigate to If you want to search for issues present in a specific project, navigate to
a project's **Issues** tab, and click on the field **Search or filter results...**. It will a project's **Issues** tab, and click on the field **Search or filter results...**. It will
...@@ -40,7 +40,7 @@ The same process is valid for merge requests. Navigate to your project's **Merge ...@@ -40,7 +40,7 @@ The same process is valid for merge requests. Navigate to your project's **Merge
and click **Search or filter results...**. Merge requests can be filtered by author, assignee, and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
milestone, and label. milestone, and label.
## Issues and merge requests per group ### Issues and merge requests per group
Similar to **Issues and merge requests per project**, you can also search for issues Similar to **Issues and merge requests per project**, you can also search for issues
within a group. Navigate to a group's **Issues** tab and query search results in within a group. Navigate to a group's **Issues** tab and query search results in
...@@ -48,6 +48,10 @@ the same way as you do for projects. ...@@ -48,6 +48,10 @@ the same way as you do for projects.
![filter issues in a group](img/group_issues_filter.png) ![filter issues in a group](img/group_issues_filter.png)
The same process is valid for merge requests. Navigate to your project's **Merge Requests** tab.
The search and filter UI currently uses dropdowns. In a future release, the same
dynamic UI as above will be carried over here.
## Search history ## Search history
You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser. You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser.
......
module Ci
module API
class API < Grape::API
include ::API::APIGuard
version 'v1', using: :path
rescue_from ActiveRecord::RecordNotFound do
rack_response({ 'message' => '404 Not found' }.to_json, 404)
end
# Retain 405 error rather than a 500 error for Grape 0.15.0+.
# https://github.com/ruby-grape/grape/blob/a3a28f5b5dfbb2797442e006dbffd750b27f2a76/UPGRADING.md#changes-to-method-not-allowed-routes
rescue_from Grape::Exceptions::MethodNotAllowed do |e|
error! e.message, e.status, e.headers
end
rescue_from Grape::Exceptions::Base do |e|
error! e.message, e.status, e.headers
end
rescue_from :all do |exception|
handle_api_exception(exception)
end
content_type :txt, 'text/plain'
content_type :json, 'application/json'
format :json
helpers ::SentryHelper
helpers ::Ci::API::Helpers
helpers ::API::Helpers
helpers Gitlab::CurrentSettings
mount ::Ci::API::Builds
mount ::Ci::API::Runners
mount ::Ci::API::Triggers
end
end
end
module Ci
module API
module Entities
class Commit < Grape::Entity
expose :id, :sha, :project_id, :created_at
expose :status, :finished_at, :duration
expose :git_commit_message, :git_author_name, :git_author_email
end
class CommitWithBuilds < Commit
expose :builds
end
class ArtifactFile < Grape::Entity
expose :filename, :size
end
class BuildOptions < Grape::Entity
expose :image
expose :services
expose :artifacts
expose :cache
expose :dependencies
expose :after_script
end
class Build < Grape::Entity
expose :id, :ref, :tag, :sha, :status
expose :name, :token, :stage
expose :project_id
expose :project_name
expose :artifacts_file, using: ArtifactFile, if: ->(build, _) { build.artifacts? }
end
class BuildCredentials < Grape::Entity
expose :type, :url, :username, :password
end
class BuildDetails < Build
expose :commands
expose :repo_url
expose :before_sha
expose :allow_git_fetch
expose :token
expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? }
expose :options do |model|
# This part ensures that output of old API is still the same after adding support
# for extended docker configuration options, used by new API
#
# I'm leaving this here, not in the model, because it should be removed at the same time
# when old API will be removed (planned for August 2017).
model.options.dup.tap do |options|
options[:image] = options[:image][:name] if options[:image].is_a?(Hash)
options[:services]&.map! do |service|
if service.is_a?(Hash)
service[:name]
else
service
end
end
end
end
expose :timeout do |model|
model.timeout
end
expose :variables
expose :depends_on_builds, using: Build
expose :credentials, using: BuildCredentials
end
class Runner < Grape::Entity
expose :id, :token
end
class RunnerProject < Grape::Entity
expose :id, :project_id, :runner_id
end
class WebHook < Grape::Entity
expose :id, :project_id, :url
end
class TriggerRequest < Grape::Entity
expose :id, :variables
expose :pipeline, using: Commit, as: :commit
end
end
end
end
module Ci
module API
class Runners < Grape::API
resource :runners do
desc 'Delete a runner'
params do
requires :token, type: String, desc: 'The unique token of the runner'
end
delete "delete" do
authenticate_runner!
status(200)
Ci::Runner.find_by_token(params[:token]).destroy
end
desc 'Register a new runner' do
success Entities::Runner
end
params do
requires :token, type: String, desc: 'The unique token of the runner'
optional :description, type: String, desc: 'The description of the runner'
optional :tag_list, type: Array[String], desc: 'A list of tags the runner should run for'
optional :run_untagged, type: Boolean, desc: 'Flag if the runner should execute untagged jobs'
optional :locked, type: Boolean, desc: 'Lock this runner for this specific project'
end
post "register" do
runner_params = declared(params, include_missing: false).except(:token)
runner =
if runner_registration_token_valid?
# Create shared runner. Requires admin access
Ci::Runner.create(runner_params.merge(is_shared: true))
elsif project = Project.find_by(runners_token: params[:token])
# Create a specific runner for project.
project.runners.create(runner_params)
end
return forbidden! unless runner
if runner.id
runner.update(get_runner_version_from_params)
present runner, with: Entities::Runner
else
not_found!
end
end
end
end
end
end
module Ci
module API
class Triggers < Grape::API
resource :projects do
desc 'Trigger a GitLab CI project build' do
success Entities::TriggerRequest
end
params do
requires :id, type: Integer, desc: 'The ID of a CI project'
requires :ref, type: String, desc: "The name of project's branch or tag"
requires :token, type: String, desc: 'The unique token of the trigger'
optional :variables, type: Hash, desc: 'Optional build variables'
end
post ":id/refs/:ref/trigger" do
project = Project.find_by(ci_id: params[:id])
trigger = Ci::Trigger.find_by_token(params[:token])
not_found! unless project && trigger
unauthorized! unless trigger.project == project
# Validate variables
variables = params[:variables].to_h
unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref], variables)
pipeline = result.pipeline
if pipeline.persisted?
present result.trigger_request, with: Entities::TriggerRequest
else
render_validation_error!(pipeline)
end
end
end
end
end
end
...@@ -81,12 +81,15 @@ module Gitlab ...@@ -81,12 +81,15 @@ module Gitlab
relative_order: index relative_order: index
) )
# Compatibility with old diffs created with Psych.
diff_hash.tap do |hash| diff_hash.tap do |hash|
diff_text = hash[:diff] diff_text = hash[:diff]
hash[:too_large] = !!hash[:too_large] hash[:too_large] = !!hash[:too_large]
hash[:a_mode] ||= guess_mode(hash[:new_file], hash[:diff])
hash[:b_mode] ||= guess_mode(hash[:deleted_file], hash[:diff])
# Compatibility with old diffs created with Psych.
if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only? if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?
hash[:binary] = true hash[:binary] = true
hash[:diff] = [diff_text].pack('m0') hash[:diff] = [diff_text].pack('m0')
...@@ -97,6 +100,15 @@ module Gitlab ...@@ -97,6 +100,15 @@ module Gitlab
[commit_rows, file_rows] [commit_rows, file_rows]
end end
# This doesn't have to be 100% accurate, because it's only used for
# display - it won't change file modes in the repository. Submodules are
# created as 600, regular files as 644.
def guess_mode(file_missing, diff)
return '0' if file_missing
diff.include?('Subproject commit') ? '160000' : '100644'
end
# Unlike MergeRequestDiff#valid_raw_diff?, don't count Rugged objects as # Unlike MergeRequestDiff#valid_raw_diff?, don't count Rugged objects as
# valid, because we don't render them usefully anyway. # valid, because we don't render them usefully anyway.
def valid_raw_diffs?(diffs) def valid_raw_diffs?(diffs)
......
module Gitlab
module BackgroundMigration
class MigrateStageStatus
STATUSES = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
class Build < ActiveRecord::Base
self.table_name = 'ci_builds'
scope :latest, -> { where(retried: [false, nil]) }
scope :created, -> { where(status: 'created') }
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') }
scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :failed_but_allowed, -> do
where(allow_failure: true, status: [:failed, :canceled])
end
scope :exclude_ignored, -> do
where("allow_failure = ? OR status IN (?)",
false, %w[created pending running success skipped])
end
def self.status_sql
scope_relevant = latest.exclude_ignored
scope_warnings = latest.failed_but_allowed
builds = scope_relevant.select('count(*)').to_sql
created = scope_relevant.created.select('count(*)').to_sql
success = scope_relevant.success.select('count(*)').to_sql
manual = scope_relevant.manual.select('count(*)').to_sql
pending = scope_relevant.pending.select('count(*)').to_sql
running = scope_relevant.running.select('count(*)').to_sql
skipped = scope_relevant.skipped.select('count(*)').to_sql
canceled = scope_relevant.canceled.select('count(*)').to_sql
warnings = scope_warnings.select('count(*) > 0').to_sql
<<-SQL.strip_heredoc
(CASE
WHEN (#{builds}) = (#{skipped}) AND (#{warnings}) THEN #{STATUSES[:success]}
WHEN (#{builds}) = (#{skipped}) THEN #{STATUSES[:skipped]}
WHEN (#{builds}) = (#{success}) THEN #{STATUSES[:success]}
WHEN (#{builds}) = (#{created}) THEN #{STATUSES[:created]}
WHEN (#{builds}) = (#{success}) + (#{skipped}) THEN #{STATUSES[:success]}
WHEN (#{builds}) = (#{success}) + (#{skipped}) + (#{canceled}) THEN #{STATUSES[:canceled]}
WHEN (#{builds}) = (#{created}) + (#{skipped}) + (#{pending}) THEN #{STATUSES[:pending]}
WHEN (#{running}) + (#{pending}) > 0 THEN #{STATUSES[:running]}
WHEN (#{manual}) > 0 THEN #{STATUSES[:manual]}
WHEN (#{created}) > 0 THEN #{STATUSES[:running]}
ELSE #{STATUSES[:failed]}
END)
SQL
end
end
def perform(start_id, stop_id)
status_sql = Build
.where('ci_builds.commit_id = ci_stages.pipeline_id')
.where('ci_builds.stage = ci_stages.name')
.status_sql
sql = <<-SQL
UPDATE ci_stages SET status = (#{status_sql})
WHERE ci_stages.status IS NULL
AND ci_stages.id BETWEEN #{start_id.to_i} AND #{stop_id.to_i}
SQL
ActiveRecord::Base.connection.execute(sql)
end
end
end
end
...@@ -606,6 +606,11 @@ module Gitlab ...@@ -606,6 +606,11 @@ module Gitlab
Arel::Nodes::SqlLiteral.new(replace.to_sql) Arel::Nodes::SqlLiteral.new(replace.to_sql)
end end
end end
def remove_foreign_key_without_error(*args)
remove_foreign_key(*args)
rescue ArgumentError
end
end end
end end
end end
...@@ -64,7 +64,6 @@ module Gitlab ...@@ -64,7 +64,6 @@ module Gitlab
end end
delegate :empty?, delegate :empty?,
:bare?,
to: :rugged to: :rugged
delegate :exists?, to: :gitaly_repository_client delegate :exists?, to: :gitaly_repository_client
...@@ -126,6 +125,8 @@ module Gitlab ...@@ -126,6 +125,8 @@ module Gitlab
# This is to work around a bug in libgit2 that causes in-memory refs to # This is to work around a bug in libgit2 that causes in-memory refs to
# be stale/invalid when packed-refs is changed. # be stale/invalid when packed-refs is changed.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333 # See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/474
def find_branch(name, force_reload = false) def find_branch(name, force_reload = false)
reload_rugged if force_reload reload_rugged if force_reload
...@@ -204,21 +205,26 @@ module Gitlab ...@@ -204,21 +205,26 @@ module Gitlab
# #
# name - The name of the tag as a String. # name - The name of the tag as a String.
def tag_exists?(name) def tag_exists?(name)
!!rugged.tags[name] gitaly_migrate(:ref_exists_tags) do |is_enabled|
if is_enabled
gitaly_ref_exists?("refs/tags/#{name}")
else
rugged_tag_exists?(name)
end
end
end end
# Returns true if the given branch exists # Returns true if the given branch exists
# #
# name - The name of the branch as a String. # name - The name of the branch as a String.
def branch_exists?(name) def branch_exists?(name)
rugged.branches.exists?(name) gitaly_migrate(:ref_exists_branches) do |is_enabled|
if is_enabled
# If the branch name is invalid (e.g. ".foo") Rugged will raise an error. gitaly_ref_exists?("refs/heads/#{name}")
# Whatever code calls this method shouldn't have to deal with that so else
# instead we just return `false` (which is true since a branch doesn't rugged_branch_exists?(name)
# exist when it has an invalid name). end
rescue Rugged::ReferenceError end
false
end end
# Returns an Array of branch and tag names # Returns an Array of branch and tag names
...@@ -226,10 +232,6 @@ module Gitlab ...@@ -226,10 +232,6 @@ module Gitlab
branch_names + tag_names branch_names + tag_names
end end
def has_commits?
!empty?
end
# Discovers the default branch based on the repository's available branches # Discovers the default branch based on the repository's available branches
# #
# - If no branches are present, returns nil # - If no branches are present, returns nil
...@@ -569,6 +571,8 @@ module Gitlab ...@@ -569,6 +571,8 @@ module Gitlab
end end
# Delete the specified branch from the repository # Delete the specified branch from the repository
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/476
def delete_branch(branch_name) def delete_branch(branch_name)
rugged.branches.delete(branch_name) rugged.branches.delete(branch_name)
end end
...@@ -578,6 +582,8 @@ module Gitlab ...@@ -578,6 +582,8 @@ module Gitlab
# Examples: # Examples:
# create_branch("feature") # create_branch("feature")
# create_branch("other-feature", "master") # create_branch("other-feature", "master")
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/476
def create_branch(ref, start_point = "HEAD") def create_branch(ref, start_point = "HEAD")
rugged_ref = rugged.branches.create(ref, start_point) rugged_ref = rugged.branches.create(ref, start_point)
target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
...@@ -587,38 +593,26 @@ module Gitlab ...@@ -587,38 +593,26 @@ module Gitlab
raise InvalidRef.new("Invalid reference #{start_point}") raise InvalidRef.new("Invalid reference #{start_point}")
end end
# Return an array of this repository's remote names
def remote_names
rugged.remotes.each_name.to_a
end
# Delete the specified remote from this repository. # Delete the specified remote from this repository.
def remote_delete(remote_name) def remote_delete(remote_name)
rugged.remotes.delete(remote_name) rugged.remotes.delete(remote_name)
nil
end end
# Add a new remote to this repository. Returns a Rugged::Remote object # Add a new remote to this repository.
def remote_add(remote_name, url) def remote_add(remote_name, url)
rugged.remotes.create(remote_name, url) rugged.remotes.create(remote_name, url)
nil
end end
# Update the specified remote using the values in the +options+ hash # Update the specified remote using the values in the +options+ hash
# #
# Example # Example
# repo.update_remote("origin", url: "path/to/repo") # repo.update_remote("origin", url: "path/to/repo")
def remote_update(remote_name, options = {}) def remote_update(remote_name, url:)
# TODO: Implement other remote options # TODO: Implement other remote options
rugged.remotes.set_url(remote_name, options[:url]) if options[:url] rugged.remotes.set_url(remote_name, url)
end nil
# Fetch the specified remote
def fetch(remote_name)
rugged.remotes[remote_name].fetch
end
# Push +*refspecs+ to the remote identified by +remote_name+.
def push(remote_name, *refspecs)
rugged.remotes[remote_name].push(refspecs)
end end
AUTOCRLF_VALUES = { AUTOCRLF_VALUES = {
...@@ -995,6 +989,34 @@ module Gitlab ...@@ -995,6 +989,34 @@ module Gitlab
raw_output.compact raw_output.compact
end end
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
def gitaly_ref_exists?(ref_name)
gitaly_ref_client.ref_exists?(ref_name)
end
# Returns true if the given tag exists
#
# name - The name of the tag as a String.
def rugged_tag_exists?(name)
!!rugged.tags[name]
end
# Returns true if the given branch exists
#
# name - The name of the branch as a String.
def rugged_branch_exists?(name)
rugged.branches.exists?(name)
# If the branch name is invalid (e.g. ".foo") Rugged will raise an error.
# Whatever code calls this method shouldn't have to deal with that so
# instead we just return `false` (which is true since a branch doesn't
# exist when it has an invalid name).
rescue Rugged::ReferenceError
false
end
def gitaly_copy_gitattributes(revision) def gitaly_copy_gitattributes(revision)
gitaly_repository_client.apply_gitattributes(revision) gitaly_repository_client.apply_gitattributes(revision)
end end
......
...@@ -80,8 +80,8 @@ module Gitlab ...@@ -80,8 +80,8 @@ module Gitlab
def tree_entries(repository, revision, path) def tree_entries(repository, revision, path)
request = Gitaly::GetTreeEntriesRequest.new( request = Gitaly::GetTreeEntriesRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
revision: revision, revision: GitalyClient.encode(revision),
path: path.presence || '.' path: path.present? ? GitalyClient.encode(path) : '.'
) )
response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request) response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request)
......
...@@ -70,6 +70,14 @@ module Gitlab ...@@ -70,6 +70,14 @@ module Gitlab
consume_tags_response(response) consume_tags_response(response)
end end
def ref_exists?(ref_name)
request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: ref_name)
response = GitalyClient.call(@storage, :ref_service, :ref_exists, request)
response.value
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
private private
def consume_refs_response(response) def consume_refs_response(response)
......
...@@ -3,6 +3,8 @@ module Gitlab ...@@ -3,6 +3,8 @@ module Gitlab
class << self class << self
def execute! def execute!
if Gitlab::CurrentSettings.sidekiq_throttling_enabled? if Gitlab::CurrentSettings.sidekiq_throttling_enabled?
require 'sidekiq-limit_fetch'
Gitlab::CurrentSettings.current_application_settings.sidekiq_throttling_queues.each do |queue| Gitlab::CurrentSettings.current_application_settings.sidekiq_throttling_queues.each do |queue|
Sidekiq::Queue[queue].limit = queue_limit Sidekiq::Queue[queue].limit = queue_limit
end end
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-13 12:07-0500\n" "POT-Creation-Date: 2017-08-18 14:15+0530\n"
"PO-Revision-Date: 2017-07-13 12:07-0500\n" "PO-Revision-Date: 2017-08-18 14:15+0530\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -31,6 +31,23 @@ msgstr[1] "" ...@@ -31,6 +31,23 @@ msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}" msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "" msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
msgstr ""
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
msgstr[0] ""
msgstr[1] ""
msgid "(checkout the %{link} for information on how to install it)."
msgstr ""
msgid "1 pipeline" msgid "1 pipeline"
msgid_plural "%d pipelines" msgid_plural "%d pipelines"
msgstr[0] "" msgstr[0] ""
...@@ -42,6 +59,9 @@ msgstr "" ...@@ -42,6 +59,9 @@ msgstr ""
msgid "About auto deploy" msgid "About auto deploy"
msgstr "" msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
msgid "Active" msgid "Active"
msgstr "" msgstr ""
...@@ -63,12 +83,27 @@ msgstr "" ...@@ -63,12 +83,27 @@ msgstr ""
msgid "Add new directory" msgid "Add new directory"
msgstr "" msgstr ""
msgid "All"
msgstr ""
msgid "Archived project! Repository is read-only" msgid "Archived project! Repository is read-only"
msgstr "" msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?" msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "" msgstr ""
msgid "Are you sure you want to discard your changes?"
msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
msgid "Are you sure?"
msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}" msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "" msgstr ""
...@@ -110,6 +145,9 @@ msgstr "" ...@@ -110,6 +145,9 @@ msgstr ""
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
msgid "Cancel edit"
msgstr ""
msgid "ChangeTypeActionLabel|Pick into branch" msgid "ChangeTypeActionLabel|Pick into branch"
msgstr "" msgstr ""
...@@ -188,6 +226,9 @@ msgstr "" ...@@ -188,6 +226,9 @@ msgstr ""
msgid "CiStatus|running" msgid "CiStatus|running"
msgstr "" msgstr ""
msgid "Comments"
msgstr ""
msgid "Commit" msgid "Commit"
msgid_plural "Commits" msgid_plural "Commits"
msgstr[0] "" msgstr[0] ""
...@@ -235,6 +276,9 @@ msgstr "" ...@@ -235,6 +276,9 @@ msgstr ""
msgid "Create New Directory" msgid "Create New Directory"
msgstr "" msgstr ""
msgid "Create a new branch"
msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}." msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "" msgstr ""
...@@ -312,9 +356,15 @@ msgstr[1] "" ...@@ -312,9 +356,15 @@ msgstr[1] ""
msgid "Description" msgid "Description"
msgstr "" msgstr ""
msgid "Details"
msgstr ""
msgid "Directory name" msgid "Directory name"
msgstr "" msgstr ""
msgid "Discard changes"
msgstr ""
msgid "Don't show again" msgid "Don't show again"
msgstr "" msgstr ""
...@@ -351,6 +401,24 @@ msgstr "" ...@@ -351,6 +401,24 @@ msgstr ""
msgid "Edit Pipeline Schedule %{id}" msgid "Edit Pipeline Schedule %{id}"
msgstr "" msgstr ""
msgid "EventFilterBy|Filter by all"
msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
msgid "EventFilterBy|Filter by issue events"
msgstr ""
msgid "EventFilterBy|Filter by merge events"
msgstr ""
msgid "EventFilterBy|Filter by push events"
msgstr ""
msgid "EventFilterBy|Filter by team"
msgstr ""
msgid "Every day (at 4:00am)" msgid "Every day (at 4:00am)"
msgstr "" msgstr ""
...@@ -398,12 +466,36 @@ msgstr "" ...@@ -398,12 +466,36 @@ msgstr ""
msgid "From merge request merge until deploy to production" msgid "From merge request merge until deploy to production"
msgstr "" msgstr ""
msgid "Git storage health information has been reset"
msgstr ""
msgid "GitLab Runner section"
msgstr ""
msgid "Go to your fork" msgid "Go to your fork"
msgstr "" msgstr ""
msgid "GoToYourFork|Fork" msgid "GoToYourFork|Fork"
msgstr "" msgstr ""
msgid "Health Check"
msgstr ""
msgid "Health information can be retrieved from the following endpoints. More information is available"
msgstr ""
msgid "HealthCheck|Access token is"
msgstr ""
msgid "HealthCheck|Healthy"
msgstr ""
msgid "HealthCheck|No Health Problems Detected"
msgstr ""
msgid "HealthCheck|Unhealthy"
msgstr ""
msgid "Home" msgid "Home"
msgstr "" msgstr ""
...@@ -413,12 +505,18 @@ msgstr "" ...@@ -413,12 +505,18 @@ msgstr ""
msgid "Import repository" msgid "Import repository"
msgstr "" msgstr ""
msgid "Install a Runner compatible with GitLab CI"
msgstr ""
msgid "Interval Pattern" msgid "Interval Pattern"
msgstr "" msgstr ""
msgid "Introducing Cycle Analytics" msgid "Introducing Cycle Analytics"
msgstr "" msgstr ""
msgid "Issue events"
msgstr ""
msgid "Jobs for last month" msgid "Jobs for last month"
msgstr "" msgstr ""
...@@ -448,6 +546,12 @@ msgstr "" ...@@ -448,6 +546,12 @@ msgstr ""
msgid "Last commit" msgid "Last commit"
msgstr "" msgstr ""
msgid "LastPushEvent|You pushed to"
msgstr ""
msgid "LastPushEvent|at"
msgstr ""
msgid "Learn more in the" msgid "Learn more in the"
msgstr "" msgstr ""
...@@ -468,9 +572,15 @@ msgstr[1] "" ...@@ -468,9 +572,15 @@ msgstr[1] ""
msgid "Median" msgid "Median"
msgstr "" msgstr ""
msgid "Merge events"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key" msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "" msgstr ""
msgid "More information is available|here"
msgstr ""
msgid "New Issue" msgid "New Issue"
msgid_plural "New Issues" msgid_plural "New Issues"
msgstr[0] "" msgstr[0] ""
...@@ -668,6 +778,9 @@ msgstr "" ...@@ -668,6 +778,9 @@ msgstr ""
msgid "Pipeline|with stages" msgid "Pipeline|with stages"
msgstr "" msgstr ""
msgid "Project"
msgstr ""
msgid "Project '%{project_name}' queued for deletion." msgid "Project '%{project_name}' queued for deletion."
msgstr "" msgstr ""
...@@ -683,6 +796,9 @@ msgstr "" ...@@ -683,6 +796,9 @@ msgstr ""
msgid "Project access must be granted explicitly to each user." msgid "Project access must be granted explicitly to each user."
msgstr "" msgstr ""
msgid "Project details"
msgstr ""
msgid "Project export could not be deleted." msgid "Project export could not be deleted."
msgstr "" msgstr ""
...@@ -698,6 +814,9 @@ msgstr "" ...@@ -698,6 +814,9 @@ msgstr ""
msgid "Project home" msgid "Project home"
msgstr "" msgstr ""
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
msgid "ProjectFeature|Disabled" msgid "ProjectFeature|Disabled"
msgstr "" msgstr ""
...@@ -719,6 +838,9 @@ msgstr "" ...@@ -719,6 +838,9 @@ msgstr ""
msgid "ProjectNetworkGraph|Graph" msgid "ProjectNetworkGraph|Graph"
msgstr "" msgstr ""
msgid "Push events"
msgstr ""
msgid "Read more" msgid "Read more"
msgstr "" msgstr ""
...@@ -755,9 +877,21 @@ msgstr "" ...@@ -755,9 +877,21 @@ msgstr ""
msgid "Remove project" msgid "Remove project"
msgstr "" msgstr ""
msgid "Repository"
msgstr ""
msgid "Request Access" msgid "Request Access"
msgstr "" msgstr ""
msgid "Reset git storage health information"
msgstr ""
msgid "Reset health check access token"
msgstr ""
msgid "Reset runners registration token"
msgstr ""
msgid "Revert this commit" msgid "Revert this commit"
msgstr "" msgstr ""
...@@ -782,6 +916,9 @@ msgstr "" ...@@ -782,6 +916,9 @@ msgstr ""
msgid "Select a timezone" msgid "Select a timezone"
msgstr "" msgstr ""
msgid "Select existing branch"
msgstr ""
msgid "Select target branch" msgid "Select target branch"
msgstr "" msgstr ""
...@@ -808,12 +945,18 @@ msgstr[1] "" ...@@ -808,12 +945,18 @@ msgstr[1] ""
msgid "Source code" msgid "Source code"
msgstr "" msgstr ""
msgid "Specify the following URL during the Runner setup:"
msgstr ""
msgid "StarProject|Star" msgid "StarProject|Star"
msgstr "" msgstr ""
msgid "Start a %{new_merge_request} with these changes" msgid "Start a %{new_merge_request} with these changes"
msgstr "" msgstr ""
msgid "Start the Runner!"
msgstr ""
msgid "Switch branch/tag" msgid "Switch branch/tag"
msgstr "" msgstr ""
...@@ -828,6 +971,9 @@ msgstr "" ...@@ -828,6 +971,9 @@ msgstr ""
msgid "Target Branch" msgid "Target Branch"
msgstr "" msgstr ""
msgid "Team"
msgstr ""
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr "" msgstr ""
...@@ -876,6 +1022,9 @@ msgstr "" ...@@ -876,6 +1022,9 @@ msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "" msgstr ""
msgid "There are problems accessing Git storage: "
msgstr ""
msgid "This means you can not push code until you create an empty repository or import existing one." msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "" msgstr ""
...@@ -1045,6 +1194,9 @@ msgstr "" ...@@ -1045,6 +1194,9 @@ msgstr ""
msgid "UploadLink|click to upload" msgid "UploadLink|click to upload"
msgstr "" msgstr ""
msgid "Use the following registration token during setup:"
msgstr ""
msgid "Use your global notification setting" msgid "Use your global notification setting"
msgstr "" msgstr ""
......
...@@ -20,8 +20,12 @@ ...@@ -20,8 +20,12 @@
"babel-preset-latest": "^6.24.0", "babel-preset-latest": "^6.24.0",
"babel-preset-stage-2": "^6.22.0", "babel-preset-stage-2": "^6.22.0",
"bootstrap-sass": "^3.3.6", "bootstrap-sass": "^3.3.6",
<<<<<<< HEAD
"clipboard": "^1.6.1", "clipboard": "^1.6.1",
"compression-webpack-plugin": "^0.3.2", "compression-webpack-plugin": "^0.3.2",
=======
"compression-webpack-plugin": "^1.0.0",
>>>>>>> 539ed0a6375d5bb6d734e688b801373e4b8006f9
"copy-webpack-plugin": "^4.0.1", "copy-webpack-plugin": "^4.0.1",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"cropper": "^2.3.0", "cropper": "^2.3.0",
...@@ -64,7 +68,7 @@ ...@@ -64,7 +68,7 @@
"vue-loader": "^11.3.4", "vue-loader": "^11.3.4",
"vue-resource": "^1.3.4", "vue-resource": "^1.3.4",
"vue-template-compiler": "^2.2.6", "vue-template-compiler": "^2.2.6",
"webpack": "^3.5.4", "webpack": "^3.5.5",
"webpack-bundle-analyzer": "^2.8.2", "webpack-bundle-analyzer": "^2.8.2",
"webpack-stats-plugin": "^0.1.5" "webpack-stats-plugin": "^0.1.5"
}, },
......
...@@ -15,4 +15,12 @@ FactoryGirl.define do ...@@ -15,4 +15,12 @@ FactoryGirl.define do
warnings: warnings) warnings: warnings)
end end
end end
factory :ci_stage_entity, class: Ci::Stage do
project factory: :project
pipeline factory: :ci_empty_pipeline
name 'test'
status 'pending'
end
end end
...@@ -41,6 +41,8 @@ describe "User Feed" do ...@@ -41,6 +41,8 @@ describe "User Feed" do
target_project: project, target_project: project,
description: "Here is the fix: ![an image](image.png)") description: "Here is the fix: ![an image](image.png)")
end end
let(:push_event) { create(:push_event, project: project, author: user) }
let!(:push_event_payload) { create(:push_event_payload, event: push_event) }
before do before do
project.team << [user, :master] project.team << [user, :master]
...@@ -70,6 +72,10 @@ describe "User Feed" do ...@@ -70,6 +72,10 @@ describe "User Feed" do
it 'has XHTML summaries in merge request descriptions' do it 'has XHTML summaries in merge request descriptions' do
expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/ expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/
end end
it 'has push event commit ID' do
expect(body).to have_content(Commit.truncate_sha(push_event.commit_id))
end
end end
end end
......
...@@ -62,6 +62,12 @@ describe EventsHelper do ...@@ -62,6 +62,12 @@ describe EventsHelper do
expect(helper.event_note(input)).to eq(expected) expect(helper.event_note(input)).to eq(expected)
end end
it 'preserves data-src for lazy images' do
input = "![ImageTest](/uploads/test.png)"
image_url = "data-src=\"/uploads/test.png\""
expect(helper.event_note(input)).to match(image_url)
end
context 'labels formatting' do context 'labels formatting' do
let(:input) { 'this should be ~label_1' } let(:input) { 'this should be ~label_1' }
......
...@@ -2,6 +2,22 @@ require 'spec_helper' ...@@ -2,6 +2,22 @@ require 'spec_helper'
require_relative '../../config/initializers/1_settings' require_relative '../../config/initializers/1_settings'
describe Settings do describe Settings do
describe '#ldap' do
it 'can be accessed with dot syntax all the way down' do
expect(Gitlab.config.ldap.servers.main.label).to eq('ldap')
end
# Specifically trying to cause this error discovered in EE when removing the
# reassignment of each server element with Settingslogic.
#
# `undefined method `label' for #<Hash:0x007fbd18b59c08>`
#
it 'can be accessed in a very specific way that breaks without reassigning each element with Settingslogic' do
server_settings = Gitlab.config.ldap.servers['main']
expect(server_settings.label).to eq('ldap')
end
end
describe '#repositories' do describe '#repositories' do
it 'assigns the default failure attributes' do it 'assigns the default failure attributes' do
repository_settings = Gitlab.config.repositories.storages['broken'] repository_settings = Gitlab.config.repositories.storages['broken']
...@@ -11,6 +27,15 @@ describe Settings do ...@@ -11,6 +27,15 @@ describe Settings do
expect(repository_settings['failure_reset_time']).to eq(1800) expect(repository_settings['failure_reset_time']).to eq(1800)
expect(repository_settings['storage_timeout']).to eq(5) expect(repository_settings['storage_timeout']).to eq(5)
end end
it 'can be accessed with dot syntax all the way down' do
expect(Gitlab.config.repositories.storages.broken.failure_count_threshold).to eq(10)
end
it 'can be accessed in a very specific way that breaks without reassigning each element with Settingslogic' do
storage_settings = Gitlab.config.repositories.storages['broken']
expect(storage_settings.failure_count_threshold).to eq(10)
end
end end
describe '#host_without_www' do describe '#host_without_www' do
......
...@@ -30,6 +30,8 @@ describe('Issue boards new issue form', () => { ...@@ -30,6 +30,8 @@ describe('Issue boards new issue form', () => {
}; };
beforeEach((done) => { beforeEach((done) => {
setFixtures('<div class="test-container"></div>');
const BoardNewIssueComp = Vue.extend(boardNewIssue); const BoardNewIssueComp = Vue.extend(boardNewIssue);
Vue.http.interceptors.push(boardsMockInterceptor); Vue.http.interceptors.push(boardsMockInterceptor);
...@@ -46,15 +48,17 @@ describe('Issue boards new issue form', () => { ...@@ -46,15 +48,17 @@ describe('Issue boards new issue form', () => {
propsData: { propsData: {
list, list,
}, },
}).$mount(); }).$mount(document.querySelector('.test-container'));
Vue.nextTick() Vue.nextTick()
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
afterEach(() => vm.$destroy());
it('calls submit if submit button is clicked', (done) => { it('calls submit if submit button is clicked', (done) => {
spyOn(vm, 'submit'); spyOn(vm, 'submit').and.callFake(e => e.preventDefault());
vm.title = 'Testing Title'; vm.title = 'Testing Title';
Vue.nextTick() Vue.nextTick()
......
...@@ -278,6 +278,25 @@ describe('Issue card component', () => { ...@@ -278,6 +278,25 @@ describe('Issue card component', () => {
nodes.includes(label1.color), nodes.includes(label1.color),
).toBe(true); ).toBe(true);
}); });
it('does not render label if label does not have an ID', (done) => {
component.issue.addLabel(new ListLabel({
title: 'closed',
}));
Vue.nextTick()
.then(() => {
expect(
component.$el.querySelectorAll('.label').length,
).toBe(2);
expect(
component.$el.textContent,
).not.toContain('closed');
done();
})
.catch(done.fail);
});
}); });
}); });
}); });
.project-item-select-holder .project-item-select-holder
%input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } } %input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } }
%a.new-project-item-link{ data: { label: 'New issue' }, href: ''} %a.new-project-item-link{ data: { label: 'New issue', type: 'issues' }, href: ''}
%i.fa.fa-spinner.spin %i.fa.fa-spinner.spin
%a.new-project-item-select-button %a.new-project-item-select-button
%i.fa.fa-caret-down %i.fa.fa-caret-down
...@@ -266,6 +266,12 @@ import '~/lib/utils/common_utils'; ...@@ -266,6 +266,12 @@ import '~/lib/utils/common_utils';
}); });
describe('gl.utils.backOff', () => { describe('gl.utils.backOff', () => {
beforeEach(() => {
// shortcut our timeouts otherwise these tests will take a long time to finish
const origSetTimeout = window.setTimeout;
spyOn(window, 'setTimeout').and.callFake(cb => origSetTimeout(cb, 0));
});
it('solves the promise from the callback', (done) => { it('solves the promise from the callback', (done) => {
const expectedResponseValue = 'Success!'; const expectedResponseValue = 'Success!';
gl.utils.backOff((next, stop) => ( gl.utils.backOff((next, stop) => (
...@@ -299,37 +305,33 @@ import '~/lib/utils/common_utils'; ...@@ -299,37 +305,33 @@ import '~/lib/utils/common_utils';
let numberOfCalls = 1; let numberOfCalls = 1;
const expectedResponseValue = 'Success!'; const expectedResponseValue = 'Success!';
gl.utils.backOff((next, stop) => ( gl.utils.backOff((next, stop) => (
new Promise((resolve) => { Promise.resolve(expectedResponseValue)
resolve(expectedResponseValue); .then((resp) => {
}).then((resp) => { if (numberOfCalls < 3) {
if (numberOfCalls < 3) { numberOfCalls += 1;
numberOfCalls += 1; next();
next(); } else {
} else { stop(resp);
stop(resp); }
} })
})
)).then((respBackoff) => { )).then((respBackoff) => {
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
expect(timeouts).toEqual([2000, 4000]);
expect(respBackoff).toBe(expectedResponseValue); expect(respBackoff).toBe(expectedResponseValue);
expect(numberOfCalls).toBe(3);
done(); done();
}); });
}, 10000); });
it('rejects the backOff promise after timing out', (done) => { it('rejects the backOff promise after timing out', (done) => {
const expectedResponseValue = 'Success!'; gl.utils.backOff(next => next(), 64000)
gl.utils.backOff(next => ( .catch((errBackoffResp) => {
new Promise((resolve) => { const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
resolve(expectedResponseValue); expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]);
}).then(() => { expect(errBackoffResp instanceof Error).toBe(true);
setTimeout(next(), 5000); // it will time out expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT');
}) done();
), 3000).catch((errBackoffResp) => { });
expect(errBackoffResp instanceof Error).toBe(true); });
expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT');
done();
});
}, 10000);
}); });
describe('gl.utils.setFavicon', () => { describe('gl.utils.setFavicon', () => {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment