Merge branch 'master' into markdown_preview_shortcut
This commit is contained in:
commit
a5079d2195
178 changed files with 2022 additions and 1487 deletions
|
@ -12,16 +12,18 @@ cache:
|
|||
|
||||
variables:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
|
||||
# retry tests only in CI environment
|
||||
RSPEC_RETRY_RETRY_COUNT: "3"
|
||||
|
||||
before_script:
|
||||
- source ./scripts/prepare_build.sh
|
||||
- ruby -v
|
||||
- which ruby
|
||||
- gem install bundler --no-ri --no-rdoc
|
||||
- retry gem install bundler --no-ri --no-rdoc
|
||||
- cp config/gitlab.yml.example config/gitlab.yml
|
||||
- touch log/application.log
|
||||
- touch log/test.log
|
||||
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
|
||||
- retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
|
||||
- RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
|
||||
|
||||
stages:
|
||||
|
|
15
CHANGELOG
15
CHANGELOG
|
@ -3,16 +3,31 @@ Please view this file on the master branch, on stable branches it's out of date.
|
|||
v 8.6.0 (unreleased)
|
||||
- Contributions to forked projects are included in calendar
|
||||
- Improve the formatting for the user page bio (Connor Shea)
|
||||
- Removed the default password from the initial admin account created during
|
||||
setup. A password can be provided during setup (see installation docs), or
|
||||
GitLab will ask the user to create a new one upon first visit.
|
||||
- Fix issue when pushing to projects ending in .wiki
|
||||
- Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
|
||||
- Don't load all of GitLab in mail_room
|
||||
- Memoize @group in Admin::GroupsController (Yatish Mehta)
|
||||
- Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
|
||||
- Strip leading and trailing spaces in URL validator (evuez)
|
||||
- Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez)
|
||||
- Return empty array instead of 404 when commit has no statuses in commit status API
|
||||
- Add support for cross-project label references
|
||||
- Update documentation to reflect Guest role not being enforced on internal projects
|
||||
- Allow search for logged out users
|
||||
- Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
|
||||
- Don't show Issues/MRs from archived projects in Groups view
|
||||
- Increase the notes polling timeout over time (Roberto Dip)
|
||||
- Add shortcut to toggle markdown preview (Florent Baldino)
|
||||
- Show labels in dashboard and group milestone views
|
||||
- Add main language of a project in the list of projects (Tiago Botelho)
|
||||
- Add ability to show archived projects on dashboard, explore and group pages
|
||||
|
||||
v 8.5.5
|
||||
- Ensure removing a project removes associated Todo entries.
|
||||
- Prevent a 500 error in Todos when author was removed.
|
||||
|
||||
v 8.5.4
|
||||
- Do not cache requests for badges (including builds badge)
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -263,7 +263,9 @@ group :development, :test do
|
|||
gem 'database_cleaner', '~> 1.4.0'
|
||||
gem 'factory_girl_rails', '~> 4.6.0'
|
||||
gem 'rspec-rails', '~> 3.3.0'
|
||||
gem 'rspec-retry'
|
||||
gem 'spinach-rails', '~> 0.2.1'
|
||||
gem 'spinach-rerun-reporter', '~> 0.0.2'
|
||||
|
||||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||
gem 'minitest', '~> 5.7.0'
|
||||
|
@ -273,7 +275,7 @@ group :development, :test do
|
|||
|
||||
gem 'capybara', '~> 2.4.0'
|
||||
gem 'capybara-screenshot', '~> 1.0.0'
|
||||
gem 'poltergeist', '~> 1.8.1'
|
||||
gem 'poltergeist', '~> 1.9.0'
|
||||
|
||||
gem 'teaspoon', '~> 1.0.0'
|
||||
gem 'teaspoon-jasmine', '~> 2.2.0'
|
||||
|
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -552,7 +552,7 @@ GEM
|
|||
parser (2.2.3.0)
|
||||
ast (>= 1.1, < 3.0)
|
||||
pg (0.18.4)
|
||||
poltergeist (1.8.1)
|
||||
poltergeist (1.9.0)
|
||||
capybara (~> 2.1)
|
||||
cliver (~> 0.3.1)
|
||||
multi_json (~> 1.0)
|
||||
|
@ -679,6 +679,8 @@ GEM
|
|||
rspec-expectations (~> 3.3.0)
|
||||
rspec-mocks (~> 3.3.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-retry (0.4.5)
|
||||
rspec-core
|
||||
rspec-support (3.3.0)
|
||||
rubocop (0.35.1)
|
||||
astrolabe (~> 1.3)
|
||||
|
@ -764,6 +766,8 @@ GEM
|
|||
capybara (>= 2.0.0)
|
||||
railties (>= 3)
|
||||
spinach (>= 0.4)
|
||||
spinach-rerun-reporter (0.0.2)
|
||||
spinach (~> 0.8)
|
||||
spring (1.6.4)
|
||||
spring-commands-rspec (1.0.4)
|
||||
spring (>= 0.9.1)
|
||||
|
@ -978,7 +982,7 @@ DEPENDENCIES
|
|||
org-ruby (~> 0.9.12)
|
||||
paranoia (~> 2.0)
|
||||
pg (~> 0.18.2)
|
||||
poltergeist (~> 1.8.1)
|
||||
poltergeist (~> 1.9.0)
|
||||
pry-rails
|
||||
quiet_assets (~> 1.0.2)
|
||||
rack-attack (~> 4.3.1)
|
||||
|
@ -999,6 +1003,7 @@ DEPENDENCIES
|
|||
rouge (~> 1.10.1)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-rails (~> 3.3.0)
|
||||
rspec-retry
|
||||
rubocop (~> 0.35.0)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
sanitize (~> 2.0)
|
||||
|
@ -1017,6 +1022,7 @@ DEPENDENCIES
|
|||
six (~> 0.2.0)
|
||||
slack-notifier (~> 1.2.0)
|
||||
spinach-rails (~> 0.2.1)
|
||||
spinach-rerun-reporter (~> 0.0.2)
|
||||
spring (~> 1.6.4)
|
||||
spring-commands-rspec (~> 1.0.4)
|
||||
spring-commands-spinach (~> 1.0.0)
|
||||
|
|
|
@ -23,7 +23,7 @@ class Dispatcher
|
|||
new Issue()
|
||||
shortcut_handler = new ShortcutsIssuable()
|
||||
new ZenMode()
|
||||
when 'projects:milestones:show'
|
||||
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
|
||||
new Milestone()
|
||||
when 'projects:milestones:new', 'projects:milestones:edit'
|
||||
new ZenMode()
|
||||
|
|
|
@ -69,7 +69,7 @@ class @Milestone
|
|||
|
||||
@bindIssuesSorting()
|
||||
@bindMergeRequestSorting()
|
||||
@bindTabsSwitching
|
||||
@bindTabsSwitching()
|
||||
|
||||
bindIssuesSorting: ->
|
||||
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
|
||||
|
@ -104,7 +104,7 @@ class @Milestone
|
|||
|
||||
).disableSelection()
|
||||
|
||||
bindMergeRequestSorting: ->
|
||||
bindTabsSwitching: ->
|
||||
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
|
||||
currentTabClass = $(e.target).data('show')
|
||||
previousTabClass = $(e.relatedTarget).data('show')
|
||||
|
@ -112,7 +112,8 @@ class @Milestone
|
|||
$(previousTabClass).hide()
|
||||
$(currentTabClass).removeClass('hidden')
|
||||
$(currentTabClass).show()
|
||||
|
||||
|
||||
bindMergeRequestSorting: ->
|
||||
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
|
||||
connectWith: ".merge_requests-sortable-list",
|
||||
dropOnEmpty: true,
|
||||
|
|
|
@ -81,8 +81,9 @@
|
|||
&::before {
|
||||
content: "\f00c";
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
top: 8px;
|
||||
left: 5px;
|
||||
top: 50%;
|
||||
margin-top: -7px;
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
font-size: inherit;
|
||||
text-rendering: auto;
|
||||
|
@ -94,8 +95,8 @@
|
|||
}
|
||||
|
||||
.dropdown-header {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
color: $dropdown-header-color;
|
||||
font-size: 13px;
|
||||
line-height: 22px;
|
||||
|
|
|
@ -19,10 +19,11 @@ li.milestone {
|
|||
width: 105px;
|
||||
}
|
||||
|
||||
.issue-row {
|
||||
.issuable-row {
|
||||
.color-label {
|
||||
border-radius: 2px;
|
||||
padding: 3px !important;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
// Issue title
|
||||
|
@ -44,20 +45,15 @@ li.milestone {
|
|||
}
|
||||
}
|
||||
|
||||
.issues-sortable-list {
|
||||
.issue-detail {
|
||||
.issues-sortable-list, .merge_requests-sortable-list {
|
||||
.issuable-detail {
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
|
||||
.issue-number{
|
||||
.issuable-number {
|
||||
color: rgba(0,0,0,0.44);
|
||||
margin-right: 5px;
|
||||
}
|
||||
.color-label {
|
||||
padding: 6px 10px;
|
||||
margin-right: 7px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
float: none;
|
||||
}
|
||||
|
|
|
@ -26,5 +26,5 @@
|
|||
margin-right: 10px;
|
||||
font-size: $gl-font-size;
|
||||
border: 1px solid;
|
||||
line-height: 40px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class Admin::GroupsController < Admin::ApplicationController
|
|||
private
|
||||
|
||||
def group
|
||||
@group = Group.find_by(path: params[:id])
|
||||
@group ||= Group.find_by(path: params[:id])
|
||||
end
|
||||
|
||||
def group_params
|
||||
|
|
15
app/controllers/concerns/filter_projects.rb
Normal file
15
app/controllers/concerns/filter_projects.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# == FilterProjects
|
||||
#
|
||||
# Controller concern to handle projects filtering
|
||||
# * by name
|
||||
# * by archived state
|
||||
#
|
||||
module FilterProjects
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def filter_projects(projects)
|
||||
projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
projects = projects.non_archived if params[:archived].blank?
|
||||
projects
|
||||
end
|
||||
end
|
|
@ -1,18 +1,15 @@
|
|||
class Dashboard::ProjectsController < Dashboard::ApplicationController
|
||||
include FilterProjects
|
||||
|
||||
before_action :event_filter
|
||||
|
||||
def index
|
||||
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = current_user.authorized_projects.sorted_by_activity
|
||||
@projects = filter_projects(@projects)
|
||||
@projects = @projects.includes(:namespace)
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
terms = params[:filter_projects]
|
||||
|
||||
if terms.present?
|
||||
@projects = @projects.search(terms)
|
||||
end
|
||||
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
|
||||
@last_push = current_user.recent_push
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -32,16 +29,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
|
|||
|
||||
def starred
|
||||
@projects = current_user.starred_projects.sorted_by_activity
|
||||
@projects = filter_projects(@projects)
|
||||
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
terms = params[:filter_projects]
|
||||
|
||||
if terms.present?
|
||||
@projects = @projects.search(terms)
|
||||
end
|
||||
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
|
||||
@last_push = current_user.recent_push
|
||||
@groups = []
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
class Explore::ProjectsController < Explore::ApplicationController
|
||||
include FilterProjects
|
||||
|
||||
def index
|
||||
@projects = ProjectsFinder.new.execute(current_user)
|
||||
@tags = @projects.tags_on(:tags)
|
||||
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
|
||||
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
|
||||
@projects = @projects.non_archived
|
||||
@projects = @projects.search(params[:search]) if params[:search].present?
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = filter_projects(@projects)
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
|
@ -22,8 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
|||
|
||||
def trending
|
||||
@projects = TrendingProjectsFinder.new.execute(current_user)
|
||||
@projects = @projects.non_archived
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = filter_projects(@projects)
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -38,7 +37,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
|||
|
||||
def starred
|
||||
@projects = ProjectsFinder.new.execute(current_user)
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = filter_projects(@projects)
|
||||
@projects = @projects.reorder('star_count DESC')
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class GroupsController < Groups::ApplicationController
|
||||
include FilterProjects
|
||||
include IssuesAction
|
||||
include MergeRequestsAction
|
||||
|
||||
|
@ -41,7 +42,8 @@ class GroupsController < Groups::ApplicationController
|
|||
def show
|
||||
@last_push = current_user.recent_push if current_user
|
||||
@projects = @projects.includes(:namespace)
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = filter_projects(@projects)
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -98,7 +100,7 @@ class GroupsController < Groups::ApplicationController
|
|||
end
|
||||
|
||||
def load_projects
|
||||
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
|
||||
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity
|
||||
end
|
||||
|
||||
# Dont allow unauthorized access to group
|
||||
|
|
|
@ -7,6 +7,9 @@ class Projects::AvatarsController < Projects::ApplicationController
|
|||
@blob = @repository.blob_at_branch('master', @project.avatar_in_git)
|
||||
if @blob
|
||||
headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
return if cached_blob?
|
||||
|
||||
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
|
||||
headers['Content-Disposition'] = 'inline'
|
||||
headers['Content-Type'] = safe_content_type(@blob)
|
||||
|
|
|
@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
@issues = @milestone.issues
|
||||
@users = @milestone.participants.uniq
|
||||
@merge_requests = @milestone.merge_requests
|
||||
@labels = @milestone.labels
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -13,6 +13,8 @@ class Projects::RawController < Projects::ApplicationController
|
|||
if @blob
|
||||
headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
return if cached_blob?
|
||||
|
||||
if @blob.lfs_pointer?
|
||||
send_lfs_object
|
||||
else
|
||||
|
|
|
@ -263,11 +263,9 @@ class IssuableFinder
|
|||
def by_label(items)
|
||||
if labels?
|
||||
if filter_by_no_label?
|
||||
items = items.
|
||||
joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
|
||||
where(label_links: { id: nil })
|
||||
items = items.without_label
|
||||
else
|
||||
items = items.joins(:labels).where(labels: { title: label_names })
|
||||
items = items.with_label(label_names)
|
||||
|
||||
if projects
|
||||
items = items.where(labels: { project_id: projects })
|
||||
|
|
|
@ -4,7 +4,7 @@ class SnippetsFinder
|
|||
|
||||
case filter
|
||||
when :all then
|
||||
snippets(current_user).fresh.non_expired
|
||||
snippets(current_user).fresh
|
||||
when :by_user then
|
||||
by_user(current_user, params[:user], params[:scope])
|
||||
when :by_project
|
||||
|
@ -27,7 +27,7 @@ class SnippetsFinder
|
|||
end
|
||||
|
||||
def by_user(current_user, user, scope)
|
||||
snippets = user.snippets.fresh.non_expired
|
||||
snippets = user.snippets.fresh
|
||||
|
||||
return snippets.are_public unless current_user
|
||||
|
||||
|
@ -48,7 +48,7 @@ class SnippetsFinder
|
|||
end
|
||||
|
||||
def by_project(current_user, project)
|
||||
snippets = project.snippets.fresh.non_expired
|
||||
snippets = project.snippets.fresh
|
||||
|
||||
if current_user
|
||||
if project.team.member?(current_user.id)
|
||||
|
|
|
@ -72,7 +72,7 @@ module ApplicationHelper
|
|||
if user_or_email.is_a?(User)
|
||||
user = user_or_email
|
||||
else
|
||||
user = User.find_by(email: user_or_email.downcase)
|
||||
user = User.find_by(email: user_or_email.try(:downcase))
|
||||
end
|
||||
|
||||
if user
|
||||
|
|
|
@ -152,4 +152,25 @@ module BlobHelper
|
|||
'application/octet-stream'
|
||||
end
|
||||
end
|
||||
|
||||
def cached_blob?
|
||||
stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
|
||||
|
||||
# Because we are opionated we set the cache headers ourselves.
|
||||
response.cache_control[:public] = @project.public?
|
||||
|
||||
if @ref && @commit && @ref == @commit.id
|
||||
# This is a link to a commit by its commit SHA. That means that the blob
|
||||
# is immutable. The only reason to invalidate the cache is if the commit
|
||||
# was deleted or if the user lost access to the repository.
|
||||
response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
|
||||
else
|
||||
# A branch or tag points at this blob. That means that the expected blob
|
||||
# value may change over time.
|
||||
response.cache_control[:max_age] = Blob::CACHE_TIME
|
||||
end
|
||||
|
||||
response.etag = @blob.id
|
||||
!stale
|
||||
end
|
||||
end
|
||||
|
|
|
@ -211,4 +211,15 @@ module CommitsHelper
|
|||
def clean(string)
|
||||
Sanitize.clean(string, remove_contents: true)
|
||||
end
|
||||
|
||||
def limited_commits(commits)
|
||||
if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
|
||||
[
|
||||
commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE),
|
||||
commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE
|
||||
]
|
||||
else
|
||||
[commits, 0]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module ExploreHelper
|
||||
def explore_projects_filter_path(options={})
|
||||
def filter_projects_path(options={})
|
||||
exist_opts = {
|
||||
sort: params[:sort],
|
||||
scope: params[:scope],
|
||||
|
@ -9,15 +9,7 @@ module ExploreHelper
|
|||
}
|
||||
|
||||
options = exist_opts.merge(options)
|
||||
|
||||
path = if explore_controller?
|
||||
explore_projects_path
|
||||
elsif current_action?(:starred)
|
||||
starred_dashboard_projects_path
|
||||
else
|
||||
dashboard_projects_path
|
||||
end
|
||||
|
||||
path = request.path
|
||||
path << "?#{options.to_param}"
|
||||
path
|
||||
end
|
||||
|
|
|
@ -50,19 +50,25 @@ module LabelsHelper
|
|||
@project.labels.pluck(:title)
|
||||
end
|
||||
|
||||
def render_colored_label(label)
|
||||
def render_colored_label(label, label_suffix = '')
|
||||
label_color = label.color || Label::DEFAULT_COLOR
|
||||
text_color = text_color_for_bg(label_color)
|
||||
|
||||
# Intentionally not using content_tag here so that this method can be called
|
||||
# by LabelReferenceFilter
|
||||
span = %(<span class="label color-label") +
|
||||
%( style="background-color: #{label_color}; color: #{text_color}">) +
|
||||
escape_once(label.name) + '</span>'
|
||||
%(style="background-color: #{label_color}; color: #{text_color}">) +
|
||||
%(#{escape_once(label.name)}#{label_suffix}</span>)
|
||||
|
||||
span.html_safe
|
||||
end
|
||||
|
||||
def render_colored_cross_project_label(label)
|
||||
label_suffix = label.project.name_with_namespace
|
||||
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
|
||||
render_colored_label(label, label_suffix)
|
||||
end
|
||||
|
||||
def suggested_colors
|
||||
[
|
||||
'#0033CC',
|
||||
|
@ -119,5 +125,6 @@ module LabelsHelper
|
|||
end
|
||||
|
||||
# Required for Banzai::Filter::LabelReferenceFilter
|
||||
module_function :render_colored_label, :text_color_for_bg, :escape_once
|
||||
module_function :render_colored_label, :render_colored_cross_project_label,
|
||||
:text_color_for_bg, :escape_once
|
||||
end
|
||||
|
|
|
@ -9,6 +9,32 @@ module MilestonesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def milestones_label_path(opts = {})
|
||||
if @project
|
||||
namespace_project_issues_path(@project.namespace, @project, opts)
|
||||
elsif @group
|
||||
issues_group_path(@group, opts)
|
||||
else
|
||||
issues_dashboard_path(opts)
|
||||
end
|
||||
end
|
||||
|
||||
def milestones_browse_issuables_path(milestone, type:)
|
||||
opts = { milestone_title: milestone.title }
|
||||
|
||||
if @project
|
||||
polymorphic_path([@project.namespace.becomes(Namespace), @project, type], opts)
|
||||
elsif @group
|
||||
polymorphic_url([type, @group], opts)
|
||||
else
|
||||
polymorphic_url([type, :dashboard], opts)
|
||||
end
|
||||
end
|
||||
|
||||
def milestone_issues_by_label_count(milestone, label, state:)
|
||||
milestone.issues.with_label(label.title).send(state).size
|
||||
end
|
||||
|
||||
def milestone_progress_bar(milestone)
|
||||
options = {
|
||||
class: 'progress-bar progress-bar-success',
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
module SnippetsHelper
|
||||
def lifetime_select_options
|
||||
options = [
|
||||
['forever', nil],
|
||||
['1 day', "#{Date.current + 1.day}"],
|
||||
['1 week', "#{Date.current + 1.week}"],
|
||||
['1 month', "#{Date.current + 1.month}"]
|
||||
]
|
||||
options_for_select(options)
|
||||
end
|
||||
|
||||
def reliable_snippet_path(snippet)
|
||||
if snippet.project_id?
|
||||
namespace_project_snippet_path(snippet.project.namespace,
|
||||
|
|
|
@ -16,6 +16,16 @@ module SortingHelper
|
|||
}
|
||||
end
|
||||
|
||||
def projects_sort_options_hash
|
||||
{
|
||||
sort_value_name => sort_title_name,
|
||||
sort_value_recently_updated => sort_title_recently_updated,
|
||||
sort_value_oldest_updated => sort_title_oldest_updated,
|
||||
sort_value_recently_created => sort_title_recently_created,
|
||||
sort_value_oldest_created => sort_title_oldest_created,
|
||||
}
|
||||
end
|
||||
|
||||
def sort_title_oldest_updated
|
||||
'Oldest updated'
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ class Ability
|
|||
when CommitStatus then commit_status_abilities(user, subject)
|
||||
when Project then project_abilities(user, subject)
|
||||
when Issue then issue_abilities(user, subject)
|
||||
when ExternalIssue then external_issue_abilities(user, subject)
|
||||
when Note then note_abilities(user, subject)
|
||||
when ProjectSnippet then project_snippet_abilities(user, subject)
|
||||
when PersonalSnippet then personal_snippet_abilities(user, subject)
|
||||
|
@ -424,6 +425,10 @@ class Ability
|
|||
end
|
||||
end
|
||||
|
||||
def external_issue_abilities(user, subject)
|
||||
project_abilities(user, subject.project)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def named_abilities(name)
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
|
||||
class Blob < SimpleDelegator
|
||||
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
|
||||
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
|
||||
|
||||
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
|
||||
#
|
||||
# This method prevents the decorated object from evaluating to "truthy" when
|
||||
|
|
|
@ -29,12 +29,15 @@ module Issuable
|
|||
scope :assigned, -> { where("assignee_id IS NOT NULL") }
|
||||
scope :unassigned, -> { where("assignee_id IS NULL") }
|
||||
scope :of_projects, ->(ids) { where(project_id: ids) }
|
||||
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
|
||||
scope :opened, -> { with_state(:opened, :reopened) }
|
||||
scope :only_opened, -> { with_state(:opened) }
|
||||
scope :only_reopened, -> { with_state(:reopened) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
|
||||
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
|
||||
scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
|
||||
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
|
||||
|
||||
scope :join_project, -> { joins(:project) }
|
||||
scope :references_project, -> { references(:project) }
|
||||
|
|
25
app/models/concerns/milestoneish.rb
Normal file
25
app/models/concerns/milestoneish.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
module Milestoneish
|
||||
def closed_items_count
|
||||
issues.closed.size + merge_requests.closed_and_merged.size
|
||||
end
|
||||
|
||||
def total_items_count
|
||||
issues.size + merge_requests.size
|
||||
end
|
||||
|
||||
def complete?
|
||||
total_items_count == closed_items_count
|
||||
end
|
||||
|
||||
def percent_complete
|
||||
((closed_items_count * 100) / total_items_count).abs
|
||||
rescue ZeroDivisionError
|
||||
0
|
||||
end
|
||||
|
||||
def remaining_days
|
||||
return 0 if !due_date || expired?
|
||||
|
||||
(due_date - Date.today).to_i
|
||||
end
|
||||
end
|
|
@ -2,16 +2,19 @@ class GlobalLabel
|
|||
attr_accessor :title, :labels
|
||||
alias_attribute :name, :title
|
||||
|
||||
delegate :color, :description, to: :@first_label
|
||||
|
||||
def self.build_collection(labels)
|
||||
labels = labels.group_by(&:title)
|
||||
|
||||
labels.map do |title, label|
|
||||
new(title, label)
|
||||
labels.map do |title, labels|
|
||||
new(title, labels)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(title, labels)
|
||||
@title = title
|
||||
@labels = labels
|
||||
@first_label = labels.find { |lbl| lbl.description.present? } || labels.first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class GlobalMilestone
|
||||
include Milestoneish
|
||||
|
||||
attr_accessor :title, :milestones
|
||||
alias_attribute :name, :title
|
||||
|
||||
|
@ -28,33 +30,7 @@ class GlobalMilestone
|
|||
end
|
||||
|
||||
def projects
|
||||
milestones.map { |milestone| milestone.project }
|
||||
end
|
||||
|
||||
def issue_count
|
||||
milestones.map { |milestone| milestone.issues.count }.sum
|
||||
end
|
||||
|
||||
def merge_requests_count
|
||||
milestones.map { |milestone| milestone.merge_requests.count }.sum
|
||||
end
|
||||
|
||||
def open_items_count
|
||||
milestones.map { |milestone| milestone.open_items_count }.sum
|
||||
end
|
||||
|
||||
def closed_items_count
|
||||
milestones.map { |milestone| milestone.closed_items_count }.sum
|
||||
end
|
||||
|
||||
def total_items_count
|
||||
milestones.map { |milestone| milestone.total_items_count }.sum
|
||||
end
|
||||
|
||||
def percent_complete
|
||||
((closed_items_count * 100) / total_items_count).abs
|
||||
rescue ZeroDivisionError
|
||||
0
|
||||
@projects ||= Project.for_milestones(milestones.map(&:id))
|
||||
end
|
||||
|
||||
def state
|
||||
|
@ -76,35 +52,20 @@ class GlobalMilestone
|
|||
end
|
||||
|
||||
def issues
|
||||
@issues ||= milestones.map(&:issues).flatten.group_by(&:state)
|
||||
@issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
|
||||
end
|
||||
|
||||
def merge_requests
|
||||
@merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
|
||||
@merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
|
||||
end
|
||||
|
||||
def participants
|
||||
@participants ||= milestones.map(&:participants).flatten.compact.uniq
|
||||
end
|
||||
|
||||
def opened_issues
|
||||
issues.values_at("opened", "reopened").compact.flatten
|
||||
end
|
||||
|
||||
def closed_issues
|
||||
issues['closed']
|
||||
end
|
||||
|
||||
def opened_merge_requests
|
||||
merge_requests.values_at("opened", "reopened").compact.flatten
|
||||
end
|
||||
|
||||
def closed_merge_requests
|
||||
merge_requests.values_at("closed", "merged", "locked").compact.flatten
|
||||
end
|
||||
|
||||
def complete?
|
||||
total_items_count == closed_items_count
|
||||
def labels
|
||||
@labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
|
||||
.sort_by!(&:title)
|
||||
end
|
||||
|
||||
def due_date
|
||||
|
|
|
@ -48,10 +48,15 @@ class Label < ActiveRecord::Base
|
|||
'~'
|
||||
end
|
||||
|
||||
##
|
||||
# Pattern used to extract label references from text
|
||||
#
|
||||
# This pattern supports cross-project references.
|
||||
#
|
||||
def self.reference_pattern
|
||||
%r{
|
||||
#{reference_prefix}
|
||||
(#{Project.reference_pattern})?
|
||||
#{Regexp.escape(reference_prefix)}
|
||||
(?:
|
||||
(?<label_id>\d+) | # Integer-based label ID, or
|
||||
(?<label_name>
|
||||
|
@ -62,24 +67,31 @@ class Label < ActiveRecord::Base
|
|||
}x
|
||||
end
|
||||
|
||||
def self.link_reference_pattern
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the String necessary to reference this Label in Markdown
|
||||
#
|
||||
# format - Symbol format to use (default: :id, optional: :name)
|
||||
#
|
||||
# Note that its argument differs from other objects implementing Referable. If
|
||||
# a non-Symbol argument is given (such as a Project), it will default to :id.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# Label.first.to_reference # => "~1"
|
||||
# Label.first.to_reference(:name) # => "~\"bug\""
|
||||
# Label.first.to_reference # => "~1"
|
||||
# Label.first.to_reference(format: :name) # => "~\"bug\""
|
||||
# Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
|
||||
#
|
||||
# Returns a String
|
||||
def to_reference(format = :id)
|
||||
if format == :name && !name.include?('"')
|
||||
%(#{self.class.reference_prefix}"#{name}")
|
||||
#
|
||||
def to_reference(from_project = nil, format: :id)
|
||||
format_reference = label_format_reference(format)
|
||||
reference = "#{self.class.reference_prefix}#{format_reference}"
|
||||
|
||||
if cross_project_reference?(from_project)
|
||||
project.to_reference + reference
|
||||
else
|
||||
"#{self.class.reference_prefix}#{id}"
|
||||
reference
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -98,4 +110,16 @@ class Label < ActiveRecord::Base
|
|||
def template?
|
||||
template
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def label_format_reference(format = :id)
|
||||
raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
|
||||
|
||||
if format == :name && !name.include?('"')
|
||||
%("#{name}")
|
||||
else
|
||||
id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -137,9 +137,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
|
||||
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
|
||||
scope :of_projects, ->(ids) { where(target_project_id: ids) }
|
||||
scope :opened, -> { with_states(:opened, :reopened) }
|
||||
scope :merged, -> { with_state(:merged) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
scope :closed_and_merged, -> { with_states(:closed, :merged) }
|
||||
|
||||
scope :join_project, -> { joins(:target_project) }
|
||||
|
@ -537,6 +535,29 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def diverged_commits_count
|
||||
cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")
|
||||
|
||||
if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha
|
||||
cache = {
|
||||
source_sha: source_sha,
|
||||
target_sha: target_sha,
|
||||
diverged_commits_count: compute_diverged_commits_count
|
||||
}
|
||||
Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
|
||||
end
|
||||
|
||||
cache[:diverged_commits_count]
|
||||
end
|
||||
|
||||
def compute_diverged_commits_count
|
||||
Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
|
||||
end
|
||||
|
||||
def diverged_from_target_branch?
|
||||
diverged_commits_count > 0
|
||||
end
|
||||
|
||||
def ci_commit
|
||||
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
include Sortable
|
||||
|
||||
# Prevent store of diff if commits amount more then 500
|
||||
COMMITS_SAFE_SIZE = 500
|
||||
COMMITS_SAFE_SIZE = 100
|
||||
|
||||
belongs_to :merge_request
|
||||
|
||||
|
|
|
@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base
|
|||
include Sortable
|
||||
include Referable
|
||||
include StripAttribute
|
||||
include Milestoneish
|
||||
|
||||
belongs_to :project
|
||||
has_many :issues
|
||||
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
|
||||
has_many :merge_requests
|
||||
has_many :participants, through: :issues, source: :assignee
|
||||
has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
|
||||
|
||||
scope :active, -> { with_state(:active) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
|
@ -92,30 +93,6 @@ class Milestone < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def open_items_count
|
||||
self.issues.opened.count + self.merge_requests.opened.count
|
||||
end
|
||||
|
||||
def closed_items_count
|
||||
self.issues.closed.count + self.merge_requests.closed_and_merged.count
|
||||
end
|
||||
|
||||
def total_items_count
|
||||
self.issues.count + self.merge_requests.count
|
||||
end
|
||||
|
||||
def percent_complete
|
||||
((closed_items_count * 100) / total_items_count).abs
|
||||
rescue ZeroDivisionError
|
||||
0
|
||||
end
|
||||
|
||||
def remaining_days
|
||||
return 0 if !due_date || expired?
|
||||
|
||||
(due_date - Date.today).to_i
|
||||
end
|
||||
|
||||
def expires_at
|
||||
if due_date
|
||||
if due_date.past?
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# file_name :string(255)
|
||||
# expires_at :datetime
|
||||
# type :string(255)
|
||||
# visibility_level :integer default(0), not null
|
||||
#
|
||||
|
|
|
@ -151,6 +151,7 @@ class Project < ActiveRecord::Base
|
|||
has_many :releases, dependent: :destroy
|
||||
has_many :lfs_objects_projects, dependent: :destroy
|
||||
has_many :lfs_objects, through: :lfs_objects_projects
|
||||
has_many :todos, dependent: :destroy
|
||||
|
||||
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
|
||||
|
||||
|
@ -215,6 +216,7 @@ class Project < ActiveRecord::Base
|
|||
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
|
||||
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
|
||||
scope :non_archived, -> { where(archived: false) }
|
||||
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
|
||||
|
||||
state_machine :import_status, initial: :none do
|
||||
event :import_start do
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# file_name :string(255)
|
||||
# expires_at :datetime
|
||||
# type :string(255)
|
||||
# visibility_level :integer default(0), not null
|
||||
#
|
||||
|
@ -23,6 +22,4 @@ class ProjectSnippet < Snippet
|
|||
|
||||
# Scopes
|
||||
scope :fresh, -> { order("created_at DESC") }
|
||||
scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
|
||||
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
|
||||
end
|
||||
|
|
|
@ -133,18 +133,18 @@ class Repository
|
|||
rugged.branches.create(branch_name, target)
|
||||
end
|
||||
|
||||
expire_branches_cache
|
||||
after_create_branch
|
||||
find_branch(branch_name)
|
||||
end
|
||||
|
||||
def add_tag(tag_name, ref, message = nil)
|
||||
expire_tags_cache
|
||||
before_push_tag
|
||||
|
||||
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
|
||||
end
|
||||
|
||||
def rm_branch(user, branch_name)
|
||||
expire_branches_cache
|
||||
before_remove_branch
|
||||
|
||||
branch = find_branch(branch_name)
|
||||
oldrev = branch.try(:target)
|
||||
|
@ -155,12 +155,12 @@ class Repository
|
|||
rugged.branches.delete(branch_name)
|
||||
end
|
||||
|
||||
expire_branches_cache
|
||||
after_remove_branch
|
||||
true
|
||||
end
|
||||
|
||||
def rm_tag(tag_name)
|
||||
expire_tags_cache
|
||||
before_remove_tag
|
||||
|
||||
gitlab_shell.rm_tag(path_with_namespace, tag_name)
|
||||
end
|
||||
|
@ -183,6 +183,14 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
def branch_count
|
||||
@branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
|
||||
end
|
||||
|
||||
def tag_count
|
||||
@tag_count ||= cache.fetch(:tag_count) { raw_repository.rugged.tags.count }
|
||||
end
|
||||
|
||||
# Return repo size in megabytes
|
||||
# Cached in redis
|
||||
def size
|
||||
|
@ -278,6 +286,16 @@ class Repository
|
|||
@has_visible_content = nil
|
||||
end
|
||||
|
||||
def expire_branch_count_cache
|
||||
cache.expire(:branch_count)
|
||||
@branch_count = nil
|
||||
end
|
||||
|
||||
def expire_tag_count_cache
|
||||
cache.expire(:tag_count)
|
||||
@tag_count = nil
|
||||
end
|
||||
|
||||
def rebuild_cache
|
||||
cache_keys.each do |key|
|
||||
cache.expire(key)
|
||||
|
@ -313,9 +331,17 @@ class Repository
|
|||
expire_root_ref_cache
|
||||
end
|
||||
|
||||
# Runs code before creating a new tag.
|
||||
def before_create_tag
|
||||
# Runs code before pushing (= creating or removing) a tag.
|
||||
def before_push_tag
|
||||
expire_cache
|
||||
expire_tags_cache
|
||||
expire_tag_count_cache
|
||||
end
|
||||
|
||||
# Runs code before removing a tag.
|
||||
def before_remove_tag
|
||||
expire_tags_cache
|
||||
expire_tag_count_cache
|
||||
end
|
||||
|
||||
# Runs code after a repository has been forked/imported.
|
||||
|
@ -330,12 +356,21 @@ class Repository
|
|||
|
||||
# Runs code after a new branch has been created.
|
||||
def after_create_branch
|
||||
expire_branches_cache
|
||||
expire_has_visible_content_cache
|
||||
expire_branch_count_cache
|
||||
end
|
||||
|
||||
# Runs code before removing an existing branch.
|
||||
def before_remove_branch
|
||||
expire_branches_cache
|
||||
end
|
||||
|
||||
# Runs code after an existing branch has been removed.
|
||||
def after_remove_branch
|
||||
expire_has_visible_content_cache
|
||||
expire_branch_count_cache
|
||||
expire_branches_cache
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
|
@ -812,6 +847,12 @@ class Repository
|
|||
raw_repository.ls_files(actual_ref)
|
||||
end
|
||||
|
||||
def main_language
|
||||
unless empty?
|
||||
Linguist::Repository.new(rugged, rugged.head.target_id).language
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# file_name :string(255)
|
||||
# expires_at :datetime
|
||||
# type :string(255)
|
||||
# visibility_level :integer default(0), not null
|
||||
#
|
||||
|
@ -46,8 +45,6 @@ class Snippet < ActiveRecord::Base
|
|||
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
|
||||
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
|
||||
scope :fresh, -> { order("created_at DESC") }
|
||||
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
|
||||
scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
|
||||
|
||||
participant :author, :notes
|
||||
|
||||
|
@ -111,10 +108,6 @@ class Snippet < ActiveRecord::Base
|
|||
nil
|
||||
end
|
||||
|
||||
def expired?
|
||||
expires_at && expires_at < Time.current
|
||||
end
|
||||
|
||||
def visibility_level_field
|
||||
visibility_level
|
||||
end
|
||||
|
|
|
@ -14,6 +14,7 @@ class GitPushService < BaseService
|
|||
# 3. Recognizes cross-references from commit messages
|
||||
# 4. Executes the project's web hooks
|
||||
# 5. Executes the project's services
|
||||
# 6. Checks if the project's main language has changed
|
||||
#
|
||||
def execute
|
||||
@project.repository.after_push_commit(branch_name)
|
||||
|
@ -42,11 +43,24 @@ class GitPushService < BaseService
|
|||
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
|
||||
process_commit_messages
|
||||
end
|
||||
# Checks if the main language has changed in the project and if so
|
||||
# it updates it accordingly
|
||||
update_main_language
|
||||
# Update merge requests that may be affected by this push. A new branch
|
||||
# could cause the last commit of a merge request to change.
|
||||
update_merge_requests
|
||||
end
|
||||
|
||||
def update_main_language
|
||||
current_language = @project.repository.main_language
|
||||
|
||||
unless current_language == @project.main_language
|
||||
return @project.update_attributes(main_language: current_language)
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def update_merge_requests
|
||||
|
@ -96,7 +110,9 @@ class GitPushService < BaseService
|
|||
# a different branch.
|
||||
closed_issues = commit.closes_issues(current_user)
|
||||
closed_issues.each do |issue|
|
||||
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
|
||||
if can?(current_user, :update_issue, issue)
|
||||
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ class GitTagPushService
|
|||
attr_accessor :project, :user, :push_data
|
||||
|
||||
def execute(project, user, oldrev, newrev, ref)
|
||||
project.repository.before_create_tag
|
||||
project.repository.before_push_tag
|
||||
|
||||
@project, @user = project, user
|
||||
@push_data = build_push_data(oldrev, newrev, ref)
|
||||
|
|
|
@ -21,7 +21,9 @@ module MergeRequests
|
|||
|
||||
closed_issues = merge_request.closes_issues(current_user)
|
||||
closed_issues.each do |issue|
|
||||
Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
|
||||
if can?(current_user, :update_issue, issue)
|
||||
Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class SystemNoteService
|
|||
def self.change_label(noteable, project, author, added_labels, removed_labels)
|
||||
labels_count = added_labels.count + removed_labels.count
|
||||
|
||||
references = ->(label) { label.to_reference(:id) }
|
||||
references = ->(label) { label.to_reference(format: :id) }
|
||||
added_labels = added_labels.map(&references).join(' ')
|
||||
removed_labels = removed_labels.map(&references).join(' ')
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
.nav-controls
|
||||
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
|
||||
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
|
||||
= render 'explore/projects/dropdown'
|
||||
= render 'shared/projects/dropdown'
|
||||
- if current_user.can_create_project?
|
||||
= link_to new_project_path, class: 'btn btn-new' do
|
||||
= icon('plus')
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
|
||||
%span.milestone-row
|
||||
- project = issue.project
|
||||
%strong #{project.name_with_namespace} ·
|
||||
= link_to [project.namespace.becomes(Namespace), project, issue] do
|
||||
%span.cgray ##{issue.iid}
|
||||
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
|
||||
.pull-right.assignee-icon
|
||||
- if issue.assignee
|
||||
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
|
|
@ -1,6 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading= title
|
||||
%ul{ class: "well-list issues-sortable-list" }
|
||||
- if issues
|
||||
- issues.each do |issue|
|
||||
= render 'issue', issue: issue
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
|
||||
%span.milestone-row
|
||||
- project = merge_request.project
|
||||
%strong #{project.name_with_namespace} ·
|
||||
= link_to [project.namespace.becomes(Namespace), project, merge_request] do
|
||||
%span.cgray ##{merge_request.iid}
|
||||
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
|
||||
.pull-right.assignee-icon
|
||||
- if merge_request.assignee
|
||||
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
|
|
@ -1,6 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading= title
|
||||
%ul{ class: "well-list merge_requests-sortable-list" }
|
||||
- if merge_requests
|
||||
- merge_requests.each do |merge_request|
|
||||
= render 'merge_request', merge_request: merge_request
|
|
@ -1,25 +1,6 @@
|
|||
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
|
||||
.row
|
||||
.col-sm-6
|
||||
%strong
|
||||
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
|
||||
.col-sm-6
|
||||
.pull-right.light #{milestone.percent_complete}% complete
|
||||
.row
|
||||
.col-sm-6
|
||||
= link_to issues_dashboard_path(milestone_title: milestone.title) do
|
||||
= pluralize milestone.issue_count, 'Issue'
|
||||
·
|
||||
= link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
|
||||
= pluralize milestone.merge_requests_count, 'Merge Request'
|
||||
.col-sm-6
|
||||
= milestone_progress_bar(milestone)
|
||||
.row
|
||||
.col-sm-6
|
||||
.expiration
|
||||
= render 'shared/milestone_expired', milestone: milestone
|
||||
.projects
|
||||
- milestone.milestones.each do |milestone|
|
||||
= link_to milestone_path(milestone) do
|
||||
%span.label.label-gray
|
||||
= milestone.project.name_with_namespace
|
||||
= render 'shared/milestones/milestone',
|
||||
milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title),
|
||||
issues_path: issues_dashboard_path(milestone_title: milestone.title),
|
||||
merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
|
||||
milestone: milestone,
|
||||
dashboard: true
|
||||
|
|
|
@ -1,105 +1,5 @@
|
|||
- page_title @milestone.title, "Milestones"
|
||||
- header_title "Milestones", dashboard_milestones_path
|
||||
|
||||
.detail-page-header
|
||||
.status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
|
||||
- if @milestone.closed?
|
||||
Closed
|
||||
- else
|
||||
Open
|
||||
%span.identifier
|
||||
Milestone #{@milestone.title}
|
||||
|
||||
.detail-page-description.gray-content-block.second-block
|
||||
%h2.title
|
||||
= markdown escape_once(@milestone.title), pipeline: :single_line
|
||||
|
||||
- if @milestone.complete? && @milestone.active?
|
||||
.alert.alert-success.prepend-top-default
|
||||
%span All issues for this milestone are closed. Navigate to the project to close the milestone.
|
||||
|
||||
.table-holder
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th Project
|
||||
%th Open issues
|
||||
%th State
|
||||
%th Due date
|
||||
- @milestone.milestones.each do |milestone|
|
||||
%tr
|
||||
%td
|
||||
= link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
|
||||
%td
|
||||
= milestone.issues.opened.count
|
||||
%td
|
||||
- if milestone.closed?
|
||||
Closed
|
||||
- else
|
||||
Open
|
||||
%td
|
||||
= milestone.expires_at
|
||||
|
||||
.context
|
||||
%p.lead
|
||||
Progress:
|
||||
#{@milestone.closed_items_count} closed
|
||||
–
|
||||
#{@milestone.open_items_count} open
|
||||
= milestone_progress_bar(@milestone)
|
||||
|
||||
%ul.nav-links.no-top.no-bottom
|
||||
%li.active
|
||||
= link_to '#tab-issues', 'data-toggle' => 'tab' do
|
||||
Issues
|
||||
%span.badge= @milestone.issue_count
|
||||
%li
|
||||
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
|
||||
Merge Requests
|
||||
%span.badge= @milestone.merge_requests_count
|
||||
%li
|
||||
= link_to '#tab-participants', 'data-toggle' => 'tab' do
|
||||
Participants
|
||||
%span.badge= @milestone.participants.count
|
||||
|
||||
.tab-content
|
||||
.tab-pane.active#tab-issues
|
||||
.gray-content-block.middle-block
|
||||
.pull-right
|
||||
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
|
||||
|
||||
.oneline
|
||||
All issues in this milestone
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-md-6
|
||||
= render 'issues', title: "Open", issues: @milestone.opened_issues
|
||||
.col-md-6
|
||||
= render 'issues', title: "Closed", issues: @milestone.closed_issues
|
||||
|
||||
.tab-pane#tab-merge-requests
|
||||
.gray-content-block.middle-block
|
||||
.pull-right
|
||||
= link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
|
||||
|
||||
.oneline
|
||||
All merge requests in this milestone
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-md-6
|
||||
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
|
||||
.col-md-6
|
||||
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
|
||||
|
||||
.tab-pane#tab-participants
|
||||
.gray-content-block.middle-block
|
||||
.oneline
|
||||
All participants to this milestone
|
||||
%ul.bordered-list
|
||||
- @milestone.participants.each do |user|
|
||||
%li
|
||||
= link_to user, title: user.name, class: "darken" do
|
||||
= image_tag avatar_icon(user, 32), class: "avatar s32"
|
||||
%strong= truncate(user.name, lenght: 40)
|
||||
%br
|
||||
%small.cgray= user.username
|
||||
= render 'shared/milestones/top', milestone: @milestone
|
||||
= render 'shared/milestones/summary', milestone: @milestone
|
||||
= render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
.todo-title
|
||||
%span.author-name
|
||||
= link_to_author todo
|
||||
- if todo.author
|
||||
= link_to_author(todo)
|
||||
- else
|
||||
(removed)
|
||||
%span.todo-label
|
||||
= todo_action_name(todo)
|
||||
= todo_target_link(todo)
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
.dropdown.inline
|
||||
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_recently_updated
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
%li
|
||||
= link_to explore_projects_filter_path(sort: sort_value_name) do
|
||||
= sort_title_name
|
||||
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do
|
||||
= sort_title_recently_created
|
||||
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
|
||||
= sort_title_oldest_created
|
||||
= link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
|
||||
= sort_title_recently_updated
|
||||
= link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
|
||||
= sort_title_oldest_updated
|
|
@ -10,11 +10,11 @@
|
|||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to explore_projects_filter_path(visibility_level: nil) do
|
||||
= link_to filter_projects_path(visibility_level: nil) do
|
||||
Any
|
||||
- Gitlab::VisibilityLevel.values.each do |level|
|
||||
%li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
|
||||
= link_to explore_projects_filter_path(visibility_level: level) do
|
||||
= link_to filter_projects_path(visibility_level: level) do
|
||||
= visibility_level_icon(level)
|
||||
= visibility_level_label(level)
|
||||
|
||||
|
@ -30,11 +30,11 @@
|
|||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to explore_projects_filter_path(tag: nil) do
|
||||
= link_to filter_projects_path(tag: nil) do
|
||||
Any
|
||||
|
||||
- @tags.each do |tag|
|
||||
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
|
||||
= link_to explore_projects_filter_path(tag: tag.name) do
|
||||
%i.fa.fa-tag
|
||||
= link_to filter_projects_path(tag: tag.name) do
|
||||
= icon('tag')
|
||||
= tag.name
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
|
||||
- if @projects.present?
|
||||
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
|
||||
- if can? current_user, :create_projects, @group
|
||||
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
|
||||
= icon('plus')
|
||||
New Project
|
||||
= render 'shared/projects/dropdown'
|
||||
- if can? current_user, :create_projects, @group
|
||||
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
|
||||
= icon('plus')
|
||||
New Project
|
||||
|
||||
= render 'shared/projects/list', projects: @projects, stars: false, skip_namespace: true
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
|
||||
%span.milestone-row
|
||||
- project = issue.project
|
||||
%strong #{project.name} ·
|
||||
= link_to [project.namespace.becomes(Namespace), project, issue] do
|
||||
%span.cgray ##{issue.iid}
|
||||
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
|
||||
.pull-right.assignee-icon
|
||||
- if issue.assignee
|
||||
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
|
|
@ -1,6 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading= title
|
||||
%ul{ class: "well-list issues-sortable-list" }
|
||||
- if issues
|
||||
- issues.each do |issue|
|
||||
= render 'issue', issue: issue
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
|
||||
%span.milestone-row
|
||||
- project = merge_request.project
|
||||
%strong #{project.name} ·
|
||||
= link_to [project.namespace.becomes(Namespace), project, merge_request] do
|
||||
%span.cgray ##{merge_request.iid}
|
||||
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
|
||||
.pull-right.assignee-icon
|
||||
- if merge_request.assignee
|
||||
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
|
|
@ -1,6 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading= title
|
||||
%ul{ class: "well-list merge_requests-sortable-list" }
|
||||
- if merge_requests
|
||||
- merge_requests.each do |merge_request|
|
||||
= render 'merge_request', merge_request: merge_request
|
|
@ -1,29 +1,5 @@
|
|||
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
|
||||
.row
|
||||
.col-sm-6
|
||||
%strong
|
||||
= link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
|
||||
.col-sm-6
|
||||
.pull-right.light #{milestone.percent_complete}% complete
|
||||
.row
|
||||
.col-sm-6
|
||||
= link_to issues_group_path(@group, milestone_title: milestone.title) do
|
||||
= pluralize milestone.issue_count, 'Issue'
|
||||
·
|
||||
= link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
|
||||
= pluralize milestone.merge_requests_count, 'Merge Request'
|
||||
.col-sm-6
|
||||
= milestone_progress_bar(milestone)
|
||||
.row
|
||||
.col-sm-6
|
||||
%div
|
||||
- milestone.milestones.each do |milestone|
|
||||
= link_to milestone_path(milestone) do
|
||||
%span.label.label-gray
|
||||
= milestone.project.name
|
||||
.col-sm-6
|
||||
- if can?(current_user, :admin_milestones, @group)
|
||||
- if milestone.closed?
|
||||
= link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
|
||||
- else
|
||||
= link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
|
||||
= render 'shared/milestones/milestone',
|
||||
milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
|
||||
issues_path: issues_group_path(@group, milestone_title: milestone.title),
|
||||
merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
|
||||
milestone: milestone
|
||||
|
|
|
@ -1,112 +1,4 @@
|
|||
- page_title @milestone.title, "Milestones"
|
||||
= render "header_title"
|
||||
|
||||
.detail-page-header
|
||||
.status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
|
||||
- if @milestone.closed?
|
||||
Closed
|
||||
- else
|
||||
Open
|
||||
%span.identifier
|
||||
Milestone #{@milestone.title}
|
||||
.pull-right
|
||||
- if can?(current_user, :admin_milestones, @group)
|
||||
- if @milestone.active?
|
||||
= link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
|
||||
- else
|
||||
= link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
|
||||
|
||||
.detail-page-description.gray-content-block.second-block
|
||||
%h2.title
|
||||
= markdown escape_once(@milestone.title), pipeline: :single_line
|
||||
|
||||
- if @milestone.complete? && @milestone.active?
|
||||
.alert.alert-success.prepend-top-default
|
||||
%span All issues for this milestone are closed. You may close the milestone now.
|
||||
|
||||
.table-holder
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th Project
|
||||
%th Open issues
|
||||
%th State
|
||||
%th Due date
|
||||
- @milestone.milestones.each do |milestone|
|
||||
%tr
|
||||
%td
|
||||
= link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
|
||||
%td
|
||||
= milestone.issues.opened.count
|
||||
%td
|
||||
- if milestone.closed?
|
||||
Closed
|
||||
- else
|
||||
Open
|
||||
%td
|
||||
= milestone.expires_at
|
||||
|
||||
.context
|
||||
%p.lead
|
||||
Progress:
|
||||
#{@milestone.closed_items_count} closed
|
||||
–
|
||||
#{@milestone.open_items_count} open
|
||||
= milestone_progress_bar(@milestone)
|
||||
|
||||
%ul.nav-links.no-top.no-bottom
|
||||
%li.active
|
||||
= link_to '#tab-issues', 'data-toggle' => 'tab' do
|
||||
Issues
|
||||
%span.badge= @milestone.issue_count
|
||||
%li
|
||||
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
|
||||
Merge Requests
|
||||
%span.badge= @milestone.merge_requests_count
|
||||
%li
|
||||
= link_to '#tab-participants', 'data-toggle' => 'tab' do
|
||||
Participants
|
||||
%span.badge= @milestone.participants.count
|
||||
|
||||
.tab-content
|
||||
.tab-pane.active#tab-issues
|
||||
.gray-content-block.middle-block
|
||||
.pull-right
|
||||
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
|
||||
|
||||
.oneline
|
||||
All issues in this milestone
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-md-6
|
||||
= render 'issues', title: "Open", issues: @milestone.opened_issues
|
||||
.col-md-6
|
||||
= render 'issues', title: "Closed", issues: @milestone.closed_issues
|
||||
|
||||
.tab-pane#tab-merge-requests
|
||||
.gray-content-block.middle-block
|
||||
.pull-right
|
||||
= link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
|
||||
|
||||
.oneline
|
||||
All merge requests in this milestone
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-md-6
|
||||
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
|
||||
.col-md-6
|
||||
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
|
||||
|
||||
.tab-pane#tab-participants
|
||||
.gray-content-block.middle-block
|
||||
.oneline
|
||||
All participants to this milestone
|
||||
|
||||
%ul.bordered-list
|
||||
- @milestone.participants.each do |user|
|
||||
%li
|
||||
= link_to user, title: user.name, class: "darken" do
|
||||
= image_tag avatar_icon(user, 32), class: "avatar s32"
|
||||
%strong= truncate(user.name, lenght: 40)
|
||||
%br
|
||||
%small.cgray= user.username
|
||||
= render 'shared/milestones/top', milestone: @milestone, group: @group
|
||||
= render 'shared/milestones/summary', milestone: @milestone
|
||||
= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
- blob = sanitize_svg(blob)
|
||||
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
|
||||
- else
|
||||
%img{src: namespace_project_raw_path(@project.namespace, @project, @id)}
|
||||
%img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
|
||||
|
|
|
@ -1 +1 @@
|
|||
$('.js-totalbranch-count').html("#{@repository.branches.size}")
|
||||
$('.js-totalbranch-count').html("#{@repository.branch_count}")
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
- commits, hidden = limited_commits(@commits)
|
||||
- commits = Commit.decorate(commits, @project)
|
||||
|
||||
%div.panel.panel-default
|
||||
.panel-heading
|
||||
Commits (#{@commits.count})
|
||||
- if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
|
||||
- if hidden > 0
|
||||
%ul.well-list
|
||||
- Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), @project).each do |commit|
|
||||
- commits.each do |commit|
|
||||
= render "projects/commits/inline_commit", commit: commit, project: @project
|
||||
%li.warning-row.unstyled
|
||||
other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
|
||||
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
|
||||
- else
|
||||
%ul.well-list= render Commit.decorate(@commits, @project), project: @project
|
||||
%ul.well-list= render commits, project: @project
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
- unless defined?(project)
|
||||
- project = @project
|
||||
|
||||
- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
|
||||
- commits, hidden = limited_commits(@commits)
|
||||
|
||||
- commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
|
||||
.row.commits-row
|
||||
.col-md-2.hidden-xs.hidden-sm
|
||||
%h5.commits-row-date
|
||||
|
@ -13,3 +15,7 @@
|
|||
%ul.bordered-list
|
||||
= render commits, project: project
|
||||
%hr.lists-separator
|
||||
|
||||
- if hidden > 0
|
||||
.alert.alert-warning
|
||||
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
= nav_link(html_options: {class: branches_tab_class}) do
|
||||
= link_to namespace_project_branches_path(@project.namespace, @project) do
|
||||
Branches
|
||||
%span.badge.js-totalbranch-count= @repository.branches.size
|
||||
%span.badge.js-totalbranch-count= @repository.branch_count
|
||||
|
||||
= nav_link(controller: [:tags, :releases]) do
|
||||
= link_to namespace_project_tags_path(@project.namespace, @project) do
|
||||
Tags
|
||||
%span.badge.js-totaltags-count= @repository.tags.length
|
||||
%span.badge.js-totaltags-count= @repository.tag_count
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
%span into
|
||||
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
|
||||
= @merge_request.target_branch
|
||||
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
|
||||
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
|
||||
|
||||
= render "projects/merge_requests/show/how_to_merge"
|
||||
= render "projects/merge_requests/widget/show.html.haml"
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
|
||||
%span
|
||||
= link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
|
||||
.issue-detail
|
||||
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
|
||||
%span.issue-number ##{issue.iid}
|
||||
- issue.labels.each do |label|
|
||||
= render_colored_label(label)
|
||||
- if issue.assignee
|
||||
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s24", alt: ''
|
|
@ -1,7 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading
|
||||
= title
|
||||
.pull-right= issues.size
|
||||
%ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id }
|
||||
- issues.sort_by(&:position).each do |issue|
|
||||
= render 'issue', issue: issue
|
|
@ -1,8 +0,0 @@
|
|||
%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) }
|
||||
%span.str-truncated
|
||||
= link_to [@project.namespace.becomes(Namespace), @project, merge_request] do
|
||||
%span.cgray ##{merge_request.iid}
|
||||
= link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
|
||||
.pull-right.assignee-icon
|
||||
- if merge_request.assignee
|
||||
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
|
|
@ -1,5 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading= title
|
||||
%ul{ class: "well-list merge_requests-sortable-list", id: "merge_requests-list-#{id}", "data-state" => id }
|
||||
- merge_requests.sort_by(&:position).each do |merge_request|
|
||||
= render 'merge_request', merge_request: merge_request
|
|
@ -1,31 +1,5 @@
|
|||
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
|
||||
.row
|
||||
.col-sm-6
|
||||
%strong
|
||||
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
|
||||
|
||||
.col-sm-6
|
||||
.pull-right.light #{milestone.percent_complete}% complete
|
||||
.row
|
||||
.col-sm-6
|
||||
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
|
||||
= pluralize milestone.issues.count, 'Issue'
|
||||
·
|
||||
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
|
||||
= pluralize milestone.merge_requests.count, 'Merge Request'
|
||||
.col-sm-6
|
||||
= milestone_progress_bar(milestone)
|
||||
|
||||
.row
|
||||
.col-sm-6
|
||||
= render 'shared/milestone_expired', milestone: milestone
|
||||
.col-sm-6
|
||||
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
|
||||
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
\
|
||||
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
|
||||
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
|
||||
= icon('trash-o')
|
||||
Delete
|
||||
= render 'shared/milestones/milestone',
|
||||
milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
|
||||
issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
|
||||
merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
|
||||
milestone: milestone
|
||||
|
|
|
@ -42,102 +42,9 @@
|
|||
= preserve do
|
||||
= markdown @milestone.description
|
||||
|
||||
- if @milestone.issues.any? && @milestone.can_be_closed?
|
||||
- if @milestone.complete? && @milestone.active?
|
||||
.alert.alert-success.prepend-top-default
|
||||
%span All issues for this milestone are closed. You may close milestone now.
|
||||
|
||||
.context.prepend-top-default
|
||||
.milestone-summary
|
||||
%h4 Progress
|
||||
%strong= @milestone.issues.count
|
||||
issues:
|
||||
%span.milestone-stat
|
||||
%strong= @milestone.open_items_count
|
||||
open and
|
||||
%strong= @milestone.closed_items_count
|
||||
closed
|
||||
%span.milestone-stat
|
||||
%strong== #{@milestone.percent_complete}%
|
||||
complete
|
||||
%span.milestone-stat
|
||||
%span.remaining-days= milestone_remaining_days(@milestone)
|
||||
%span.pull-right.tab-issues-buttons
|
||||
- if 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
|
||||
%i.fa.fa-plus
|
||||
New Issue
|
||||
- if can?(current_user, :read_issue, @project)
|
||||
= link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
|
||||
%span.pull-right.tab-merge-requests-buttons.hidden
|
||||
- if can?(current_user, :read_merge_request, @project)
|
||||
= link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
|
||||
|
||||
= milestone_progress_bar(@milestone)
|
||||
|
||||
%ul.nav-links.no-top.no-bottom
|
||||
%li.active
|
||||
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
|
||||
Issues
|
||||
%span.badge= @issues.count
|
||||
%li
|
||||
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
|
||||
Merge Requests
|
||||
%span.badge= @merge_requests.count
|
||||
%li
|
||||
= link_to '#tab-participants', 'data-toggle' => 'tab' do
|
||||
Participants
|
||||
%span.badge= @users.count
|
||||
%li
|
||||
= link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
|
||||
Labels
|
||||
%span.badge= @labels.count
|
||||
|
||||
.tab-content.milestone-content
|
||||
.tab-pane.active#tab-issues
|
||||
.row.prepend-top-default
|
||||
.col-md-4
|
||||
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
|
||||
.col-md-4
|
||||
= render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned, id: 'ongoing')
|
||||
.col-md-4
|
||||
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
|
||||
|
||||
.tab-pane#tab-merge-requests
|
||||
.row.prepend-top-default
|
||||
.col-md-3
|
||||
= render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
|
||||
.col-md-3
|
||||
= render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
|
||||
.col-md-3
|
||||
= render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
|
||||
.col-md-3
|
||||
.panel.panel-primary
|
||||
.panel-heading Merged
|
||||
%ul.well-list
|
||||
- @merge_requests.merged.each do |merge_request|
|
||||
= render 'merge_request', merge_request: merge_request
|
||||
|
||||
.tab-pane#tab-participants
|
||||
%ul.bordered-list
|
||||
- @users.each do |user|
|
||||
%li
|
||||
= link_to user, title: user.name, class: "darken" do
|
||||
= image_tag avatar_icon(user, 32), class: "avatar s32"
|
||||
%strong= truncate(user.name, lenght: 40)
|
||||
%br
|
||||
%small.cgray= user.username
|
||||
|
||||
.tab-pane#tab-labels
|
||||
%ul.bordered-list.manage-labels-list
|
||||
- @labels.each do |label|
|
||||
%li
|
||||
= render_colored_label(label)
|
||||
- args = [@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title, label_name: label.title]
|
||||
- options = args.extract_options!
|
||||
|
||||
%span.issues-count
|
||||
= link_to namespace_project_issues_path(*args, options.merge(state: 'opened')) do
|
||||
= pluralize label.open_issues_count, 'open issue'
|
||||
%span.issues-count
|
||||
= link_to namespace_project_issues_path(*args, options.merge(state: 'closed')) do
|
||||
= pluralize label.closed_issues_count, 'closed issue'
|
||||
= render 'shared/milestones/summary', milestone: @milestone, project: @project
|
||||
= render 'shared/milestones/tabs', milestone: @milestone
|
||||
|
|
25
app/views/shared/milestones/_issuable.html.haml
Normal file
25
app/views/shared/milestones/_issuable.html.haml
Normal file
|
@ -0,0 +1,25 @@
|
|||
-# @project is present when viewing Project's milestone
|
||||
- project = @project || issuable.project
|
||||
- assignee = issuable.assignee
|
||||
- issuable_type = issuable.class.table_name
|
||||
- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
|
||||
|
||||
%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) }
|
||||
%span
|
||||
- if show_project_name
|
||||
%strong #{project.name} ·
|
||||
- elsif show_full_project_name
|
||||
%strong #{project.name_with_namespace} ·
|
||||
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
|
||||
%div{class: 'issuable-detail'}
|
||||
= link_to [project.namespace.becomes(Namespace), project, issuable] do
|
||||
%span{ class: 'issuable-number' }>= issuable.to_reference
|
||||
|
||||
- issuable.labels.each do |label|
|
||||
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
|
||||
- render_colored_label(label)
|
||||
|
||||
- if assignee
|
||||
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
|
||||
class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
|
||||
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
|
16
app/views/shared/milestones/_issuables.html.haml
Normal file
16
app/views/shared/milestones/_issuables.html.haml
Normal file
|
@ -0,0 +1,16 @@
|
|||
- show_counter = local_assigns.fetch(:show_counter, false)
|
||||
- primary = local_assigns.fetch(:primary, false)
|
||||
- panel_class = primary ? 'panel-primary' : 'panel-default'
|
||||
|
||||
.panel{ class: panel_class }
|
||||
.panel-heading
|
||||
= title
|
||||
- if show_counter
|
||||
.pull-right= issuables.size
|
||||
|
||||
- class_prefix = dom_class(issuables).pluralize
|
||||
%ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
|
||||
= render partial: 'shared/milestones/issuable',
|
||||
collection: issuables.sort_by(&:position),
|
||||
as: :issuable,
|
||||
locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
|
10
app/views/shared/milestones/_issues_tab.html.haml
Normal file
10
app/views/shared/milestones/_issues_tab.html.haml
Normal file
|
@ -0,0 +1,10 @@
|
|||
- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
|
||||
show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-md-4
|
||||
= render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true)
|
||||
.col-md-4
|
||||
= render 'shared/milestones/issuables', args.merge(title: 'Ongoing Issues (open and assigned)', issuables: issues.opened.assigned, id: 'ongoing', show_counter: true)
|
||||
.col-md-4
|
||||
= render 'shared/milestones/issuables', args.merge(title: 'Completed Issues (closed)', issuables: issues.closed, id: 'closed', show_counter: true)
|
18
app/views/shared/milestones/_labels_tab.html.haml
Normal file
18
app/views/shared/milestones/_labels_tab.html.haml
Normal file
|
@ -0,0 +1,18 @@
|
|||
%ul.bordered-list.manage-labels-list
|
||||
- labels.each do |label|
|
||||
- options = { milestone_title: @milestone.title, label_name: label.title }
|
||||
|
||||
%li
|
||||
%span.label-row
|
||||
= link_to milestones_label_path(options) do
|
||||
- render_colored_label(label)
|
||||
%span.prepend-left-10
|
||||
= markdown(label.description, pipeline: :single_line)
|
||||
|
||||
.pull-right
|
||||
%strong.issues-count
|
||||
= link_to milestones_label_path(options.merge(state: 'opened')) do
|
||||
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
|
||||
%strong.issues-count
|
||||
= link_to milestones_label_path(options.merge(state: 'closed')) do
|
||||
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
|
12
app/views/shared/milestones/_merge_requests_tab.haml
Normal file
12
app/views/shared/milestones/_merge_requests_tab.haml
Normal file
|
@ -0,0 +1,12 @@
|
|||
- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
|
||||
show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-md-3
|
||||
= render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned')
|
||||
.col-md-3
|
||||
= render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing')
|
||||
.col-md-3
|
||||
= render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed')
|
||||
.col-md-3
|
||||
= render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true)
|
45
app/views/shared/milestones/_milestone.html.haml
Normal file
45
app/views/shared/milestones/_milestone.html.haml
Normal file
|
@ -0,0 +1,45 @@
|
|||
- dashboard = local_assigns[:dashboard]
|
||||
- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first)
|
||||
|
||||
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
|
||||
.row
|
||||
.col-sm-6
|
||||
%strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
|
||||
.col-sm-6
|
||||
.pull-right.light #{milestone.percent_complete}% complete
|
||||
.row
|
||||
.col-sm-6
|
||||
= link_to pluralize(milestone.issues.size, 'Issue'), issues_path
|
||||
·
|
||||
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
|
||||
.col-sm-6= milestone_progress_bar(milestone)
|
||||
- if milestone.is_a?(GlobalMilestone)
|
||||
.row
|
||||
.col-sm-6
|
||||
.expiration= render('shared/milestone_expired', milestone: milestone)
|
||||
.projects
|
||||
- milestone.milestones.each do |milestone|
|
||||
= link_to milestone_path(milestone) do
|
||||
%span.label.label-gray
|
||||
= dashboard ? milestone.project.name_with_namespace : milestone.project.name
|
||||
- if @group
|
||||
.col-sm-6
|
||||
- if can?(current_user, :admin_milestones, @group)
|
||||
- if milestone.closed?
|
||||
= link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
|
||||
- else
|
||||
= link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
|
||||
|
||||
- if @project
|
||||
.row
|
||||
.col-sm-6= render('shared/milestone_expired', milestone: milestone)
|
||||
.col-sm-6
|
||||
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
|
||||
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
\
|
||||
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
|
||||
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
|
||||
= icon('trash-o')
|
||||
Delete
|
8
app/views/shared/milestones/_participants_tab.html.haml
Normal file
8
app/views/shared/milestones/_participants_tab.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
|||
%ul.bordered-list
|
||||
- users.each do |user|
|
||||
%li
|
||||
= link_to user, title: user.name, class: "darken" do
|
||||
= image_tag avatar_icon(user, 32), class: "avatar s32"
|
||||
%strong= truncate(user.name, lenght: 40)
|
||||
%br
|
||||
%small.cgray= user.username
|
28
app/views/shared/milestones/_summary.html.haml
Normal file
28
app/views/shared/milestones/_summary.html.haml
Normal file
|
@ -0,0 +1,28 @@
|
|||
- project = local_assigns[:project]
|
||||
|
||||
.context.prepend-top-default
|
||||
.milestone-summary
|
||||
%h4 Progress
|
||||
%strong= milestone.issues.size
|
||||
issues:
|
||||
%span.milestone-stat
|
||||
%strong= milestone.issues.opened.size
|
||||
open and
|
||||
%strong= milestone.issues.closed.size
|
||||
closed
|
||||
%span.milestone-stat
|
||||
%strong== #{milestone.percent_complete}%
|
||||
complete
|
||||
|
||||
%span.milestone-stat
|
||||
%span.remaining-days= milestone_remaining_days(milestone)
|
||||
%span.pull-right.tab-issues-buttons
|
||||
- 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
|
||||
%i.fa.fa-plus
|
||||
New Issue
|
||||
= link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped"
|
||||
%span.pull-right.tab-merge-requests-buttons.hidden
|
||||
= link_to 'Browse Merge Requests', milestones_browse_issuables_path(milestone, type: :merge_requests), class: "btn btn-grouped"
|
||||
|
||||
= milestone_progress_bar(milestone)
|
30
app/views/shared/milestones/_tabs.html.haml
Normal file
30
app/views/shared/milestones/_tabs.html.haml
Normal file
|
@ -0,0 +1,30 @@
|
|||
%ul.nav-links.no-top.no-bottom
|
||||
%li.active
|
||||
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
|
||||
Issues
|
||||
%span.badge= milestone.issues.size
|
||||
%li
|
||||
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
|
||||
Merge Requests
|
||||
%span.badge= milestone.merge_requests.size
|
||||
%li
|
||||
= link_to '#tab-participants', 'data-toggle' => 'tab' do
|
||||
Participants
|
||||
%span.badge= milestone.participants.count
|
||||
%li
|
||||
= link_to '#tab-labels', 'data-toggle' => 'tab' do
|
||||
Labels
|
||||
%span.badge= milestone.labels.count
|
||||
|
||||
- show_project_name = local_assigns.fetch(:show_project_name, false)
|
||||
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
|
||||
|
||||
.tab-content.milestone-content
|
||||
.tab-pane.active#tab-issues
|
||||
= render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
|
||||
.tab-pane#tab-merge-requests
|
||||
= render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
|
||||
.tab-pane#tab-participants
|
||||
= render 'shared/milestones/participants_tab', users: milestone.participants
|
||||
.tab-pane#tab-labels
|
||||
= render 'shared/milestones/labels_tab', labels: milestone.labels
|
58
app/views/shared/milestones/_top.html.haml
Normal file
58
app/views/shared/milestones/_top.html.haml
Normal file
|
@ -0,0 +1,58 @@
|
|||
- page_title milestone.title, "Milestones"
|
||||
|
||||
- group = local_assigns[:group]
|
||||
|
||||
.detail-page-header
|
||||
.status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
|
||||
- if milestone.closed?
|
||||
Closed
|
||||
- elsif milestone.expired?
|
||||
Expired
|
||||
- else
|
||||
Open
|
||||
%span.identifier
|
||||
Milestone #{milestone.title}
|
||||
- if milestone.expires_at
|
||||
%span.creator
|
||||
·
|
||||
= milestone.expires_at
|
||||
- if group
|
||||
.pull-right
|
||||
- if can?(current_user, :admin_milestones, group)
|
||||
- if milestone.active?
|
||||
= link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
|
||||
- else
|
||||
= link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
|
||||
|
||||
.detail-page-description.gray-content-block.second-block
|
||||
%h2.title
|
||||
= markdown escape_once(milestone.title), pipeline: :single_line
|
||||
|
||||
- if milestone.complete? && milestone.active?
|
||||
.alert.alert-success.prepend-top-default
|
||||
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
|
||||
%span All issues for this milestone are closed. #{close_msg}
|
||||
|
||||
.table-holder
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th Project
|
||||
%th Open issues
|
||||
%th State
|
||||
%th Due date
|
||||
- milestone.milestones.each do |ms|
|
||||
%tr
|
||||
%td
|
||||
- project_name = group ? ms.project.name : ms.project.name_with_namespace
|
||||
= link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
|
||||
%td
|
||||
= ms.issues.opened.count
|
||||
%td
|
||||
- if ms.closed?
|
||||
Closed
|
||||
- else
|
||||
Open
|
||||
%td
|
||||
= ms.expires_at
|
||||
|
22
app/views/shared/projects/_dropdown.html.haml
Normal file
22
app/views/shared/projects/_dropdown.html.haml
Normal file
|
@ -0,0 +1,22 @@
|
|||
- @sort ||= sort_value_recently_updated
|
||||
- archived = params[:archived]
|
||||
.dropdown.inline
|
||||
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
%span.light
|
||||
= projects_sort_options_hash[@sort]
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
|
||||
%li.dropdown-header
|
||||
Sort by
|
||||
- projects_sort_options_hash.each do |value, title|
|
||||
%li
|
||||
= link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
|
||||
= title
|
||||
|
||||
%li.divider
|
||||
%li
|
||||
= link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do
|
||||
Hide archived projects
|
||||
%li
|
||||
= link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
|
||||
Show archived projects
|
|
@ -28,6 +28,9 @@
|
|||
= project.name
|
||||
|
||||
.controls
|
||||
- if project.main_language
|
||||
%span
|
||||
= project.main_language
|
||||
- if ci_commit
|
||||
%span
|
||||
= render_ci_status(ci_commit)
|
||||
|
|
|
@ -7,8 +7,6 @@ Rails.application.configure do
|
|||
# and recreated between test runs. Don't rely on the data there!
|
||||
config.cache_classes = false
|
||||
|
||||
config.cache_store = :null_store
|
||||
|
||||
# Configure static asset server for tests with Cache-Control for performance
|
||||
config.serve_static_files = true
|
||||
config.static_cache_control = "public, max-age=3600"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddMainLanguageToRepository < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :projects, :main_language, :string
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class RemoveExpiresAtFromSnippets < ActiveRecord::Migration
|
||||
def change
|
||||
remove_column :snippets, :expires_at, :datetime
|
||||
end
|
||||
end
|
16
db/migrate/20160309140734_fix_todos.rb
Normal file
16
db/migrate/20160309140734_fix_todos.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class FixTodos < ActiveRecord::Migration
|
||||
def up
|
||||
execute <<-SQL
|
||||
DELETE FROM todos
|
||||
WHERE todos.target_type IN ('Commit', 'ProjectSnippet')
|
||||
OR NOT EXISTS (
|
||||
SELECT *
|
||||
FROM projects
|
||||
WHERE projects.id = todos.project_id
|
||||
)
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160222153918) do
|
||||
ActiveRecord::Schema.define(version: 20160309140734) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -697,6 +697,7 @@ ActiveRecord::Schema.define(version: 20160222153918) do
|
|||
t.integer "build_timeout", default: 3600, null: false
|
||||
t.boolean "pending_delete", default: false
|
||||
t.boolean "public_builds", default: true, null: false
|
||||
t.string "main_language"
|
||||
end
|
||||
|
||||
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
|
||||
|
@ -777,7 +778,6 @@ ActiveRecord::Schema.define(version: 20160222153918) do
|
|||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "file_name"
|
||||
t.datetime "expires_at"
|
||||
t.string "type"
|
||||
t.integer "visibility_level", default: 0, null: false
|
||||
end
|
||||
|
@ -785,7 +785,6 @@ ActiveRecord::Schema.define(version: 20160222153918) do
|
|||
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
|
||||
add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree
|
||||
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
|
||||
add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
|
||||
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
|
||||
add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree
|
||||
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
|
||||
|
|
|
@ -213,8 +213,7 @@ Example response:
|
|||
|
||||
## Commit status
|
||||
|
||||
Since GitLab 8.1, this is the new commit status API. The documentation in
|
||||
[ci/api/commits](../ci/api/commits.md) is deprecated.
|
||||
Since GitLab 8.1, this is the new commit status API.
|
||||
|
||||
### Get the status of a commit
|
||||
|
||||
|
|
|
@ -145,7 +145,6 @@ Parameters:
|
|||
"state": "active",
|
||||
"created_at": "2013-09-30T13:46:01Z"
|
||||
},
|
||||
"expires_at": null,
|
||||
"updated_at": "2013-10-02T07:34:20Z",
|
||||
"created_at": "2013-10-02T07:34:20Z"
|
||||
}
|
||||
|
|
|
@ -51,7 +51,6 @@ Parameters:
|
|||
"state": "active",
|
||||
"created_at": "2012-05-23T08:00:58Z"
|
||||
},
|
||||
"expires_at": null,
|
||||
"updated_at": "2012-06-28T10:52:04Z",
|
||||
"created_at": "2012-06-28T10:52:04Z"
|
||||
}
|
||||
|
|
|
@ -151,6 +151,8 @@ Parameters:
|
|||
"name": "John Smith",
|
||||
"state": "active",
|
||||
"created_at": "2012-05-23T08:00:58Z",
|
||||
"confirmed_at": "2012-05-23T08:00:58Z",
|
||||
"last_sign_in_at": "2015-03-23T08:00:58Z",
|
||||
"bio": null,
|
||||
"skype": "",
|
||||
"linkedin": "",
|
||||
|
|
|
@ -1,86 +1,22 @@
|
|||
# GitLab CI API
|
||||
|
||||
## Purpose
|
||||
|
||||
Main purpose of GitLab CI API is to provide necessary data and context for
|
||||
GitLab CI Runners.
|
||||
|
||||
For consumer API take a look at this [documentation](../../api/README.md) where
|
||||
you will find all relevant information.
|
||||
|
||||
## API Prefix
|
||||
|
||||
Current CI API prefix is `/ci/api/v1`.
|
||||
|
||||
You need to prepend this prefix to all examples in this documentation, like:
|
||||
|
||||
GET /ci/api/v1/builds/:id/artifacts
|
||||
|
||||
## Resources
|
||||
|
||||
- [Projects](projects.md)
|
||||
- [Runners](runners.md)
|
||||
- [Commits](commits.md)
|
||||
- [Builds](builds.md)
|
||||
|
||||
|
||||
## Authentication
|
||||
|
||||
GitLab CI API uses different types of authentication depends on what API you use.
|
||||
Each API document has section with information about authentication you need to use.
|
||||
|
||||
GitLab CI API has 4 authentication methods:
|
||||
|
||||
* GitLab user token & GitLab url
|
||||
* GitLab CI project token
|
||||
* GitLab CI runners registration token
|
||||
* GitLab CI runner token
|
||||
|
||||
|
||||
### Authentication #1: GitLab user token & GitLab url
|
||||
|
||||
Authentication is done by
|
||||
sending the `private-token` of a valid user and the `url` of an
|
||||
authorized GitLab instance via a query string along with the API
|
||||
request:
|
||||
|
||||
GET http://gitlab.example.com/ci/api/v1/projects?private_token=QVy1PB7sTxfy4pqfZM1U&url=http://demo.gitlab.com/
|
||||
|
||||
If preferred, you may instead send the `private-token` as a header in
|
||||
your request:
|
||||
|
||||
curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://gitlab.example.com/ci/api/v1/projects?url=http://demo.gitlab.com/"
|
||||
|
||||
|
||||
### Authentication #2: GitLab CI project token
|
||||
|
||||
Each project in GitLab CI has it own token.
|
||||
It can be used to get project commits and builds information.
|
||||
You can use project token only for certain project.
|
||||
|
||||
### Authentication #3: GitLab CI runners registration token
|
||||
|
||||
This token is not persisted and is generated on each application start.
|
||||
It can be used only for registering new runners in system. You can find it on
|
||||
GitLab CI Runners web page https://gitlab-ci.example.com/admin/runners
|
||||
|
||||
### Authentication #4: GitLab CI runner token
|
||||
|
||||
Every GitLab CI runner has it own token that allow it to receive and update
|
||||
GitLab CI builds. This token exists of internal purposes and should be used only
|
||||
by runners
|
||||
|
||||
## JSON
|
||||
|
||||
All API requests are serialized using JSON. You don't need to specify
|
||||
`.json` at the end of API URL.
|
||||
|
||||
## Status codes
|
||||
|
||||
The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
|
||||
|
||||
API request types:
|
||||
|
||||
- `GET` requests access one or more resources and return the result as JSON
|
||||
- `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
|
||||
- `GET`, `PUT` and `DELETE` return `200 OK` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
|
||||
- `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
|
||||
|
||||
The following list shows the possible return codes for API requests.
|
||||
|
||||
Return values:
|
||||
|
||||
- `200 OK` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
|
||||
- `201 Created` - The `POST` request was successful and the resource is returned as JSON
|
||||
- `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
|
||||
- `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
|
||||
- `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
|
||||
- `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
|
||||
- `405 Method Not Allowed` - The request is not supported
|
||||
- `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
|
||||
- `422 Unprocessable` - The entity could not be processed
|
||||
- `500 Server Error` - While handling the request something went wrong on the server side
|
||||
- [Runners](runners.md)
|
||||
|
|
|
@ -1,85 +1,73 @@
|
|||
# Builds API
|
||||
|
||||
This API used by runners to receive and update builds.
|
||||
API used by runners to receive and update builds.
|
||||
|
||||
__Authentication is done by runner token__
|
||||
_**Note:** This API is intended to be used only by Runners as their own
|
||||
communication channel. For the consumer API see the
|
||||
[Builds API](../../api/builds.md)._
|
||||
|
||||
## Authentication
|
||||
|
||||
This API uses two types of authentication:
|
||||
|
||||
1. Unique runner's token
|
||||
|
||||
Token assigned to runner after it has been registered.
|
||||
|
||||
2. Using build authorization token
|
||||
|
||||
This is project's CI token that can be found in Continuous Integration
|
||||
project settings.
|
||||
|
||||
Build authorization token can be passed as a parameter or a value of
|
||||
`BUILD-TOKEN` header. This method are interchangeable.
|
||||
|
||||
## Builds
|
||||
|
||||
### Runs oldest pending build by runner
|
||||
|
||||
POST /ci/builds/register
|
||||
POST /ci/api/v1/builds/register
|
||||
|
||||
Parameters:
|
||||
|
||||
* `token` (required) - The unique token of runner
|
||||
* `token` (required) - Unique runner token
|
||||
|
||||
Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 48584,
|
||||
"ref": "0.1.1",
|
||||
"tag": true,
|
||||
"sha": "d63117656af6ff57d99e50cc270f854691f335ad",
|
||||
"status": "success",
|
||||
"name": "pages",
|
||||
"token": "9dd60b4f1a439d1765357446c1084c",
|
||||
"stage": "test",
|
||||
"project_id": 479,
|
||||
"project_name": "test",
|
||||
"commands": "echo commands",
|
||||
"repo_url": "http://gitlab-ci-token:token@gitlab.example/group/test.git",
|
||||
"before_sha": "0000000000000000000000000000000000000000",
|
||||
"allow_git_fetch": false,
|
||||
"options": {
|
||||
"image": "docker:image",
|
||||
"artifacts": {
|
||||
"paths": [
|
||||
"public"
|
||||
]
|
||||
},
|
||||
"cache": {
|
||||
"paths": [
|
||||
"vendor"
|
||||
]
|
||||
}
|
||||
},
|
||||
"timeout": 3600,
|
||||
"variables": [
|
||||
{
|
||||
"key": "CI_BUILD_TAG",
|
||||
"value": "0.1.1",
|
||||
"public": true
|
||||
}
|
||||
],
|
||||
"depends_on_builds": [
|
||||
{
|
||||
"id": 48584,
|
||||
"ref": "0.1.1",
|
||||
"tag": true,
|
||||
"sha": "d63117656af6ff57d99e50cc270f854691f335ad",
|
||||
"status": "success",
|
||||
"name": "build",
|
||||
"token": "9dd60b4f1a439d1765357446c1084c",
|
||||
"stage": "build",
|
||||
"project_id": 479,
|
||||
"project_name": "test",
|
||||
"artifacts_file": {
|
||||
"filename": "artifacts.zip",
|
||||
"size": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Update details of an existing build
|
||||
|
||||
PUT /ci/builds/:id
|
||||
PUT /ci/api/v1/builds/:id
|
||||
|
||||
Parameters:
|
||||
|
||||
* `id` (required) - The ID of a project
|
||||
* `token` (required) - Unique runner token
|
||||
* `state` (optional) - The state of a build
|
||||
* `trace` (optional) - The trace of a build
|
||||
|
||||
### Upload artifacts to build
|
||||
|
||||
POST /ci/api/v1/builds/:id/artifacts
|
||||
|
||||
Parameters:
|
||||
|
||||
* `id` (required) - The ID of a build
|
||||
* `token` (required) - The build authorization token
|
||||
* `file` (required) - Artifacts file
|
||||
|
||||
### Download the artifacts file from build
|
||||
|
||||
GET /ci/api/v1/builds/:id/artifacts
|
||||
|
||||
Parameters:
|
||||
|
||||
* `id` (required) - The ID of a build
|
||||
* `token` (required) - The build authorization token
|
||||
|
||||
### Remove the artifacts file from build
|
||||
|
||||
DELETE /ci/api/v1/builds/:id/artifacts
|
||||
|
||||
Parameters:
|
||||
|
||||
* ` id` (required) - The ID of a build
|
||||
* `token` (required) - The build authorization token
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
# Commits API
|
||||
|
||||
**DEPRECATED**
|
||||
|
||||
Since GitLab 8.1, there is a new commit status API. Please see the [revised
|
||||
documentation](../../api/commits.md#commit-status).
|
||||
|
||||
---
|
||||
|
||||
__Authentication is done by GitLab CI project token__
|
||||
|
||||
## Commits
|
||||
|
||||
### Retrieve all commits per project
|
||||
|
||||
Get list of commits per project
|
||||
|
||||
GET /ci/commits
|
||||
|
||||
Parameters:
|
||||
|
||||
* `project_id` (required) - The ID of a project
|
||||
* `project_token` (requires) - Project token
|
||||
* `page` (optional)
|
||||
* `per_page` (optional) - items per request (default is 20)
|
||||
|
||||
Returns:
|
||||
|
||||
```json
|
||||
[{
|
||||
"id": 3,
|
||||
"ref": "master",
|
||||
"sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
|
||||
"project_id": 2,
|
||||
"before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
|
||||
"created_at": "2014-11-05T09:46:35.247Z",
|
||||
"status": "success",
|
||||
"finished_at": "2014-11-05T09:46:44.254Z",
|
||||
"duration": 5.062692165374756,
|
||||
"git_commit_message": "wow\n",
|
||||
"git_author_name": "Administrator",
|
||||
"git_author_email": "admin@example.com",
|
||||
"builds": [{
|
||||
"id": 7,
|
||||
"project_id": 2,
|
||||
"ref": "master",
|
||||
"status": "success",
|
||||
"finished_at": "2014-11-05T09:46:44.254Z",
|
||||
"created_at": "2014-11-05T09:46:35.259Z",
|
||||
"updated_at": "2014-11-05T09:46:44.255Z",
|
||||
"sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
|
||||
"started_at": "2014-11-05T09:46:39.192Z",
|
||||
"before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
|
||||
"runner_id": 1,
|
||||
"coverage": null,
|
||||
"commit_id": 3
|
||||
}]
|
||||
}]
|
||||
```
|
||||
|
||||
### Create commit
|
||||
|
||||
Inform GitLab CI about new commit you want it to build.
|
||||
|
||||
__If commit already exists in GitLab CI it will not be created__
|
||||
|
||||
|
||||
POST /ci/commits
|
||||
|
||||
Parameters:
|
||||
|
||||
* `project_id` (required) - The ID of a project
|
||||
* `project_token` (requires) - Project token
|
||||
* `data` (required) - Push data. For example see comment in `lib/api/commits.rb`
|
||||
|
||||
Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"ref": "master",
|
||||
"sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
|
||||
"project_id": 2,
|
||||
"before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
|
||||
"created_at": "2014-11-05T09:46:35.247Z",
|
||||
"status": "success",
|
||||
"finished_at": "2014-11-05T09:46:44.254Z",
|
||||
"duration": 5.062692165374756,
|
||||
"git_commit_message": "wow\n",
|
||||
"git_author_name": "Administrator",
|
||||
"git_author_email": "admin@example.com",
|
||||
"builds": [{
|
||||
"id": 7,
|
||||
"project_id": 2,
|
||||
"ref": "master",
|
||||
"status": "success",
|
||||
"finished_at": "2014-11-05T09:46:44.254Z",
|
||||
"created_at": "2014-11-05T09:46:35.259Z",
|
||||
"updated_at": "2014-11-05T09:46:44.255Z",
|
||||
"sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
|
||||
"started_at": "2014-11-05T09:46:39.192Z",
|
||||
"before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
|
||||
"runner_id": 1,
|
||||
"coverage": null,
|
||||
"commit_id": 3
|
||||
}]
|
||||
}
|
||||
```
|
|
@ -1,149 +0,0 @@
|
|||
# Projects API
|
||||
|
||||
This API is intended to aid in the setup and configuration of
|
||||
projects on GitLab CI.
|
||||
|
||||
__Authentication is done by GitLab user token & GitLab url__
|
||||
|
||||
## Projects
|
||||
|
||||
### List Authorized Projects
|
||||
|
||||
Lists all projects that the authenticated user has access to.
|
||||
|
||||
```
|
||||
GET /ci/projects
|
||||
```
|
||||
|
||||
Returns:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id" : 271,
|
||||
"name" : "gitlabhq",
|
||||
"timeout" : 1800,
|
||||
"token" : "iPWx6WM4lhHNedGfBpPJNP",
|
||||
"default_ref" : "master",
|
||||
"gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
|
||||
"path" : "gitlab/gitlab-shell",
|
||||
"always_build" : false,
|
||||
"polling_interval" : null,
|
||||
"public" : false,
|
||||
"ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
|
||||
"gitlab_id" : 3
|
||||
},
|
||||
{
|
||||
"id" : 272,
|
||||
"name" : "gitlab-ci",
|
||||
"timeout" : 1800,
|
||||
"token" : "iPWx6WM4lhHNedGfBpPJNP",
|
||||
"default_ref" : "master",
|
||||
"gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
|
||||
"path" : "gitlab/gitlab-shell",
|
||||
"always_build" : false,
|
||||
"polling_interval" : null,
|
||||
"public" : false,
|
||||
"ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
|
||||
"gitlab_id" : 4
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### List Owned Projects
|
||||
|
||||
Lists all projects that the authenticated user owns.
|
||||
|
||||
```
|
||||
GET /ci/projects/owned
|
||||
```
|
||||
|
||||
Returns:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id" : 272,
|
||||
"name" : "gitlab-ci",
|
||||
"timeout" : 1800,
|
||||
"token" : "iPWx6WM4lhHNedGfBpPJNP",
|
||||
"default_ref" : "master",
|
||||
"gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
|
||||
"path" : "gitlab/gitlab-shell",
|
||||
"always_build" : false,
|
||||
"polling_interval" : null,
|
||||
"public" : false,
|
||||
"ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
|
||||
"gitlab_id" : 4
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Single Project
|
||||
|
||||
Returns information about a single project for which the user is
|
||||
authorized.
|
||||
|
||||
GET /ci/projects/:id
|
||||
|
||||
Parameters:
|
||||
|
||||
* `id` (required) - The ID of the GitLab CI project
|
||||
|
||||
### Create Project
|
||||
|
||||
Creates a GitLab CI project using GitLab project details.
|
||||
|
||||
POST /ci/projects
|
||||
|
||||
Parameters:
|
||||
|
||||
* `name` (required) - The name of the project
|
||||
* `gitlab_id` (required) - The ID of the project on the GitLab instance
|
||||
* `default_ref` (optional) - The branch to run on (default to `master`)
|
||||
|
||||
### Update Project
|
||||
|
||||
Updates a GitLab CI project using GitLab project details that the
|
||||
authenticated user has access to.
|
||||
|
||||
PUT /ci/projects/:id
|
||||
|
||||
Parameters:
|
||||
|
||||
* `name` - The name of the project
|
||||
* `default_ref` - The branch to run on (default to `master`)
|
||||
|
||||
### Remove Project
|
||||
|
||||
Removes a GitLab CI project that the authenticated user has access to.
|
||||
|
||||
DELETE /ci/projects/:id
|
||||
|
||||
Parameters:
|
||||
|
||||
* `id` (required) - The ID of the GitLab CI project
|
||||
|
||||
### Link Project to Runner
|
||||
|
||||
Links a runner to a project so that it can make builds (only via
|
||||
authorized user).
|
||||
|
||||
POST /ci/projects/:id/runners/:runner_id
|
||||
|
||||
Parameters:
|
||||
|
||||
* `id` (required) - The ID of the GitLab CI project
|
||||
* `runner_id` (required) - The ID of the GitLab CI runner
|
||||
|
||||
### Remove Project from Runner
|
||||
|
||||
Removes a runner from a project so that it can not make builds (only
|
||||
via authorized user).
|
||||
|
||||
DELETE /ci/projects/:id/runners/:runner_id
|
||||
|
||||
Parameters:
|
||||
|
||||
* `id` (required) - The ID of the GitLab CI project
|
||||
* `runner_id` (required) - The ID of the GitLab CI runner
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue