Merge branch 'master' into merge-if-green
# Conflicts: # app/views/projects/merge_requests/widget/_heading.html.haml # app/views/projects/merge_requests/widget/open/_accept.html.haml
This commit is contained in:
commit
8fb49a4b70
79 changed files with 956 additions and 262 deletions
|
@ -3,9 +3,12 @@ Please view this file on the master branch, on stable branches it's out of date.
|
|||
v 8.3.0 (unreleased)
|
||||
- Merge when build succeeds (Zeger-Jan van de Weg)
|
||||
- Bump gollum-lib to 4.1.0 (Stan Hu)
|
||||
- Fix broken group avatar upload under "New group" (Stan Hu)
|
||||
- Update project repositorize size and commit count during import:repos task (Stan Hu)
|
||||
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
|
||||
- Handle and report SSL errors in Web hook test (Stan Hu)
|
||||
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
|
||||
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
|
||||
- Fix 500 error when update group member permission
|
||||
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
|
||||
- Recognize issue/MR/snippet/commit links as references
|
||||
|
@ -20,6 +23,8 @@ v 8.3.0 (unreleased)
|
|||
- Fix 500 error when creating a merge request that removes a submodule
|
||||
- Run custom Git hooks when branch is created or deleted.
|
||||
- Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch
|
||||
- Add languages page to graphs
|
||||
- Block LDAP user when they are no longer found in the LDAP server
|
||||
|
||||
v 8.2.3
|
||||
- Fix application settings cache not expiring after changes (Stan Hu)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
groups_path: "/api/:version/groups.json"
|
||||
group_path: "/api/:version/groups/:id.json"
|
||||
namespaces_path: "/api/:version/namespaces.json"
|
||||
group_projects_path: "/api/:version/groups/:id/projects.json"
|
||||
projects_path: "/api/:version/projects.json"
|
||||
|
||||
group: (group_id, callback) ->
|
||||
url = Api.buildUrl(Api.group_path)
|
||||
|
@ -44,6 +46,35 @@
|
|||
).done (namespaces) ->
|
||||
callback(namespaces)
|
||||
|
||||
# Return projects list. Filtered by query
|
||||
projects: (query, callback) ->
|
||||
url = Api.buildUrl(Api.projects_path)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data:
|
||||
private_token: gon.api_token
|
||||
search: query
|
||||
per_page: 20
|
||||
dataType: "json"
|
||||
).done (projects) ->
|
||||
callback(projects)
|
||||
|
||||
# Return group projects list. Filtered by query
|
||||
groupProjects: (group_id, query, callback) ->
|
||||
url = Api.buildUrl(Api.group_projects_path)
|
||||
url = url.replace(':id', group_id)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data:
|
||||
private_token: gon.api_token
|
||||
search: query
|
||||
per_page: 20
|
||||
dataType: "json"
|
||||
).done (projects) ->
|
||||
callback(projects)
|
||||
|
||||
buildUrl: (url) ->
|
||||
url = gon.relative_url_root + url if gon.relative_url_root?
|
||||
return url.replace(':version', gon.api_version)
|
||||
|
|
|
@ -83,7 +83,7 @@ class Dispatcher
|
|||
when 'projects:project_members:index'
|
||||
new ProjectMembers()
|
||||
new UsersSelect()
|
||||
when 'groups:new', 'groups:edit', 'admin:groups:edit'
|
||||
when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
|
||||
new GroupAvatar()
|
||||
when 'projects:tree:show'
|
||||
new TreeView()
|
||||
|
|
|
@ -3,7 +3,7 @@ class @NewCommitForm
|
|||
@newBranch = form.find('.js-new-branch')
|
||||
@originalBranch = form.find('.js-original-branch')
|
||||
@createMergeRequest = form.find('.js-create-merge-request')
|
||||
@createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
|
||||
@createMergeRequestContainer = form.find('.js-create-merge-request-container')
|
||||
|
||||
@renderDestination()
|
||||
@newBranch.keyup @renderDestination
|
||||
|
@ -12,10 +12,10 @@ class @NewCommitForm
|
|||
different = @newBranch.val() != @originalBranch.val()
|
||||
|
||||
if different
|
||||
@createMergeRequestFormGroup.show()
|
||||
@createMergeRequestContainer.show()
|
||||
@createMergeRequest.prop('checked', true) unless @wasDifferent
|
||||
else
|
||||
@createMergeRequestFormGroup.hide()
|
||||
@createMergeRequestContainer.hide()
|
||||
@createMergeRequest.prop('checked', false)
|
||||
|
||||
@wasDifferent = different
|
||||
|
|
39
app/assets/javascripts/project_select.js.coffee
Normal file
39
app/assets/javascripts/project_select.js.coffee
Normal file
|
@ -0,0 +1,39 @@
|
|||
class @ProjectSelect
|
||||
constructor: ->
|
||||
$('.ajax-project-select').each (i, select) ->
|
||||
@groupId = $(select).data('group-id')
|
||||
@includeGroups = $(select).data('include-groups')
|
||||
|
||||
placeholder = "Search for project"
|
||||
placeholder += " or group" if @includeGroups
|
||||
|
||||
$(select).select2
|
||||
placeholder: placeholder
|
||||
minimumInputLength: 0
|
||||
query: (query) =>
|
||||
finalCallback = (projects) ->
|
||||
data = { results: projects }
|
||||
query.callback(data)
|
||||
|
||||
if @includeGroups
|
||||
projectsCallback = (projects) ->
|
||||
groupsCallback = (groups) ->
|
||||
data = groups.concat(projects)
|
||||
finalCallback(data)
|
||||
|
||||
Api.groups query.term, false, groupsCallback
|
||||
else
|
||||
projectsCallback = finalCallback
|
||||
|
||||
if @groupId
|
||||
Api.groupProjects @groupId, query.term, projectsCallback
|
||||
else
|
||||
Api.projects query.term, projectsCallback
|
||||
|
||||
id: (project) ->
|
||||
project.web_url
|
||||
|
||||
text: (project) ->
|
||||
project.name_with_namespace || project.name
|
||||
|
||||
dropdownCssClass: "ajax-project-dropdown"
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
/* Common styles for all types */
|
||||
.bs-callout {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
margin: $gl-padding 0;
|
||||
padding: $gl-padding;
|
||||
border-left: 3px solid $border-color;
|
||||
color: $text-color;
|
||||
background: $background-color;
|
||||
|
@ -42,4 +42,3 @@
|
|||
border-color: #5cA64d;
|
||||
color: #3c763d;
|
||||
}
|
||||
|
||||
|
|
|
@ -333,7 +333,7 @@ table {
|
|||
}
|
||||
|
||||
.well {
|
||||
margin-bottom: 0;
|
||||
margin-bottom: $gl-padding;
|
||||
}
|
||||
|
||||
.search_box {
|
||||
|
@ -379,9 +379,8 @@ table {
|
|||
text-align: center;
|
||||
margin-top: 5px;
|
||||
margin-bottom: $gl-padding;
|
||||
height: 56px;
|
||||
height: auto;
|
||||
margin-top: -$gl-padding;
|
||||
padding-top: $gl-padding;
|
||||
|
||||
&.no-bottom {
|
||||
margin-bottom: 0;
|
||||
|
@ -390,6 +389,18 @@ table {
|
|||
&.no-top {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
li a {
|
||||
display: inline-block;
|
||||
padding-top: $gl-padding;
|
||||
padding-bottom: 11px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
&.bottom-border {
|
||||
border-bottom: 1px solid $border-color;
|
||||
height: 57px;
|
||||
}
|
||||
}
|
||||
|
||||
.center-middle-menu {
|
||||
|
@ -437,3 +448,16 @@ table {
|
|||
.alert, .progress {
|
||||
margin-bottom: $gl-padding;
|
||||
}
|
||||
|
||||
.new-project-item-select-holder {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
.new-project-item-select {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 250px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
position: relative;
|
||||
background: $background-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
padding: 10px $gl-padding;
|
||||
|
|
|
@ -72,13 +72,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
&.styled {
|
||||
li {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** light list with border-bottom between li **/
|
||||
ul.bordered-list {
|
||||
|
|
|
@ -82,9 +82,6 @@
|
|||
}
|
||||
|
||||
.center-top-menu {
|
||||
height: 45px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
li a {
|
||||
font-size: 14px;
|
||||
padding: 19px 10px;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
.panel {
|
||||
margin-bottom: $gl-padding;
|
||||
|
||||
|
||||
.panel-heading {
|
||||
padding: 10px $gl-padding;
|
||||
padding: 7px $gl-padding;
|
||||
line-height: 42px !important;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: $gl-padding;
|
||||
|
||||
|
|
|
@ -220,6 +220,7 @@ pre {
|
|||
|
||||
.monospace {
|
||||
font-family: $monospace_font;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
code {
|
||||
|
|
|
@ -67,9 +67,4 @@
|
|||
color: #3084bb !important;
|
||||
}
|
||||
}
|
||||
|
||||
.build-top-menu {
|
||||
margin-top: 0;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@
|
|||
font-family: $monospace_font;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
font-size: 90%;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-build-token {
|
||||
float: left;
|
||||
padding: 6px 20px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.profile-avatar-form-option {
|
||||
hr {
|
||||
margin: 10px 0;
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
.gitlab-ui-dev-kit {
|
||||
> h2 {
|
||||
font-size: 27px;
|
||||
border-bottom: 1px solid #CCC;
|
||||
color: #666;
|
||||
margin: 30px 0;
|
||||
margin: 35px 0 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class Projects::ApplicationController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def ci_enabled
|
||||
def builds_enabled
|
||||
return render_404 unless @project.builds_enabled?
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
|
|||
before_action :require_non_empty_project
|
||||
before_action :assign_ref_vars
|
||||
before_action :authorize_download_code!
|
||||
before_action :ci_enabled, only: :ci
|
||||
before_action :builds_enabled, only: :ci
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
|
@ -34,6 +34,26 @@ class Projects::GraphsController < Projects::ApplicationController
|
|||
@charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
|
||||
end
|
||||
|
||||
def languages
|
||||
@languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages
|
||||
total = @languages.map(&:last).sum
|
||||
|
||||
@languages = @languages.map do |language|
|
||||
name, share = language
|
||||
color = Digest::SHA256.hexdigest(name)[0...6]
|
||||
{
|
||||
value: (share.to_f * 100 / total).round(2),
|
||||
label: name,
|
||||
color: "##{color}",
|
||||
highlight: "##{color}"
|
||||
}
|
||||
end
|
||||
|
||||
@languages.sort! do |x, y|
|
||||
y[:value] <=> x[:value]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_graph
|
||||
|
|
|
@ -278,6 +278,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
|
||||
@merge_request_diff = @merge_request.merge_request_diff
|
||||
|
||||
@ci_commit = @merge_request.ci_commit
|
||||
|
||||
if @merge_request.locked_long_ago?
|
||||
@merge_request.unlock_mr
|
||||
@merge_request.close
|
||||
|
|
|
@ -8,6 +8,10 @@ module CiStatusHelper
|
|||
ci_icon_for_status(ci_commit.status)
|
||||
end
|
||||
|
||||
def ci_status_label(ci_commit)
|
||||
ci_label_for_status(ci_commit.status)
|
||||
end
|
||||
|
||||
def ci_status_color(ci_commit)
|
||||
case ci_commit.status
|
||||
when 'success'
|
||||
|
@ -23,7 +27,15 @@ module CiStatusHelper
|
|||
|
||||
def ci_status_with_icon(status)
|
||||
content_tag :span, class: "ci-status ci-#{status}" do
|
||||
ci_icon_for_status(status) + ' '.html_safe + status
|
||||
ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status)
|
||||
end
|
||||
end
|
||||
|
||||
def ci_label_for_status(status)
|
||||
if status == 'success'
|
||||
'passed'
|
||||
else
|
||||
status
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -46,7 +58,7 @@ module CiStatusHelper
|
|||
def render_ci_status(ci_commit)
|
||||
link_to ci_status_path(ci_commit),
|
||||
class: "c#{ci_status_color(ci_commit)}",
|
||||
title: "Build status: #{ci_commit.status}",
|
||||
title: "Build status: #{ci_status_label(ci_commit)}",
|
||||
data: { toggle: 'tooltip', placement: 'left' } do
|
||||
ci_status_icon(ci_commit)
|
||||
end
|
||||
|
|
|
@ -4,7 +4,8 @@ module PageLayoutHelper
|
|||
|
||||
@page_title.push(*titles.compact) if titles.any?
|
||||
|
||||
@page_title.join(" | ")
|
||||
# Segments are seperated by middot
|
||||
@page_title.join(" \u00b7 ")
|
||||
end
|
||||
|
||||
def header_title(title = nil, title_url = nil)
|
||||
|
|
|
@ -48,6 +48,19 @@ module SelectsHelper
|
|||
select2_tag(id, opts)
|
||||
end
|
||||
|
||||
def project_select_tag(id, opts = {})
|
||||
opts[:class] ||= ''
|
||||
opts[:class] << ' ajax-project-select'
|
||||
|
||||
unless opts.delete(:scope) == :all
|
||||
if @group
|
||||
opts['data-group-id'] = @group.id
|
||||
end
|
||||
end
|
||||
|
||||
hidden_field_tag(id, opts[:selected], opts)
|
||||
end
|
||||
|
||||
def select2_tag(id, opts = {})
|
||||
css_class = ''
|
||||
css_class << 'multiselect ' if opts[:multiple]
|
||||
|
|
|
@ -207,7 +207,7 @@ module Ci
|
|||
end
|
||||
|
||||
def ci_yaml_file
|
||||
gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
|
||||
@ci_yaml_file ||= gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
= render 'shared/project_limit'
|
||||
|
||||
%ul.center-top-menu
|
||||
= nav_link(path: ['projects#index', 'root#index']) do
|
||||
= nav_link(page: [dashboard_projects_path, root_path]) do
|
||||
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
|
||||
Your Projects
|
||||
= nav_link(page: starred_dashboard_projects_path) do
|
||||
|
|
|
@ -4,14 +4,20 @@
|
|||
- if current_user
|
||||
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
|
||||
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
.pull-left
|
||||
- if current_user
|
||||
.hidden-xs.pull-left
|
||||
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
|
||||
%i.fa.fa-rss
|
||||
|
||||
.append-bottom-20
|
||||
.pull-right
|
||||
- if current_user
|
||||
.hidden-xs.pull-left.prepend-top-20
|
||||
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do
|
||||
%i.fa.fa-rss
|
||||
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
|
||||
|
||||
= render 'shared/issuable/filter', type: :issues
|
||||
|
||||
= render 'shared/issues'
|
||||
.gray-content-block.second-block
|
||||
List all issues from all projects you have access to.
|
||||
|
||||
.prepend-top-default
|
||||
= render 'shared/issues'
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
- page_title "Merge Requests"
|
||||
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
|
||||
|
||||
.append-bottom-20
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
|
||||
|
||||
= render 'shared/issuable/filter', type: :merge_requests
|
||||
= render 'shared/merge_requests'
|
||||
|
||||
.gray-content-block.second-block
|
||||
List all merge requests from all projects you have access to.
|
||||
|
||||
.prepend-top-default
|
||||
= render 'shared/merge_requests'
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
- page_title "Milestones"
|
||||
- header_title "Milestones", dashboard_milestones_path
|
||||
- header_title "Milestones", dashboard_milestones_path
|
||||
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
|
||||
|
||||
= render 'shared/milestones_filter'
|
||||
= render 'shared/milestones_filter'
|
||||
|
||||
.gray-content-block
|
||||
.oneline
|
||||
List all milestones from all projects you have access to.
|
||||
List all milestones from all projects you have access to.
|
||||
|
||||
.milestones
|
||||
%ul.content-list
|
||||
|
|
|
@ -4,21 +4,24 @@
|
|||
- if current_user
|
||||
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
|
||||
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
.pull-left
|
||||
- if current_user
|
||||
.hidden-xs.pull-left
|
||||
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
|
||||
%i.fa.fa-rss
|
||||
|
||||
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
|
||||
|
||||
= render 'shared/issuable/filter', type: :issues
|
||||
|
||||
= render 'shared/issuable/filter', type: :issues
|
||||
.gray-content-block.second-block
|
||||
.pull-right
|
||||
- if current_user
|
||||
.hidden-xs.pull-left
|
||||
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do
|
||||
%i.fa.fa-rss
|
||||
%div
|
||||
Only issues from
|
||||
%strong #{@group.name}
|
||||
group are listed here.
|
||||
- if current_user
|
||||
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
|
||||
Only issues from
|
||||
%strong #{@group.name}
|
||||
group are listed here.
|
||||
- if current_user
|
||||
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
|
||||
|
||||
.prepend-top-default
|
||||
= render 'shared/issues'
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
- page_title "Merge Requests"
|
||||
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
|
||||
|
||||
= render 'shared/issuable/filter', type: :merge_requests
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
|
||||
|
||||
= render 'shared/issuable/filter', type: :merge_requests
|
||||
|
||||
.gray-content-block.second-block
|
||||
%div
|
||||
Only merge requests from
|
||||
%strong #{@group.name}
|
||||
group are listed here.
|
||||
- if current_user
|
||||
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
|
||||
Only merge requests from
|
||||
%strong #{@group.name}
|
||||
group are listed here.
|
||||
- if current_user
|
||||
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
|
||||
|
||||
.prepend-top-default
|
||||
= render 'shared/merge_requests'
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
- page_title "Milestones"
|
||||
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
|
||||
|
||||
= render 'shared/milestones_filter'
|
||||
.gray-content-block
|
||||
- if can?(current_user, :admin_milestones, @group)
|
||||
.pull-right
|
||||
%span.pull-right.hidden-xs
|
||||
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
|
||||
New Milestone
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
- if can?(current_user, :admin_milestones, @group)
|
||||
.pull-right
|
||||
%span.pull-right.hidden-xs
|
||||
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
|
||||
= icon('plus')
|
||||
New Milestone
|
||||
|
||||
= render 'shared/milestones_filter'
|
||||
|
||||
.gray-content-block
|
||||
Only milestones from
|
||||
%strong #{@group.name}
|
||||
group are listed here.
|
||||
|
||||
.oneline
|
||||
Only milestones from
|
||||
%strong #{@group.name}
|
||||
group are listed here.
|
||||
.milestones
|
||||
%ul.content-list
|
||||
- if @milestones.blank?
|
||||
|
|
|
@ -31,11 +31,9 @@
|
|||
|
||||
%h2#blocks Blocks
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .gray-content-block
|
||||
|
||||
|
||||
|
||||
.gray-content-block.middle-block
|
||||
%h4 Normal block inside content
|
||||
= lorem
|
||||
|
@ -45,9 +43,28 @@
|
|||
= lorem
|
||||
|
||||
|
||||
%h4
|
||||
%code .cover-block
|
||||
%br
|
||||
.cover-block
|
||||
.avatar-holder
|
||||
= image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
|
||||
.cover-title
|
||||
John Smith
|
||||
|
||||
.cover-desc
|
||||
= lorem
|
||||
|
||||
.cover-controls
|
||||
= link_to '#', class: 'btn btn-gray' do
|
||||
= icon('pencil')
|
||||
|
||||
= link_to '#', class: 'btn btn-gray' do
|
||||
= icon('rss')
|
||||
|
||||
%h2#lists Lists
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .content-list
|
||||
%ul.content-list
|
||||
%li
|
||||
|
@ -57,7 +74,7 @@
|
|||
%li
|
||||
One item
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .well-list
|
||||
%ul.well-list
|
||||
%li
|
||||
|
@ -67,7 +84,7 @@
|
|||
%li
|
||||
One item
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .panel .well-list
|
||||
|
||||
.panel.panel-default
|
||||
|
@ -80,7 +97,7 @@
|
|||
%li
|
||||
One item
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .bordered-list
|
||||
%ul.bordered-list
|
||||
%li
|
||||
|
@ -121,7 +138,7 @@
|
|||
|
||||
%h2#navs Navigation
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .center-top-menu
|
||||
.example
|
||||
%ul.center-top-menu
|
||||
|
@ -130,7 +147,7 @@
|
|||
%li
|
||||
%a Closed
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .btn-group.btn-group-next
|
||||
.example
|
||||
%div.btn-group.btn-group-next
|
||||
|
@ -138,7 +155,7 @@
|
|||
%a.btn Closed
|
||||
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code .nav.nav-tabs
|
||||
.example
|
||||
%ul.nav.nav-tabs
|
||||
|
@ -204,7 +221,7 @@
|
|||
|
||||
%h2#forms Forms
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code form.horizontal-form
|
||||
|
||||
%form.form-horizontal
|
||||
|
@ -226,7 +243,7 @@
|
|||
.col-sm-offset-2.col-sm-10
|
||||
%button.btn.btn-default{:type => "submit"} Sign in
|
||||
|
||||
%h3
|
||||
%h4
|
||||
%code form
|
||||
|
||||
%form
|
||||
|
@ -243,7 +260,7 @@
|
|||
%button.btn.btn-default{:type => "submit"} Sign in
|
||||
|
||||
%h2#file File
|
||||
%h3
|
||||
%h4
|
||||
%code .file-holder
|
||||
|
||||
- blob = Snippet.new(content: "Wow\nSuch\nFile")
|
||||
|
@ -254,13 +271,12 @@
|
|||
.file-actions
|
||||
.btn-group
|
||||
%a.btn Edit
|
||||
%a.btn Remove
|
||||
%a.btn.btn-danger Remove
|
||||
.file-contenta.code
|
||||
= render 'shared/file_highlight', blob: blob
|
||||
|
||||
|
||||
%h2#markdown Markdown
|
||||
%h3
|
||||
%h4
|
||||
%code .md or .wiki and others
|
||||
|
||||
Markdown rendering has a bit different css and presented in next UI elements:
|
||||
|
|
|
@ -26,11 +26,11 @@
|
|||
- else
|
||||
%span You don`t have one yet. Click generate to fix it.
|
||||
|
||||
.form-actions
|
||||
- if current_user.private_token
|
||||
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
|
||||
- else
|
||||
= f.submit 'Generate', class: "btn btn-default btn-build-token"
|
||||
.form-actions
|
||||
- if current_user.private_token
|
||||
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
|
||||
- else
|
||||
= f.submit 'Generate', class: "btn btn-default"
|
||||
|
||||
- unless current_user.ldap_user?
|
||||
.panel.panel-default
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
$('#key_key').on('focusout', function(){
|
||||
var title = $('#key_title'),
|
||||
val = $('#key_key').val(),
|
||||
comment = val.match(/^\S+ \S+ (.+)$/);
|
||||
comment = val.match(/^\S+ \S+ (.+)\n?$/);
|
||||
|
||||
if( comment && comment.length > 1 && title.val() == '' ){
|
||||
$('#key_title').val( comment[1] );
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- if ci_commit
|
||||
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
|
||||
= ci_status_icon(ci_commit)
|
||||
= ci_commit.status
|
||||
= ci_status_label(ci_commit)
|
||||
|
||||
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
|
||||
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
%div
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
|
||||
%strong.str-truncated= branch.name
|
||||
|
||||
- if branch.name == @repository.root_ref
|
||||
%span.label.label-primary default
|
||||
- elsif @repository.merged_to_root_ref? branch.name
|
||||
%span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
|
||||
merged
|
||||
|
||||
- if branch.name == @repository.root_ref
|
||||
%span.label.label-primary default
|
||||
- elsif @repository.merged_to_root_ref? branch.name
|
||||
%span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
|
||||
merged
|
||||
|
||||
- if @project.protected_branch? branch.name
|
||||
%span.label.label-success
|
||||
%i.fa.fa-lock
|
||||
protected
|
||||
- if @project.protected_branch? branch.name
|
||||
%span.label.label-success
|
||||
%i.fa.fa-lock
|
||||
protected
|
||||
.controls.hidden-xs
|
||||
- if create_mr_button?(@repository.root_ref, branch.name)
|
||||
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
|
||||
|
@ -26,7 +26,7 @@
|
|||
Compare
|
||||
|
||||
- if can_remove_branch?(@project, branch.name)
|
||||
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, remote: true do
|
||||
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
|
||||
= icon("trash-o")
|
||||
|
||||
- if commit
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
|
||||
- if @ci_project && can?(current_user, :manage_builds, @project)
|
||||
.pull-left.hidden-xs
|
||||
- if @all_builds.running_or_pending.any?
|
||||
= link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
|
||||
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
|
||||
|
||||
%ul.center-top-menu
|
||||
%li{class: ('active' if @scope.nil?)}
|
||||
|
@ -50,4 +50,3 @@
|
|||
= render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true
|
||||
|
||||
= paginate @builds, theme: 'gitlab'
|
||||
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
- page_title "#{@build.name} (#{@build.id})", "Builds"
|
||||
- page_title "#{@build.name} (##{@build.id})", "Builds"
|
||||
= render "header_title"
|
||||
|
||||
.build-page
|
||||
.gray-content-block
|
||||
.gray-content-block.top-block
|
||||
Build ##{@build.id} for commit
|
||||
%strong.monospace
|
||||
= link_to @build.commit.short_sha, ci_status_path(@build.commit)
|
||||
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
|
||||
from
|
||||
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
|
||||
|
||||
#up-build-trace
|
||||
- if @commit.matrix_for_ref?(@build.ref)
|
||||
%ul.center-top-menu.build-top-menu
|
||||
%ul.center-top-menu.no-top.no-bottom
|
||||
- @commit.latest_builds_for_ref(@build.ref).each do |build|
|
||||
%li{class: ('active' if build == @build) }
|
||||
= link_to namespace_project_build_path(@project.namespace, @project, build) do
|
||||
|
@ -22,7 +21,6 @@
|
|||
- else
|
||||
= build.id
|
||||
|
||||
|
||||
- if @build.retried?
|
||||
%li.active
|
||||
%a
|
||||
|
@ -31,7 +29,7 @@
|
|||
%i.fa.fa-warning
|
||||
This build was retried.
|
||||
|
||||
.gray-content-block.second-block
|
||||
.gray-content-block.middle-block
|
||||
.build-head
|
||||
.clearfix
|
||||
= ci_status_with_icon(@build.status)
|
||||
|
@ -140,7 +138,7 @@
|
|||
%h4.title
|
||||
Commit
|
||||
.pull-right
|
||||
%small
|
||||
%small
|
||||
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
|
||||
%p
|
||||
%span.attr-name Branch:
|
||||
|
@ -162,7 +160,7 @@
|
|||
|
||||
- if @builds.present?
|
||||
.build-widget
|
||||
%h4.title #{pluralize(@builds.count(:id), "other build")} for
|
||||
%h4.title #{pluralize(@builds.count(:id), "other build")} for
|
||||
= succeed ":" do
|
||||
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
|
||||
%table.table.builds
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
|
||||
%p
|
||||
%span.light Commit
|
||||
= link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
|
||||
= link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace", data: { clipboard_text: @commit.id }
|
||||
= clipboard_button
|
||||
.commit-info-row
|
||||
%span.light Authored by
|
||||
%strong
|
||||
|
@ -44,7 +45,7 @@
|
|||
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
|
||||
= ci_status_icon(@ci_commit)
|
||||
build:
|
||||
= @ci_commit.status
|
||||
= ci_status_label(@ci_commit)
|
||||
|
||||
.commit-info-row.branches
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
%tr.commit_status
|
||||
%td.status
|
||||
= ci_status_with_icon(commit_status.status)
|
||||
- if commit_status.target_url
|
||||
= link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
|
||||
= ci_icon_for_status(commit_status.status)
|
||||
= commit_status.status
|
||||
- else
|
||||
= ci_status_with_icon(commit_status.status)
|
||||
|
||||
%td.commit_status-link
|
||||
- if commit_status.target_url
|
||||
= link_to commit_status.target_url do
|
||||
%strong Build ##{commit_status.id}
|
||||
%strong ##{commit_status.id}
|
||||
- else
|
||||
%strong Build ##{commit_status.id}
|
||||
%strong ##{commit_status.id}
|
||||
|
||||
- if commit_status.show_warning?
|
||||
%i.fa.fa-warning.text-warning
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
= link_to 'Contributors', namespace_project_graph_path
|
||||
= nav_link(action: :commits) do
|
||||
= link_to 'Commits', commits_namespace_project_graph_path
|
||||
= nav_link(action: :languages) do
|
||||
= link_to 'Languages', languages_namespace_project_graph_path
|
||||
- if @project.builds_enabled?
|
||||
= nav_link(action: :ci) do
|
||||
= link_to ci_namespace_project_graph_path do
|
||||
|
|
32
app/views/projects/graphs/languages.html.haml
Normal file
32
app/views/projects/graphs/languages.html.haml
Normal file
|
@ -0,0 +1,32 @@
|
|||
- page_title "Languages", "Graphs"
|
||||
= render "header_title"
|
||||
= render 'head'
|
||||
|
||||
.gray-content-block.append-bottom-default
|
||||
.oneline
|
||||
Programming languages used in this repository
|
||||
|
||||
.row
|
||||
.col-md-8
|
||||
%canvas#languages-chart{ height: 400 }
|
||||
.col-md-4
|
||||
%ul.bordered-list
|
||||
- @languages.each do |language|
|
||||
%li
|
||||
%span{ style: "color: #{language[:color]}" }
|
||||
= icon('circle')
|
||||
|
||||
= language[:label]
|
||||
.pull-right
|
||||
= language[:value]
|
||||
\%
|
||||
|
||||
:javascript
|
||||
var data = #{@languages.to_json};
|
||||
var ctx = $("#languages-chart").get(0).getContext("2d");
|
||||
var options = {
|
||||
scaleOverlay: true,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
var myPieChart = new Chart(ctx).Pie(data, options);
|
|
@ -12,15 +12,12 @@
|
|||
.col-md-9
|
||||
.votes-holder.pull-right
|
||||
#votes= render 'votes/votes_block', votable: @issue
|
||||
.participants
|
||||
%span= pluralize(@participants.count, 'participant')
|
||||
- @participants.each do |participant|
|
||||
= link_to_member(@project, participant, name: false, size: 24)
|
||||
= render "shared/issuable/participants"
|
||||
.col-md-3
|
||||
.input-group.cross-project-reference
|
||||
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
|
||||
= cross_project_reference(@project, @issue)
|
||||
= clipboard_button(clipboard_target: '#cross-project-reference')
|
||||
= clipboard_button(clipboard_target: 'span#cross-project-reference')
|
||||
|
||||
.row
|
||||
%section.col-md-9
|
||||
|
|
|
@ -12,16 +12,16 @@
|
|||
.col-md-9
|
||||
.votes-holder.pull-right
|
||||
#votes= render 'votes/votes_block', votable: @merge_request
|
||||
= render "projects/merge_requests/show/participants"
|
||||
= render "shared/issuable/participants"
|
||||
.col-md-3
|
||||
.input-group.cross-project-reference
|
||||
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
|
||||
= cross_project_reference(@project, @merge_request)
|
||||
= clipboard_button(clipboard_target: '#cross-project-reference')
|
||||
= clipboard_button(clipboard_target: 'span#cross-project-reference')
|
||||
|
||||
.row
|
||||
%section.col-md-9
|
||||
= render "projects/notes/notes_with_form"
|
||||
.voting_notes#notes= render "projects/notes/notes_with_form"
|
||||
%aside.col-md-3
|
||||
.issuable-affix
|
||||
.context
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
.mr-compare.merge-request
|
||||
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
|
||||
%li.commits-tab
|
||||
= link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
|
||||
= link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
|
||||
Commits
|
||||
%span.badge= @commits.size
|
||||
%li.diffs-tab.active
|
||||
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
|
||||
= link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
|
||||
Changes
|
||||
%span.badge= @diffs.size
|
||||
|
||||
|
|
|
@ -44,15 +44,15 @@
|
|||
- if @commits.present?
|
||||
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
|
||||
%li.notes-tab
|
||||
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
|
||||
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
|
||||
Discussion
|
||||
%span.badge= @merge_request.mr_and_commit_notes.user.count
|
||||
%li.commits-tab
|
||||
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
|
||||
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
|
||||
Commits
|
||||
%span.badge= @commits.size
|
||||
%li.diffs-tab
|
||||
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
|
||||
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
|
||||
Changes
|
||||
%span.badge= @merge_request.diffs.size
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
- if @merge_request.ci_commit
|
||||
- status = @merge_request.ci_commit.status
|
||||
- if @ci_commit
|
||||
- status = @ci_commit.status
|
||||
.mr-widget-heading
|
||||
.ci_widget{class: "ci-#{status}"}
|
||||
= ci_status_icon(@merge_request.ci_commit)
|
||||
= ci_status_icon(@ci_commit)
|
||||
%span CI build #{status}
|
||||
for #{@merge_request.last_commit_short_sha}.
|
||||
%span.ci-coverage
|
||||
= link_to "View build details", ci_status_path(@merge_request.ci_commit)
|
||||
= link_to "View build details", ci_status_path(@ci_commit)
|
||||
|
||||
- elsif @merge_request.has_ci?
|
||||
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
- status_class = @merge_request.ci_commit ? "ci-#{@merge_request.ci_commit.status}" : nil
|
||||
- status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil
|
||||
|
||||
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
|
||||
= hidden_field_tag :authenticity_token, form_authenticity_token
|
||||
.accept-merge-holder.clearfix.js-toggle-container
|
||||
.clearfix
|
||||
.accept-action
|
||||
- if @merge_request.ci_commit && @merge_request.ci_commit.active?
|
||||
- if @ci_commit && @ci_commit.active?
|
||||
%span.btn-group
|
||||
= link_to "#", class: "btn btn-create merge_when_build_succeeds" do
|
||||
Merge When Build Succeeds
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
%h4
|
||||
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
|
||||
to be merged automatically when
|
||||
#{link_to "the build", ci_status_path(@merge_request.ci_commit)} succeeds.
|
||||
to be merged automatically when the build succeeds.
|
||||
%div
|
||||
- should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present?
|
||||
%p
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
- page_title "Milestones"
|
||||
= render "header_title"
|
||||
= render 'shared/milestones_filter'
|
||||
|
||||
.gray-content-block
|
||||
.pull-right
|
||||
- if can? current_user, :admin_milestone, @project
|
||||
|
||||
.project-issuable-filter
|
||||
.controls
|
||||
- if can?(current_user, :admin_milestone, @project)
|
||||
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
|
||||
%i.fa.fa-plus
|
||||
New Milestone
|
||||
.oneline
|
||||
Milestone allows you to group issues and set due date for it
|
||||
|
||||
= render 'shared/milestones_filter'
|
||||
|
||||
.gray-content-block
|
||||
Milestone allows you to group issues and set due date for it
|
||||
|
||||
.milestones
|
||||
%ul.content-list
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
.append-bottom-20
|
||||
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
|
||||
.pull-right.visible-lg.light You can move around the graph by using the arrow keys.
|
||||
.gray-content-block.top-block.append-bottom-default
|
||||
.tree-ref-holder
|
||||
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
|
||||
|
||||
.oneline
|
||||
You can move around the graph by using the arrow keys.
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
group members
|
||||
%small
|
||||
(#{members.count})
|
||||
.pull-right
|
||||
= link_to group_group_members_path(@group), class: 'btn' do
|
||||
= icon('pencil-square-o')
|
||||
Edit group members
|
||||
- if can?(current_user, :admin_group_member, @group)
|
||||
.pull-right
|
||||
= link_to group_group_members_path(@group), class: 'btn' do
|
||||
= icon('pencil-square-o')
|
||||
Manage group members
|
||||
%ul.content-list
|
||||
- members.each do |member|
|
||||
= render 'groups/group_members/group_member', member: member, show_controls: false
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%p.light Keep stable branches secure and force developers to use Merge Requests
|
||||
%hr
|
||||
|
||||
.well.append-bottom-20
|
||||
.well
|
||||
%p Protected branches are designed to
|
||||
%ul
|
||||
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
|
||||
|
|
|
@ -11,11 +11,17 @@
|
|||
= strip_gpg_signature(tag.message)
|
||||
|
||||
.controls
|
||||
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do
|
||||
= icon("pencil")
|
||||
- if can? current_user, :download_code, @project
|
||||
- if can?(current_user, :download_code, @project)
|
||||
= render 'projects/tags/download', ref: tag.name, project: @project
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has_tooltip', title: "Edit release notes" do
|
||||
= icon("pencil")
|
||||
|
||||
- if can?(current_user, :admin_project, @project)
|
||||
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
|
||||
= icon("trash-o")
|
||||
|
||||
- if commit
|
||||
= render 'projects/branches/commit', commit: commit, project: @project
|
||||
- else
|
||||
|
|
|
@ -5,17 +5,17 @@
|
|||
.gray-content-block
|
||||
.pull-right
|
||||
- if can?(current_user, :push_code, @project)
|
||||
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do
|
||||
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has_tooltip', title: 'Edit release notes' do
|
||||
= icon("pencil")
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse source code' do
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse files' do
|
||||
= icon('files-o')
|
||||
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse commits' do
|
||||
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse commits' do
|
||||
= icon('history')
|
||||
- if can? current_user, :download_code, @project
|
||||
= render 'projects/tags/download', ref: @tag.name, project: @project
|
||||
- if can?(current_user, :admin_project, @project)
|
||||
.pull-right
|
||||
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
|
||||
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
|
||||
%i.fa.fa-trash-o
|
||||
.title
|
||||
%strong= @tag.name
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
- if allowed_tree_edit?
|
||||
%li
|
||||
%span.dropdown
|
||||
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
|
||||
%a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
|
||||
= icon('plus')
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
.col-sm-10
|
||||
= text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch"
|
||||
|
||||
.form-group.js-create-merge-request-form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
- nonce = SecureRandom.hex
|
||||
= label_tag "create_merge_request-#{nonce}" do
|
||||
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
|
||||
Start a <strong>new merge request</strong> with this commit
|
||||
.js-create-merge-request-container
|
||||
.checkbox
|
||||
- nonce = SecureRandom.hex
|
||||
= label_tag "create_merge_request-#{nonce}" do
|
||||
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
|
||||
Start a <strong>new merge request</strong> with these changes
|
||||
|
||||
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
|
||||
|
|
20
app/views/shared/_new_project_item_select.html.haml
Normal file
20
app/views/shared/_new_project_item_select.html.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
- if @projects.any?
|
||||
.prepend-left-10.new-project-item-select-holder
|
||||
= project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
|
||||
%a.btn.btn-new.new-project-item-select-button
|
||||
= icon('plus')
|
||||
= local_assigns[:label]
|
||||
%b.caret
|
||||
|
||||
:javascript
|
||||
$('.new-project-item-select-button').on('click', function() {
|
||||
$('.new-project-item-select').select2('open');
|
||||
});
|
||||
|
||||
var relativePath = '#{local_assigns[:path]}';
|
||||
|
||||
$('.new-project-item-select').on('click', function() {
|
||||
window.location = $(this).val() + '/' + relativePath;
|
||||
});
|
||||
|
||||
new ProjectSelect()
|
|
@ -1,4 +1,5 @@
|
|||
.participants
|
||||
%span #{@participants.count} participants
|
||||
%span
|
||||
= pluralize @participants.count, "participant"
|
||||
- @participants.each do |participant|
|
||||
= link_to_member(@project, participant, name: false, size: 24)
|
|
@ -73,7 +73,7 @@
|
|||
.user-calendar-activities
|
||||
|
||||
|
||||
%ul.center-middle-menu
|
||||
%ul.center-top-menu.no-top.no-bottom.bottom-border
|
||||
%li.active
|
||||
= link_to "#activity", 'data-toggle' => 'tab' do
|
||||
Activity
|
||||
|
|
54
bin/parallel-rsync-repos
Executable file
54
bin/parallel-rsync-repos
Executable file
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env bash
|
||||
# this script should run as the 'git' user, not root, because 'root' should not
|
||||
# own intermediate directories created by rsync.
|
||||
#
|
||||
# Example invocation:
|
||||
# find /var/opt/gitlab/git-data/repositories -maxdepth 2 | \
|
||||
# parallel-rsync-repos transfer-success.log /var/opt/gitlab/git-data/repositories /mnt/gitlab/repositories
|
||||
#
|
||||
# You can also rsync to a remote destination.
|
||||
#
|
||||
# parallel-rsync-repos transfer-success.log /var/opt/gitlab/git-data/repositories user@host:/mnt/gitlab/repositories
|
||||
#
|
||||
# If you need to pass extra options to rsync, set the RSYNC variable
|
||||
#
|
||||
# env RSYNC='rsync --rsh="foo bar"' parallel-rsync-repos transfer-success.log /src dest
|
||||
#
|
||||
|
||||
LOGFILE=$1
|
||||
SRC=$2
|
||||
DEST=$3
|
||||
|
||||
if [ -z "$LOGFILE" ] || [ -z "$SRC" ] || [ -z "$DEST" ] ; then
|
||||
echo "Usage: $0 LOGFILE SRC DEST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JOBS" ] ; then
|
||||
JOBS=10
|
||||
fi
|
||||
|
||||
if [ -z "$RSYNC" ] ; then
|
||||
RSYNC=rsync
|
||||
fi
|
||||
|
||||
if ! cd $SRC ; then
|
||||
echo "cd $SRC failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rsyncjob() {
|
||||
relative_dir="./${1#$SRC}"
|
||||
|
||||
if ! $RSYNC --delete --relative -a "$relative_dir" "$DEST" ; then
|
||||
echo "rsync $1 failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$1" >> $LOGFILE
|
||||
}
|
||||
|
||||
export LOGFILE SRC DEST RSYNC
|
||||
export -f rsyncjob
|
||||
|
||||
parallel -j$JOBS --progress rsyncjob
|
|
@ -16,7 +16,7 @@ OmniAuth.config.allowed_request_methods = [:post]
|
|||
#In case of auto sign-in, the GET method is used (users don't get to click on a button)
|
||||
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
|
||||
OmniAuth.config.before_request_phase do |env|
|
||||
OmniAuth::RequestForgeryProtection.new(env).call
|
||||
OmniAuth::RequestForgeryProtection.call(env)
|
||||
end
|
||||
|
||||
if Gitlab.config.omniauth.enabled
|
||||
|
|
|
@ -500,6 +500,7 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
get :commits
|
||||
get :ci
|
||||
get :languages
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Groups
|
||||
|
||||
## List project groups
|
||||
## List groups
|
||||
|
||||
Get a list of groups. (As user: my groups, as admin: all groups)
|
||||
|
||||
|
@ -21,6 +21,70 @@ GET /groups
|
|||
|
||||
You can search for groups by name or path, see below.
|
||||
|
||||
|
||||
## List a group's projects
|
||||
|
||||
Get a list of projects in this group.
|
||||
|
||||
```
|
||||
GET /groups/:id/projects
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `archived` (optional) - if passed, limit by archived status
|
||||
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
|
||||
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
|
||||
- `search` (optional) - Return list of authorized projects according to a search criteria
|
||||
- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 4,
|
||||
"description": null,
|
||||
"default_branch": "master",
|
||||
"public": false,
|
||||
"visibility_level": 0,
|
||||
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
|
||||
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
|
||||
"web_url": "http://example.com/diaspora/diaspora-client",
|
||||
"tag_list": [
|
||||
"example",
|
||||
"disapora client"
|
||||
],
|
||||
"owner": {
|
||||
"id": 3,
|
||||
"name": "Diaspora",
|
||||
"created_at": "2013-09-30T13: 46: 02Z"
|
||||
},
|
||||
"name": "Diaspora Client",
|
||||
"name_with_namespace": "Diaspora / Diaspora Client",
|
||||
"path": "diaspora-client",
|
||||
"path_with_namespace": "diaspora/diaspora-client",
|
||||
"issues_enabled": true,
|
||||
"merge_requests_enabled": true,
|
||||
"builds_enabled": true,
|
||||
"wiki_enabled": true,
|
||||
"snippets_enabled": false,
|
||||
"created_at": "2013-09-30T13: 46: 02Z",
|
||||
"last_activity_at": "2013-09-30T13: 46: 02Z",
|
||||
"creator_id": 3,
|
||||
"namespace": {
|
||||
"created_at": "2013-09-30T13: 46: 02Z",
|
||||
"description": "",
|
||||
"id": 3,
|
||||
"name": "Diaspora",
|
||||
"owner_id": 1,
|
||||
"path": "diaspora",
|
||||
"updated_at": "2013-09-30T13: 46: 02Z"
|
||||
},
|
||||
"archived": false,
|
||||
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Details of a group
|
||||
|
||||
Get all details of a group.
|
||||
|
@ -186,7 +250,7 @@ To get more (up to 100), pass the following as an argument to the API call:
|
|||
/groups?per_page=100
|
||||
```
|
||||
|
||||
And to switch pages add:
|
||||
And to switch pages add:
|
||||
```
|
||||
/groups?per_page=100&page=2
|
||||
```
|
||||
```
|
||||
|
|
|
@ -13,6 +13,12 @@ An LDAP user who is allowed to change their email on the LDAP server can [take o
|
|||
|
||||
We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on the LDAP server.
|
||||
|
||||
If a user is deleted from the LDAP server, they will be blocked in GitLab as well.
|
||||
Users will be immediately blocked from logging in. However, there is an LDAP check
|
||||
cache time of one hour. The means users that are already logged in or are using Git
|
||||
over SSH will still be able to access GitLab for up to one hour. Manually block
|
||||
the user in the GitLab Admin area to immediately block all access.
|
||||
|
||||
## Configuring GitLab for LDAP integration
|
||||
|
||||
To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
|
||||
|
@ -192,4 +198,4 @@ Not supported by GitLab's configuration options.
|
|||
When setting `method: ssl`, the underlying authentication method used by
|
||||
`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
|
||||
the LDAP server before any LDAP-protocol data is exchanged but no validation of
|
||||
the LDAP server's SSL certificate is performed.
|
||||
the LDAP server's SSL certificate is performed.
|
||||
|
|
180
doc/operations/moving_repositories.md
Normal file
180
doc/operations/moving_repositories.md
Normal file
|
@ -0,0 +1,180 @@
|
|||
# Moving repositories managed by GitLab
|
||||
|
||||
Sometimes you need to move all repositories managed by GitLab to
|
||||
another filesystem or another server. In this document we will look
|
||||
at some of the ways you can copy all your repositories from
|
||||
`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`.
|
||||
|
||||
We will look at three scenarios: the target directory is empty, the
|
||||
target directory contains an outdated copy of the repositories, and
|
||||
how to deal with thousands of repositories.
|
||||
|
||||
**Each of the approaches we list can/will overwrite data in the
|
||||
target directory `/mnt/gitlab/repositories`. Do not mix up the
|
||||
source and the target.**
|
||||
|
||||
## Target directory is empty: use a tar pipe
|
||||
|
||||
If the target directory `/mnt/gitlab/repositories` is empty the
|
||||
simplest thing to do is to use a tar pipe. This method has low
|
||||
overhead and tar is almost always already installed on your system.
|
||||
However, it is not possible to resume an interrupted tar pipe: if
|
||||
that happens then all data must be copied again.
|
||||
|
||||
```
|
||||
# As the git user
|
||||
tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
|
||||
tar -C /mnt/gitlab/repositories -xf -
|
||||
```
|
||||
|
||||
If you want to see progress, replace `-xf` with `-xvf`.
|
||||
|
||||
### Tar pipe to another server
|
||||
|
||||
You can also use a tar pipe to copy data to another server. If your
|
||||
'git' user has SSH access to the newserver as 'git@newserver', you
|
||||
can pipe the data through SSH.
|
||||
|
||||
```
|
||||
# As the git user
|
||||
tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
|
||||
ssh git@newserver tar -C /mnt/gitlab/repositories -xf -
|
||||
```
|
||||
|
||||
If you want to compress the data before it goes over the network
|
||||
(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`.
|
||||
|
||||
## The target directory contains an outdated copy of the repositories: use rsync
|
||||
|
||||
If the target directory already contains a partial / outdated copy
|
||||
of the repositories it may be wasteful to copy all the data again
|
||||
with tar. In this scenario it is better to use rsync. This utility
|
||||
is either already installed on your system or easily installable
|
||||
via apt, yum etc.
|
||||
|
||||
```
|
||||
# As the 'git' user
|
||||
rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
|
||||
/mnt/gitlab/repositories
|
||||
```
|
||||
|
||||
The `/.` in the command above is very important, without it you can
|
||||
easily get the wrong directory structure in the target directory.
|
||||
If you want to see progress, replace `-a` with `-av`.
|
||||
|
||||
### Single rsync to another server
|
||||
|
||||
If the 'git' user on your source system has SSH access to the target
|
||||
server you can send the repositories over the network with rsync.
|
||||
|
||||
```
|
||||
# As the 'git' user
|
||||
rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
|
||||
git@newserver:/mnt/gitlab/repositories
|
||||
```
|
||||
|
||||
## Thousands of Git repositories: use one rsync per repository
|
||||
|
||||
Every time you start an rsync job it has to inspect all files in
|
||||
the source directory, all files in the target directory, and then
|
||||
decide what files to copy or not. If the source or target directory
|
||||
has many contents this startup phase of rsync can become a burden
|
||||
for your GitLab server. In cases like this you can make rsync's
|
||||
life easier by dividing its work in smaller pieces, and sync one
|
||||
repository at a time.
|
||||
|
||||
In addition to rsync we will use [GNU
|
||||
Parallel](http://www.gnu.org/software/parallel/). This utility is
|
||||
not included in GitLab so you need to install it yourself with apt
|
||||
or yum. Also note that the GitLab scripts we used below were added
|
||||
in GitLab 8.1.
|
||||
|
||||
** This process does not clean up repositories at the target location that no
|
||||
longer exist at the source. ** If you start using your GitLab instance with
|
||||
`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos`
|
||||
after switching to the new repository storage directory.
|
||||
|
||||
### Parallel rsync for all repositories known to GitLab
|
||||
|
||||
This will sync repositories with 10 rsync processes at a time. We keep
|
||||
track of progress so that the transfer can be restarted if necessary.
|
||||
|
||||
First we create a new directory, owned by 'git', to hold transfer
|
||||
logs. We assume the directory is empty before we start the transfer
|
||||
procedure, and that we are the only ones writing files in it.
|
||||
|
||||
```
|
||||
# Omnibus
|
||||
sudo mkdir /var/opt/gitlab/transfer-logs
|
||||
sudo chown git:git /var/opt/gitlab/transfer-logs
|
||||
|
||||
# Source
|
||||
sudo -u git -H mkdir /home/git/transfer-logs
|
||||
```
|
||||
|
||||
We seed the process with a list of the directories we want to copy.
|
||||
|
||||
```
|
||||
# Omnibus
|
||||
sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt'
|
||||
|
||||
# Source
|
||||
cd /home/git/gitlab
|
||||
sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt'
|
||||
```
|
||||
|
||||
Now we can start the transfer. The command below is idempotent, and
|
||||
the number of jobs done by GNU Parallel should converge to zero. If it
|
||||
does not some repositories listed in all-repos-1234.txt may have been
|
||||
deleted/renamed before they could be copied.
|
||||
|
||||
```
|
||||
# Omnibus
|
||||
sudo -u git sh -c '
|
||||
cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\
|
||||
/usr/bin/env JOBS=10 \
|
||||
/opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
|
||||
/var/opt/gitlab/transfer-logs/succes-$(date +%s).log \
|
||||
/var/opt/gitlab/git-data/repositories \
|
||||
/mnt/gitlab/repositories
|
||||
'
|
||||
|
||||
# Source
|
||||
cd /home/git/gitlab
|
||||
sudo -u git -H sh -c '
|
||||
cat /home/git/transfer-logs/* | sort | uniq -u |\
|
||||
/usr/bin/env JOBS=10 \
|
||||
bin/parallel-rsync-repos \
|
||||
/home/git/transfer-logs/succes-$(date +%s).log \
|
||||
/home/git/repositories \
|
||||
/mnt/gitlab/repositories
|
||||
`
|
||||
```
|
||||
|
||||
### Parallel rsync only for repositories with recent activity
|
||||
|
||||
Suppose you have already done one sync that started after 2015-10-1 12:00 UTC.
|
||||
Then you might only want to sync repositories that were changed via GitLab
|
||||
_after_ that time. You can use the 'SINCE' variable to tell 'rake
|
||||
gitlab:list_repos' to only print repositories with recent activity.
|
||||
|
||||
```
|
||||
# Omnibus
|
||||
sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
|
||||
sudo -u git \
|
||||
/usr/bin/env JOBS=10 \
|
||||
/opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
|
||||
succes-$(date +%s).log \
|
||||
/var/opt/gitlab/git-data/repositories \
|
||||
/mnt/gitlab/repositories
|
||||
|
||||
# Source
|
||||
cd /home/git/gitlab
|
||||
sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
|
||||
sudo -u git -H \
|
||||
/usr/bin/env JOBS=10 \
|
||||
bin/parallel-rsync-repos \
|
||||
succes-$(date +%s).log \
|
||||
/home/git/repositories \
|
||||
/mnt/gitlab/repositories
|
||||
```
|
30
doc/raketasks/list_repos.md
Normal file
30
doc/raketasks/list_repos.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Listing repository directories
|
||||
|
||||
You can print a list of all Git repositories on disk managed by
|
||||
GitLab with the following command:
|
||||
|
||||
```
|
||||
# Omnibus
|
||||
sudo gitlab-rake gitlab:list_repos
|
||||
|
||||
# Source
|
||||
cd /home/git/gitlab
|
||||
sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production
|
||||
```
|
||||
|
||||
If you only want to list projects with recent activity you can pass
|
||||
a date with the 'SINCE' environment variable. The time you specify
|
||||
is parsed by the Rails [TimeZone#parse
|
||||
function](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-i-parse).
|
||||
|
||||
```
|
||||
# Omnibus
|
||||
sudo gitlab-rake gitlab:list_repos SINCE='Sep 1 2015'
|
||||
|
||||
# Source
|
||||
cd /home/git/gitlab
|
||||
sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production SINCE='Sep 1 2015'
|
||||
```
|
||||
|
||||
Note that the projects listed are NOT sorted by activity; they use
|
||||
the default ordering of the GitLab Rails application.
|
|
@ -18,3 +18,8 @@ Feature: Project Graph
|
|||
Given project "Shop" has CI enabled
|
||||
When I visit project "Shop" CI graph page
|
||||
Then page should have CI graphs
|
||||
|
||||
@javascript
|
||||
Scenario: I should see project languages graphs
|
||||
When I visit project "Shop" languages graph page
|
||||
Then page should have languages graphs
|
||||
|
|
|
@ -14,6 +14,15 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
|
|||
visit commits_namespace_project_graph_path(project.namespace, project, "master")
|
||||
end
|
||||
|
||||
step 'I visit project "Shop" languages graph page' do
|
||||
visit languages_namespace_project_graph_path(project.namespace, project, "master")
|
||||
end
|
||||
|
||||
step 'page should have languages graphs' do
|
||||
expect(page).to have_content "Ruby 66.63 %"
|
||||
expect(page).to have_content "JavaScript 22.96 %"
|
||||
end
|
||||
|
||||
step 'page should have commits graphs' do
|
||||
expect(page).to have_content "Commit statistics for master"
|
||||
expect(page).to have_content "Commits per day of month"
|
||||
|
|
|
@ -65,6 +65,18 @@ module API
|
|||
DestroyGroupService.new(group, current_user).execute
|
||||
end
|
||||
|
||||
# Get a list of projects in this group
|
||||
#
|
||||
# Example Request:
|
||||
# GET /groups/:id/projects
|
||||
get ":id/projects" do
|
||||
group = find_group(params[:id])
|
||||
projects = group.projects
|
||||
projects = filter_projects(projects)
|
||||
projects = paginate projects
|
||||
present projects, with: Entities::Project
|
||||
end
|
||||
|
||||
# Transfer a project to the Group namespace
|
||||
#
|
||||
# Parameters:
|
||||
|
|
|
@ -37,13 +37,15 @@ module Gitlab
|
|||
|
||||
# Block user in GitLab if he/she was blocked in AD
|
||||
if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
|
||||
user.block unless user.blocked?
|
||||
user.block
|
||||
false
|
||||
else
|
||||
user.activate if user.blocked? && !ldap_config.block_auto_created_users
|
||||
true
|
||||
end
|
||||
else
|
||||
# Block the user if they no longer exist in LDAP/AD
|
||||
user.block
|
||||
false
|
||||
end
|
||||
rescue
|
||||
|
|
|
@ -1,66 +1,21 @@
|
|||
# Protects OmniAuth request phase against CSRF.
|
||||
|
||||
module OmniAuth
|
||||
# Based on ActionController::RequestForgeryProtection.
|
||||
class RequestForgeryProtection
|
||||
def initialize(env)
|
||||
@env = env
|
||||
end
|
||||
module RequestForgeryProtection
|
||||
class Controller < ActionController::Base
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
def request
|
||||
@request ||= ActionDispatch::Request.new(@env)
|
||||
end
|
||||
|
||||
def session
|
||||
request.session
|
||||
end
|
||||
|
||||
def reset_session
|
||||
request.reset_session
|
||||
end
|
||||
|
||||
def params
|
||||
request.params
|
||||
end
|
||||
|
||||
def call
|
||||
verify_authenticity_token
|
||||
end
|
||||
|
||||
def verify_authenticity_token
|
||||
if !verified_request?
|
||||
Rails.logger.warn "Can't verify CSRF token authenticity" if Rails.logger
|
||||
handle_unverified_request
|
||||
def index
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def protect_against_forgery?
|
||||
ApplicationController.allow_forgery_protection
|
||||
def self.app
|
||||
@app ||= Controller.action(:index)
|
||||
end
|
||||
|
||||
def request_forgery_protection_token
|
||||
ApplicationController.request_forgery_protection_token
|
||||
end
|
||||
|
||||
def forgery_protection_strategy
|
||||
ApplicationController.forgery_protection_strategy
|
||||
end
|
||||
|
||||
def verified_request?
|
||||
!protect_against_forgery? || request.get? || request.head? ||
|
||||
form_authenticity_token == params[request_forgery_protection_token] ||
|
||||
form_authenticity_token == request.headers['X-CSRF-Token']
|
||||
end
|
||||
|
||||
def handle_unverified_request
|
||||
forgery_protection_strategy.new(self).handle_unverified_request
|
||||
end
|
||||
|
||||
# Sets the token value for the current session.
|
||||
def form_authenticity_token
|
||||
session[:_csrf_token] ||= SecureRandom.base64(32)
|
||||
def self.call(env)
|
||||
app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
55
lib/tasks/gitlab/git.rake
Normal file
55
lib/tasks/gitlab/git.rake
Normal file
|
@ -0,0 +1,55 @@
|
|||
namespace :gitlab do
|
||||
namespace :git do
|
||||
|
||||
desc "GitLab | Git | Repack"
|
||||
task repack: :environment do
|
||||
failures = perform_git_cmd(%W(git repack -a --quiet), "Repacking repo")
|
||||
if failures.empty?
|
||||
puts "Done".green
|
||||
else
|
||||
output_failures(failures)
|
||||
end
|
||||
end
|
||||
|
||||
desc "GitLab | Git | Run garbage collection on all repos"
|
||||
task gc: :environment do
|
||||
failures = perform_git_cmd(%W(git gc --auto --quiet), "Garbage Collecting")
|
||||
if failures.empty?
|
||||
puts "Done".green
|
||||
else
|
||||
output_failures(failures)
|
||||
end
|
||||
end
|
||||
|
||||
desc "GitLab | Git | Prune all repos"
|
||||
task prune: :environment do
|
||||
failures = perform_git_cmd(%W(git prune), "Git Prune")
|
||||
if failures.empty?
|
||||
puts "Done".green
|
||||
else
|
||||
output_failures(failures)
|
||||
end
|
||||
end
|
||||
|
||||
def perform_git_cmd(cmd, message)
|
||||
puts "Starting #{message} on all repositories"
|
||||
|
||||
failures = []
|
||||
all_repos do |repo|
|
||||
if system(*cmd, chdir: repo)
|
||||
puts "Performed #{message} at #{repo}"
|
||||
else
|
||||
failures << repo
|
||||
end
|
||||
end
|
||||
|
||||
failures
|
||||
end
|
||||
|
||||
def output_failures(failures)
|
||||
puts "The following repositories reported errors:".red
|
||||
failures.each { |f| puts "- #{f}" }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -64,6 +64,8 @@ namespace :gitlab do
|
|||
|
||||
if project.persisted?
|
||||
puts " * Created #{project.name} (#{repo_path})".green
|
||||
project.update_repository_size
|
||||
project.update_commit_count
|
||||
else
|
||||
puts " * Failed trying to create #{project.name} (#{repo_path})".red
|
||||
puts " Errors: #{project.errors.messages}".red
|
||||
|
|
17
lib/tasks/gitlab/list_repos.rake
Normal file
17
lib/tasks/gitlab/list_repos.rake
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace :gitlab do
|
||||
task list_repos: :environment do
|
||||
scope = Project
|
||||
if ENV['SINCE']
|
||||
date = Time.parse(ENV['SINCE'])
|
||||
warn "Listing repositories with activity or changes since #{date}"
|
||||
project_ids = Project.where('last_activity_at > ? OR updated_at > ?', date, date).pluck(:id).sort
|
||||
namespace_ids = Namespace.where(['updated_at > ?', date]).pluck(:id).sort
|
||||
scope = scope.where('id IN (?) OR namespace_id in (?)', project_ids, namespace_ids)
|
||||
end
|
||||
scope.find_each do |project|
|
||||
base = File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace)
|
||||
puts base + '.git'
|
||||
puts base + '.wiki.git'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -118,4 +118,12 @@ namespace :gitlab do
|
|||
false
|
||||
end
|
||||
end
|
||||
|
||||
def all_repos
|
||||
IO.popen(%W(find #{Gitlab.config.gitlab_shell.repos_path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
|
||||
find.each_line do |path|
|
||||
yield path.chomp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ describe "Builds" do
|
|||
end
|
||||
|
||||
it { expect(page).to have_content 'Running' }
|
||||
it { expect(page).to have_content 'Cancel all' }
|
||||
it { expect(page).to have_content 'Cancel running' }
|
||||
it { expect(page).to have_content @build.short_sha }
|
||||
it { expect(page).to have_content @build.ref }
|
||||
it { expect(page).to have_content @build.name }
|
||||
|
@ -32,7 +32,7 @@ describe "Builds" do
|
|||
end
|
||||
|
||||
it { expect(page).to have_content 'No builds to show' }
|
||||
it { expect(page).to have_content 'Cancel all' }
|
||||
it { expect(page).to have_content 'Cancel running' }
|
||||
end
|
||||
|
||||
context "All builds" do
|
||||
|
@ -45,7 +45,7 @@ describe "Builds" do
|
|||
it { expect(page).to have_content @build.short_sha }
|
||||
it { expect(page).to have_content @build.ref }
|
||||
it { expect(page).to have_content @build.name }
|
||||
it { expect(page).to_not have_content 'Cancel all' }
|
||||
it { expect(page).to_not have_content 'Cancel running' }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -53,11 +53,11 @@ describe "Builds" do
|
|||
before do
|
||||
@build.run!
|
||||
visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
|
||||
click_link "Cancel all"
|
||||
click_link "Cancel running"
|
||||
end
|
||||
|
||||
it { expect(page).to have_content 'No builds to show' }
|
||||
it { expect(page).to_not have_content 'Cancel all' }
|
||||
it { expect(page).to_not have_content 'Cancel running' }
|
||||
end
|
||||
|
||||
describe "GET /:project/builds/:id" do
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
%ul.nav.nav-tabs.merge-request-tabs
|
||||
%li.notes-tab
|
||||
%a{href: '/foo/bar/merge_requests/1', data: {target: '#notes', action: 'notes', toggle: 'tab'}}
|
||||
%a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}}
|
||||
Discussion
|
||||
%li.commits-tab
|
||||
%a{href: '/foo/bar/merge_requests/1/commits', data: {target: '#commits', action: 'commits', toggle: 'tab'}}
|
||||
%a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}}
|
||||
Commits
|
||||
%li.diffs-tab
|
||||
%a{href: '/foo/bar/merge_requests/1/diffs', data: {target: '#diffs', action: 'diffs', toggle: 'tab'}}
|
||||
%a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}}
|
||||
Diffs
|
||||
|
||||
.tab-content
|
||||
|
|
|
@ -13,6 +13,11 @@ describe Gitlab::LDAP::Access do
|
|||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
|
||||
it 'should block user in GitLab' do
|
||||
access.allowed?
|
||||
expect(user).to be_blocked
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is found' do
|
||||
|
|
|
@ -10,6 +10,8 @@ describe API::API, api: true do
|
|||
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
|
||||
let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) }
|
||||
let!(:group2) { create(:group) }
|
||||
let!(:project1) { create(:project, namespace: group1) }
|
||||
let!(:project2) { create(:project, namespace: group2) }
|
||||
|
||||
before do
|
||||
group1.add_owner(user1)
|
||||
|
@ -67,7 +69,7 @@ describe API::API, api: true do
|
|||
it "should return any existing group" do
|
||||
get api("/groups/#{group2.id}", admin)
|
||||
expect(response.status).to eq(200)
|
||||
json_response['name'] == group2.name
|
||||
expect(json_response['name']).to eq(group2.name)
|
||||
end
|
||||
|
||||
it "should not return a non existing group" do
|
||||
|
@ -80,7 +82,7 @@ describe API::API, api: true do
|
|||
it 'should return any existing group' do
|
||||
get api("/groups/#{group1.path}", admin)
|
||||
expect(response.status).to eq(200)
|
||||
json_response['name'] == group2.name
|
||||
expect(json_response['name']).to eq(group1.name)
|
||||
end
|
||||
|
||||
it 'should not return a non existing group' do
|
||||
|
@ -95,6 +97,59 @@ describe API::API, api: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET /groups/:id/projects" do
|
||||
context "when authenticated as user" do
|
||||
it "should return the group's projects" do
|
||||
get api("/groups/#{group1.id}/projects", user1)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first['name']).to eq(project1.name)
|
||||
end
|
||||
|
||||
it "should not return a non existing group" do
|
||||
get api("/groups/1328/projects", user1)
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it "should not return a group not attached to user1" do
|
||||
get api("/groups/#{group2.id}/projects", user1)
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated as admin" do
|
||||
it "should return any existing group" do
|
||||
get api("/groups/#{group2.id}/projects", admin)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first['name']).to eq(project2.name)
|
||||
end
|
||||
|
||||
it "should not return a non existing group" do
|
||||
get api("/groups/1328/projects", admin)
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using group path in URL' do
|
||||
it 'should return any existing group' do
|
||||
get api("/groups/#{group1.path}/projects", admin)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response.first['name']).to eq(project1.name)
|
||||
end
|
||||
|
||||
it 'should not return a non existing group' do
|
||||
get api('/groups/unknown/projects', admin)
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it 'should not return a group not attached to user1' do
|
||||
get api("/groups/#{group2.path}/projects", user1)
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /groups" do
|
||||
context "when authenticated as user without group permissions" do
|
||||
it "should not create group" do
|
||||
|
|
Loading…
Reference in a new issue