Commit 0b2469a4 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/persistent-import-data

parents c607b1c9 9ca633eb
# Prefer single quotes
StringLiterals:
EnforcedStyle: single_quotes
Enabled: true
stage:
before:
- cp config/gitlab.teatro.yml config/gitlab.yml
- mkdir /apps/gitlab-satellites
- mkdir /apps/repositories
database:
- RAILS_ENV=development force=yes bundle exec rake db:create gitlab:setup
\ No newline at end of file
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.10.0 (unreleased) v 8.10.0 (unreleased)
- Expose {should,force}_remove_source_branch (Ben Boeckel)
- Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Fix commit builds API, return all builds for all pipelines for given commit. !4849
- Replace Haml with Hamlit to make view rendering faster. !3666 - Replace Haml with Hamlit to make view rendering faster. !3666
- Expire the branch cache after `git gc` runs
- Refactor repository paths handling to allow multiple git mount points - Refactor repository paths handling to allow multiple git mount points
- Optimize system note visibility checking by memoizing the visible reference count !5070 - Optimize system note visibility checking by memoizing the visible reference count !5070
- Add Application Setting to configure default Repository Path for new projects - Add Application Setting to configure default Repository Path for new projects
- Delete award emoji when deleting a user
- Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell) - Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell)
- Wrap code blocks on Activies and Todos page. !4783 (winniehell) - Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Align flash messages with left side of page content !4959 (winniehell) - Align flash messages with left side of page content !4959 (winniehell)
...@@ -30,7 +33,9 @@ v 8.10.0 (unreleased) ...@@ -30,7 +33,9 @@ v 8.10.0 (unreleased)
- Exclude email check from the standard health check - Exclude email check from the standard health check
- Updated layout for Projects, Groups, Users on Admin area !4424 - Updated layout for Projects, Groups, Users on Admin area !4424
- Fix changing issue state columns in milestone view - Fix changing issue state columns in milestone view
- Update health_check gem to version 2.1.0
- Add notification settings dropdown for groups - Add notification settings dropdown for groups
- Render inline diffs for multiple changed lines following eachother
- Wildcards for protected branches. !4665 - Wildcards for protected branches. !4665
- Allow importing from Github using Personal Access Tokens. (Eric K Idema) - Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Todos !3188 (Robert Schilling) - API: Todos !3188 (Robert Schilling)
...@@ -38,6 +43,7 @@ v 8.10.0 (unreleased) ...@@ -38,6 +43,7 @@ v 8.10.0 (unreleased)
- Add "Enabled Git access protocols" to Application Settings - Add "Enabled Git access protocols" to Application Settings
- Diffs will create button/diff form on demand no on server side - Diffs will create button/diff form on demand no on server side
- Reduce size of HTML used by diff comment forms - Reduce size of HTML used by diff comment forms
- Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard)
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Only show New Snippet button to users that can create snippets. - Only show New Snippet button to users that can create snippets.
- PipelinesFinder uses git cache data - PipelinesFinder uses git cache data
...@@ -46,14 +52,17 @@ v 8.10.0 (unreleased) ...@@ -46,14 +52,17 @@ v 8.10.0 (unreleased)
- Collapse large diffs by default (!4990) - Collapse large diffs by default (!4990)
- Check for conflicts with existing Project's wiki path when creating a new project. - Check for conflicts with existing Project's wiki path when creating a new project.
- Show last push widget in upstream after push to fork - Show last push widget in upstream after push to fork
- Cache todos pending/done dashboard query counts.
- Don't instantiate a git tree on Projects show default view - Don't instantiate a git tree on Projects show default view
- Bump Rinku to 2.0.0 - Bump Rinku to 2.0.0
- Remove unused front-end variable -> default_issues_tracker - Remove unused front-end variable -> default_issues_tracker
- ObjectRenderer retrieve renderer content using Rails.cache.read_multi
- Better caching of git calls on ProjectsController#show. - Better caching of git calls on ProjectsController#show.
- Avoid to retrieve MR closes_issues as much as possible. - Avoid to retrieve MR closes_issues as much as possible.
- Add API endpoint for a group issues !4520 (mahcsig) - Add API endpoint for a group issues !4520 (mahcsig)
- Add Bugzilla integration !4930 (iamtjg) - Add Bugzilla integration !4930 (iamtjg)
- Instrument Rinku usage - Instrument Rinku usage
- Be explicit to define merge request discussion variables
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
...@@ -67,12 +76,25 @@ v 8.10.0 (unreleased) ...@@ -67,12 +76,25 @@ v 8.10.0 (unreleased)
- Allow '?', or '&' for label names - Allow '?', or '&' for label names
- Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
- Add date when user joined the team on the member page - Add date when user joined the team on the member page
- Fix 404 redirect after validation fails importing a GitLab project - Fix 404 redirect after validation fails importing a GitLab project
- Fix 404 redirect after validation fails importing a GitLab project - Fix 404 redirect after validation fails importing a GitLab project
- Added setting to set new users by default as external !4545 (Dravere) - Added setting to set new users by default as external !4545 (Dravere)
- Add min value for project limit field on user's form !3622 (jastkand) - Add min value for project limit field on user's form !3622 (jastkand)
- Reset project pushes_since_gc when we enqueue the git gc call
- Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt) - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt)
- Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel)
- Style of import project buttons were fixed in the new project page. !5183 (rdemirbay)
- Fix GitHub client requests when rate limit is disabled
- Optimistic locking for Issues and Merge Requests (Title and description overriding prevention)
- Redesign Builds and Pipelines pages
- Change status color and icon for running builds
v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154
- Fix log statements in import/export. !5129
- Fix commit avatar alignment in compare view. !5128
- Fix broken migration in MySQL. !5005
- Overwrite Host and X-Forwarded-Host headers in NGINX !5213
v 8.9.7 (unreleased) v 8.9.7 (unreleased)
- Fix import_data wrongly saved as a result of an invalid import_url - Fix import_data wrongly saved as a result of an invalid import_url
......
...@@ -344,7 +344,7 @@ gem 'oauth2', '~> 1.2.0' ...@@ -344,7 +344,7 @@ gem 'oauth2', '~> 1.2.0'
gem 'paranoia', '~> 2.0' gem 'paranoia', '~> 2.0'
# Health check # Health check
gem 'health_check', '~> 1.5.1' gem 'health_check', '~> 2.1.0'
# System information # System information
gem 'vmstat', '~> 2.1.0' gem 'vmstat', '~> 2.1.0'
......
...@@ -322,8 +322,8 @@ GEM ...@@ -322,8 +322,8 @@ GEM
thor thor
tilt tilt
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1) health_check (2.1.0)
rails (>= 2.3.0) rails (>= 4.0)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
mimemagic mimemagic
...@@ -870,7 +870,7 @@ DEPENDENCIES ...@@ -870,7 +870,7 @@ DEPENDENCIES
grape (~> 0.13.0) grape (~> 0.13.0)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
hamlit (~> 2.5) hamlit (~> 2.5)
health_check (~> 1.5.1) health_check (~> 2.1.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
httparty (~> 0.13.3) httparty (~> 0.13.3)
......
class @Diff class @Diff
UNFOLD_COUNT = 20 UNFOLD_COUNT = 20
constructor: -> constructor: ->
$('.files .diff-file').singleFileDiff()
@filesCommentButton = $('.files .diff-file').filesCommentButton() @filesCommentButton = $('.files .diff-file').filesCommentButton()
$(document).off('click', '.js-unfold') $(document).off('click', '.js-unfold')
......
$ -> $ ->
$(".protected-branches-list :checkbox").change (e) -> $(".protected-branches-list :checkbox").change (e) ->
name = $(this).attr("name") name = $(this).attr("name")
if name == "developers_can_push" if name == "developers_can_push" || name == "developers_can_merge"
id = $(this).val() id = $(this).val()
checked = $(this).is(":checked") can_push = $(this).is(":checked")
url = $(this).data("url") url = $(this).data("url")
$.ajax $.ajax
type: "PUT" type: "PATCH"
url: url url: url
dataType: "json" dataType: "json"
data: data:
id: id id: id
protected_branch: protected_branch:
developers_can_push: checked "#{name}": can_push
success: -> success: ->
row = $(e.target) row = $(e.target)
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
} }
&.s16 { width: 16px; height: 16px; margin-right: 6px; } &.s16 { width: 16px; height: 16px; margin-right: 6px; }
&.s20 { width: 20px; height: 20px; margin-right: 7px; }
&.s24 { width: 24px; height: 24px; margin-right: 8px; } &.s24 { width: 24px; height: 24px; margin-right: 8px; }
&.s26 { width: 26px; height: 26px; margin-right: 8px; } &.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; } &.s32 { width: 32px; height: 32px; margin-right: 10px; }
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
.toggle-nav-collapse, .toggle-nav-collapse,
.pin-nav-btn { .pin-nav-btn {
color: $color-light; color: $color-light;
background: $color;
&:hover { &:hover {
color: $white-light; color: $white-light;
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
transition: width $sidebar-transition-duration; transition: width $sidebar-transition-duration;
@include box-shadow(2px 0 16px 0 #bbb); @include box-shadow(2px 0 16px 0 $black-transparent);
} }
} }
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
} }
a { a {
padding: 7px 15px 7px 12px; padding: 7px 16px;
font-size: $gl-font-size; font-size: $gl-font-size;
line-height: 24px; line-height: 24px;
display: block; display: block;
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
} }
} }
.toggle-nav-collapse { .sidebar-action-buttons {
width: $sidebar_width; width: $sidebar_width;
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -117,12 +117,37 @@ ...@@ -117,12 +117,37 @@
padding: 5px 0; padding: 5px 0;
font-size: 18px; font-size: 18px;
line-height: 30px; line-height: 30px;
.toggle-nav-collapse {
left: 0;
}
.pin-nav-btn {
right: 0;
display: none;
@media (min-width: $sidebar-breakpoint) {
display: block;
}
.fa {
transition: transform .15s;
}
&.is-active {
.fa {
transform: rotate(90deg);
}
}
}
} }
.nav-header-btn { .nav-header-btn {
padding: 10px 5px; padding: 10px 16px;
color: inherit; color: inherit;
transition-duration: .3s; transition-duration: .3s;
position: absolute;
top: 0;
&:hover, &:hover,
&:focus { &:focus {
...@@ -131,30 +156,6 @@ ...@@ -131,30 +156,6 @@
} }
} }
.pin-nav-btn {
display: none;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
width: $sidebar_width;
line-height: 30px;
@media (min-width: $sidebar-breakpoint) {
display: block;
}
.fa {
transition: transform .15s;
}
&.is-active {
.fa {
transform: rotate(90deg);
}
}
}
.page-sidebar-collapsed { .page-sidebar-collapsed {
padding-left: 0; padding-left: 0;
......
...@@ -17,6 +17,7 @@ $focus-border-color: #3aabf0; ...@@ -17,6 +17,7 @@ $focus-border-color: #3aabf0;
$table-border-color: #f0f0f0; $table-border-color: #f0f0f0;
$background-color: #fafafa; $background-color: #fafafa;
$dark-background-color: #f7f7f7; $dark-background-color: #f7f7f7;
$table-text-gray: #8f8f8f;
/* /*
* Text * Text
......
...@@ -83,14 +83,6 @@ ...@@ -83,14 +83,6 @@
} }
} }
table.builds {
.build-link {
a {
color: $gl-dark-link-color;
}
}
}
.build-trace { .build-trace {
background: $ci-output-bg; background: $ci-output-bg;
color: $ci-text-color; color: $ci-text-color;
......
...@@ -162,9 +162,15 @@ ...@@ -162,9 +162,15 @@
} }
.filtered-labels { .filtered-labels {
font-size: 0;
padding: 12px 16px;
.label-row { .label-row {
margin-top: 4px;
margin-bottom: 4px;
&:not(:last-child) { &:not(:last-child) {
margin-right: 5px; margin-right: 8px;
} }
} }
......
...@@ -73,11 +73,14 @@ ...@@ -73,11 +73,14 @@
color: #888; color: #888;
} }
&.ci-pending, &.ci-pending {
&.ci-running {
color: $gl-warning; color: $gl-warning;
} }
&.ci-running {
color: $blue-normal;
}
&.ci-failed, &.ci-failed,
&.ci-error { &.ci-error {
color: $gl-danger; color: $gl-danger;
......
.pipelines { .pipelines {
.stage { .stage {
max-width: 100px; max-width: 80px;
width: 80px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.duration, .finished_at {
margin: 4px 0;
}
.commit-title { .commit-title {
margin: 0; margin: 0;
} }
...@@ -22,3 +19,136 @@ ...@@ -22,3 +19,136 @@
margin: 4px; margin: 4px;
} }
} }
.content-list {
&.pipelines,
&.builds-content-list {
width: 100%;
overflow: auto;
}
}
.table.builds {
min-width: 1100px;
tr {
th {
padding: 16px;
border: none;
}
}
tbody {
border-top-width: 1px;
}
.branch-commit {
.branch-name {
margin-left: 8px;
font-weight: bold;
max-width: 180px;
overflow: hidden;
display: inline-block;
white-space: nowrap;
vertical-align: top;
text-overflow: ellipsis;
}
svg {
margin: 0 6px;
height: 14px;
width: auto;
vertical-align: middle;
}
.commit-id {
color: $gl-link-color;
margin-right: 8px;
}
.commit-title {
margin-top: 4px;
max-width: 320px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.avatar {
margin-left: 0;
}
.label-container {
.label {
margin-top: 5px;
}
}
}
.duration,
.finished-at {
color: $table-text-gray;
margin: 4px 0;
.fa {
font-size: 12px;
}
svg {
height: 12px;
width: auto;
vertical-align: middle;
}
.fa,
svg {
margin-right: 5px;
}
}
.pipeline-actions {
.btn {
margin: 0;
color: $table-text-gray;
}
.dropdown-toggle,
.dropdown-menu {
color: $table-text-gray;
.fa {
color: $table-text-gray;
margin-right: 6px;
font-size: 14px;
}
}
.btn-remove {
color: $white-light;
}
.btn-group {
&.open {
.btn-default {
background-color: $white-normal;
border-color: $border-white-normal;
}
}
}
}
.build-link {
a {
color: $gl-dark-link-color;
}
}
.btn-group.open .dropdown-toggle {
box-shadow: none;
}
}
...@@ -340,23 +340,30 @@ a.deploy-project-label { ...@@ -340,23 +340,30 @@ a.deploy-project-label {
.project-import { .project-import {
.form-group { .form-group {
margin-bottom: 0; margin-bottom: 5px;
} }
.import-buttons { .import-buttons {
padding-left: 0; padding-left: 0;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
-webkit-flex-wrap: wrap; -webkit-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
.btn { .btn {
margin-right: 10px; margin: 0 10px 10px 0;
padding: 8px 12px; padding: 8px;
} }
&> div {
margin-bottom: 14px; > div {
padding-left: 0; padding-left: 0;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
.btn {
margin-right: 0;
}
} }
} }
} }
......
...@@ -32,11 +32,15 @@ ...@@ -32,11 +32,15 @@
border-color: $gl-gray; border-color: $gl-gray;
} }
&.ci-pending, &.ci-pending {
&.ci-running {
color: $gl-warning; color: $gl-warning;
border-color: $gl-warning; border-color: $gl-warning;
} }
&.ci-running {
color: $blue-normal;
border-color: $blue-normal;
}
} }
.ci-status-icon-success { .ci-status-icon-success {
...@@ -45,10 +49,12 @@ ...@@ -45,10 +49,12 @@
.ci-status-icon-failed { .ci-status-icon-failed {
color: $gl-danger; color: $gl-danger;
} }
.ci-status-icon-running,
.ci-status-icon-pending { .ci-status-icon-pending {
color: $gl-warning; color: $gl-warning;
} }
.ci-status-icon-running {
color: $blue-normal;
}
.ci-status-icon-canceled, .ci-status-icon-canceled,
.ci-status-icon-disabled, .ci-status-icon-disabled,
.ci-status-icon-not-found, .ci-status-icon-not-found,
......
class Dashboard::TodosController < Dashboard::ApplicationController class Dashboard::TodosController < Dashboard::ApplicationController
include TodosHelper
before_action :find_todos, only: [:index, :destroy_all] before_action :find_todos, only: [:index, :destroy_all]
def index def index
...@@ -13,7 +11,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -13,7 +11,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' } format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
format.js { head :ok } format.js { head :ok }
format.json { render json: { count: todos_pending_count, done_count: todos_done_count } } format.json { render json: todos_counts }
end end
end end
...@@ -23,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -23,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { head :ok } format.js { head :ok }
format.json { render json: { count: todos_pending_count, done_count: todos_done_count } } format.json { render json: todos_counts }
end end
end end
...@@ -36,4 +34,11 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -36,4 +34,11 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def find_todos def find_todos
@todos ||= TodosFinder.new(current_user, params).execute @todos ||= TodosFinder.new(current_user, params).execute
end end
def todos_counts
{
count: TodosFinder.new(current_user, state: :pending).execute.count,
done_count: TodosFinder.new(current_user, state: :done).execute.count
}
end
end end
...@@ -107,7 +107,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -107,7 +107,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Only allow properly saved users to login. # Only allow properly saved users to login.
if @user.persisted? && @user.valid? if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider']) log_audit_event(@user, with: oauth['provider'])
sign_in_and_redirect(@user) if @user.two_factor_enabled?
prompt_for_two_factor(@user)
else
sign_in_and_redirect(@user)
end
else else
error_message = @user.errors.full_messages.to_sentence error_message = @user.errors.full_messages.to_sentence
......
...@@ -119,6 +119,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -119,6 +119,10 @@ class Projects::IssuesController < Projects::ApplicationController
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end end
end end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end end
def referenced_merge_requests def referenced_merge_requests
...@@ -216,7 +220,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -216,7 +220,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params def issue_params
params.require(:issue).permit( params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential, :title, :assignee_id, :position, :description, :confidential,
:milestone_id, :due_date, :state_event, :task_num, label_ids: [] :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: []
) )
end end
......
...@@ -56,7 +56,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -56,7 +56,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html format.html { define_discussion_vars }
format.json do format.json do
render json: @merge_request render json: @merge_request
...@@ -82,7 +82,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -82,7 +82,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff @merge_request_diff = @merge_request.merge_request_diff
respond_to do |format| respond_to do |format|
format.html format.html { define_discussion_vars }
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
end end
end end
...@@ -108,7 +108,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -108,7 +108,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def commits def commits
respond_to do |format| respond_to do |format|
format.html { render 'show' } format.html do
define_discussion_vars
render 'show'
end
format.json do format.json do
# Get commits from repository # Get commits from repository
# or from cache if already merged # or from cache if already merged
...@@ -123,7 +127,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -123,7 +127,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def builds def builds
respond_to do |format| respond_to do |format|
format.html { render 'show' } format.html do
define_discussion_vars
render 'show'
end
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } } format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
end end
end end
...@@ -188,6 +196,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -188,6 +196,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
else else
render "edit" render "edit"
end end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end end
def remove_wip def remove_wip
...@@ -353,14 +364,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -353,14 +364,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.unlock_mr @merge_request.unlock_mr
@merge_request.close @merge_request.close
end end
if request.format == :html || action_name == 'show'
define_show_html_vars
end
end end
# Discussion tab data is only required on html requests # Discussion tab data is rendered on html responses of actions
def define_show_html_vars # :show, :diff, :commits, :builds. but not when request the data through AJAX
def define_discussion_vars
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @noteable) @note = @project.notes.new(noteable: @noteable)
...@@ -419,7 +427,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -419,7 +427,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:title, :assignee_id, :source_project_id, :source_branch, :title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id, :target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, :force_remove_source_branch, :state_event, :description, :task_num, :force_remove_source_branch,
label_ids: [] :lock_version, label_ids: []
) )
end end
......
...@@ -50,6 +50,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -50,6 +50,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
end end
def protected_branch_params def protected_branch_params
params.require(:protected_branch).permit(:name, :developers_can_push) params.require(:protected_branch).permit(:name, :developers_can_push, :developers_can_merge)
end end
end end
...@@ -5,7 +5,7 @@ class Projects::TodosController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class Projects::TodosController < Projects::ApplicationController
todo = TodoService.new.mark_todo(issuable, current_user) todo = TodoService.new.mark_todo(issuable, current_user)
render json: { render json: {
count: current_user.todos_pending_count, count: TodosFinder.new(current_user, state: :pending).execute.count,
delete_path: dashboard_todo_path(todo) delete_path: dashboard_todo_path(todo)
} }
end end
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# action_id: integer # action_id: integer
# author_id: integer # author_id: integer
# project_id; integer # project_id; integer
# state: 'pending' or 'done' # state: 'pending' (default) or 'done'
# type: 'Issue' or 'MergeRequest' # type: 'Issue' or 'MergeRequest'
# #
...@@ -37,7 +37,7 @@ class TodosFinder ...@@ -37,7 +37,7 @@ class TodosFinder
private private
def action_id? def action_id?
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED, Todo::MARKED].include?(action_id.to_i) action_id.present? && Todo::ACTION_NAMES.has_key?(action_id.to_i)
end end
def action_id def action_id
......
...@@ -31,7 +31,7 @@ module AppearancesHelper ...@@ -31,7 +31,7 @@ module AppearancesHelper
end end
end end
def navbar_icon(icon_name, size: 16) def custom_icon(icon_name, size: 16)
render "shared/icons/#{icon_name}.svg", size: size render "shared/icons/#{icon_name}.svg", size: size
end end
end end
...@@ -12,7 +12,7 @@ module BranchesHelper ...@@ -12,7 +12,7 @@ module BranchesHelper
def can_push_branch?(project, branch_name) def can_push_branch?(project, branch_name)
return false unless project.repository.branch_exists?(branch_name) return false unless project.repository.branch_exists?(branch_name)
::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(branch_name) ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch_name)
end end
def project_branches def project_branches
......
...@@ -29,8 +29,10 @@ module CiStatusHelper ...@@ -29,8 +29,10 @@ module CiStatusHelper
'check' 'check'
when 'failed' when 'failed'
'close' 'close'
when 'running', 'pending' when 'pending'
'clock-o' 'clock-o'
when 'running'
'spinner'
else else
'circle' 'circle'
end end
......
module TodosHelper module TodosHelper
def todos_pending_count def todos_pending_count
TodosFinder.new(current_user, state: :pending).execute.count @todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count
end end
def todos_done_count def todos_done_count
TodosFinder.new(current_user, state: :done).execute.count @todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count
end end
def todo_action_name(todo) def todo_action_name(todo)
...@@ -13,6 +13,7 @@ module TodosHelper ...@@ -13,6 +13,7 @@ module TodosHelper
when Todo::MENTIONED then 'mentioned you on' when Todo::MENTIONED then 'mentioned you on'
when Todo::BUILD_FAILED then 'The build failed for your' when Todo::BUILD_FAILED then 'The build failed for your'
when Todo::MARKED then 'added a todo for' when Todo::MARKED then 'added a todo for'
when Todo::APPROVAL_REQUIRED then 'set you as an approver for'
end end
end end
......
...@@ -87,6 +87,12 @@ module Issuable ...@@ -87,6 +87,12 @@ module Issuable
User.find(assignee_id_was).update_cache_counts if assignee_id_was User.find(assignee_id_was).update_cache_counts if assignee_id_was
assignee.update_cache_counts if assignee assignee.update_cache_counts if assignee
end end
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
title_changed? || description_changed?
end
end end
module ClassMethods module ClassMethods
......
...@@ -552,7 +552,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -552,7 +552,13 @@ class MergeRequest < ActiveRecord::Base
end end
def can_be_merged_by?(user) def can_be_merged_by?(user)
::Gitlab::GitAccess.new(user, project, 'web').can_push_to_branch?(target_branch) access = ::Gitlab::UserAccess.new(user, project: project)
access.can_push_to_branch?(target_branch) || access.can_merge_to_branch?(target_branch)
end
def can_be_merged_via_command_line_by?(user)
access = ::Gitlab::UserAccess.new(user, project: project)
access.can_push_to_branch?(target_branch)
end end
def mergeable_ci_state? def mergeable_ci_state?
......
...@@ -836,6 +836,10 @@ class Project < ActiveRecord::Base ...@@ -836,6 +836,10 @@ class Project < ActiveRecord::Base
protected_branches.matching(branch_name).any?(&:developers_can_push) protected_branches.matching(branch_name).any?(&:developers_can_push)
end end
def developers_can_merge_to_protected_branch?(branch_name)
protected_branches.matching(branch_name).any?(&:developers_can_merge)
end
def forked? def forked?
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end end
......
...@@ -769,9 +769,9 @@ class Repository ...@@ -769,9 +769,9 @@ class Repository
end end
end end
def merge(user, source_sha, target_branch, options = {}) def merge(user, merge_request, options = {})
our_commit = rugged.branches[target_branch].target our_commit = rugged.branches[merge_request.target_branch].target
their_commit = rugged.lookup(source_sha) their_commit = rugged.lookup(merge_request.diff_head_sha)
raise "Invalid merge target" if our_commit.nil? raise "Invalid merge target" if our_commit.nil?
raise "Invalid merge source" if their_commit.nil? raise "Invalid merge source" if their_commit.nil?
...@@ -779,14 +779,16 @@ class Repository ...@@ -779,14 +779,16 @@ class Repository
merge_index = rugged.merge_commits(our_commit, their_commit) merge_index = rugged.merge_commits(our_commit, their_commit)
return false if merge_index.conflicts? return false if merge_index.conflicts?
commit_with_hooks(user, target_branch) do |ref| commit_with_hooks(user, merge_request.target_branch) do |tmp_ref|
actual_options = options.merge( actual_options = options.merge(
parents: [our_commit, their_commit], parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged), tree: merge_index.write_tree(rugged),
update_ref: ref update_ref: tmp_ref
) )
Rugged::Commit.create(rugged, actual_options) commit_id = Rugged::Commit.create(rugged, actual_options)
merge_request.update(in_progress_merge_commit_sha: commit_id)
commit_id
end end
end end
......
class Todo < ActiveRecord::Base class Todo < ActiveRecord::Base
ASSIGNED = 1 ASSIGNED = 1
MENTIONED = 2 MENTIONED = 2
BUILD_FAILED = 3 BUILD_FAILED = 3
MARKED = 4 MARKED = 4
APPROVAL_REQUIRED = 5 # This is an EE-only feature
ACTION_NAMES = { ACTION_NAMES = {
ASSIGNED => :assigned, ASSIGNED => :assigned,
MENTIONED => :mentioned, MENTIONED => :mentioned,
BUILD_FAILED => :build_failed, BUILD_FAILED => :build_failed,
MARKED => :marked MARKED => :marked,
APPROVAL_REQUIRED => :approval_required
} }
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
......
...@@ -87,7 +87,7 @@ class User < ActiveRecord::Base ...@@ -87,7 +87,7 @@ class User < ActiveRecord::Base
has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy has_many :notification_settings, dependent: :destroy
has_many :award_emoji, as: :awardable, dependent: :destroy has_many :award_emoji, dependent: :destroy
# #
# Validations # Validations
......
...@@ -23,7 +23,7 @@ module Commits ...@@ -23,7 +23,7 @@ module Commits
private private
def check_push_permissions def check_push_permissions
allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch) allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
unless allowed unless allowed
raise ValidationError.new('You are not allowed to push into this branch') raise ValidationError.new('You are not allowed to push into this branch')
...@@ -31,7 +31,7 @@ module Commits ...@@ -31,7 +31,7 @@ module Commits
true true
end end
def create_target_branch(new_branch) def create_target_branch(new_branch)
# Temporary branch exists and contains the change commit # Temporary branch exists and contains the change commit
return success if repository.find_branch(new_branch) return success if repository.find_branch(new_branch)
......
...@@ -42,7 +42,7 @@ module Files ...@@ -42,7 +42,7 @@ module Files
end end
def validate def validate
allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch) allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
unless allowed unless allowed
raise_error("You are not allowed to push into this branch") raise_error("You are not allowed to push into this branch")
......
...@@ -89,7 +89,8 @@ class GitPushService < BaseService ...@@ -89,7 +89,8 @@ class GitPushService < BaseService
# Set protection on the default branch if configured # Set protection on the default branch if configured
if current_application_settings.default_branch_protection != PROTECTION_NONE if current_application_settings.default_branch_protection != PROTECTION_NONE
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
@project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push }) developers_can_merge = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? true : false
@project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push, developers_can_merge: developers_can_merge })
end end
end end
......
...@@ -34,7 +34,7 @@ module MergeRequests ...@@ -34,7 +34,7 @@ module MergeRequests
committer: committer committer: committer
} }
commit_id = repository.merge(current_user, merge_request.diff_head_sha, merge_request.target_branch, options) commit_id = repository.merge(current_user, merge_request, options)
merge_request.update(merge_commit_sha: commit_id) merge_request.update(merge_commit_sha: commit_id)
rescue GitHooksService::PreReceiveError => e rescue GitHooksService::PreReceiveError => e
merge_request.update(merge_error: e.message) merge_request.update(merge_error: e.message)
...@@ -43,6 +43,8 @@ module MergeRequests ...@@ -43,6 +43,8 @@ module MergeRequests
merge_request.update(merge_error: "Something went wrong during merge") merge_request.update(merge_error: "Something went wrong during merge")
Rails.logger.error(e.message) Rails.logger.error(e.message)
false false
ensure
merge_request.update(in_progress_merge_commit_sha: nil)
end end
def after_merge def after_merge
......
...@@ -48,7 +48,7 @@ module MergeRequests ...@@ -48,7 +48,7 @@ module MergeRequests
end end
def force_push? def force_push?
Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev) Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
end end
# Refresh merge request diff if we push to source or target branch of merge request # Refresh merge request diff if we push to source or target branch of merge request
......
...@@ -7,8 +7,6 @@ ...@@ -7,8 +7,6 @@
# #
module Projects module Projects
class HousekeepingService < BaseService class HousekeepingService < BaseService
include Gitlab::ShellAdapter
LEASE_TIMEOUT = 3600 LEASE_TIMEOUT = 3600
class LeaseTaken < StandardError class LeaseTaken < StandardError
...@@ -24,11 +22,7 @@ module Projects ...@@ -24,11 +22,7 @@ module Projects
def execute def execute
raise LeaseTaken unless try_obtain_lease raise LeaseTaken unless try_obtain_lease
GitlabShellOneShotWorker.perform_async(:gc, @project.repository_storage_path, @project.path_with_namespace) execute_gitlab_shell_gc
ensure
Gitlab::Metrics.measure(:reset_pushes_since_gc) do
update_pushes_since_gc(0)
end
end end
def needed? def needed?
...@@ -36,19 +30,27 @@ module Projects ...@@ -36,19 +30,27 @@ module Projects
end end
def increment! def increment!
Gitlab::Metrics.measure(:increment_pushes_since_gc) do if Gitlab::ExclusiveLease.new("project_housekeeping:increment!:#{@project.id}", timeout: 60).try_obtain
update_pushes_since_gc(@project.pushes_since_gc + 1) Gitlab::Metrics.measure(:increment_pushes_since_gc) do
update_pushes_since_gc(@project.pushes_since_gc + 1)
end
end end
end end
private private
def update_pushes_since_gc(new_value) def execute_gitlab_shell_gc
if Gitlab::ExclusiveLease.new("project_housekeeping:update_pushes_since_gc:#{project.id}", timeout: 60).try_obtain GitGarbageCollectWorker.perform_async(@project.id)
@project.update_column(:pushes_since_gc, new_value) ensure
Gitlab::Metrics.measure(:reset_pushes_since_gc) do
update_pushes_since_gc(0)
end end
end end
def update_pushes_since_gc(new_value)
@project.update_column(:pushes_since_gc, new_value)
end
def try_obtain_lease def try_obtain_lease
Gitlab::Metrics.measure(:obtain_housekeeping_lease) do Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
......
- project = build.project - project = build.project
%tr.build %tr.build.commit
%td.status %td.status
= ci_status_with_icon(build.status) = ci_status_with_icon(build.status)
%td.build-link %td
- if can?(current_user, :read_build, build.project) .branch-commit
= link_to namespace_project_build_url(build.project.namespace, build.project, build) do - if can?(current_user, :read_build, build.project)
%strong Build ##{build.id} = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
- else %span.build-link ##{build.id}
%strong Build ##{build.id} - else
%span.build-link ##{build.id}
- if build.stuck? - if build.stuck?
%i.fa.fa-warning.text-warning %i.fa.fa-warning.text-warning
%td - if build.ref
- if project = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
= link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) - else
.light none
= custom_icon("icon_commit")
%td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id"
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
.label-container
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
= tag
- if build.try(:trigger_request)
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
%td %td
- if build.ref - if project
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
- else
.light none
%td %td
- if build.try(:runner) - if build.try(:runner)
...@@ -36,22 +46,15 @@ ...@@ -36,22 +46,15 @@
#{build.stage} / #{build.name} #{build.stage} / #{build.name}
%td %td
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
= tag
- if build.try(:trigger_request)
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
%td.duration
- if build.duration - if build.duration
#{duration_in_words(build.finished_at, build.started_at)} %p.duration
= custom_icon("icon_timer")
= duration_in_numbers(build.finished_at, build.started_at)
%td.timestamp
- if build.finished_at - if build.finished_at
%span #{time_ago_with_tooltip(build.finished_at)} %p.finished-at
= icon("calendar")
%span #{time_ago_with_tooltip(build.finished_at)}
- if defined?(coverage) && coverage - if defined?(coverage) && coverage
%td.coverage %td.coverage
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.row-content-block.second-block .row-content-block.second-block
#{(@scope || 'all').capitalize} builds #{(@scope || 'all').capitalize} builds
%ul.content-list %ul.content-list.builds-content-list
- if @builds.blank? - if @builds.blank?
%li %li
.nothing-here-block No builds to show .nothing-here-block No builds to show
...@@ -37,15 +37,11 @@ ...@@ -37,15 +37,11 @@
%thead %thead
%tr %tr
%th Status %th Status
%th Build ID
%th Project
%th Commit %th Commit
%th Ref %th Project
%th Runner %th Runner
%th Name %th Name
%th Tags %th
%th Duration
%th Finished at
%th %th
- @builds.each do |build| - @builds.each do |build|
......
...@@ -9,14 +9,14 @@ ...@@ -9,14 +9,14 @@
%span %span
To do To do
%span.badge %span.badge
= todos_pending_count = number_with_delimiter(todos_pending_count)
- todo_done_active = ('active' if params[:state] == 'done') - todo_done_active = ('active' if params[:state] == 'done')
%li{class: "todos-done #{todo_done_active}"} %li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do = link_to todos_filter_path(state: 'done') do
%span %span
Done Done
%span.badge %span.badge
= todos_done_count = number_with_delimiter(todos_done_count)
.nav-controls .nav-controls
- if @todos.any?(&:pending?) - if @todos.any?(&:pending?)
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
.nav-controls .nav-controls
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
= link_to new_group_milestone_path(@group), class: "btn btn-new" do = link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus')
New Milestone New Milestone
.row-content-block .row-content-block
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
- if can? current_user, :admin_group, @group - if can? current_user, :admin_group, @group
.controls .controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
= icon('plus')
New Project New Project
%ul.well-list %ul.well-list
- @projects.each do |project| - @projects.each do |project|
......
...@@ -6,8 +6,7 @@ ...@@ -6,8 +6,7 @@
.cover-block.groups-cover-block .cover-block.groups-cover-block
%div{ class: container_class } %div{ class: container_class }
= link_to group_icon(@group), target: '_blank' do = image_tag group_icon(@group), class: "avatar group-avatar s70"
= image_tag group_icon(@group), class: "avatar group-avatar s70"
.group-info .group-info
.cover-title .cover-title
%h1 %h1
......
= link_to '#', class: 'nav-header-btn text-center toggle-nav-collapse', title: "Open/Close" do
%span.sr-only Toggle navigation
= icon('bars')
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
= render partial: 'layouts/collapse_button' .sidebar-action-buttons
= link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do
%span.sr-only Toggle navigation
= icon('bars')
= link_to '#', class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
%span.sr-only Toggle navigation pinning
= icon('thumb-tack')
- if defined?(sidebar) && sidebar - if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}" = render "layouts/nav/#{sidebar}"
- elsif current_user - elsif current_user
...@@ -8,9 +15,6 @@ ...@@ -8,9 +15,6 @@
- else - else
= render 'layouts/nav/explore' = render 'layouts/nav/explore'
= link_to '#', class: "nav-header-btn text-center pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
%span.sr-only Toggle navigation pinning
= icon('thumb-tack')
- if defined?(nav) && nav - if defined?(nav) && nav
.layout-nav .layout-nav
.container-fluid .container-fluid
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
.icon-container
= navbar_icon('project')
%span %span
Projects Projects
= nav_link(controller: :todos) do = nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do = link_to dashboard_todos_path, title: 'Todos' do
.icon-container
= icon('bell fw')
%span %span
Todos Todos
%span.count= number_with_delimiter(todos_pending_count) %span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
.icon-container
= navbar_icon('activity')
%span %span
Activity Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
.icon-container
= navbar_icon('group')
%span %span
Groups Groups
= nav_link(controller: 'dashboard/milestones') do = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, title: 'Milestones' do
.icon-container
= navbar_icon('milestones')
%span %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
.icon-container
= navbar_icon('issues')
%span %span
Issues Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count) %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
.icon-container
= navbar_icon('mr')
%span %span
Merge Requests Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, title: 'Snippets' do
.icon-container
= icon('clipboard fw')
%span %span
Snippets Snippets
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help' do = link_to help_path, title: 'Help' do
.icon-container
= icon('question-circle fw')
%span %span
Help Help
= nav_link(html_options: {class: profile_tab_class}) do = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
.icon-container
= icon('user fw')
%span %span
Profile Settings Profile Settings
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
%span %span
Applications Applications
= nav_link(controller: :personal_access_tokens) do = nav_link(controller: :personal_access_tokens) do
= link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
%span %span
Personal Access Tokens Access Tokens
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails' do = link_to profile_emails_path, title: 'Emails' do
%span %span
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
= link_to ci_lint_path, class: 'btn btn-default' do = link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint %span CI Lint
%ul.content-list %ul.content-list.builds-content-list
- if @builds.blank? - if @builds.blank?
%li %li
.nothing-here-block No builds to show .nothing-here-block No builds to show
...@@ -46,14 +46,10 @@ ...@@ -46,14 +46,10 @@
%thead %thead
%tr %tr
%th Status %th Status
%th Build ID
%th Commit %th Commit
%th Ref
%th Stage %th Stage
%th Name %th Name
%th Tags %th
%th Duration
%th Finished at
- if @project.build_coverage_enabled? - if @project.build_coverage_enabled?
%th Coverage %th Coverage
%th %th
......
%tr.build %tr.build.commit
%td.status %td.status
- if can?(current_user, :read_build, build) - if can?(current_user, :read_build, build)
= ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
- else - else
= ci_status_with_icon(build.status) = ci_status_with_icon(build.status)
%td.build-link %td
- if can?(current_user, :read_build, build) .branch-commit
= link_to namespace_project_build_url(build.project.namespace, build.project, build) do - if can?(current_user, :read_build, build)
%strong ##{build.id} = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
- else %span ##{build.id}
%strong ##{build.id} - else
%span ##{build.id}
- if build.stuck? - if build.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
- if defined?(retried) && retried - if defined?(retried) && retried
= icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
- if defined?(commit_sha) && commit_sha - if defined?(ref) && ref
%td - if build.ref
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
- else
.light none
= custom_icon("icon_commit")
- if defined?(commit_sha) && commit_sha
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
.label-container
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
= tag
- if build.try(:trigger_request)
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
- if defined?(retried) && retried
%span.label.label-warning retried
- if defined?(ref) && ref
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- else
.light none
- if defined?(runner) && runner - if defined?(runner) && runner
%td %td
...@@ -43,25 +56,14 @@ ...@@ -43,25 +56,14 @@
= build.name = build.name
%td %td
.label-container
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
= tag
- if build.try(:trigger_request)
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
- if defined?(retried) && retried
%span.label.label-warning retried
%td.duration
- if build.duration - if build.duration
#{duration_in_words(build.finished_at, build.started_at)} %p.duration
= custom_icon("icon_timer")
%td.timestamp = duration_in_numbers(build.finished_at, build.started_at)
- if build.finished_at - if build.finished_at
%span #{time_ago_with_tooltip(build.finished_at)} %p.finished-at
= icon("calendar")
%span #{time_ago_with_tooltip(build.finished_at)}
- if defined?(coverage) && coverage - if defined?(coverage) && coverage
%td.coverage %td.coverage
...@@ -79,4 +81,4 @@ ...@@ -79,4 +81,4 @@
= icon('remove', class: 'cred') = icon('remove', class: 'cred')
- elsif defined?(allow_retry) && allow_retry && build.retryable? - elsif defined?(allow_retry) && allow_retry && build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('refresh') = icon('repeat')
- status = pipeline.status - status = pipeline.status
%tr.commit %tr.commit
%td.commit-link %td.commit-link
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: "ci-status ci-#{status}" do = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
= ci_icon_for_status(status) = ci_status_with_icon(status)
%strong ##{pipeline.id}
%td %td
%div.branch-commit .branch-commit
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
%span ##{pipeline.id}
- if pipeline.ref - if pipeline.ref
= link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace" = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
&middot; = custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace" = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
&nbsp;
- if pipeline.tag? - if pipeline.tag?
%span.label.label-primary tag %span.label.label-primary tag
- elsif pipeline.latest? - elsif pipeline.latest?
...@@ -25,6 +26,7 @@ ...@@ -25,6 +26,7 @@
%p.commit-title %p.commit-title
- if commit = pipeline.commit - if commit = pipeline.commit
= commit_author_avatar(commit, size: 20)
= link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message" = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
...@@ -45,22 +47,37 @@ ...@@ -45,22 +47,37 @@
%td %td
- if pipeline.started_at && pipeline.finished_at - if pipeline.started_at && pipeline.finished_at
%p.duration %p.duration
= custom_icon("icon_timer")
= duration_in_numbers(pipeline.finished_at, pipeline.started_at) = duration_in_numbers(pipeline.finished_at, pipeline.started_at)
- if pipeline.finished_at
%p.finished-at
= icon("calendar")
#{time_ago_with_tooltip(pipeline.finished_at)}
%td %td.pipeline-actions
.controls.hidden-xs.pull-right .controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? } - artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present? - if artifacts.present?
.dropdown.inline.build-artifacts .btn-group.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} .btn-group
= icon('download') %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
%b.caret = icon("play")
%ul.dropdown-menu.dropdown-menu-align-right %b.caret
- artifacts.each do |build| %ul.dropdown-menu.dropdown-menu-align-right
%li %li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do = link_to '#' do
= icon("download") = icon("play")
%span Download '#{build.name}' artifacts %span Deploy to production
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
= icon("download")
%span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project) - if can?(current_user, :update_pipeline, @project)
- if pipeline.retryable? - if pipeline.retryable?
......
...@@ -42,9 +42,7 @@ ...@@ -42,9 +42,7 @@
%th Status %th Status
%th Build ID %th Build ID
%th Name %th Name
%th Tags %th
%th Duration
%th Finished at
- if pipeline.project.build_coverage_enabled? - if pipeline.project.build_coverage_enabled?
%th Coverage %th Coverage
%th %th
......
...@@ -50,10 +50,12 @@ ...@@ -50,10 +50,12 @@
%td.duration %td.duration
- if generic_commit_status.duration - if generic_commit_status.duration
= icon("clock-o")
#{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)} #{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)}
%td.timestamp %td.timestamp
- if generic_commit_status.finished_at - if generic_commit_status.finished_at
= icon("calendar")
%span #{time_ago_with_tooltip(generic_commit_status.finished_at)} %span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
- if defined?(coverage) && coverage - if defined?(coverage) && coverage
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
Code, test, and deploy together Code, test, and deploy together
.blank-state .blank-state
.blank-state-icon .blank-state-icon
= navbar_icon("issues", size: 50) = custom_icon("issues", size: 50)
%h3.blank-state-title %h3.blank-state-title
You don't have any issues right now. You don't have any issues right now.
%p.blank-state-text %p.blank-state-text
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%p %p
Please resolve these conflicts or Please resolve these conflicts or
- if @merge_request.can_be_merged_by?(current_user) - if @merge_request.can_be_merged_via_command_line_by?(current_user)
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}. #{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
- else - else
ask someone with write access to this repository to merge this request manually. ask someone with write access to this repository to merge this request manually.
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.nav-controls .nav-controls
- if can? current_user, :create_pipeline, @project - if can? current_user, :create_pipeline, @project
= link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
New pipeline Run pipeline
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
= link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
...@@ -45,13 +45,13 @@ ...@@ -45,13 +45,13 @@
.table-holder .table-holder
%table.table.builds %table.table.builds
%tbody %tbody
%th ID %th Status
%th Commit %th Commit
- stages.each do |stage| - stages.each do |stage|
%th.stage %th.stage
%span.has-tooltip{ title: "#{stage.titleize}" } %span.has-tooltip{ title: "#{stage.titleize}" }
= stage.titleize = stage.titleize
%th Duration %th
%th %th
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
.table-responsive .table-responsive
%table.table.protected-branches-list %table.table.protected-branches-list
%colgroup %colgroup
%col{ width: "20%" }
%col{ width: "30%" } %col{ width: "30%" }
%col{ width: "25%" } %col{ width: "25%" }
%col{ width: "25%" } %col{ width: "25%" }
...@@ -18,6 +19,7 @@ ...@@ -18,6 +19,7 @@
%th Protected Branch %th Protected Branch
%th Commit %th Commit
%th Developers Can Push %th Developers Can Push
%th Developers Can Merge
- if can_admin_project - if can_admin_project
%th %th
%tbody %tbody
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
(branch was removed from repository) (branch was removed from repository)
%td %td
= check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url }) = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url })
%td
= check_box_tag("developers_can_merge", protected_branch.id, protected_branch.developers_can_merge, data: { url: url })
- if can_admin_project - if can_admin_project
%td %td
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right" = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right"
...@@ -36,6 +36,14 @@ ...@@ -36,6 +36,14 @@
= f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0" = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
%p.light.append-bottom-0 %p.light.append-bottom-0
Allow developers to push to this branch Allow developers to push to this branch
.form-group
= f.check_box :developers_can_merge, class: "pull-left"
.prepend-left-20
= f.label :developers_can_merge, "Developers can merge", class: "label-light append-bottom-0"
%p.light.append-bottom-0
Allow developers to accept merge requests to this branch
= f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
%hr %hr
= render "branches_list" = render "branches_list"
- page_title "Snippets" - page_title "Snippets"
.row-content-block.top-block .sub-header-block
.pull-right .pull-right
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
......
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
<path fill="#8F8F8F" fill-rule="evenodd" d="M28.7769836,18 C27.8675252,13.9920226 24.2831748,11 20,11 C15.7168252,11 12.1324748,13.9920226 11.2230164,18 L4.0085302,18 C2.90195036,18 2,18.8954305 2,20 C2,21.1122704 2.8992496,22 4.0085302,22 L11.2230164,22 C12.1324748,26.0079774 15.7168252,29 20,29 C24.2831748,29 27.8675252,26.0079774 28.7769836,22 L35.9914698,22 C37.0980496,22 38,21.1045695 38,20 C38,18.8877296 37.1007504,18 35.9914698,18 L28.7769836,18 L28.7769836,18 Z M20,25 C22.7614237,25 25,22.7614237 25,20 C25,17.2385763 22.7614237,15 20,15 C17.2385763,15 15,17.2385763 15,20 C15,22.7614237 17.2385763,25 20,25 L20,25 Z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><g fill="#8F8F8F" fill-rule="evenodd"><path d="M29.513 10.134A15.922 15.922 0 0 0 23 7.28V6h2.993C26.55 6 27 5.552 27 5V2a1 1 0 0 0-1.007-1H14.007C13.45 1 13 1.448 13 2v3a1 1 0 0 0 1.007 1H17v1.28C9.597 8.686 4 15.19 4 23c0 8.837 7.163 16 16 16s16-7.163 16-16c0-3.461-1.099-6.665-2.967-9.283l1.327-1.58a2.498 2.498 0 0 0-.303-3.53 2.499 2.499 0 0 0-3.528.315l-1.016 1.212zM20 34c6.075 0 11-4.925 11-11s-4.925-11-11-11S9 16.925 9 23s4.925 11 11 11z"/><path d="M19 21h-4.002c-.552 0-.998.452-.998 1.01v1.98c0 .567.447 1.01.998 1.01h7.004c.274 0 .521-.111.701-.291a.979.979 0 0 0 .297-.704v-8.01c0-.54-.452-.995-1.01-.995h-1.98a.997.997 0 0 0-1.01.995V21z"/></g></svg>
\ No newline at end of file
= form_errors(issuable) = form_errors(issuable)
- if @conflict
.alert.alert-danger
Someone edited the #{issuable.class.model_name.human.downcase} the same time you did.
Please check out
= link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank"
and make sure your changes will not unintentionally remove theirs
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10 .col-sm-10
...@@ -149,3 +156,5 @@ ...@@ -149,3 +156,5 @@
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped' method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
= f.hidden_field :lock_version
...@@ -26,7 +26,6 @@ ...@@ -26,7 +26,6 @@
%span.pull-right.tab-issues-buttons %span.pull-right.tab-issues-buttons
- if project && can?(current_user, :create_issue, project) - if project && can?(current_user, :create_issue, project)
= link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
New Issue New Issue
= link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped" = link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped"
%span.pull-right.tab-merge-requests-buttons.hidden %span.pull-right.tab-merge-requests-buttons.hidden
......
class GitGarbageCollectWorker
include Sidekiq::Worker
include Gitlab::ShellAdapter
sidekiq_options queue: :gitlab_shell, retry: false
def perform(project_id)
project = Project.find(project_id)
gitlab_shell.gc(project.repository_storage_path, project.path_with_namespace)
# Expire the branch cache in case garbage collection caused a ref lookup to fail
project.repository.after_create_branch
end
end
class GitlabShellOneShotWorker
include Sidekiq::Worker
include Gitlab::ShellAdapter
sidekiq_options queue: :gitlab_shell, retry: false
def perform(action, *arg)
gitlab_shell.send(action, *arg)
end
end
production: &base
gitlab:
host: localhost
port: 80
https: false
user: root
email_from: example@example.com
support_email: support@example.com
default_projects_features:
issues: true
merge_requests: true
wiki: true
snippets: false
visibility_level: "private" # can be "private" | "internal" | "public"
issues_tracker:
gravatar:
enabled: true # Use user avatar image from Gravatar.com (default: true)
ldap:
enabled: false
host: '_your_ldap_server'
port: 636
uid: 'sAMAccountName'
method: 'ssl' # "tls" or "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
allow_username_or_email_login: true
base: ''
user_filter: ''
omniauth:
enabled: false
satellites:
# Relative paths are relative to Rails.root (default: tmp/repo_satellites/)
path: /apps/gitlab-satellites/
backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
repositories:
storages: # REPO PATHS MUST NOT BE A SYMLINK!!!
default: /apps/repositories/
gitlab_shell:
path: /apps/gitlab-shell/
hooks_path: /apps/gitlab-shell/hooks/
upload_pack: true
receive_pack: true
git:
bin_path: /usr/bin/git
max_size: 5242880 # 5.megabytes
timeout: 10
extra:
development:
<<: *base
test:
<<: *base
gravatar:
enabled: true
gitlab:
host: localhost
port: 80
issues_tracker:
redmine:
title: "Redmine"
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
staging:
<<: *base
# Email forcibly included in the standard checks, but the email health check
# doesn't support the full range of SMTP options, which can result in failures
# for valid SMTP configurations.
# Overwrite the HealthCheck's detection of whether email is configured
# in order to avoid the email check during standard checks
module HealthCheck
class Utils
def self.mailer_configured?
false
end
end
end
HealthCheck.setup do |config| HealthCheck.setup do |config|
config.standard_checks = ['database', 'migrations', 'cache'] config.standard_checks = ['database', 'migrations', 'cache']
config.full_checks = ['database', 'migrations', 'cache'] config.full_checks = ['database', 'migrations', 'cache']
......
class AddDevelopersCanMergeToProtectedBranches < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def change
add_column_with_default :protected_branches, :developers_can_merge, :boolean, default: false, allow_null: false
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddColumnInProgressMergeCommitShaToMergeRequests < ActiveRecord::Migration
def change
add_column :merge_requests, :in_progress_merge_commit_sha, :string
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddLockToIssuables < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_column_with_default :issues, :lock_version, :integer, default: 0
add_column_with_default :merge_requests, :lock_version, :integer, default: 0
end
def down
remove_column :issues, :lock_version
remove_column :merge_requests, :lock_version
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveAwardEmojisWithNoUser < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def up
AwardEmoji.joins('LEFT JOIN users ON users.id = user_id').where('users.id IS NULL').destroy_all
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# 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.
ActiveRecord::Schema.define(version: 20160705163108) do ActiveRecord::Schema.define(version: 20160712171823) do
# 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"
...@@ -70,11 +70,11 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -70,11 +70,11 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.string "recaptcha_site_key" t.string "recaptcha_site_key"
t.string "recaptcha_private_key" t.string "recaptcha_private_key"
t.integer "metrics_port", default: 8089 t.integer "metrics_port", default: 8089
t.boolean "akismet_enabled", default: false
t.string "akismet_api_key"
t.integer "metrics_sample_interval", default: 15 t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false t.boolean "sentry_enabled", default: false
t.string "sentry_dsn" t.string "sentry_dsn"
t.boolean "akismet_enabled", default: false
t.string "akismet_api_key"
t.boolean "email_author_in_body", default: false t.boolean "email_author_in_body", default: false
t.integer "default_group_visibility" t.integer "default_group_visibility"
t.boolean "repository_checks_enabled", default: false t.boolean "repository_checks_enabled", default: false
...@@ -84,10 +84,10 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -84,10 +84,10 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.string "health_check_access_token" t.string "health_check_access_token"
t.boolean "send_user_confirmation_email", default: false t.boolean "send_user_confirmation_email", default: false
t.integer "container_registry_token_expire_delay", default: 5 t.integer "container_registry_token_expire_delay", default: 5
t.boolean "user_default_external", default: false, null: false
t.text "after_sign_up_text" t.text "after_sign_up_text"
t.string "repository_storage", default: "default" t.string "repository_storage", default: "default"
t.string "enabled_git_access_protocol" t.string "enabled_git_access_protocol"
t.boolean "user_default_external", default: false, null: false
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
...@@ -165,8 +165,8 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -165,8 +165,8 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.text "artifacts_metadata" t.text "artifacts_metadata"
t.integer "erased_by_id" t.integer "erased_by_id"
t.datetime "erased_at" t.datetime "erased_at"
t.string "environment"
t.datetime "artifacts_expire_at" t.datetime "artifacts_expire_at"
t.string "environment"
t.integer "artifacts_size" t.integer "artifacts_size"
end end
...@@ -481,10 +481,11 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -481,10 +481,11 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.string "state" t.string "state"
t.integer "iid" t.integer "iid"
t.integer "updated_by_id" t.integer "updated_by_id"
t.integer "moved_to_id"
t.boolean "confidential", default: false t.boolean "confidential", default: false
t.datetime "deleted_at" t.datetime "deleted_at"
t.date "due_date" t.date "due_date"
t.integer "moved_to_id" t.integer "lock_version", default: 0, null: false
end end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
...@@ -624,6 +625,8 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -624,6 +625,8 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.integer "merge_user_id" t.integer "merge_user_id"
t.string "merge_commit_sha" t.string "merge_commit_sha"
t.datetime "deleted_at" t.datetime "deleted_at"
t.integer "lock_version", default: 0, null: false
t.string "in_progress_merge_commit_sha"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
...@@ -773,10 +776,10 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -773,10 +776,10 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.integer "user_id", null: false t.integer "user_id", null: false
t.string "token", null: false t.string "token", null: false
t.string "name", null: false t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "revoked", default: false t.boolean "revoked", default: false
t.datetime "expires_at" t.datetime "expires_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end end
add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
...@@ -858,11 +861,12 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -858,11 +861,12 @@ ActiveRecord::Schema.define(version: 20160705163108) do
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
create_table "protected_branches", force: :cascade do |t| create_table "protected_branches", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.string "name", null: false t.string "name", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.boolean "developers_can_push", default: false, null: false t.boolean "developers_can_push", default: false, null: false
t.boolean "developers_can_merge", default: false, null: false
end end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
...@@ -896,9 +900,9 @@ ActiveRecord::Schema.define(version: 20160705163108) do ...@@ -896,9 +900,9 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.string "type" t.string "type"
t.string "title" t.string "title"
t.integer "project_id" t.integer "project_id"
t.datetime "created_at" t.datetime "created_at", null: false
t.datetime "updated_at" t.datetime "updated_at", null: false
t.boolean "active", default: false, null: false t.boolean "active", null: false
t.text "properties" t.text "properties"
t.boolean "template", default: false t.boolean "template", default: false
t.boolean "push_events", default: true t.boolean "push_events", default: true
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
- [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab. - [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab.
- [Permissions](permissions/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do. - [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
- [Profile Settings](profile/README.md) - [Profile Settings](profile/README.md)
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
......
...@@ -68,7 +68,9 @@ Parameters: ...@@ -68,7 +68,9 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1 "user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false
} }
] ]
``` ```
...@@ -132,7 +134,9 @@ Parameters: ...@@ -132,7 +134,9 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"user_notes_count": 1 "user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false
} }
``` ```
...@@ -233,6 +237,8 @@ Parameters: ...@@ -233,6 +237,8 @@ Parameters:
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"user_notes_count": 1, "user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
"changes": [ "changes": [
{ {
"old_path": "VERSION", "old_path": "VERSION",
...@@ -312,7 +318,9 @@ Parameters: ...@@ -312,7 +318,9 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"user_notes_count": 0 "user_notes_count": 0,
"should_remove_source_branch": true,
"force_remove_source_branch": false
} }
``` ```
...@@ -383,7 +391,9 @@ Parameters: ...@@ -383,7 +391,9 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"user_notes_count": 1 "user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false
} }
``` ```
...@@ -481,7 +491,9 @@ Parameters: ...@@ -481,7 +491,9 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"user_notes_count": 1 "user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false
} }
``` ```
...@@ -547,7 +559,9 @@ Parameters: ...@@ -547,7 +559,9 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"user_notes_count": 1 "user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false
} }
``` ```
...@@ -866,7 +880,9 @@ Example response: ...@@ -866,7 +880,9 @@ Example response:
"merge_when_build_succeeds": false, "merge_when_build_succeeds": false,
"merge_status": "unchecked", "merge_status": "unchecked",
"subscribed": true, "subscribed": true,
"user_notes_count": 7 "user_notes_count": 7,
"should_remove_source_branch": true,
"force_remove_source_branch": false
}, },
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7",
"body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.",
......
...@@ -15,7 +15,7 @@ Parameters: ...@@ -15,7 +15,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, or `marked`. | | `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, or `approval_required`. |
| `author_id` | integer | no | The ID of an author | | `author_id` | integer | no | The ID of an author |
| `project_id` | integer | no | The ID of a project | | `project_id` | integer | no | The ID of a project |
| `state` | string | no | The state of the todo. Can be either `pending` or `done` | | `state` | string | no | The state of the todo. Can be either `pending` or `done` |
......
...@@ -15,6 +15,6 @@ ...@@ -15,6 +15,6 @@
- [Use SSH keys in your build environment](ssh_keys/README.md) - [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger builds through the API](triggers/README.md) - [Trigger builds through the API](triggers/README.md)
- [Build artifacts](build_artifacts/README.md) - [Build artifacts](build_artifacts/README.md)
- [User permissions](permissions/README.md) - [User permissions](../user/permissions.md#gitlab-ci)
- [API](../api/ci/README.md) - [API](../api/ci/README.md)
- [CI services (linked docker containers)](services/README.md) - [CI services (linked docker containers)](services/README.md)
# Users Permissions # Users Permissions
GitLab CI relies on user's role on the GitLab. There are three permissions levels on GitLab CI: admin, master, developer, other. This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-ci).
Admin user can perform any actions on GitLab CI in scope of instance and project. Also user with admin permission can use admin interface.
| Action | Guest, Reporter | Developer | Master | Admin |
|---------------------------------------|-----------------|-------------|----------|--------|
| See commits and builds | ✓ | ✓ | ✓ | ✓ |
| Retry or cancel build | | ✓ | ✓ | ✓ |
| Remove project | | | ✓ | ✓ |
| Create project | | | ✓ | ✓ |
| Change project configuration | | | ✓ | ✓ |
| Add specific runners | | | ✓ | ✓ |
| Add shared runners | | | | ✓ |
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
...@@ -27,6 +27,8 @@ We try to keep the amount of tabs in the header navigation between 5 and 10 so t ...@@ -27,6 +27,8 @@ We try to keep the amount of tabs in the header navigation between 5 and 10 so t
tab should represent separate functionality. Everything related to the issue tab should represent separate functionality. Everything related to the issue
tracker should be under the 'Issues' tab while everything related to the wiki should tracker should be under the 'Issues' tab while everything related to the wiki should
be under 'Wiki' tab and so on and so forth. be under 'Wiki' tab and so on and so forth.
When adding a new tab to the header don't use more than 2 words for text in the link.
We want to keep links short and easy to remember and fit all of them in the small screen.
## Mobile screen size ## Mobile screen size
......
...@@ -138,7 +138,7 @@ This setting is only available on GitLab 8.7 and above. ...@@ -138,7 +138,7 @@ This setting is only available on GitLab 8.7 and above.
SAML login includes support for external groups. You can define in the SAML SAML login includes support for external groups. You can define in the SAML
settings which groups, to which your users belong in your IdP, you wish to be settings which groups, to which your users belong in your IdP, you wish to be
marked as [external](../permissions/permissions.md). marked as [external](../user/permissions.md).
### Requirements ### Requirements
...@@ -306,4 +306,4 @@ For this you need take the following into account: ...@@ -306,4 +306,4 @@ For this you need take the following into account:
validators are optional validators are optional
Make sure that one of the above described scenarios is valid, or the requests will Make sure that one of the above described scenarios is valid, or the requests will
fail with one of the mentioned errors. fail with one of the mentioned errors.
\ No newline at end of file
# Permissions # Permissions
Users have different abilities depending on the access level they have in a particular group or project. This document was moved to [user/permissions.md](../user/permissions.md).
If a user is both in a project group and in the project itself, the highest permission level is used.
If a user is a GitLab administrator they receive all permissions.
On public and internal projects the Guest role is not enforced.
All users will be able to create issues, leave comments, and pull or download the project code.
To add or import a user, you can follow the [project users and members
documentation](../workflow/add-user/add-user.md).
## Project
| Action | Guest | Reporter | Developer | Master | Owner |
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
| See a commit status | | ✓ | ✓ | ✓ | ✓ |
| See a container registry | | ✓ | ✓ | ✓ | ✓ |
| See environments | | ✓ | ✓ | ✓ | ✓ |
| Manage merge requests | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry builds | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
| Update a container registry | | | ✓ | ✓ | ✓ |
| Remove a container registry image | | | ✓ | ✓ | ✓ |
| Create new environments | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
| Enable/disable branch protection | | | | ✓ | ✓ |
| Turn on/off prot. branch push for devs| | | | ✓ | ✓ |
| Rewrite/remove git tags | | | | ✓ | ✓ |
| Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ |
| Configure project hooks | | | | ✓ | ✓ |
| Manage runners | | | | ✓ | ✓ |
| Manage build triggers | | | | ✓ | ✓ |
| Manage variables | | | | ✓ | ✓ |
| Delete environments | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Force push to protected branches [^2] | | | | | |
| Remove protected branches [^2] | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings
[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
## Group
In order for a group to appear as public and be browsable, it must contain at
least one public project.
Any user can remove themselves from a group, unless they are the last Owner of the group.
| Action | Guest | Reporter | Developer | Master | Owner |
|-------------------------|-------|----------|-----------|--------|-------|
| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
| Edit group | | | | | ✓ |
| Create project in group | | | | ✓ | ✓ |
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
## External Users
In cases where it is desired that a user has access only to some internal or
private projects, there is the option of creating **External Users**. This
feature may be useful when for example a contractor is working on a given
project and should only have access to that project.
External users can only access projects to which they are explicitly granted
access, thus hiding all other internal or private ones from them. Access can be
granted by adding the user as member to the project or group.
They will, like usual users, receive a role in the project or group with all
the abilities that are mentioned in the table above. They cannot however create
groups or projects, and they have the same access as logged out users in all
other cases.
An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you
will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**.
\ No newline at end of file
...@@ -17,7 +17,7 @@ Public projects can be cloned **without any** authentication. ...@@ -17,7 +17,7 @@ Public projects can be cloned **without any** authentication.
They will also be listed on the public access directory (`/public`). They will also be listed on the public access directory (`/public`).
**Any logged in user** will have [Guest](../permissions/permissions.md) **Any logged in user** will have [Guest](../user/permissions.md)
permissions on the repository. permissions on the repository.
### Internal projects ### Internal projects
...@@ -27,7 +27,7 @@ Internal projects can be cloned by any logged in user. ...@@ -27,7 +27,7 @@ Internal projects can be cloned by any logged in user.
They will also be listed on the public access directory (`/public`) for logged They will also be listed on the public access directory (`/public`) for logged
in users. in users.
Any logged in user will have [Guest](../permissions/permissions.md) permissions Any logged in user will have [Guest](../user/permissions.md) permissions
on the repository. on the repository.
### How to change project visibility ### How to change project visibility
......
# Permissions
Users have different abilities depending on the access level they have in a
particular group or project. If a user is both in a group's project and the
project itself, the highest permission level is used.
On public and internal projects the Guest role is not enforced. All users will
be able to create issues, leave comments, and pull or download the project code.
GitLab administrators receive all permissions.
To add or import a user, you can follow the [project users and members
documentation](../workflow/add-user/add-user.md).
## Project
The following table depicts the various user permission levels in a project.
| Action | Guest | Reporter | Developer | Master | Owner |
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
| See a commit status | | ✓ | ✓ | ✓ | ✓ |
| See a container registry | | ✓ | ✓ | ✓ | ✓ |
| See environments | | ✓ | ✓ | ✓ | ✓ |
| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry builds | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
| Update a container registry | | | ✓ | ✓ | ✓ |
| Remove a container registry image | | | ✓ | ✓ | ✓ |
| Create new environments | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
| Enable/disable branch protection | | | | ✓ | ✓ |
| Turn on/off protected branch push for devs| | | | ✓ | ✓ |
| Rewrite/remove Git tags | | | | ✓ | ✓ |
| Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ |
| Configure project hooks | | | | ✓ | ✓ |
| Manage runners | | | | ✓ | ✓ |
| Manage build triggers | | | | ✓ | ✓ |
| Manage variables | | | | ✓ | ✓ |
| Delete environments | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Force push to protected branches [^2] | | | | | |
| Remove protected branches [^2] | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings
[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
## Group
Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
group.
| Action | Guest | Reporter | Developer | Master | Owner |
|-------------------------|-------|----------|-----------|--------|-------|
| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
| Edit group | | | | | ✓ |
| Create project in group | | | | ✓ | ✓ |
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
## External Users
In cases where it is desired that a user has access only to some internal or
private projects, there is the option of creating **External Users**. This
feature may be useful when for example a contractor is working on a given
project and should only have access to that project.
External users can only access projects to which they are explicitly granted
access, thus hiding all other internal or private ones from them. Access can be
granted by adding the user as member to the project or group.
They will, like usual users, receive a role in the project or group with all
the abilities that are mentioned in the table above. They cannot however create
groups or projects, and they have the same access as logged out users in all
other cases.
An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you
will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**.
## GitLab CI
GitLab CI permissions rely on the role the user has in GitLab. There are four
permission levels it total:
- admin
- master
- developer
- guest/reporter
The admin user can perform any action on GitLab CI in scope of the GitLab
instance and project. In addition, all admins can use the admin interface under
`/admin/runners`.
| Action | Guest, Reporter | Developer | Master | Admin |
|---------------------------------------|-----------------|-------------|----------|--------|
| See commits and builds | ✓ | ✓ | ✓ | ✓ |
| Retry or cancel build | | ✓ | ✓ | ✓ |
| Remove project | | | ✓ | ✓ |
| Create project | | | ✓ | ✓ |
| Change project configuration | | | ✓ | ✓ |
| Add specific runners | | | ✓ | ✓ |
| Add shared runners | | | | ✓ |
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
...@@ -23,7 +23,7 @@ want to add. ...@@ -23,7 +23,7 @@ want to add.
--- ---
Select the user and the [permission level](../../permissions/permissions.md) Select the user and the [permission level](../../user/permissions.md)
that you'd like to give the user. Note that you can select more than one user. that you'd like to give the user. Note that you can select more than one user.
![Give user permissions](img/add_user_give_permissions.png) ![Give user permissions](img/add_user_give_permissions.png)
......
...@@ -38,7 +38,7 @@ Forking a project is in most cases a two-step process. ...@@ -38,7 +38,7 @@ Forking a project is in most cases a two-step process.
--- ---
After the forking is done, you can start working on the newly created After the forking is done, you can start working on the newly created
repository. There, you will have full [Owner](../permissions/permissions.md) repository. There, you will have full [Owner](../user/permissions.md)
access, so you can set it up as you please. access, so you can set it up as you please.
## Merging upstream ## Merging upstream
......
...@@ -12,7 +12,7 @@ A protected branch does three simple things: ...@@ -12,7 +12,7 @@ A protected branch does three simple things:
You can make any branch a protected branch. GitLab makes the master branch a protected branch by default. You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
To protect a branch, user needs to have at least a Master permission level, see [permissions document](../permissions/permissions.md). To protect a branch, user needs to have at least a Master permission level, see [permissions document](../user/permissions.md).
![protected branches page](protected_branches/protected_branches1.png) ![protected branches page](protected_branches/protected_branches1.png)
......
...@@ -89,7 +89,7 @@ Feature: Project Merge Requests ...@@ -89,7 +89,7 @@ Feature: Project Merge Requests
Then The list should be sorted by "Oldest updated" Then The list should be sorted by "Oldest updated"
@javascript @javascript
Scenario: Visiting Merge Requests from a differente Project after sorting Scenario: Visiting Merge Requests from a different Project after sorting
Given I visit project "Shop" merge requests page Given I visit project "Shop" merge requests page
And I sort the list by "Oldest updated" And I sort the list by "Oldest updated"
And I visit dashboard merge requests page And I visit dashboard merge requests page
......
...@@ -207,6 +207,8 @@ module API ...@@ -207,6 +207,8 @@ module API
merge_request.subscribed?(options[:current_user]) merge_request.subscribed?(options[:current_user])
end end
expose :user_notes_count expose :user_notes_count
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
end end
class MergeRequestChanges < MergeRequest class MergeRequestChanges < MergeRequest
......
...@@ -17,7 +17,7 @@ module API ...@@ -17,7 +17,7 @@ module API
def current_user def current_user
@current_user ||= (find_user_by_private_token || doorkeeper_guard) @current_user ||= (find_user_by_private_token || doorkeeper_guard)
unless @current_user && Gitlab::UserAccess.allowed?(@current_user) unless @current_user && Gitlab::UserAccess.new(@current_user).allowed?
return nil return nil
end end
......
...@@ -3,6 +3,10 @@ module Banzai ...@@ -3,6 +3,10 @@ module Banzai
Renderer.render(text, context) Renderer.render(text, context)
end end
def self.cache_collection_render(texts_and_contexts)
Renderer.cache_collection_render(texts_and_contexts)
end
def self.render_result(text, context = {}) def self.render_result(text, context = {})
Renderer.render_result(text, context) Renderer.render_result(text, context)
end end
......
...@@ -39,9 +39,7 @@ module Banzai ...@@ -39,9 +39,7 @@ module Banzai
# Renders the attribute of every given object. # Renders the attribute of every given object.
def render_objects(objects, attribute) def render_objects(objects, attribute)
objects.map do |object| render_attributes(objects, attribute)
render_attribute(object, attribute)
end
end end
# Redacts the list of documents. # Redacts the list of documents.
...@@ -64,16 +62,21 @@ module Banzai ...@@ -64,16 +62,21 @@ module Banzai
context context
end end
# Renders the attribute of an object. # Renders the attributes of a set of objects.
# #
# Returns a `Nokogiri::HTML::Document`. # Returns an Array of `Nokogiri::HTML::Document`.
def render_attribute(object, attribute) def render_attributes(objects, attribute)
context = context_for(object, attribute) strings_and_contexts = objects.map do |object|
context = context_for(object, attribute)
string = object.__send__(attribute)
string = object.__send__(attribute) { text: string, context: context }
html = Banzai.render(string, context) end
Banzai::Pipeline[:relative_link].to_document(html, context) Banzai.cache_collection_render(strings_and_contexts).each_with_index.map do |html, index|
Banzai::Pipeline[:relative_link].to_document(html, strings_and_contexts[index][:context])
end
end end
def base_context def base_context
......
...@@ -10,7 +10,7 @@ module Banzai ...@@ -10,7 +10,7 @@ module Banzai
# requiring XHTML, such as Atom feeds, need to call `post_process` on the # requiring XHTML, such as Atom feeds, need to call `post_process` on the
# result, providing the appropriate `pipeline` option. # result, providing the appropriate `pipeline` option.
# #
# markdown - Markdown String # text - Markdown String
# context - Hash of context options passed to our HTML Pipeline # context - Hash of context options passed to our HTML Pipeline
# #
# Returns an HTML-safe String # Returns an HTML-safe String
...@@ -29,6 +29,58 @@ module Banzai ...@@ -29,6 +29,58 @@ module Banzai
end end
end end
# Perform multiple render from an Array of Markdown String into an
# Array of HTML-safe String of HTML.
#
# As the rendered Markdown String can be already cached read all the data
# from the cache using Rails.cache.read_multi operation. If the Markdown String
# is not in the cache or it's not cacheable (no cache_key entry is provided in
# the context) the Markdown String is rendered and stored in the cache so the
# next render call gets the rendered HTML-safe String from the cache.
#
# For further explanation see #render method comments.
#
# texts_and_contexts - An Array of Hashes that contains the Markdown String (:text)
# an options passed to our HTML Pipeline (:context)
#
# If on the :context you specify a :cache_key entry will be used to retrieve it
# and cache the result of rendering the Markdown String.
#
# Returns an Array containing HTML-safe String instances.
#
# Example:
# texts_and_contexts
# => [{ text: '### Hello',
# context: { cache_key: [note, :note] } }]
def self.cache_collection_render(texts_and_contexts)
items_collection = texts_and_contexts.each_with_index do |item, index|
context = item[:context]
cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline])
item[:cache_key] = cache_key if cache_key
end
cacheable_items, non_cacheable_items = items_collection.partition { |item| item.key?(:cache_key) }
items_in_cache = []
items_not_in_cache = []
unless cacheable_items.empty?
items_in_cache = Rails.cache.read_multi(*cacheable_items.map { |item| item[:cache_key] })
items_not_in_cache = cacheable_items.reject do |item|
item[:rendered] = items_in_cache[item[:cache_key]]
items_in_cache.key?(item[:cache_key])
end
end
(items_not_in_cache + non_cacheable_items).each do |item|
item[:rendered] = render(item[:text], item[:context])
Rails.cache.write(item[:cache_key], item[:rendered]) if item[:cache_key]
end
items_collection.map { |item| item[:rendered] }
end
def self.render_result(text, context = {}) def self.render_result(text, context = {})
text = Pipeline[:pre_process].to_html(text, context) if text text = Pipeline[:pre_process].to_html(text, context) if text
...@@ -78,5 +130,13 @@ module Banzai ...@@ -78,5 +130,13 @@ module Banzai
return unless cache_key return unless cache_key
["banzai", *cache_key, pipeline_name || :full] ["banzai", *cache_key, pipeline_name || :full]
end end
# To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key.
# Other option will be to generate stringified keys on our side and don't delegate to Rails.cache.expanded_key
# method.
def self.full_cache_multi_key(cache_key, pipeline_name)
return unless cache_key
Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
end
end end
end end
...@@ -14,9 +14,10 @@ module Gitlab ...@@ -14,9 +14,10 @@ module Gitlab
OWNER = 50 OWNER = 50
# Branch protection settings # Branch protection settings
PROTECTION_NONE = 0 PROTECTION_NONE = 0
PROTECTION_DEV_CAN_PUSH = 1 PROTECTION_DEV_CAN_PUSH = 1
PROTECTION_FULL = 2 PROTECTION_FULL = 2
PROTECTION_DEV_CAN_MERGE = 3
class << self class << self
def values def values
...@@ -54,6 +55,7 @@ module Gitlab ...@@ -54,6 +55,7 @@ module Gitlab
def protection_options def protection_options
{ {
"Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE, "Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE,
"Protected against pushes: Developers cannot push new commits, but are allowed to accept merge requests to the branch." => PROTECTION_DEV_CAN_MERGE,
"Partially protected: Developers can push new commits, but cannot force push or delete the branch. Masters can do all of those." => PROTECTION_DEV_CAN_PUSH, "Partially protected: Developers can push new commits, but cannot force push or delete the branch. Masters can do all of those." => PROTECTION_DEV_CAN_PUSH,
"Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL, "Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL,
} }
......
module Gitlab
module Checks
class ChangeAccess
attr_reader :user_access, :project
def initialize(change, user_access:, project:)
@oldrev, @newrev, @ref = change.split(' ')
@branch_name = branch_name(@ref)
@user_access = user_access
@project = project
end
def exec
error = protected_branch_checks || tag_checks || push_checks
if error
GitAccessStatus.new(false, error)
else
GitAccessStatus.new(true)
end
end
protected
def protected_branch_checks
return unless project.protected_branch?(@branch_name)
if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches)
return "You are not allowed to force push code to a protected branch on this project."
elsif Gitlab::Git.blank_ref?(@newrev) && user_access.cannot_do_action?(:remove_protected_branches)
return "You are not allowed to delete protected branches from this project."
end
if matching_merge_request?
if user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
return
else
"You are not allowed to merge code into protected branches on this project."
end
else
if user_access.can_push_to_branch?(@branch_name)
return
else
"You are not allowed to push code to protected branches on this project."
end
end
end
def tag_checks
tag_ref = tag_name(@ref)
if tag_ref && protected_tag?(tag_ref) && user_access.cannot_do_action?(:admin_project)
"You are not allowed to change existing tags on this project."
end
end
def push_checks
if user_access.cannot_do_action?(:push_code)
"You are not allowed to push code to this project."
end
end
private
def protected_tag?(tag_name)
project.repository.tag_exists?(tag_name)
end
def forced_push?
Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
end
def matching_merge_request?
Checks::MatchingMergeRequest.new(@newrev, @branch_name, @project).match?
end
def branch_name(ref)
ref = @ref.to_s
if Gitlab::Git.branch_ref?(ref)
Gitlab::Git.ref_name(ref)
else
nil
end
end
def tag_name(ref)
ref = @ref.to_s
if Gitlab::Git.tag_ref?(ref)
Gitlab::Git.ref_name(ref)
else
nil
end
end
end
end
end
module Gitlab
module Checks
class ForcePush
def self.force_push?(project, oldrev, newrev)
return false if project.empty_repo?
# Created or deleted branch
if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
false
else
missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
missed_refs.split("\n").size > 0
end
end
end
end
end
module Gitlab
module Checks
class MatchingMergeRequest
def initialize(newrev, branch_name, project)
@newrev = newrev
@branch_name = branch_name
@project = project
end
def match?
@project.merge_requests
.with_state(:locked)
.where(in_progress_merge_commit_sha: @newrev, target_branch: @branch_name)
.exists?
end
end
end
end
module Gitlab module Gitlab
module Diff module Diff
class InlineDiff class InlineDiff
# Regex to find a run of deleted lines followed by the same number of added lines
LINE_PAIRS_PATTERN = %r{
# Runs start at the beginning of the string (the first line) or after a space (for an unchanged line)
(?:\A|\s)
# This matches a number of `-`s followed by the same number of `+`s through recursion
(?<del_ins>
-
\g<del_ins>?
\+
)
# Runs end at the end of the string (the last line) or before a space (for an unchanged line)
(?=\s|\z)
}x.freeze
attr_accessor :old_line, :new_line, :offset attr_accessor :old_line, :new_line, :offset
def self.for_lines(lines) def self.for_lines(lines)
local_edit_indexes = self.find_local_edits(lines) changed_line_pairs = self.find_changed_line_pairs(lines)
inline_diffs = [] inline_diffs = []
local_edit_indexes.each do |index| changed_line_pairs.each do |old_index, new_index|
old_index = index
new_index = index + 1
old_line = lines[old_index] old_line = lines[old_index]
new_line = lines[new_index] new_line = lines[new_index]
...@@ -51,18 +65,28 @@ module Gitlab ...@@ -51,18 +65,28 @@ module Gitlab
private private
def self.find_local_edits(lines) # Finds pairs of old/new line pairs that represent the same line that changed
line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' } def self.find_changed_line_pairs(lines)
joined_line_prefixes = " #{line_prefixes.join} " # Prefixes of all diff lines, indicating their types
# For example: `" - + -+ ---+++ --+ -++"`
offset = 0 line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
local_edit_indexes = []
while index = joined_line_prefixes.index(" -+ ", offset) changed_line_pairs = []
local_edit_indexes << index line_prefixes.scan(LINE_PAIRS_PATTERN) do
offset = index + 1 # For `"---+++"`, `begin_index == 0`, `end_index == 6`
begin_index, end_index = Regexp.last_match.offset(:del_ins)
# For `"---+++"`, `changed_line_count == 3`
changed_line_count = (end_index - begin_index) / 2
halfway_index = begin_index + changed_line_count
(begin_index...halfway_index).each do |i|
# For `"---+++"`, index 1 maps to 1 + 3 = 4
changed_line_pairs << [i, i + changed_line_count]
end
end end
local_edit_indexes changed_line_pairs
end end
def longest_common_prefix(a, b) def longest_common_prefix(a, b)
......
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.
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.
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.
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