BigW Consortium Gitlab

Commit a623add8 by Regis

Merge branch 'master' into auto-pipelines-vue

parents 18cfacc9 46eb0ebf
...@@ -4,37 +4,6 @@ entry. ...@@ -4,37 +4,6 @@ entry.
## 8.14.0 (2016-11-22) ## 8.14.0 (2016-11-22)
- Use separate email-token for incoming email and revert back the inactive feature. !5914
- Replace jQuery.timeago with timeago.js. !6274 (ClemMakesApps)
- Add CI notifications. Who triggered a pipeline would receive an email after the pipeline is succeeded or failed. Users could also update notification settings accordingly. !6342
- Finer-grained Git gargage collection. !6588
- Introduce better credential and error checking to `rake gitlab:ldap:check`. !6601
- Process commits using a dedicated Sidekiq worker. !6802
- Fix showing pipeline status for a given commit from correct branch. !7034
- Add query param to filter users by external & blocked type. !7109 (Yatish Mehta)
- Issues atom feed url reflect filters on dashboard. !7114 (Lucas Deschamps)
- Add setting to only allow merge requests to be merged when all discussions are resolved. !7125 (Rodolfo Arruda)
- Remove an extra leading space from diff paste data. !7133 (Hiroyuki Sato)
- Fix 404 on network page when entering non-existent git revision. !7172 (Hiroyuki Sato)
- Rewrite git blame spinach feature tests to rspec feature tests. !7197 (Lisanne Fellinger)
- Only skip group when it's actually a group in the "Share with group" select. !7262
- Introduce round-robin project creation to spread load over multiple shards. !7266
- Ensure merge request's "remove branch" accessors return booleans. !7267
- Expose label IDs in API. !7275 (Rares Sfirlogea)
- Fix invalid filename validation on eslint. !7281
- API: Ability to retrieve version information. !7286 (Robert Schilling)
- Set default Sidekiq retries to 3. !7294
- Return 400 when creating a system hook fails. !7350 (Robert Schilling)
- Use the Gitlab Workhorse HTTP header in the admin dashboard. (Chris Wright)
- Add an index for project_id in project_import_data to improve performance.
- Fix broken link to observatory cli on Frontend Dev Guide. (Sam Rose)
- Faster search inside Project.
- Clicking "force remove source branch" label now toggles the checkbox again.
- Allow to test JIRA service settings without having a repository.
- Fix: Guest sees some repository details and gets 404.
- Bump omniauth-gitlab to 1.0.2 to fix incompatibility with omniauth-oauth2.
- Fix: Todos Filter Shows All Users.
- Fix broken commits search.
- Show correct environment log in admin/logs (@duk3luk3 !7191) - Show correct environment log in admin/logs (@duk3luk3 !7191)
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117 - Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
- Diff collapse won't shift when collapsing. - Diff collapse won't shift when collapsing.
......
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
/*= require_directory ./u2f */ /*= require_directory ./u2f */
/*= require_directory . */ /*= require_directory . */
/*= require fuzzaldrin-plus */ /*= require fuzzaldrin-plus */
/*= require es6-promise.auto */
(function () { (function () {
document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch); document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
......
...@@ -141,6 +141,10 @@ ...@@ -141,6 +141,10 @@
&.btn-save { &.btn-save {
@include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light); @include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light);
} }
&.btn-remove {
@include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
}
} }
&.btn-gray { &.btn-gray {
......
...@@ -63,7 +63,11 @@ header { ...@@ -63,7 +63,11 @@ header {
&:focus, &:focus,
&:active { &:active {
background-color: $background-color; background-color: $background-color;
color: darken($gl-icon-color, 50%); color: darken($gl-icon-color, 30%);
.todos-pending-count {
background: darken($todo-alert-blue, 10%);
}
} }
.fa-caret-down { .fa-caret-down {
...@@ -153,7 +157,7 @@ header { ...@@ -153,7 +157,7 @@ header {
padding-right: 20px; padding-right: 20px;
margin: 0; margin: 0;
font-size: 19px; font-size: 19px;
max-width: 400px; max-width: 385px;
display: inline-block; display: inline-block;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
...@@ -194,7 +198,7 @@ header { ...@@ -194,7 +198,7 @@ header {
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: darken($color: $gl-text-color, $amount: 50%); color: darken($color: $gl-text-color, $amount: 30%);
} }
} }
...@@ -223,6 +227,14 @@ header { ...@@ -223,6 +227,14 @@ header {
} }
} }
.page-sidebar-pinned.right-sidebar-expanded {
@media (max-width: $screen-lg-min) {
.header-content .title {
width: 300px;
}
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
header .container-fluid { header .container-fluid {
font-size: 18px; font-size: 18px;
......
...@@ -252,7 +252,7 @@ $award-emoji-new-btn-icon-color: #dcdcdc; ...@@ -252,7 +252,7 @@ $award-emoji-new-btn-icon-color: #dcdcdc;
*/ */
$search-input-border-color: rgba(#4688f1, .8); $search-input-border-color: rgba(#4688f1, .8);
$search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: 244px; $search-input-width: 220px;
$location-badge-color: #aaa; $location-badge-color: #aaa;
$location-badge-bg: $gray-normal; $location-badge-bg: $gray-normal;
$location-badge-active-bg: #4f91f8; $location-badge-active-bg: #4f91f8;
......
...@@ -141,6 +141,22 @@ ul.notes { ...@@ -141,6 +141,22 @@ ul.notes {
} }
} }
.page-sidebar-pinned.right-sidebar-expanded {
@media (max-width: $screen-lg-min) {
.note-header {
.note-headline-light {
display: block;
}
.note-actions {
position: absolute;
right: 0;
top: 0;
}
}
}
}
// Diff code in discussion view // Diff code in discussion view
.discussion-body .diff-file { .discussion-body .diff-file {
.file-title { .file-title {
......
...@@ -33,10 +33,9 @@ ...@@ -33,10 +33,9 @@
} }
.search-input { .search-input {
padding-right: 20px;
border: none; border: none;
font-size: 14px; font-size: 14px;
padding: 0; padding: 0 20px 0 0;
margin-left: 5px; margin-left: 5px;
line-height: 25px; line-height: 25px;
width: 98%; width: 98%;
...@@ -158,7 +157,6 @@ ...@@ -158,7 +157,6 @@
width: 68%; width: 68%;
} }
} }
} }
.search-holder { .search-holder {
...@@ -235,5 +233,4 @@ ...@@ -235,5 +233,4 @@
&:focus { &:focus {
color: $gl-link-color; color: $gl-link-color;
} }
} }
...@@ -4,7 +4,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -4,7 +4,7 @@ class Projects::BranchesController < Projects::ApplicationController
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy] before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
def index def index
@sort = params[:sort].presence || sort_value_name @sort = params[:sort].presence || sort_value_name
...@@ -62,6 +62,13 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -62,6 +62,13 @@ class Projects::BranchesController < Projects::ApplicationController
end end
end end
def destroy_all_merged
DeleteMergedBranchesService.new(@project, current_user).async_execute
redirect_to namespace_project_branches_path(@project.namespace, @project),
notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
end
private private
def ref def ref
......
...@@ -31,10 +31,6 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -31,10 +31,6 @@ class Projects::LfsApiController < Projects::GitHttpClientController
private private
def objects
@objects ||= (params[:objects] || []).to_a
end
def existing_oids def existing_oids
@existing_oids ||= begin @existing_oids ||= begin
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
......
...@@ -30,6 +30,10 @@ module LfsHelper ...@@ -30,6 +30,10 @@ module LfsHelper
ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
end end
def objects
@objects ||= (params[:objects] || []).to_a
end
def user_can_download_code? def user_can_download_code?
has_authentication_ability?(:download_code) && can?(user, :download_code, project) has_authentication_ability?(:download_code) && can?(user, :download_code, project)
end end
......
module TriggersHelper module TriggersHelper
def builds_trigger_url(project_id) def builds_trigger_url(project_id, ref: nil)
if ref.nil?
"#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds" "#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds"
else
"#{Settings.gitlab.url}/api/v3/projects/#{project_id}/ref/#{ref}/trigger/builds"
end
end end
end end
...@@ -35,6 +35,7 @@ class Project < ActiveRecord::Base ...@@ -35,6 +35,7 @@ class Project < ActiveRecord::Base
default_value_for :builds_enabled, gitlab_config_features.builds default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
after_create :ensure_dir_exist after_create :ensure_dir_exist
after_create :create_project_feature, unless: :project_feature after_create :create_project_feature, unless: :project_feature
...@@ -1334,10 +1335,6 @@ class Project < ActiveRecord::Base ...@@ -1334,10 +1335,6 @@ class Project < ActiveRecord::Base
end end
end end
def only_allow_merge_if_all_discussions_are_resolved
super || false
end
private private
def pushes_since_gc_redis_key def pushes_since_gc_redis_key
......
...@@ -631,7 +631,7 @@ class Repository ...@@ -631,7 +631,7 @@ class Repository
@head_tree ||= Tree.new(self, head_commit.sha, nil) @head_tree ||= Tree.new(self, head_commit.sha, nil)
end end
def tree(sha = :head, path = nil) def tree(sha = :head, path = nil, recursive: false)
if sha == :head if sha == :head
if path.nil? if path.nil?
return head_tree return head_tree
...@@ -640,7 +640,7 @@ class Repository ...@@ -640,7 +640,7 @@ class Repository
end end
end end
Tree.new(self, sha, path) Tree.new(self, sha, path, recursive: recursive)
end end
def blob_at_branch(branch_name, path) def blob_at_branch(branch_name, path)
......
...@@ -3,15 +3,16 @@ class Tree ...@@ -3,15 +3,16 @@ class Tree
attr_accessor :repository, :sha, :path, :entries attr_accessor :repository, :sha, :path, :entries
def initialize(repository, sha, path = '/') def initialize(repository, sha, path = '/', recursive: false)
path = '/' if path.blank? path = '/' if path.blank?
@repository = repository @repository = repository
@sha = sha @sha = sha
@path = path @path = path
@recursive = recursive
git_repo = @repository.raw_repository git_repo = @repository.raw_repository
@entries = Gitlab::Git::Tree.where(git_repo, @sha, @path) @entries = get_entries(git_repo, @sha, @path, recursive: @recursive)
end end
def readme def readme
...@@ -58,4 +59,21 @@ class Tree ...@@ -58,4 +59,21 @@ class Tree
def sorted_entries def sorted_entries
trees + blobs + submodules trees + blobs + submodules
end end
private
def get_entries(git_repo, sha, path, recursive: false)
current_path_entries = Gitlab::Git::Tree.where(git_repo, sha, path)
ordered_entries = []
current_path_entries.each do |entry|
ordered_entries << entry
if recursive && entry.dir?
ordered_entries.concat(get_entries(git_repo, sha, entry.path, recursive: true))
end
end
ordered_entries
end
end end
require_relative 'base_service'
class DeleteMergedBranchesService < BaseService
def async_execute
DeleteMergedBranchesWorker.perform_async(project.id, current_user.id)
end
def execute
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :push_code, project)
branches = project.repository.branch_names
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch)
end
end
end
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
= render "shared/empty_states/todos_all_done.svg" = render "shared/empty_states/todos_all_done.svg"
- if todos_filter_empty? - if todos_filter_empty?
%h4.text-center %h4.text-center
Good job! Looks like you don't have any todos left. = Gitlab.config.gitlab.no_todos_messages.sample
%p.text-center %p.text-center
Are you looking for things to do? Take a look at Are you looking for things to do? Take a look at
= succeed "," do = succeed "," do
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
= sort_title_oldest_updated = sort_title_oldest_updated
- if can? current_user, :push_code, @project - if can? current_user, :push_code, @project
= link_to namespace_project_merged_branches_path(@project.namespace, @project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
Delete merged branches
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
New branch New branch
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
- elsif diff_file.renamed_file - elsif diff_file.renamed_file
.nothing-here-block File moved .nothing-here-block File moved
- elsif blob.image? - elsif blob.image?
- old_blob = diff_file.old_blob(diff_commit) - old_blob = diff_file.old_blob(diff_file.old_content_commit || @base_commit)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob
- else - else
.nothing-here-block No preview for this file type .nothing-here-block No preview for this file type
...@@ -76,6 +76,16 @@ ...@@ -76,6 +76,16 @@
script: script:
- "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
%h5.prepend-top-default %h5.prepend-top-default
Use webhook
%p.light
Add the following webhook to another project for Push and Tag push events.
The project will be rebuilt at the corresponding event.
%pre
:plain
#{builds_trigger_url(@project.id, ref: 'REF_NAME')}?token=TOKEN
%h5.prepend-top-default
Pass build variables Pass build variables
%p.light %p.light
...@@ -83,10 +93,18 @@ ...@@ -83,10 +93,18 @@
%code variables[VARIABLE]=VALUE %code variables[VARIABLE]=VALUE
to an API request. Variable values can be used to distinguish between triggered builds and normal builds. to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
%pre.append-bottom-0 With cURL:
%pre
:plain :plain
curl -X POST \ curl -X POST \
-F token=TOKEN \ -F token=TOKEN \
-F "ref=REF_NAME" \ -F "ref=REF_NAME" \
-F "variables[RUN_NIGHTLY_BUILD]=true" \ -F "variables[RUN_NIGHTLY_BUILD]=true" \
#{builds_trigger_url(@project.id)} #{builds_trigger_url(@project.id)}
%p.light
With webhook:
%pre.append-bottom-0
:plain
#{builds_trigger_url(@project.id, ref: 'REF_NAME')}?token=TOKEN&variables[RUN_NIGHTLY_BUILD]=true
class DeleteMergedBranchesWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
def perform(project_id, user_id)
begin
project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound
return
end
user = User.find(user_id)
begin
DeleteMergedBranchesService.new(project, user).execute
rescue Gitlab::Access::AccessDeniedError
return
end
end
end
---
title: Add setting to only allow merge requests to be merged when all discussions are resolved
merge_request: 7125
author: Rodolfo Arruda
---
title: Add button to delete all merged branches
merge_request: 6449
author: Toon Claes
---
title: Use the Gitlab Workhorse HTTP header in the admin dashboard
merge_request:
author: Chris Wright
---
title: 'Fix: Todos Filter Shows All Users'
merge_request:
author:
---
title: Issues atom feed url reflect filters on dashboard
merge_request: 7114
author: Lucas Deschamps
---
title: Rewrite git blame spinach feature tests to rspec feature tests
merge_request: 7197
author: Lisanne Fellinger
---
title: Make it possible to trigger builds from webhooks
merge_request: 7022
author: Dmitry Poray
---
title: Add query param to filter users by external & blocked type
merge_request: 7109
author: Yatish Mehta
---
title: Only skip group when it's actually a group in the "Share with group" select
merge_request: 7262
author:
---
title: 'Fix: Guest sees some repository details and gets 404'
merge_request:
author:
---
title: Introduce round-robin project creation to spread load over multiple shards
merge_request: 7266
author:
---
title: Ensure merge request's "remove branch" accessors return booleans
merge_request: 7267
author:
---
title: Fix broken commits search
merge_request:
author:
---
title: Adds es6-promise Polyfill
merge_request: 7482
author:
---
title: Expose label IDs in API
merge_request: 7275
author: Rares Sfirlogea
---
title: Add an index for project_id in project_import_data to improve performance
merge_request:
author:
---
title: "API: Ability to retrieve version information"
merge_request: 7286
author: Robert Schilling
---
title: Return 400 when creating a system hook fails
merge_request: 7350
author: Robert Schilling
---
title: Fix broken link to observatory cli on Frontend Dev Guide
merge_request:
author: Sam Rose
---
title: Faster search inside Project
merge_request:
author:
---
title: Fix 404 on network page when entering non-existent git revision
merge_request: 7172
author: Hiroyuki Sato
---
title: Fix invalid filename validation on eslint
merge_request: 7281
author:
---
title: Give search-input correct padding-right value
merge_request: 7407
author: Philip Karpiak
---
title: Clicking "force remove source branch" label now toggles the checkbox again
merge_request:
author:
---
title: Omniauth auto link LDAP user falls back to find by DN when user cannot be found
by UID
merge_request: 7002
author:
---
title: Finer-grained Git gargage collection
merge_request: 6588
author:
---
title: Show random messages when the To Do list is empty
merge_request: 6818
author: Josep Llaneras
---
title: Allow to test JIRA service settings without having a repository
merge_request:
author:
---
title: Introduce better credential and error checking to `rake gitlab:ldap:check`
merge_request: 6601
author:
---
title: API: allow recursive tree request
merge_request: 6088
author: Rebeca Méndez
---
title: Add CI notifications. Who triggered a pipeline would receive an email after
the pipeline is succeeded or failed. Users could also update notification settings
accordingly
merge_request: 6342
author:
---
title: Process commits using a dedicated Sidekiq worker
merge_request: 6802
author:
---
title: Remove an extra leading space from diff paste data
merge_request: 7133
author: Hiroyuki Sato
---
title: Bump omniauth-gitlab to 1.0.2 to fix incompatibility with omniauth-oauth2
merge_request:
author:
---
title: Fix showing pipeline status for a given commit from correct branch
merge_request: 7034
author:
---
title: Set default Sidekiq retries to 3
merge_request: 7294
author:
---
title: Fix Error 500 when creating a merge request that contains an image that was deleted and added
merge_request: 7457
author:
---
title: Replace jQuery.timeago with timeago.js
merge_request: 6274
author: ClemMakesApps
---
title: Use separate email-token for incoming email and revert back the inactive feature
merge_request: 5914
author:
...@@ -215,6 +215,7 @@ Settings.gitlab.default_projects_features['visibility_level'] = Settings.send( ...@@ -215,6 +215,7 @@ Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(
Settings.gitlab['domain_whitelist'] ||= [] Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project] Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project]
Settings.gitlab['trusted_proxies'] ||= [] Settings.gitlab['trusted_proxies'] ||= []
Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml'))
# #
# CI # CI
......
# When the Todos list on the user's dashboard becomes empty, one of the messages below shows up randomly.
#
# If you come up with a fun one, please feel free to contribute it to GitLab!
# https://about.gitlab.com/contributing/
---
- Good job! Looks like you don't have any todos left.
- Coffee really tastes better without any todos left.
- Isn't an empty To Do list beautiful?
- Time for a rewarding coffee break
- Give yourself a pat on the back!
- High five!
- Hence forth you shall be known as 'Todo Destroyer'
\ No newline at end of file
...@@ -125,6 +125,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: ...@@ -125,6 +125,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
end end
resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
delete :merged_branches, controller: 'branches', action: :destroy_all_merged
resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do
resource :release, only: [:edit, :update] resource :release, only: [:edit, :update]
end end
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
- [project_service, 1] - [project_service, 1]
- [clear_database_cache, 1] - [clear_database_cache, 1]
- [delete_user, 1] - [delete_user, 1]
- [delete_merged_branches, 1]
- [expire_build_instance_artifacts, 1] - [expire_build_instance_artifacts, 1]
- [group_destroy, 1] - [group_destroy, 1]
- [irker, 1] - [irker, 1]
......
...@@ -240,3 +240,21 @@ Example response: ...@@ -240,3 +240,21 @@ Example response:
"branch_name": "newbranch" "branch_name": "newbranch"
} }
``` ```
## Delete merged branches
Will delete all branches that are merged into the project's default branch.
```
DELETE /projects/:id/repository/merged_branches
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
It returns `200` to indicate deletion of all merged branches was started.
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/merged_branches"
```
...@@ -13,44 +13,58 @@ Parameters: ...@@ -13,44 +13,58 @@ Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `path` (optional) - The path inside repository. Used to get contend of subdirectories - `path` (optional) - The path inside repository. Used to get contend of subdirectories
- `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch - `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
- `recursive` (optional) - Boolean value used to get a recursive tree (false by default)
```json ```json
[ [
{ {
"name": "assets", "id": "a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba",
"name": "html",
"type": "tree", "type": "tree",
"mode": "040000", "path": "files/html",
"id": "6229c43a7e16fcc7e95f923f8ddadb8281d9c6c6" "mode": "040000"
}, },
{ {
"name": "contexts", "id": "4535904260b1082e14f867f7a24fd8c21495bde3",
"name": "images",
"type": "tree", "type": "tree",
"mode": "040000", "path": "files/images",
"id": "faf1cdf33feadc7973118ca42d35f1e62977e91f" "mode": "040000"
}, },
{ {
"name": "controllers", "id": "31405c5ddef582c5a9b7a85230413ff90e2fe720",
"name": "js",
"type": "tree", "type": "tree",
"mode": "040000", "path": "files/js",
"id": "95633e8d258bf3dfba3a5268fb8440d263218d74" "mode": "040000"
}, },
{ {
"name": "Rakefile", "id": "cc71111cfad871212dc99572599a568bfe1e7e00",
"type": "blob", "name": "lfs",
"mode": "100644", "type": "tree",
"id": "35b2f05cbb4566b71b34554cf184a9d0bd9d46d6" "path": "files/lfs",
"mode": "040000"
}, },
{ {
"name": "VERSION", "id": "fd581c619bf59cfdfa9c8282377bb09c2f897520",
"type": "blob", "name": "markdown",
"mode": "100644", "type": "tree",
"id": "803e4a4f3727286c3093c63870c2b6524d30ec4f" "path": "files/markdown",
"mode": "040000"
},
{
"id": "23ea4d11a4bdd960ee5320c5cb65b5b3fdbc60db",
"name": "ruby",
"type": "tree",
"path": "files/ruby",
"mode": "040000"
}, },
{ {
"name": "config.ru", "id": "7d70e02340bac451f281cecf0a980907974bd8be",
"name": "whitespace",
"type": "blob", "type": "blob",
"mode": "100644", "path": "files/whitespace",
"id": "dfd2d862237323aa599be31b473d70a8a817943b" "mode": "100644"
} }
] ]
``` ```
......
...@@ -58,6 +58,22 @@ below. ...@@ -58,6 +58,22 @@ below.
See the [Examples](#examples) section for more details on how to actually See the [Examples](#examples) section for more details on how to actually
trigger a rebuild. trigger a rebuild.
## Trigger a build from webhook
> Introduced in GitLab 8.14.
To trigger a build from webhook of another project you need to add the following
webhook url for Push and Tag push events:
```
https://gitlab.example.com/api/v3/projects/:id/ref/:ref/trigger/builds?token=TOKEN
```
> **Note**:
- `ref` should be passed as part of url in order to take precedence over `ref`
from webhook body that designates the branchref that fired the trigger in the source repository.
- `ref` should be url encoded if contains slashes.
## Pass build variables to a trigger ## Pass build variables to a trigger
You can pass any number of arbitrary variables in the trigger API call and they You can pass any number of arbitrary variables in the trigger API call and they
...@@ -169,6 +185,14 @@ curl --request POST \ ...@@ -169,6 +185,14 @@ curl --request POST \
https://gitlab.example.com/api/v3/projects/9/trigger/builds https://gitlab.example.com/api/v3/projects/9/trigger/builds
``` ```
### Using webhook to trigger builds
You can add the following webhook to another project in order to trigger a build:
```
https://gitlab.example.com/api/v3/projects/9/ref/master/trigger/builds?token=TOKEN&variables[UPLOAD_TO_S3]=true
```
### Using cron to trigger nightly builds ### Using cron to trigger nightly builds
Whether you craft a script or just run cURL directly, you can trigger builds Whether you craft a script or just run cURL directly, you can trigger builds
......
...@@ -48,7 +48,11 @@ at Super User also has relevant information. ...@@ -48,7 +48,11 @@ at Super User also has relevant information.
**Omnibus Trusted Chain** **Omnibus Trusted Chain**
It is enough to concatenate the certificate to the main trusted certificate: [Install the self signed certificate or custom certificate authorities](http://docs.gitlab.com/omnibus/common_installation_problems/README.html#using-self-signed-certificate-or-custom-certificate-authorities)
in to GitLab Omnibus.
It is enough to concatenate the certificate to the main trusted certificate
however it may be overwritten during upgrades:
```bash ```bash
cat jira.pem >> /opt/gitlab/embedded/ssl/certs/cacert.pem cat jira.pem >> /opt/gitlab/embedded/ssl/certs/cacert.pem
......
...@@ -48,7 +48,7 @@ module API ...@@ -48,7 +48,7 @@ module API
put ':id/access_requests/:user_id/approve' do put ':id/access_requests/:user_id/approve' do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
member = ::Members::ApproveAccessRequestService.new(source, current_user, declared(params)).execute member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute
status :created status :created
present member.user, with: Entities::Member, member: member present member.user, with: Entities::Member, member: member
......
...@@ -128,6 +128,18 @@ module API ...@@ -128,6 +128,18 @@ module API
render_api_error!(result[:message], result[:return_code]) render_api_error!(result[:message], result[:return_code])
end end
end end
# Delete all merged branches
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# DELETE /projects/:id/repository/branches/delete_merged
delete ":id/repository/merged_branches" do
DeleteMergedBranchesService.new(user_project, current_user).async_execute
status(200)
end
end end
end end
end end
...@@ -36,8 +36,7 @@ module API ...@@ -36,8 +36,7 @@ module API
optional :font, type: String, desc: 'Foreground color' optional :font, type: String, desc: 'Foreground color'
end end
post do post do
create_params = declared(params, include_missing: false).to_h message = BroadcastMessage.create(declared_params(include_missing: false))
message = BroadcastMessage.create(create_params)
if message.persisted? if message.persisted?
present message, with: Entities::BroadcastMessage present message, with: Entities::BroadcastMessage
...@@ -73,9 +72,8 @@ module API ...@@ -73,9 +72,8 @@ module API
end end
put ':id' do put ':id' do
message = find_message message = find_message
update_params = declared(params, include_missing: false).to_h
if message.update(update_params) if message.update(declared_params(include_missing: false))
present message, with: Entities::BroadcastMessage present message, with: Entities::BroadcastMessage
else else
render_validation_error!(message) render_validation_error!(message)
......
...@@ -53,7 +53,7 @@ module API ...@@ -53,7 +53,7 @@ module API
post ":id/repository/commits" do post ":id/repository/commits" do
authorize! :push_code, user_project authorize! :push_code, user_project
attrs = declared(params) attrs = declared_params
attrs[:source_branch] = attrs[:branch_name] attrs[:source_branch] = attrs[:branch_name]
attrs[:target_branch] = attrs[:branch_name] attrs[:target_branch] = attrs[:branch_name]
attrs[:actions].map! do |action| attrs[:actions].map! do |action|
......
...@@ -82,7 +82,7 @@ module API ...@@ -82,7 +82,7 @@ module API
end end
post ":id/#{path}/:key_id/enable" do post ":id/#{path}/:key_id/enable" do
key = ::Projects::EnableDeployKeyService.new(user_project, key = ::Projects::EnableDeployKeyService.new(user_project,
current_user, declared(params)).execute current_user, declared_params).execute
if key if key
present key, with: Entities::SSHKey present key, with: Entities::SSHKey
......
...@@ -159,7 +159,7 @@ module API ...@@ -159,7 +159,7 @@ module API
end end
class RepoTreeObject < Grape::Entity class RepoTreeObject < Grape::Entity
expose :id, :name, :type expose :id, :name, :type, :path
expose :mode do |obj, options| expose :mode do |obj, options|
filemode = obj.mode.to_s(8) filemode = obj.mode.to_s(8)
......
...@@ -32,8 +32,7 @@ module API ...@@ -32,8 +32,7 @@ module API
post ':id/environments' do post ':id/environments' do
authorize! :create_environment, user_project authorize! :create_environment, user_project
create_params = declared(params, include_parent_namespaces: false).to_h environment = user_project.environments.create(declared_params)
environment = user_project.environments.create(create_params)
if environment.persisted? if environment.persisted?
present environment, with: Entities::Environment present environment, with: Entities::Environment
...@@ -56,7 +55,7 @@ module API ...@@ -56,7 +55,7 @@ module API
environment = user_project.environments.find(params[:environment_id]) environment = user_project.environments.find(params[:environment_id])
update_params = declared(params, include_missing: false).extract!(:name, :external_url).to_h update_params = declared_params(include_missing: false).extract!(:name, :external_url)
if environment.update(update_params) if environment.update(update_params)
present environment, with: Entities::Environment present environment, with: Entities::Environment
else else
......
module API module API
# groups API
class Groups < Grape::API class Groups < Grape::API
before { authenticate! } before { authenticate! }
helpers do
params :optional_params do
optional :description, type: String, desc: 'The description of the group'
optional :visibility_level, type: Integer, desc: 'The visibility level of the group'
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
end
end
resource :groups do resource :groups do
# Get a groups list desc 'Get a groups list' do
# success Entities::Group
# Parameters: end
# skip_groups (optional) - Array of group ids to exclude from list params do
# all_available (optional, boolean) - Show all group that you have access to optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
# Example Request: optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
# GET /groups optional :search, type: String, desc: 'Search for a specific group'
end
get do get do
@groups = if current_user.admin groups = if current_user.admin
Group.all Group.all
elsif params[:all_available] elsif params[:all_available]
GroupsFinder.new.execute(current_user) GroupsFinder.new.execute(current_user)
...@@ -20,99 +29,83 @@ module API ...@@ -20,99 +29,83 @@ module API
current_user.groups current_user.groups
end end
@groups = @groups.search(params[:search]) if params[:search].present? groups = groups.search(params[:search]) if params[:search].present?
@groups = @groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
@groups = paginate @groups present paginate(groups), with: Entities::Group
present @groups, with: Entities::Group
end end
# Get list of owned groups for authenticated user desc 'Get list of owned groups for authenticated user' do
# success Entities::Group
# Example Request: end
# GET /groups/owned
get '/owned' do get '/owned' do
@groups = current_user.owned_groups groups = current_user.owned_groups
@groups = paginate @groups present paginate(groups), with: Entities::Group, user: current_user
present @groups, with: Entities::Group, user: current_user
end end
# Create group. Available only for users who can create groups. desc 'Create a group. Available only for users who can create groups.' do
# success Entities::Group
# Parameters: end
# name (required) - The name of the group params do
# path (required) - The path of the group requires :name, type: String, desc: 'The name of the group'
# description (optional) - The description of the group requires :path, type: String, desc: 'The path of the group'
# visibility_level (optional) - The visibility level of the group use :optional_params
# lfs_enabled (optional) - Enable/disable LFS for the projects in this group end
# request_access_enabled (optional) - Allow users to request member access
# Example Request:
# POST /groups
post do post do
authorize! :create_group authorize! :create_group
required_attributes! [:name, :path]
attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
@group = Group.new(attrs)
if @group.save if group.persisted?
@group.add_owner(current_user) present group, with: Entities::Group
present @group, with: Entities::Group
else else
render_api_error!("Failed to save group #{@group.errors.messages}", 400) render_api_error!("Failed to save group #{group.errors.messages}", 400)
end
end end
end end
# Update group. Available only for users who can administrate groups. params do
# requires :id, type: String, desc: 'The ID of a group'
# Parameters: end
# id (required) - The ID of a group resource :groups do
# path (optional) - The path of the group desc 'Update a group. Available only for users who can administrate groups.' do
# description (optional) - The description of the group success Entities::Group
# visibility_level (optional) - The visibility level of the group end
# lfs_enabled (optional) - Enable/disable LFS for the projects in this group params do
# request_access_enabled (optional) - Allow users to request member access optional :name, type: String, desc: 'The name of the group'
# Example Request: optional :path, type: String, desc: 'The path of the group'
# PUT /groups/:id use :optional_params
at_least_one_of :name, :path, :description, :visibility_level,
:lfs_enabled, :request_access_enabled
end
put ':id' do put ':id' do
group = find_group(params[:id]) group = find_group(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
if ::Groups::UpdateService.new(group, current_user, attrs).execute
present group, with: Entities::GroupDetail present group, with: Entities::GroupDetail
else else
render_validation_error!(group) render_validation_error!(group)
end end
end end
# Get a single group, with containing projects desc 'Get a single group, with containing projects.' do
# success Entities::GroupDetail
# Parameters: end
# id (required) - The ID of a group
# Example Request:
# GET /groups/:id
get ":id" do get ":id" do
group = find_group(params[:id]) group = find_group(params[:id])
present group, with: Entities::GroupDetail present group, with: Entities::GroupDetail
end end
# Remove group desc 'Remove a group.'
#
# Parameters:
# id (required) - The ID of a group
# Example Request:
# DELETE /groups/:id
delete ":id" do delete ":id" do
group = find_group(params[:id]) group = find_group(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
DestroyGroupService.new(group, current_user).execute DestroyGroupService.new(group, current_user).execute
end end
# Get a list of projects in this group desc 'Get a list of projects in this group.' do
# success Entities::Project
# Example Request: end
# GET /groups/:id/projects
get ":id/projects" do get ":id/projects" do
group = find_group(params[:id]) group = find_group(params[:id])
projects = GroupProjectsFinder.new(group).execute(current_user) projects = GroupProjectsFinder.new(group).execute(current_user)
...@@ -120,13 +113,12 @@ module API ...@@ -120,13 +113,12 @@ module API
present projects, with: Entities::Project, user: current_user present projects, with: Entities::Project, user: current_user
end end
# Transfer a project to the Group namespace desc 'Transfer a project to the group namespace. Available only for admin.' do
# success Entities::GroupDetail
# Parameters: end
# id - group id params do
# project_id - project id requires :project_id, type: String, desc: 'The ID of the project'
# Example Request: end
# POST /groups/:id/projects/:project_id
post ":id/projects/:project_id" do post ":id/projects/:project_id" do
authenticated_as_admin! authenticated_as_admin!
group = Group.find_by(id: params[:id]) group = Group.find_by(id: params[:id])
...@@ -134,7 +126,7 @@ module API ...@@ -134,7 +126,7 @@ module API
result = ::Projects::TransferService.new(project, current_user).execute(group) result = ::Projects::TransferService.new(project, current_user).execute(group)
if result if result
present group present group, with: Entities::GroupDetail
else else
render_api_error!("Failed to transfer project #{project.errors.messages}", 400) render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
end end
......
...@@ -30,10 +30,7 @@ module API ...@@ -30,10 +30,7 @@ module API
conflict!('Label already exists') if label conflict!('Label already exists') if label
priority = params.delete(:priority) priority = params.delete(:priority)
label_params = declared(params, label = user_project.labels.create(declared_params(include_missing: false))
include_parent_namespaces: false,
include_missing: false).to_h
label = user_project.labels.create(label_params)
if label.valid? if label.valid?
label.prioritize!(user_project, priority) if priority label.prioritize!(user_project, priority) if priority
...@@ -77,11 +74,9 @@ module API ...@@ -77,11 +74,9 @@ module API
update_priority = params.key?(:priority) update_priority = params.key?(:priority)
priority = params.delete(:priority) priority = params.delete(:priority)
label_params = declared(params, label_params = declared_params(include_missing: false)
include_parent_namespaces: false,
include_missing: false).to_h
# Rename new name to the actual label attribute name # Rename new name to the actual label attribute name
label_params[:name] = label_params.delete('new_name') if label_params.key?('new_name') label_params[:name] = label_params.delete(:new_name) if label_params.key?(:new_name)
render_validation_error!(label) unless label.update(label_params) render_validation_error!(label) unless label.update(label_params)
......
...@@ -120,7 +120,7 @@ module API ...@@ -120,7 +120,7 @@ module API
if member.nil? if member.nil?
{ message: "Access revoked", id: params[:user_id].to_i } { message: "Access revoked", id: params[:user_id].to_i }
else else
::Members::DestroyService.new(source, current_user, declared(params)).execute ::Members::DestroyService.new(source, current_user, declared_params).execute
present member.user, with: Entities::Member, member: member present member.user, with: Entities::Member, member: member
end end
......
...@@ -5,23 +5,23 @@ module API ...@@ -5,23 +5,23 @@ module API
NOTEABLE_TYPES = [Issue, MergeRequest, Snippet] NOTEABLE_TYPES = [Issue, MergeRequest, Snippet]
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do resource :projects do
NOTEABLE_TYPES.each do |noteable_type| NOTEABLE_TYPES.each do |noteable_type|
noteables_str = noteable_type.to_s.underscore.pluralize noteables_str = noteable_type.to_s.underscore.pluralize
noteable_id_str = "#{noteable_type.to_s.underscore}_id"
desc 'Get a list of project +noteable+ notes' do
# Get a list of project +noteable+ notes success Entities::Note
# end
# Parameters: params do
# id (required) - The ID of a project requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
# noteable_id (required) - The ID of an issue or snippet end
# Example Request: get ":id/#{noteables_str}/:noteable_id/notes" do
# GET /projects/:id/issues/:noteable_id/notes noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
# GET /projects/:id/snippets/:noteable_id/notes
get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do if can?(current_user, noteable_read_ability_name(noteable), noteable)
@noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym])
if can?(current_user, noteable_read_ability_name(@noteable), @noteable)
# We exclude notes that are cross-references and that cannot be viewed # We exclude notes that are cross-references and that cannot be viewed
# by the current user. By doing this exclusion at this level and not # by the current user. By doing this exclusion at this level and not
# at the DB query level (which we cannot in that case), the current # at the DB query level (which we cannot in that case), the current
...@@ -31,7 +31,7 @@ module API ...@@ -31,7 +31,7 @@ module API
# paginate() only works with a relation. This could lead to a # paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes # mismatch between the pagination headers info and the actual notes
# array returned, but this is really a edge-case. # array returned, but this is really a edge-case.
paginate(@noteable.notes). paginate(noteable.notes).
reject { |n| n.cross_reference_not_visible_for?(current_user) } reject { |n| n.cross_reference_not_visible_for?(current_user) }
present notes, with: Entities::Note present notes, with: Entities::Note
else else
...@@ -39,44 +39,40 @@ module API ...@@ -39,44 +39,40 @@ module API
end end
end end
# Get a single +noteable+ note desc 'Get a single +noteable+ note' do
# success Entities::Note
# Parameters: end
# id (required) - The ID of a project params do
# noteable_id (required) - The ID of an issue or snippet requires :note_id, type: Integer, desc: 'The ID of a note'
# note_id (required) - The ID of a note requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
# Example Request: end
# GET /projects/:id/issues/:noteable_id/notes/:note_id get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
# GET /projects/:id/snippets/:noteable_id/notes/:note_id noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do note = noteable.notes.find(params[:note_id])
@noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym]) can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
@note = @noteable.notes.find(params[:note_id])
can_read_note = can?(current_user, noteable_read_ability_name(@noteable), @noteable) && !@note.cross_reference_not_visible_for?(current_user)
if can_read_note if can_read_note
present @note, with: Entities::Note present note, with: Entities::Note
else else
not_found!("Note") not_found!("Note")
end end
end end
# Create a new +noteable+ note desc 'Create a new +noteable+ note' do
# success Entities::Note
# Parameters: end
# id (required) - The ID of a project params do
# noteable_id (required) - The ID of an issue or snippet requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
# body (required) - The content of a note requires :body, type: String, desc: 'The content of a note'
# created_at (optional) - The date optional :created_at, type: String, desc: 'The creation date of the note'
# Example Request: end
# POST /projects/:id/issues/:noteable_id/notes post ":id/#{noteables_str}/:noteable_id/notes" do
# POST /projects/:id/snippets/:noteable_id/notes
post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
required_attributes! [:body] required_attributes! [:body]
opts = { opts = {
note: params[:body], note: params[:body],
noteable_type: noteables_str.classify, noteable_type: noteables_str.classify,
noteable_id: params[noteable_id_str] noteable_id: params[:noteable_id]
} }
if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
...@@ -92,19 +88,15 @@ module API ...@@ -92,19 +88,15 @@ module API
end end
end end
# Modify existing +noteable+ note desc 'Update an existing +noteable+ note' do
# success Entities::Note
# Parameters: end
# id (required) - The ID of a project params do
# noteable_id (required) - The ID of an issue or snippet requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
# node_id (required) - The ID of a note requires :note_id, type: Integer, desc: 'The ID of a note'
# body (required) - New content of a note requires :body, type: String, desc: 'The content of a note'
# Example Request: end
# PUT /projects/:id/issues/:noteable_id/notes/:note_id put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
# PUT /projects/:id/snippets/:noteable_id/notes/:node_id
put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
required_attributes! [:body]
note = user_project.notes.find(params[:note_id]) note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note authorize! :admin_note, note
...@@ -113,25 +105,23 @@ module API ...@@ -113,25 +105,23 @@ module API
note: params[:body] note: params[:body]
} }
@note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note) note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note)
if @note.valid? if note.valid?
present @note, with: Entities::Note present note, with: Entities::Note
else else
render_api_error!("Failed to save note #{note.errors.messages}", 400) render_api_error!("Failed to save note #{note.errors.messages}", 400)
end end
end end
# Delete a +noteable+ note desc 'Delete a +noteable+ note' do
# success Entities::Note
# Parameters: end
# id (required) - The ID of a project params do
# noteable_id (required) - The ID of an issue, MR, or snippet requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
# node_id (required) - The ID of a note requires :note_id, type: Integer, desc: 'The ID of a note'
# Example Request: end
# DELETE /projects/:id/issues/:noteable_id/notes/:note_id delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
# DELETE /projects/:id/snippets/:noteable_id/notes/:node_id
delete ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
note = user_project.notes.find(params[:note_id]) note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note authorize! :admin_note, note
......
...@@ -33,10 +33,9 @@ module API ...@@ -33,10 +33,9 @@ module API
begin begin
notification_setting.transaction do notification_setting.transaction do
new_notification_email = params.delete(:notification_email) new_notification_email = params.delete(:notification_email)
declared_params = declared(params, include_missing: false).to_h
current_user.update(notification_email: new_notification_email) if new_notification_email current_user.update(notification_email: new_notification_email) if new_notification_email
notification_setting.update(declared_params) notification_setting.update(declared_params(include_missing: false))
end end
rescue ArgumentError => e # catch level enum error rescue ArgumentError => e # catch level enum error
render_api_error! e.to_s, 400 render_api_error! e.to_s, 400
...@@ -81,9 +80,7 @@ module API ...@@ -81,9 +80,7 @@ module API
notification_setting = current_user.notification_settings_for(source) notification_setting = current_user.notification_settings_for(source)
begin begin
declared_params = declared(params, include_missing: false).to_h notification_setting.update(declared_params(include_missing: false))
notification_setting.update(declared_params)
rescue ArgumentError => e # catch level enum error rescue ArgumentError => e # catch level enum error
render_api_error! e.to_s, 400 render_api_error! e.to_s, 400
end end
......
...@@ -51,8 +51,7 @@ module API ...@@ -51,8 +51,7 @@ module API
use :project_hook_properties use :project_hook_properties
end end
post ":id/hooks" do post ":id/hooks" do
new_hook_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h hook = user_project.hooks.new(declared_params(include_missing: false))
hook = user_project.hooks.new(new_hook_params)
if hook.save if hook.save
present hook, with: Entities::ProjectHook present hook, with: Entities::ProjectHook
...@@ -71,12 +70,9 @@ module API ...@@ -71,12 +70,9 @@ module API
use :project_hook_properties use :project_hook_properties
end end
put ":id/hooks/:hook_id" do put ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params[:hook_id]) hook = user_project.hooks.find(params.delete(:hook_id))
new_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h
new_params.delete('hook_id')
if hook.update_attributes(new_params) if hook.update_attributes(declared_params(include_missing: false))
present hook, with: Entities::ProjectHook present hook, with: Entities::ProjectHook
else else
error!("Invalid url given", 422) if hook.errors[:url].present? error!("Invalid url given", 422) if hook.errors[:url].present?
......
...@@ -21,16 +21,18 @@ module API ...@@ -21,16 +21,18 @@ module API
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
# recursive (optional) - Used to get a recursive tree
# Example Request: # Example Request:
# GET /projects/:id/repository/tree # GET /projects/:id/repository/tree
get ':id/repository/tree' do get ':id/repository/tree' do
ref = params[:ref_name] || user_project.try(:default_branch) || 'master' ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
path = params[:path] || nil path = params[:path] || nil
recursive = to_boolean(params[:recursive])
commit = user_project.commit(ref) commit = user_project.commit(ref)
not_found!('Tree') unless commit not_found!('Tree') unless commit
tree = user_project.repository.tree(commit.id, path) tree = user_project.repository.tree(commit.id, path, recursive: recursive)
present tree.sorted_entries, with: Entities::RepoTreeObject present tree.sorted_entries, with: Entities::RepoTreeObject
end end
......
...@@ -27,7 +27,7 @@ module API ...@@ -27,7 +27,7 @@ module API
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
end end
post do post do
hook = SystemHook.new declared(params, include_missing: false).to_h hook = SystemHook.new(declared_params(include_missing: false))
if hook.save if hook.save
present hook, with: Entities::Hook present hook, with: Entities::Hook
......
...@@ -40,10 +40,9 @@ module API ...@@ -40,10 +40,9 @@ module API
end end
post ':id/repository/tags' do post ':id/repository/tags' do
authorize_push_project authorize_push_project
create_params = declared(params)
result = CreateTagService.new(user_project, current_user). result = CreateTagService.new(user_project, current_user).
execute(create_params[:tag_name], create_params[:ref], create_params[:message], create_params[:release_description]) execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
if result[:status] == :success if result[:status] == :success
present result[:tag], present result[:tag],
......
...@@ -12,7 +12,7 @@ module API ...@@ -12,7 +12,7 @@ module API
requires :token, type: String, desc: 'The unique token of trigger' requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build' optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end end
post ":id/trigger/builds" do post ":id/(ref/:ref/)trigger/builds" do
project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s) trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger not_found! unless project && trigger
......
...@@ -335,7 +335,7 @@ module API ...@@ -335,7 +335,7 @@ module API
requires :id, type: String, desc: 'The user ID' requires :id, type: String, desc: 'The user ID'
end end
get ':id/events' do get ':id/events' do
user = User.find_by(id: declared(params).id) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
events = user.events. events = user.events.
......
...@@ -55,6 +55,12 @@ module Gitlab ...@@ -55,6 +55,12 @@ module Gitlab
repository.commit(deleted_file ? old_ref : new_ref) repository.commit(deleted_file ? old_ref : new_ref)
end end
def old_content_commit
return unless diff_refs
repository.commit(old_ref)
end
def old_ref def old_ref
diff_refs.try(:base_sha) diff_refs.try(:base_sha)
end end
...@@ -111,13 +117,10 @@ module Gitlab ...@@ -111,13 +117,10 @@ module Gitlab
diff_lines.count(&:removed?) diff_lines.count(&:removed?)
end end
def old_blob(commit = content_commit) def old_blob(commit = old_content_commit)
return unless commit return unless commit
parent_id = commit.parent_id repository.blob_at(commit.id, old_path)
return unless parent_id
repository.blob_at(parent_id, old_path)
end end
def blob(commit = content_commit) def blob(commit = content_commit)
......
...@@ -102,6 +102,8 @@ module Gitlab ...@@ -102,6 +102,8 @@ module Gitlab
Gitlab::LDAP::Config.providers.each do |provider| Gitlab::LDAP::Config.providers.each do |provider|
adapter = Gitlab::LDAP::Adapter.new(provider) adapter = Gitlab::LDAP::Adapter.new(provider)
@ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter) @ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
# The `uid` might actually be a DN. Try it next.
@ldap_person ||= Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
break if @ldap_person break if @ldap_person
end end
@ldap_person @ldap_person
......
...@@ -3,11 +3,11 @@ require 'spec_helper' ...@@ -3,11 +3,11 @@ require 'spec_helper'
describe Projects::BranchesController do describe Projects::BranchesController do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:developer) { create(:user) }
before do before do
sign_in(user)
project.team << [user, :master] project.team << [user, :master]
project.team << [user, :developer]
allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz']) allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0']) allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
...@@ -19,6 +19,8 @@ describe Projects::BranchesController do ...@@ -19,6 +19,8 @@ describe Projects::BranchesController do
context "on creation of a new branch" do context "on creation of a new branch" do
before do before do
sign_in(user)
post :create, post :create,
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
project_id: project.to_param, project_id: project.to_param,
...@@ -68,6 +70,10 @@ describe Projects::BranchesController do ...@@ -68,6 +70,10 @@ describe Projects::BranchesController do
let(:branch) { "1-feature-branch" } let(:branch) { "1-feature-branch" }
let!(:issue) { create(:issue, project: project) } let!(:issue) { create(:issue, project: project) }
before do
sign_in(user)
end
it 'redirects' do it 'redirects' do
post :create, post :create,
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
...@@ -94,6 +100,10 @@ describe Projects::BranchesController do ...@@ -94,6 +100,10 @@ describe Projects::BranchesController do
describe "POST destroy with HTML format" do describe "POST destroy with HTML format" do
render_views render_views
before do
sign_in(user)
end
it 'returns 303' do it 'returns 303' do
post :destroy, post :destroy,
format: :html, format: :html,
...@@ -109,6 +119,8 @@ describe Projects::BranchesController do ...@@ -109,6 +119,8 @@ describe Projects::BranchesController do
render_views render_views
before do before do
sign_in(user)
post :destroy, post :destroy,
format: :js, format: :js,
id: branch, id: branch,
...@@ -139,4 +151,42 @@ describe Projects::BranchesController do ...@@ -139,4 +151,42 @@ describe Projects::BranchesController do
it { expect(response).to have_http_status(404) } it { expect(response).to have_http_status(404) }
end end
end end
describe "DELETE destroy_all_merged" do
def destroy_all_merged
delete :destroy_all_merged,
namespace_id: project.namespace.to_param,
project_id: project.to_param
end
context 'when user is allowed to push' do
before do
sign_in(user)
end
it 'redirects to branches' do
destroy_all_merged
expect(response).to redirect_to namespace_project_branches_path(project.namespace, project)
end
it 'starts worker to delete merged branches' do
expect_any_instance_of(DeleteMergedBranchesService).to receive(:async_execute)
destroy_all_merged
end
end
context 'when user is not allowed to push' do
before do
sign_in(developer)
end
it 'responds with status 404' do
destroy_all_merged
expect(response).to have_http_status(404)
end
end
end
end end
...@@ -67,4 +67,14 @@ feature 'Create New Merge Request', feature: true, js: true do ...@@ -67,4 +67,14 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content('Source branch "non-exist-source" does not exist') expect(page).to have_content('Source branch "non-exist-source" does not exist')
expect(page).to have_content('Target branch "non-exist-target" does not exist') expect(page).to have_content('Target branch "non-exist-target" does not exist')
end end
context 'when a branch contains commits that both delete and add the same image' do
it 'renders the diff successfully' do
visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
click_link "Changes"
expect(page).to have_content "6049019_460s.jpg"
end
end
end end
...@@ -44,7 +44,7 @@ describe 'Dashboard Todos', feature: true do ...@@ -44,7 +44,7 @@ describe 'Dashboard Todos', feature: true do
end end
it 'shows "All done" message' do it 'shows "All done" message' do
expect(page).to have_content("Good job! Looks like you don't have any todos left.") expect(page).to have_selector('.todos-all-done', count: 1)
end end
end end
...@@ -64,7 +64,7 @@ describe 'Dashboard Todos', feature: true do ...@@ -64,7 +64,7 @@ describe 'Dashboard Todos', feature: true do
end end
it 'shows "All done" message' do it 'shows "All done" message' do
expect(page).to have_content("Good job! Looks like you don't have any todos left.") expect(page).to have_selector('.todos-all-done', count: 1)
end end
end end
end end
...@@ -152,7 +152,7 @@ describe 'Dashboard Todos', feature: true do ...@@ -152,7 +152,7 @@ describe 'Dashboard Todos', feature: true do
within('.todos-pending-count') { expect(page).to have_content '0' } within('.todos-pending-count') { expect(page).to have_content '0' }
expect(page).to have_content 'To do 0' expect(page).to have_content 'To do 0'
expect(page).to have_content 'Done 0' expect(page).to have_content 'Done 0'
expect(page).to have_content "Good job! Looks like you don't have any todos left." expect(page).to have_selector('.todos-all-done', count: 1)
end end
end end
end end
......
...@@ -46,4 +46,28 @@ describe Gitlab::Diff::File, lib: true do ...@@ -46,4 +46,28 @@ describe Gitlab::Diff::File, lib: true do
expect(diff_file.collapsed?).to eq(false) expect(diff_file.collapsed?).to eq(false)
end end
end end
describe '#old_content_commit' do
it 'returns base commit' do
old_content_commit = diff_file.old_content_commit
expect(old_content_commit.id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
end
end
describe '#old_blob' do
it 'returns blob of commit of base commit' do
old_data = diff_file.old_blob.data
expect(old_data).to include('raise "System commands must be given as an array of strings"')
end
end
describe '#blob' do
it 'returns blob of new commit' do
data = diff_file.blob.data
expect(data).to include('raise RuntimeError, "System commands must be given as an array of strings"')
end
end
end end
...@@ -137,11 +137,12 @@ describe Gitlab::OAuth::User, lib: true do ...@@ -137,11 +137,12 @@ describe Gitlab::OAuth::User, lib: true do
allow(ldap_user).to receive(:username) { uid } allow(ldap_user).to receive(:username) { uid }
allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] }
allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
end end
context "and no account for the LDAP user" do context "and no account for the LDAP user" do
it "creates a user with dual LDAP and omniauth identities" do it "creates a user with dual LDAP and omniauth identities" do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save oauth_user.save
expect(gl_user).to be_valid expect(gl_user).to be_valid
...@@ -159,6 +160,8 @@ describe Gitlab::OAuth::User, lib: true do ...@@ -159,6 +160,8 @@ describe Gitlab::OAuth::User, lib: true do
context "and LDAP user has an account already" do context "and LDAP user has an account already" do
let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
it "adds the omniauth identity to the LDAP account" do it "adds the omniauth identity to the LDAP account" do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save oauth_user.save
expect(gl_user).to be_valid expect(gl_user).to be_valid
...@@ -172,6 +175,24 @@ describe Gitlab::OAuth::User, lib: true do ...@@ -172,6 +175,24 @@ describe Gitlab::OAuth::User, lib: true do
]) ])
end end
end end
context 'when an LDAP person is not found by uid' do
it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil)
allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
oauth_user.save
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash)
.to match_array(
[
{ provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
{ provider: 'twitter', extern_uid: uid }
]
)
end
end
end end
context "and no corresponding LDAP person" do context "and no corresponding LDAP person" do
......
...@@ -299,4 +299,20 @@ describe API::API, api: true do ...@@ -299,4 +299,20 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Cannot remove HEAD branch') expect(json_response['message']).to eq('Cannot remove HEAD branch')
end end
end end
describe "DELETE /projects/:id/repository/merged_branches" do
before do
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
end
it 'returns 200' do
delete api("/projects/#{project.id}/repository/merged_branches", user)
expect(response).to have_http_status(200)
end
it 'returns a 403 error if guest' do
delete api("/projects/#{project.id}/repository/merged_branches", user2)
expect(response).to have_http_status(403)
end
end
end end
...@@ -167,7 +167,7 @@ describe API::API, api: true do ...@@ -167,7 +167,7 @@ describe API::API, api: true do
end end
it 'returns 404 for a non existing group' do it 'returns 404 for a non existing group' do
put api('/groups/1328', user1) put api('/groups/1328', user1), name: new_group_name
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
......
...@@ -494,12 +494,6 @@ describe API::API, api: true do ...@@ -494,12 +494,6 @@ describe API::API, api: true do
expect(json_response['milestone']['id']).to eq(milestone.id) expect(json_response['milestone']['id']).to eq(milestone.id)
end end
it "returns 400 when source_branch is specified" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
expect(response).to have_http_status(400)
end
it "returns merge_request with renamed target_branch" do it "returns merge_request with renamed target_branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
......
...@@ -18,6 +18,7 @@ describe API::API, api: true do ...@@ -18,6 +18,7 @@ describe API::API, api: true do
it "returns project commits" do it "returns project commits" do
get api("/projects/#{project.id}/repository/tree", user) get api("/projects/#{project.id}/repository/tree", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
...@@ -43,6 +44,40 @@ describe API::API, api: true do ...@@ -43,6 +44,40 @@ describe API::API, api: true do
end end
end end
describe 'GET /projects/:id/repository/tree?recursive=1' do
context 'authorized user' do
before { project.team << [user2, :reporter] }
it 'should return recursive project paths tree' do
get api("/projects/#{project.id}/repository/tree?recursive=1", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response[4]['name']).to eq('html')
expect(json_response[4]['path']).to eq('files/html')
expect(json_response[4]['type']).to eq('tree')
expect(json_response[4]['mode']).to eq('040000')
end
it 'returns a 404 for unknown ref' do
get api("/projects/#{project.id}/repository/tree?ref_name=foo&recursive=1", user)
expect(response).to have_http_status(404)
expect(json_response).to be_an Object
json_response['message'] == '404 Tree Not Found'
end
end
context "unauthorized user" do
it "does not return project commits" do
get api("/projects/#{project.id}/repository/tree?recursive=1")
expect(response).to have_http_status(401)
end
end
end
describe "GET /projects/:id/repository/blobs/:sha" do describe "GET /projects/:id/repository/blobs/:sha" do
it "gets the raw file contents" do it "gets the raw file contents" do
get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user) get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user)
......
...@@ -54,6 +54,13 @@ describe API::API do ...@@ -54,6 +54,13 @@ describe API::API do
expect(pipeline.builds.size).to eq(5) expect(pipeline.builds.size).to eq(5)
end end
it 'creates builds on webhook from other gitlab repository and branch' do
expect do
post api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(5)
expect(response).to have_http_status(201)
end
it 'returns bad request with no builds created if there\'s no commit for that ref' do it 'returns bad request with no builds created if there\'s no commit for that ref' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
......
require 'spec_helper'
describe DeleteMergedBranchesService, services: true do
subject(:service) { described_class.new(project, project.owner) }
let(:project) { create(:project) }
context '#execute' do
context 'unprotected branches' do
before do
service.execute
end
it 'deletes a branch that was merged' do
expect(project.repository.branch_names).not_to include('improve/awesome')
end
it 'keeps branch that is unmerged' do
expect(project.repository.branch_names).to include('feature')
end
it 'keeps "master"' do
expect(project.repository.branch_names).to include('master')
end
end
context 'protected branches' do
before do
create(:protected_branch, name: 'improve/awesome', project: project)
service.execute
end
it 'keeps protected branch' do
expect(project.repository.branch_names).to include('improve/awesome')
end
end
context 'user without rights' do
let(:user) { create(:user) }
it 'cannot execute' do
expect { described_class.new(project, user).execute }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
context '#async_execute' do
it 'calls DeleteMergedBranchesWorker async' do
expect(DeleteMergedBranchesWorker).to receive(:perform_async)
service.async_execute
end
end
end
...@@ -35,6 +35,7 @@ module TestEnv ...@@ -35,6 +35,7 @@ module TestEnv
'conflict-missing-side' => 'eb227b3', 'conflict-missing-side' => 'eb227b3',
'conflict-non-utf8' => 'd0a293c', 'conflict-non-utf8' => 'd0a293c',
'conflict-too-large' => '39fa04f', 'conflict-too-large' => '39fa04f',
'deleted-image-test' => '6c17798'
} }
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment