Merge branch 'master' into merge-request-push-compare-ui
This commit is contained in:
commit
2872c75266
39 changed files with 444 additions and 79 deletions
14
CHANGELOG
14
CHANGELOG
|
@ -15,6 +15,7 @@ v 8.12.0 (unreleased)
|
|||
- Update gitlab shell secret file also when it is empty. !3774 (glensc)
|
||||
- Give project selection dropdowns responsive width, make non-wrapping.
|
||||
- Make push events have equal vertical spacing.
|
||||
- API: Ensure invitees are not returned in Members API.
|
||||
- Add two-factor recovery endpoint to internal API !5510
|
||||
- Pass the "Remember me" value to the U2F authentication form
|
||||
- Only update projects.last_activity_at once per hour when creating a new event
|
||||
|
@ -24,9 +25,11 @@ v 8.12.0 (unreleased)
|
|||
- Change logo animation to CSS (ClemMakesApps)
|
||||
- Instructions for enabling Git packfile bitmaps !6104
|
||||
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
|
||||
- Fix long comments in diffs messing with table width
|
||||
- Fix pagination on user snippets page
|
||||
- Run CI builds with the permissions of users !5735
|
||||
- Fix sorting of issues in API
|
||||
- Fix download artifacts button links !6407
|
||||
- Sort project variables by key. !6275 (Diego Souza)
|
||||
- Ensure specs on sorting of issues in API are deterministic on MySQL
|
||||
- Added ability to use predefined CI variables for environment name
|
||||
|
@ -56,6 +59,7 @@ v 8.12.0 (unreleased)
|
|||
- Add hover color to emoji icon (ClemMakesApps)
|
||||
- Increase ci_builds artifacts_size column to 8-byte integer to allow larger files
|
||||
- Add textarea autoresize after comment (ClemMakesApps)
|
||||
- Do not write SSH public key 'comments' to authorized_keys !6381
|
||||
- Refresh todos count cache when an Issue/MR is deleted
|
||||
- Fix branches page dropdown sort alignment (ClemMakesApps)
|
||||
- Hides merge request button on branches page is user doesn't have permissions
|
||||
|
@ -109,6 +113,7 @@ v 8.12.0 (unreleased)
|
|||
- Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell)
|
||||
- Fix repo title alignment (ClemMakesApps)
|
||||
- Change update interval of contacted_at
|
||||
- Add LFS support to SSH !6043
|
||||
- Fix branch title trailing space on hover (ClemMakesApps)
|
||||
- Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8)
|
||||
- Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
|
||||
|
@ -604,6 +609,7 @@ v 8.10.0
|
|||
- Export and import avatar as part of project import/export
|
||||
- Fix migration corrupting import data for old version upgrades
|
||||
- Show tooltip on GitLab export link in new project page
|
||||
- Fix import_data wrongly saved as a result of an invalid import_url !5206
|
||||
|
||||
v 8.9.9
|
||||
- Exclude some pending or inactivated rows in Member scopes
|
||||
|
@ -624,12 +630,6 @@ v 8.9.6
|
|||
- Keeps issue number when importing from Gitlab.com
|
||||
- Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska)
|
||||
|
||||
v 8.9.7 (unreleased)
|
||||
- Fix import_data wrongly saved as a result of an invalid import_url
|
||||
|
||||
v 8.9.6
|
||||
- Fix importing of events under notes for GitLab projects
|
||||
|
||||
v 8.9.5
|
||||
- Add more debug info to import/export and memory killer. !5108
|
||||
- Fixed avatar alignment in new MR view. !5095
|
||||
|
@ -1895,7 +1895,7 @@ v 8.1.3
|
|||
- Use issue editor as cross reference comment author when issue is edited with a new mention
|
||||
- Add Facebook authentication
|
||||
|
||||
v 8.1.1
|
||||
v 8.1.2
|
||||
- Fix cloning Wiki repositories via HTTP (Stan Hu)
|
||||
- Add migration to remove satellites directory
|
||||
- Fix specific runners visibility
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.8.1
|
||||
0.8.2
|
||||
|
|
|
@ -27,10 +27,11 @@
|
|||
$(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
|
||||
$(window).off('resize.build').on('resize.build', this.hideSidebar);
|
||||
$(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
|
||||
$('#js-build-scroll > a').off('click').on('click', this.stepTrace);
|
||||
this.updateArtifactRemoveDate();
|
||||
if ($('#build-trace').length) {
|
||||
this.getInitialBuildTrace();
|
||||
this.initScrollButtonAffix();
|
||||
this.initScrollButtons();
|
||||
}
|
||||
if (this.build_status === "running" || this.build_status === "pending") {
|
||||
$('#autoscroll-button').on('click', function() {
|
||||
|
@ -106,7 +107,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
Build.prototype.initScrollButtonAffix = function() {
|
||||
Build.prototype.initScrollButtons = function() {
|
||||
var $body, $buildScroll, $buildTrace;
|
||||
$buildScroll = $('#js-build-scroll');
|
||||
$body = $('body');
|
||||
|
@ -165,6 +166,14 @@
|
|||
this.populateJobs(stage);
|
||||
};
|
||||
|
||||
Build.prototype.stepTrace = function(e) {
|
||||
e.preventDefault();
|
||||
$currentTarget = $(e.currentTarget);
|
||||
$.scrollTo($currentTarget.attr('href'), {
|
||||
offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
|
||||
});
|
||||
};
|
||||
|
||||
return Build;
|
||||
|
||||
})();
|
||||
|
|
|
@ -607,13 +607,15 @@
|
|||
selectedObject = this.renderedData[selectedIndex];
|
||||
}
|
||||
}
|
||||
field = [];
|
||||
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
|
||||
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
|
||||
if (isInput) {
|
||||
field = $(this.el);
|
||||
} else {
|
||||
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']");
|
||||
} else if(value) {
|
||||
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
|
||||
}
|
||||
if (el.hasClass(ACTIVE_CLASS)) {
|
||||
if (field.length && el.hasClass(ACTIVE_CLASS)) {
|
||||
el.removeClass(ACTIVE_CLASS);
|
||||
if (isInput) {
|
||||
field.val('');
|
||||
|
@ -623,7 +625,7 @@
|
|||
} else if (el.hasClass(INDETERMINATE_CLASS)) {
|
||||
el.addClass(ACTIVE_CLASS);
|
||||
el.removeClass(INDETERMINATE_CLASS);
|
||||
if (value == null) {
|
||||
if (field.length && value == null) {
|
||||
field.remove();
|
||||
}
|
||||
if (!field.length && fieldName) {
|
||||
|
@ -636,7 +638,7 @@
|
|||
this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
|
||||
}
|
||||
}
|
||||
if (value == null) {
|
||||
if (field.length && value == null) {
|
||||
field.remove();
|
||||
}
|
||||
// Toggle active class for the tick mark
|
||||
|
@ -644,7 +646,7 @@
|
|||
if (value != null) {
|
||||
if (!field.length && fieldName) {
|
||||
this.addInput(fieldName, value, selectedObject);
|
||||
} else {
|
||||
} else if (field.length) {
|
||||
field.val(value).trigger('change');
|
||||
}
|
||||
}
|
||||
|
@ -794,4 +796,4 @@
|
|||
});
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
}).call(this);
|
|
@ -166,7 +166,7 @@
|
|||
instance.addInput(this.fieldName, label.id);
|
||||
}
|
||||
}
|
||||
if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) {
|
||||
if (this.id(label) && $form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + this.id(label).toString().replace(/'/g, '\\\'') + "']").length) {
|
||||
selectedClass.push('is-active');
|
||||
}
|
||||
if ($dropdown.hasClass('js-multiselect') && removesAll) {
|
||||
|
|
|
@ -68,6 +68,11 @@
|
|||
border-collapse: separate;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
table-layout: fixed;
|
||||
|
||||
.diff-line-num {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.line_holder td {
|
||||
line-height: $code_line_height;
|
||||
|
@ -98,10 +103,6 @@
|
|||
}
|
||||
|
||||
tr.line_holder.parallel {
|
||||
.old_line, .new_line {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
td.line_content.parallel {
|
||||
width: 46%;
|
||||
}
|
||||
|
|
|
@ -127,9 +127,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def ci?
|
||||
authentication_result.ci? &&
|
||||
authentication_project &&
|
||||
authentication_project == project
|
||||
authentication_result.ci?(project)
|
||||
end
|
||||
|
||||
def lfs_deploy_token?
|
||||
authentication_result.lfs_deploy_token?(project)
|
||||
end
|
||||
|
||||
def authentication_has_download_access?
|
||||
|
|
|
@ -25,7 +25,7 @@ module LfsHelper
|
|||
def lfs_download_access?
|
||||
return false unless project.lfs_enabled?
|
||||
|
||||
project.public? || ci? || user_can_download_code? || build_can_download_code?
|
||||
project.public? || ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
|
||||
end
|
||||
|
||||
def user_can_download_code?
|
||||
|
|
|
@ -330,13 +330,23 @@ class Event < ActiveRecord::Base
|
|||
|
||||
# Don't even bother obtaining a lock if the last update happened less than
|
||||
# 60 minutes ago.
|
||||
return if project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
|
||||
return if recent_update?
|
||||
|
||||
return unless Gitlab::ExclusiveLease.
|
||||
new("project:update_last_activity_at:#{project.id}",
|
||||
timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
|
||||
try_obtain
|
||||
return unless try_obtain_lease
|
||||
|
||||
project.update_column(:last_activity_at, created_at)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recent_update?
|
||||
project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
|
||||
end
|
||||
|
||||
def try_obtain_lease
|
||||
Gitlab::ExclusiveLease.
|
||||
new("project:update_last_activity_at:#{project.id}",
|
||||
timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
|
||||
try_obtain
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,6 +37,6 @@
|
|||
%li.dropdown-header Previous Artifacts
|
||||
- artifacts.each do |job|
|
||||
%li
|
||||
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do
|
||||
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
|
||||
%i.fa.fa-download
|
||||
%span Download '#{job.name}'
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
- if params[:label_name].present?
|
||||
- if params[:label_name].respond_to?('any?')
|
||||
- params[:label_name].each do |label|
|
||||
= hidden_field_tag "label_name[]", u(label), id: nil
|
||||
= hidden_field_tag "label_name[]", label, id: nil
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
|
||||
%span.dropdown-toggle-text
|
||||
|
|
|
@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of
|
|||
cd /home/git
|
||||
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
|
||||
cd gitlab-workhorse
|
||||
sudo -u git -H git checkout v0.8.1
|
||||
sudo -u git -H git checkout v0.8.2
|
||||
sudo -u git -H make
|
||||
|
||||
### Initialize Database and Activate Advanced Features
|
||||
|
|
|
@ -82,7 +82,7 @@ GitLab 8.1.
|
|||
```bash
|
||||
cd /home/git/gitlab-workhorse
|
||||
sudo -u git -H git fetch --all
|
||||
sudo -u git -H git checkout v0.8.1
|
||||
sudo -u git -H git checkout v0.8.2
|
||||
sudo -u git -H make
|
||||
```
|
||||
|
||||
|
|
|
@ -45,5 +45,5 @@ In `config/gitlab.yml`:
|
|||
* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
|
||||
is not supported
|
||||
* Currently, removing LFS objects from GitLab Git LFS storage is not supported
|
||||
* LFS authentications via SSH is not supported for the time being
|
||||
* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2.
|
||||
* LFS authentications via SSH was added with GitLab 8.12
|
||||
* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
|
||||
|
|
|
@ -35,6 +35,10 @@ Documentation for GitLab instance administrators is under [LFS administration do
|
|||
credentials store is recommended
|
||||
* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
|
||||
to add the URL to Git config manually (see #troubleshooting)
|
||||
|
||||
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
|
||||
still goes over HTTP, but now the SSH client passes the correct credentials
|
||||
to the Git LFS client, so no action is required by the user.
|
||||
|
||||
## Using Git LFS
|
||||
|
||||
|
@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs"
|
|||
|
||||
### Credentials are always required when pushing an object
|
||||
|
||||
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
|
||||
still goes over HTTP, but now the SSH client passes the correct credentials
|
||||
to the Git LFS client, so no action is required by the user.
|
||||
|
||||
Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
|
||||
the LFS object on every push for every object, user HTTPS credentials are required.
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ module API
|
|||
|
||||
access_requesters = paginate(source.requesters.includes(:user))
|
||||
|
||||
present access_requesters.map(&:user), with: Entities::AccessRequester, access_requesters: access_requesters
|
||||
present access_requesters.map(&:user), with: Entities::AccessRequester, source: source
|
||||
end
|
||||
|
||||
# Request access to the group/project
|
||||
|
|
|
@ -105,18 +105,18 @@ module API
|
|||
|
||||
class Member < UserBasic
|
||||
expose :access_level do |user, options|
|
||||
member = options[:member] || options[:members].find { |m| m.user_id == user.id }
|
||||
member = options[:member] || options[:source].members.find_by(user_id: user.id)
|
||||
member.access_level
|
||||
end
|
||||
expose :expires_at do |user, options|
|
||||
member = options[:member] || options[:members].find { |m| m.user_id == user.id }
|
||||
member = options[:member] || options[:source].members.find_by(user_id: user.id)
|
||||
member.expires_at
|
||||
end
|
||||
end
|
||||
|
||||
class AccessRequester < UserBasic
|
||||
expose :requested_at do |user, options|
|
||||
access_requester = options[:access_requester] || options[:access_requesters].find { |m| m.user_id == user.id }
|
||||
access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id)
|
||||
access_requester.requested_at
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,6 +82,19 @@ module API
|
|||
response
|
||||
end
|
||||
|
||||
post "/lfs_authenticate" do
|
||||
status 200
|
||||
|
||||
key = Key.find(params[:key_id])
|
||||
token_handler = Gitlab::LfsToken.new(key)
|
||||
|
||||
{
|
||||
username: token_handler.actor_name,
|
||||
lfs_token: token_handler.generate,
|
||||
repository_http_path: project.http_url_to_repo
|
||||
}
|
||||
end
|
||||
|
||||
get "/merge_request_urls" do
|
||||
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
|
||||
end
|
||||
|
|
|
@ -18,11 +18,11 @@ module API
|
|||
get ":id/members" do
|
||||
source = find_source(source_type, params[:id])
|
||||
|
||||
members = source.members.includes(:user)
|
||||
members = members.joins(:user).merge(User.search(params[:query])) if params[:query]
|
||||
members = paginate(members)
|
||||
users = source.users
|
||||
users = users.merge(User.search(params[:query])) if params[:query]
|
||||
users = paginate(users)
|
||||
|
||||
present members.map(&:user), with: Entities::Member, members: members
|
||||
present users, with: Entities::Member, source: source
|
||||
end
|
||||
|
||||
# Get a group/project member
|
||||
|
|
|
@ -11,6 +11,7 @@ module Gitlab
|
|||
build_access_token_check(login, password) ||
|
||||
user_with_password_for_git(login, password) ||
|
||||
oauth_access_token_check(login, password) ||
|
||||
lfs_token_check(login, password) ||
|
||||
personal_access_token_check(login, password) ||
|
||||
Gitlab::Auth::Result.new
|
||||
|
||||
|
@ -102,6 +103,30 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def lfs_token_check(login, password)
|
||||
deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
|
||||
|
||||
actor =
|
||||
if deploy_key_matches
|
||||
DeployKey.find(deploy_key_matches[1])
|
||||
else
|
||||
User.by_login(login)
|
||||
end
|
||||
|
||||
return unless actor
|
||||
|
||||
token_handler = Gitlab::LfsToken.new(actor)
|
||||
|
||||
authentication_abilities =
|
||||
if token_handler.user?
|
||||
full_authentication_abilities
|
||||
else
|
||||
read_authentication_abilities
|
||||
end
|
||||
|
||||
Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.value, password)
|
||||
end
|
||||
|
||||
def build_access_token_check(login, password)
|
||||
return unless login == 'gitlab-ci-token'
|
||||
return unless password
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
module Gitlab
|
||||
module Auth
|
||||
Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
|
||||
def ci?
|
||||
type == :ci
|
||||
def ci?(for_project)
|
||||
type == :ci &&
|
||||
project &&
|
||||
project == for_project
|
||||
end
|
||||
|
||||
def lfs_deploy_token?(for_project)
|
||||
type == :lfs_deploy_token &&
|
||||
actor &&
|
||||
actor.projects.include?(for_project)
|
||||
end
|
||||
|
||||
def success?
|
||||
|
|
|
@ -6,7 +6,12 @@ module Gitlab
|
|||
|
||||
KeyAdder = Struct.new(:io) do
|
||||
def add_key(id, key)
|
||||
key.gsub!(/[[:space:]]+/, ' ').strip!
|
||||
key = Gitlab::Shell.strip_key(key)
|
||||
# Newline and tab are part of the 'protocol' used to transmit id+key to the other end
|
||||
if key.include?("\t") || key.include?("\n")
|
||||
raise Error.new("Invalid key: #{key.inspect}")
|
||||
end
|
||||
|
||||
io.puts("#{id}\t#{key}")
|
||||
end
|
||||
end
|
||||
|
@ -16,6 +21,10 @@ module Gitlab
|
|||
@version_required ||= File.read(Rails.root.
|
||||
join('GITLAB_SHELL_VERSION')).strip
|
||||
end
|
||||
|
||||
def strip_key(key)
|
||||
key.split(/ /)[0, 2].join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
# Init new repository
|
||||
|
@ -107,7 +116,7 @@ module Gitlab
|
|||
#
|
||||
def add_key(key_id, key_content)
|
||||
Gitlab::Utils.system_silent([gitlab_shell_keys_path,
|
||||
'add-key', key_id, key_content])
|
||||
'add-key', key_id, self.class.strip_key(key_content)])
|
||||
end
|
||||
|
||||
# Batch-add keys to authorized_keys
|
||||
|
|
|
@ -70,7 +70,7 @@ module Gitlab
|
|||
private
|
||||
|
||||
def user_options(field, value, limit)
|
||||
options = { attributes: %W(#{config.uid} cn mail dn) }
|
||||
options = { attributes: user_attributes }
|
||||
options[:size] = limit if limit
|
||||
|
||||
if field.to_sym == :dn
|
||||
|
@ -98,6 +98,10 @@ module Gitlab
|
|||
filter
|
||||
end
|
||||
end
|
||||
|
||||
def user_attributes
|
||||
%W(#{config.uid} cn mail dn)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
54
lib/gitlab/lfs_token.rb
Normal file
54
lib/gitlab/lfs_token.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
module Gitlab
|
||||
class LfsToken
|
||||
attr_accessor :actor
|
||||
|
||||
TOKEN_LENGTH = 50
|
||||
EXPIRY_TIME = 1800
|
||||
|
||||
def initialize(actor)
|
||||
@actor =
|
||||
case actor
|
||||
when DeployKey, User
|
||||
actor
|
||||
when Key
|
||||
actor.user
|
||||
else
|
||||
raise 'Bad Actor'
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
token = Devise.friendly_token(TOKEN_LENGTH)
|
||||
|
||||
Gitlab::Redis.with do |redis|
|
||||
redis.set(redis_key, token, ex: EXPIRY_TIME)
|
||||
end
|
||||
|
||||
token
|
||||
end
|
||||
|
||||
def value
|
||||
Gitlab::Redis.with do |redis|
|
||||
redis.get(redis_key)
|
||||
end
|
||||
end
|
||||
|
||||
def user?
|
||||
actor.is_a?(User)
|
||||
end
|
||||
|
||||
def type
|
||||
actor.is_a?(User) ? :lfs_token : :lfs_deploy_token
|
||||
end
|
||||
|
||||
def actor_name
|
||||
actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redis_key
|
||||
"gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,7 +44,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def file_name_regex_message
|
||||
"can contain only letters, digits, '_', '-', '@' and '.'. "
|
||||
"can contain only letters, digits, '_', '-', '@' and '.'."
|
||||
end
|
||||
|
||||
def file_path_regex
|
||||
|
@ -52,7 +52,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def file_path_regex_message
|
||||
"can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. "
|
||||
"can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'."
|
||||
end
|
||||
|
||||
def directory_traversal_regex
|
||||
|
@ -60,7 +60,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def directory_traversal_regex_message
|
||||
"cannot include directory traversal. "
|
||||
"cannot include directory traversal."
|
||||
end
|
||||
|
||||
def archive_formats_regex
|
||||
|
|
|
@ -10,6 +10,15 @@ then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure that the CHANGELOG does not contain duplicate versions
|
||||
DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^v [0-9.]+' CHANGELOG | sed 's| (unreleased)||' | sort | uniq -d)
|
||||
if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ]
|
||||
then
|
||||
echo '✖ ERROR: Duplicate versions in CHANGELOG:' >&2
|
||||
echo "${DUPLICATE_CHANGELOG_VERSIONS}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✔ Linting passed"
|
||||
exit 0
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
FactoryGirl.define do
|
||||
factory :event do
|
||||
project
|
||||
author factory: :user
|
||||
|
||||
factory :closed_issue_event do
|
||||
project
|
||||
action { Event::CLOSED }
|
||||
target factory: :closed_issue
|
||||
author factory: :user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -101,7 +101,7 @@ describe 'Filter issues', feature: true do
|
|||
expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label')
|
||||
end
|
||||
|
||||
it 'filters by no label' do
|
||||
it 'filters by a label' do
|
||||
find('.dropdown-menu-labels a', text: label.title).click
|
||||
page.within '.labels-filter' do
|
||||
expect(page).to have_content label.title
|
||||
|
@ -109,7 +109,7 @@ describe 'Filter issues', feature: true do
|
|||
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
|
||||
end
|
||||
|
||||
it 'filters by wont fix labels' do
|
||||
it "filters by `won't fix` and another label" do
|
||||
find('.dropdown-menu-labels a', text: label.title).click
|
||||
page.within '.labels-filter' do
|
||||
expect(page).to have_content wontfix.title
|
||||
|
@ -117,6 +117,33 @@ describe 'Filter issues', feature: true do
|
|||
end
|
||||
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title)
|
||||
end
|
||||
|
||||
it "filters by `won't fix` label followed by another label after page load" do
|
||||
find('.dropdown-menu-labels a', text: wontfix.title).click
|
||||
# Close label dropdown to load
|
||||
find('body').click
|
||||
expect(find('.filtered-labels')).to have_content(wontfix.title)
|
||||
|
||||
find('.js-label-select').click
|
||||
wait_for_ajax
|
||||
find('.dropdown-menu-labels a', text: label.title).click
|
||||
# Close label dropdown to load
|
||||
find('body').click
|
||||
expect(find('.filtered-labels')).to have_content(label.title)
|
||||
|
||||
find('.js-label-select').click
|
||||
wait_for_ajax
|
||||
expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active')
|
||||
expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active')
|
||||
end
|
||||
|
||||
it "selects and unselects `won't fix`" do
|
||||
find('.dropdown-menu-labels a', text: wontfix.title).click
|
||||
find('.dropdown-menu-labels a', text: wontfix.title).click
|
||||
# Close label dropdown to load
|
||||
find('body').click
|
||||
expect(page).not_to have_css('.filtered-labels')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Filter issues for assignee and label from issues#index' do
|
||||
|
|
|
@ -33,7 +33,11 @@ feature 'Download buttons in branches page', feature: true do
|
|||
end
|
||||
|
||||
scenario 'shows download artifacts button' do
|
||||
expect(page).to have_link "Download '#{build.name}'"
|
||||
href = latest_succeeded_namespace_project_artifacts_path(
|
||||
project.namespace, project, 'binary-encoding/download',
|
||||
job: 'build')
|
||||
|
||||
expect(page).to have_link "Download '#{build.name}'", href: href
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,11 @@ feature 'Download buttons in files tree', feature: true do
|
|||
end
|
||||
|
||||
scenario 'shows download artifacts button' do
|
||||
expect(page).to have_link "Download '#{build.name}'"
|
||||
href = latest_succeeded_namespace_project_artifacts_path(
|
||||
project.namespace, project, "#{project.default_branch}/download",
|
||||
job: 'build')
|
||||
|
||||
expect(page).to have_link "Download '#{build.name}'", href: href
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,7 +33,11 @@ feature 'Download buttons in project main page', feature: true do
|
|||
end
|
||||
|
||||
scenario 'shows download artifacts button' do
|
||||
expect(page).to have_link "Download '#{build.name}'"
|
||||
href = latest_succeeded_namespace_project_artifacts_path(
|
||||
project.namespace, project, "#{project.default_branch}/download",
|
||||
job: 'build')
|
||||
|
||||
expect(page).to have_link "Download '#{build.name}'", href: href
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,11 @@ feature 'Download buttons in tags page', feature: true do
|
|||
end
|
||||
|
||||
scenario 'shows download artifacts button' do
|
||||
expect(page).to have_link "Download '#{build.name}'"
|
||||
href = latest_succeeded_namespace_project_artifacts_path(
|
||||
project.namespace, project, "#{tag}/download",
|
||||
job: 'build')
|
||||
|
||||
expect(page).to have_link "Download '#{build.name}'", href: href
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -61,6 +61,24 @@ describe Gitlab::Auth, lib: true do
|
|||
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
|
||||
end
|
||||
|
||||
it 'recognizes user lfs tokens' do
|
||||
user = create(:user)
|
||||
ip = 'ip'
|
||||
token = Gitlab::LfsToken.new(user).generate
|
||||
|
||||
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
|
||||
expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
|
||||
end
|
||||
|
||||
it 'recognizes deploy key lfs tokens' do
|
||||
key = create(:deploy_key)
|
||||
ip = 'ip'
|
||||
token = Gitlab::LfsToken.new(key).generate
|
||||
|
||||
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}")
|
||||
expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
|
||||
end
|
||||
|
||||
it 'recognizes OAuth tokens' do
|
||||
user = create(:user)
|
||||
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'spec_helper'
|
||||
require 'stringio'
|
||||
|
||||
describe Gitlab::Shell, lib: true do
|
||||
let(:project) { double('Project', id: 7, path: 'diaspora') }
|
||||
|
@ -44,15 +45,38 @@ describe Gitlab::Shell, lib: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#add_key' do
|
||||
it 'removes trailing garbage' do
|
||||
allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
|
||||
expect(Gitlab::Utils).to receive(:system_silent).with(
|
||||
[:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
|
||||
)
|
||||
|
||||
gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
|
||||
end
|
||||
end
|
||||
|
||||
describe Gitlab::Shell::KeyAdder, lib: true do
|
||||
describe '#add_key' do
|
||||
it 'normalizes space characters in the key' do
|
||||
io = spy
|
||||
it 'removes trailing garbage' do
|
||||
io = spy(:io)
|
||||
adder = described_class.new(io)
|
||||
|
||||
adder.add_key('key-42', "sha-rsa foo\tbar\tbaz")
|
||||
adder.add_key('key-42', "ssh-rsa foo bar\tbaz")
|
||||
|
||||
expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz")
|
||||
expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
|
||||
end
|
||||
|
||||
it 'raises an exception if the key contains a tab' do
|
||||
expect do
|
||||
described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
|
||||
end.to raise_error(Gitlab::Shell::Error)
|
||||
end
|
||||
|
||||
it 'raises an exception if the key contains a newline' do
|
||||
expect do
|
||||
described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned")
|
||||
end.to raise_error(Gitlab::Shell::Error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
51
spec/lib/gitlab/lfs_token_spec.rb
Normal file
51
spec/lib/gitlab/lfs_token_spec.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::LfsToken, lib: true do
|
||||
describe '#generate and #value' do
|
||||
shared_examples 'an LFS token generator' do
|
||||
it 'returns a randomly generated token' do
|
||||
token = handler.generate
|
||||
|
||||
expect(token).not_to be_nil
|
||||
expect(token).to be_a String
|
||||
expect(token.length).to eq 50
|
||||
end
|
||||
|
||||
it 'returns the correct token based on the key' do
|
||||
token = handler.generate
|
||||
|
||||
expect(handler.value).to eq(token)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the actor is a user' do
|
||||
let(:actor) { create(:user) }
|
||||
let(:handler) { described_class.new(actor) }
|
||||
|
||||
it_behaves_like 'an LFS token generator'
|
||||
|
||||
it 'returns the correct username' do
|
||||
expect(handler.actor_name).to eq(actor.username)
|
||||
end
|
||||
|
||||
it 'returns the correct token type' do
|
||||
expect(handler.type).to eq(:lfs_token)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the actor is a deploy key' do
|
||||
let(:actor) { create(:deploy_key) }
|
||||
let(:handler) { described_class.new(actor) }
|
||||
|
||||
it_behaves_like 'an LFS token generator'
|
||||
|
||||
it 'returns the correct username' do
|
||||
expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}")
|
||||
end
|
||||
|
||||
it 'returns the correct token type' do
|
||||
expect(handler.type).to eq(:lfs_deploy_token)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -308,20 +308,23 @@ describe Project, models: true do
|
|||
end
|
||||
|
||||
describe 'last_activity methods' do
|
||||
let(:project) { create(:project) }
|
||||
let(:last_event) { double(created_at: Time.now) }
|
||||
let(:timestamp) { Time.now - 2.hours }
|
||||
let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
|
||||
|
||||
describe 'last_activity' do
|
||||
it 'alias last_activity to last_event' do
|
||||
allow(project).to receive(:last_event).and_return(last_event)
|
||||
last_event = create(:event, project: project)
|
||||
|
||||
expect(project.last_activity).to eq(last_event)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'last_activity_date' do
|
||||
it 'returns the creation date of the project\'s last event if present' do
|
||||
create(:event, project: project)
|
||||
expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i)
|
||||
expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true)
|
||||
new_event = create(:event, project: project, created_at: Time.now)
|
||||
|
||||
expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
|
||||
end
|
||||
|
||||
it 'returns the project\'s last update date if it has no events' do
|
||||
|
|
|
@ -100,6 +100,43 @@ describe API::API, api: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe "POST /internal/lfs_authenticate" do
|
||||
before do
|
||||
project.team << [user, :developer]
|
||||
end
|
||||
|
||||
context 'user key' do
|
||||
it 'returns the correct information about the key' do
|
||||
lfs_auth(key.id, project)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['username']).to eq(user.username)
|
||||
expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
|
||||
|
||||
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
|
||||
end
|
||||
|
||||
it 'returns a 404 when the wrong key is provided' do
|
||||
lfs_auth(nil, project)
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'deploy key' do
|
||||
let(:key) { create(:deploy_key) }
|
||||
|
||||
it 'returns the correct information about the key' do
|
||||
lfs_auth(key.id, project)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}")
|
||||
expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
|
||||
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /internal/discover" do
|
||||
it do
|
||||
get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
|
||||
|
@ -389,4 +426,13 @@ describe API::API, api: true do
|
|||
protocol: 'ssh'
|
||||
)
|
||||
end
|
||||
|
||||
def lfs_auth(key_id, project)
|
||||
post(
|
||||
api("/internal/lfs_authenticate"),
|
||||
key_id: key_id,
|
||||
secret_token: secret_token,
|
||||
project: project.path_with_namespace
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,20 +30,29 @@ describe API::Members, api: true do
|
|||
let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
|
||||
end
|
||||
|
||||
context 'when authenticated as a non-member' do
|
||||
%i[access_requester stranger].each do |type|
|
||||
context "as a #{type}" do
|
||||
it 'returns 200' do
|
||||
user = public_send(type)
|
||||
get api("/#{source_type.pluralize}/#{source.id}/members", user)
|
||||
%i[master developer access_requester stranger].each do |type|
|
||||
context "when authenticated as a #{type}" do
|
||||
it 'returns 200' do
|
||||
user = public_send(type)
|
||||
get api("/#{source_type.pluralize}/#{source.id}/members", user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response.size).to eq(2)
|
||||
end
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response.size).to eq(2)
|
||||
expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not return invitees' do
|
||||
create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
|
||||
|
||||
get api("/#{source_type.pluralize}/#{source.id}/members", developer)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response.size).to eq(2)
|
||||
expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
|
||||
end
|
||||
|
||||
it 'finds members with query string' do
|
||||
get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
|
||||
|
||||
|
|
|
@ -245,6 +245,18 @@ describe 'Git LFS API and storage' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when deploy key is authorized' do
|
||||
let(:key) { create(:deploy_key) }
|
||||
let(:authorization) { authorize_deploy_key }
|
||||
|
||||
let(:update_permissions) do
|
||||
project.deploy_keys << key
|
||||
project.lfs_objects << lfs_object
|
||||
end
|
||||
|
||||
it_behaves_like 'responds with a file'
|
||||
end
|
||||
|
||||
context 'when build is authorized as' do
|
||||
let(:authorization) { authorize_ci_project }
|
||||
|
||||
|
@ -1097,6 +1109,10 @@ describe 'Git LFS API and storage' do
|
|||
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
|
||||
end
|
||||
|
||||
def authorize_deploy_key
|
||||
ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate)
|
||||
end
|
||||
|
||||
def fork_project(project, user, object = nil)
|
||||
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
|
||||
Projects::ForkService.new(project, user, {}).execute
|
||||
|
|
Loading…
Reference in a new issue