Commit 19246b14 authored by Nick Thomas's avatar Nick Thomas

Branch merge

parents 5f47d815 a5cd9c9a
......@@ -20,6 +20,8 @@ variables:
before_script:
- source ./scripts/prepare_build.sh
- cp config/gitlab.yml.example config/gitlab.yml
- mkdir -p tmp/tests
- mount -t tmpfs tmpfs tmp/tests || echo "tmpfs mount failed, falling back to disc"
- bundle --version
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
- retry gem install knapsack
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.13.0 (unreleased)
- Truncate long labels with ellipsis in labels page
- Update runner version only when updating contacted_at
- Add link from system note to compare with previous version
- Improve issue load time performance by avoiding ORDER BY in find_by call
- Use gitlab-shell v3.6.2 (GIT TRACE logging)
- Add `/projects/visible` API endpoint (Ben Boeckel)
- Fix centering of custom header logos (Ashley Dumaine)
- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
- AbstractReferenceFilter caches project_refs on RequestStore when active
- Replaced the check sign to arrow in the show build view. !6501
- Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar)
......@@ -15,6 +18,7 @@ v 8.13.0 (unreleased)
- Keep refs for each deployment
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Add more tests for calendar contribution (ClemMakesApps)
- Update Gitlab Shell to fix some problems with moving projects between storages
- Cache rendered markdown in the database, rather than Redis
- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
- Simplify Mentionable concern instance methods
......@@ -28,6 +32,7 @@ v 8.13.0 (unreleased)
- Allow the Koding integration to be configured through the API
- Add new issue button to each list on Issues Board
- Added soft wrap button to repository file/blob editor
- Update namespace validation to forbid reserved names (.git and .atom) (Will Starms)
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
- Fix todos page mobile viewport layout (ClemMakesApps)
- Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps)
......@@ -35,6 +40,7 @@ v 8.13.0 (unreleased)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Fix that manual jobs would no longer block jobs in the next stage. !6604
- Add configurable email subject suffix (Fu Xu)
- Added tooltip to fork count on project show page. (Justin DiPierro)
- Use a ConnectionPool for Rails.cache on Sidekiq servers
- Replace `alias_method_chain` with `Module#prepend`
- Enable GitLab Import/Export for non-admin users.
......@@ -46,6 +52,7 @@ v 8.13.0 (unreleased)
- Prevent flash alert text from being obscured when container is fluid
- Append issue template to existing description !6149 (Joseph Frazier)
- Trending projects now only show public projects and the list of projects is cached for a day
- Memoize Gitlab Shell's secret token (!6599, Justin DiPierro)
- Revoke button in Applications Settings underlines on hover.
- Use higher size on Gitlab::Redis connection pool on Sidekiq servers
- Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska)
......@@ -58,19 +65,25 @@ v 8.13.0 (unreleased)
- Replace bootstrap caret with fontawesome caret (ClemMakesApps)
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
- Add organization field to user profile
- Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts)
- Fix deploy status responsiveness error !6633
- Make searching for commits case insensitive
- Fix resolved discussion display in side-by-side diff view !6575
- Optimize GitHub importing for speed and memory
- API: expose pipeline data in builds API (!6502, Guilherme Salazar)
- Notify the Merger about merge after successful build (Dimitris Karakasilis)
- Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein)
- Reduce queries needed to find users using their SSH keys when pushing commits
- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska)
- Fix broken repository 500 errors in project list
- Fix Pipeline list commit column width should be adjusted
- Close todos when accepting merge requests via the API !6486 (tonygambone)
- Changed Slack service user referencing from full name to username (Sebastian Poxhofer)
- Retouch environments list and deployments list
- Add Container Registry on/off status to Admin Area !6638 (the-undefined)
- Grouped pipeline dropdown is a scrollable container
- Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi)
- Fix a typo in doc/api/labels.md
v 8.12.5 (unreleased)
......@@ -108,6 +121,7 @@ v 8.12.2
- Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv)
- Fix resolve discussion buttons endpoint path
- Refactor remnants of CoffeeScript destructured opts and super !6261
- Prevent running GfmAutocomplete setup for each diff note !6569
v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
......@@ -311,6 +325,7 @@ v 8.11.7
- Avoid conflict with admin labels when importing GitHub labels. !6158
- Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234
- Allow the Rails cookie to be used for API authentication.
- Updating verbiage on git basics to be more intuitive
v 8.11.6
- Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
......@@ -471,6 +486,7 @@ v 8.11.0
- Add pipeline events hook
- Bump gitlab_git to speedup DiffCollection iterations
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
- Clarify documentation for Runners API (Gennady Trafimenkov)
- Make branches sortable without push permission !5462 (winniehell)
- Check for Ci::Build artifacts at database level on pipeline partial
- Convert image diff background image to CSS (ClemMakesApps)
......
......@@ -226,8 +226,7 @@ a feedback issue (if there isn't one already) and leave a comment asking for it
to be marked as `Accepting merge requests`. Please include screenshots or
wireframes if the feature will also change the UI.
Merge requests can be filed either at [GitLab.com][gitlab-mr-tracker] or at
[github.com][github-mr-tracker].
Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
If you are new to GitLab development (or web development in general), see the
[I want to contribute!](#i-want-to-contribute) section to get you started with
......@@ -246,10 +245,17 @@ tests are least likely to receive timely feedback. The workflow to make a merge
request is as follows:
1. Fork the project into your personal space on GitLab.com
1. Create a feature branch, branch away from `master`.
1. Create a feature branch, branch away from `master`
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide]
1. Add your changes to the [CHANGELOG](CHANGELOG):
1. If you are fixing a ~regression issue, you can add your entry to the next
patch release (e.g. `8.12.5` if current version is `8.12.4`)
1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if
current version is `8.12.4`
1. Please add your entry at a random place among the entries of the targeted
release
1. If you are writing documentation, make sure to follow the
[documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by
[squashing them][git-squash]
1. Push the commit(s) to your fork
......@@ -258,7 +264,7 @@ request is as follows:
1. The MR description should give a motive for your change and the method you
used to achieve it, see the [merge request description format]
(#merge-request-description-format)
1. If the MR changes the UI it should include before and after screenshots
1. If the MR changes the UI it should include *Before* and *After* screenshots
1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R`
1. Link any relevant [issues][ce-tracker] in the merge request description and
......@@ -270,7 +276,9 @@ request is as follows:
[shell command guidelines](doc/development/shell_commands.md)
1. If your code creates new files on disk please read the
[shared files guidelines](doc/development/shared_files.md).
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
1. When writing commit messages please follow
[these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
[guidelines](http://chris.beams.io/posts/git-commit/).
1. If your merge request adds one or more migrations, make sure to execute all
migrations on a fresh database before the MR is reviewed. If the review leads
to large changes in the MR, do this again once the review is complete.
......@@ -305,23 +313,6 @@ Please ensure that your merge request meets the contribution acceptance criteria
When having your code reviewed and when reviewing merge requests please take the
[code review guidelines](doc/development/code_review.md) into account.
### Merge request description format
Please submit merge requests using the following template in the merge request
description area. Copy-paste it to retain the markdown format.
```
## What does this MR do?
## Are there points in the code the reviewer needs to double check?
## Why was this MR needed?
## What are the relevant issue numbers?
## Screenshots (if relevant)
```
### Contribution acceptance criteria
1. The change is as small as possible
......@@ -333,8 +324,8 @@ description area. Copy-paste it to retain the markdown format.
aforementioned failing test
1. Your MR initially contains a single commit (please use `git rebase -i` to
squash commits)
1. Your changes can merge without problems (if not please merge `master`, never
rebase commits pushed to the remote server)
1. Your changes can merge without problems (if not please rebase if you're the
only one working on your feature branch, otherwise, merge `master`)
1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine
things, send separate merge requests if needed)
......@@ -352,7 +343,10 @@ description area. Copy-paste it to retain the markdown format.
entire line to follow it. This prevents linting tools from generating warnings.
- Don't touch neighbouring lines. As an exception, automatic mass
refactoring modifications may leave style non-compliant.
1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.), they should conform to our [Licensing guidelines][license-finder-doc]. See the instructions in that document for help if your MR fails the "license-finder" test with a "Dependencies that need approval" error.
1. If the merge request adds any new libraries (gems, JavaScript libraries,
etc.), they should conform to our [Licensing guidelines][license-finder-doc].
See the instructions in that document for help if your MR fails the
"license-finder" test with a "Dependencies that need approval" error.
## Changes for Stable Releases
......@@ -468,7 +462,6 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
......
# GitLab
[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
......
......@@ -52,37 +52,27 @@
}
}
},
setup: function(input) {
setup: _.debounce(function(input) {
// Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input');
// destroy previous instances
this.destroyAtWho();
// set up instances
this.setupAtWho();
if (this.dataSource) {
if (!this.dataLoading && !this.cachedData) {
this.dataLoading = true;
setTimeout((function(_this) {
return function() {
var fetch;
fetch = _this.fetchData(_this.dataSource);
return fetch.done(function(data) {
_this.dataLoading = false;
return _this.loadData(data);
});
};
// We should wait until initializations are done
// and only trigger the last .setup since
// The previous .dataSource belongs to the previous issuable
// and the last one will have the **proper** .dataSource property
// TODO: Make this a singleton and turn off events when moving to another page
})(this), 1000);
}
if (this.cachedData != null) {
return this.loadData(this.cachedData);
}
if (this.dataSource && !this.dataLoading && !this.cachedData) {
this.dataLoading = true;
return this.fetchData(this.dataSource)
.done((data) => {
this.dataLoading = false;
this.loadData(data);
});
};
if (this.cachedData != null) {
return this.loadData(this.cachedData);
}
},
}, 1000),
setupAtWho: function() {
// Emoji
this.input.atwho({
......
......@@ -738,6 +738,7 @@
return false;
}
if (currentKeyCode === 13 && currentIndex !== -1) {
e.preventDefault();
_this.selectRowAtIndex();
}
};
......
.environments-container,
.deployments-container {
width: 100%;
overflow: auto;
}
.environments {
.deployment-column {
.avatar {
float: none;
}
}
.commit-title {
margin: 0;
......@@ -9,6 +20,7 @@
width: 12px;
}
.external-url,
.dropdown-new {
color: $table-text-gray;
}
......@@ -21,16 +33,35 @@
}
}
.build-link,
.branch-name {
color: $gl-dark-link-color;
}
.deployment {
.build-column {
.build-link {
color: $gl-dark-link-color;
}
.avatar {
float: none;
}
}
}
}
.table.builds.environments {
min-width: 500px;
.icon-container {
width: 20px;
text-align: center;
}
.branch-commit {
.commit-id {
margin-right: 0;
}
}
}
......@@ -59,6 +59,13 @@
width: 200px;
margin-bottom: 0;
}
.label {
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
max-width: 100%;
}
}
.label-description {
......
module Ci
class ApplicationController < ::ApplicationController
def self.railtie_helpers_paths
"app/helpers/ci"
end
end
end
module Ci
class LintsController < ApplicationController
class LintsController < ::ApplicationController
before_action :authenticate_user!
def show
......
module Ci
class ProjectsController < Ci::ApplicationController
class ProjectsController < ::ApplicationController
before_action :project
before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index]
......
......@@ -4,15 +4,18 @@ module AvatarsHelper
user: commit_or_event.author,
user_name: commit_or_event.author_name,
user_email: commit_or_event.author_email,
css_class: 'hidden-xs'
}))
end
def user_avatar(options = {})
avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name]
css_class = options[:css_class] || ''
avatar = image_tag(
avatar_icon(options[:user] || options[:user_email], avatar_size),
class: "avatar has-tooltip hidden-xs s#{avatar_size}",
class: "avatar has-tooltip s#{avatar_size} #{css_class}",
alt: "#{user_name}'s avatar",
title: user_name,
data: { container: 'body' }
......
......@@ -113,14 +113,13 @@ module IssuesHelper
end
end
def award_user_list(awards, current_user)
def award_user_list(awards, current_user, limit: 10)
names = awards.map do |award|
award.user == current_user ? 'You' : award.user.name
end
# Take first 9 OR current user + first 9
current_user_name = names.delete('You')
names = names.first(9).insert(0, current_user_name).compact
names = names.insert(0, current_user_name).compact.first(limit)
names << "#{awards.size - names.size} more." if awards.size > names.size
......
class CommitStatus < ActiveRecord::Base
include HasStatus
include Importable
include AfterCommitQueue
self.table_name = 'ci_builds'
......@@ -85,25 +86,34 @@ class CommitStatus < ActiveRecord::Base
end
after_transition do |commit_status, transition|
commit_status.pipeline.try do |pipeline|
break if transition.loopback?
if commit_status.complete?
ProcessPipelineWorker.perform_async(pipeline.id)
return if transition.loopback?
commit_status.run_after_commit do
pipeline.try do |pipeline|
if complete?
ProcessPipelineWorker.perform_async(pipeline.id)
else
UpdatePipelineWorker.perform_async(pipeline.id)
end
end
UpdatePipelineWorker.perform_async(pipeline.id)
end
true
end
after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
commit_status.run_after_commit do
# TODO, temporary fix for race condition
UpdatePipelineWorker.new.perform(pipeline.id)
MergeRequests::MergeWhenBuildSucceedsService
.new(pipeline.project, nil).trigger(self)
end
end
after_transition any => :failed do |commit_status|
MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status)
commit_status.run_after_commit do
MergeRequests::AddTodoWhenBuildFailsService
.new(pipeline.project, nil).execute(self)
end
end
end
......
......@@ -340,7 +340,7 @@ class Event < ActiveRecord::Base
# update the project. Only one query should actually perform the update,
# hence we add the extra WHERE clause for last_activity_at.
Project.unscoped.where(id: project_id).
where('last_activity_at > ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago).
where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago).
update_all(last_activity_at: created_at)
end
......
......@@ -61,15 +61,13 @@ class Namespace < ActiveRecord::Base
def clean_path(path)
path = path.dup
# Get the email username by removing everything after an `@` sign.
path.gsub!(/@.*\z/, "")
# Usernames can't end in .git, so remove it.
path.gsub!(/\.git\z/, "")
# Remove dashes at the start of the username.
path.gsub!(/\A-+/, "")
# Remove periods at the end of the username.
path.gsub!(/\.+\z/, "")
path.gsub!(/@.*\z/, "")
# Remove everything that's not in the list of allowed characters.
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
# Remove trailing violations ('.atom', '.git', or '.')
path.gsub!(/(\.atom|\.git|\.)*\z/, "")
# Remove leading violations ('-')
path.gsub!(/\A\-+/, "")
# Users with the great usernames of "." or ".." would end up with a blank username.
# Work around that by setting their username to "blank", followed by a counter.
......
......@@ -122,8 +122,10 @@ class Repository
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
ref ||= root_ref
# Limited to 1000 commits for now, could be parameterized?
args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
args = %W(
#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
--max-count #{limit} --grep=#{query} --regexp-ignore-case
)
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
......
......@@ -943,7 +943,7 @@ class User < ActiveRecord::Base
if domain_matches?(allowed_domains, self.email)
valid = true
else
error = "is not whitelisted. Email domains valid for registration are: #{allowed_domains.join(', ')}"
error = "domain is not authorized for sign-up"
valid = false
end
end
......
......@@ -101,7 +101,6 @@ class ProjectPolicy < BasePolicy
can! :admin_milestone
can! :admin_project_snippet
can! :admin_project_member
can! :admin_merge_request
can! :admin_note
can! :admin_wiki
can! :admin_project
......@@ -151,11 +150,18 @@ class ProjectPolicy < BasePolicy
def team_access!(user)
access = project.team.max_member_access(user.id)
guest_access! if access >= Gitlab::Access::GUEST
reporter_access! if access >= Gitlab::Access::REPORTER
team_member_reporter_access! if access >= Gitlab::Access::REPORTER
developer_access! if access >= Gitlab::Access::DEVELOPER
master_access! if access >= Gitlab::Access::MASTER
return if access < Gitlab::Access::GUEST
guest_access!
return if access < Gitlab::Access::REPORTER
reporter_access!
team_member_reporter_access!
return if access < Gitlab::Access::DEVELOPER
developer_access!
return if access < Gitlab::Access::MASTER
master_access!
end
def archived_access!
......
......@@ -16,6 +16,8 @@ module Ci
process_stage(index)
end
@pipeline.update_status
# Return a flag if a when builds got enqueued
new_builds.flatten.any?
end
......
......@@ -128,7 +128,7 @@
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{class: ('active' if build == @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do
= icon('right-arrow')
= icon('arrow-right')
= ci_icon_for_status(build.status)
%span
- if build.name
......
......@@ -5,10 +5,10 @@
= custom_icon('icon_fork')
%span Fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn has-tooltip' do
= custom_icon('icon_fork')
%span Fork
%div.count-with-arrow
%span.arrow
= link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
= link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count has-tooltip' do
= @project.forks_count
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
.pull-right
- external_url = deployment.environment.external_url
- if external_url
= link_to external_url, target: '_blank', class: 'btn external-url' do
= icon('external-link')
- actions = deployment.manual_actions
- if actions.present?
.inline
......
......@@ -5,14 +5,16 @@
%td
= render 'projects/deployments/commit', deployment: deployment
%td
%td.build-column
- if deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
= user_avatar(user: deployment.user, size: 20)
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
- if deployment.user
by
= user_avatar(user: deployment.user, size: 20)
%td
#{time_ago_with_tooltip(deployment.created_at)}
%td
%td.hidden-xs
= render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
......@@ -4,10 +4,17 @@
%td
= link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment)
%td
%td.deployment-column
- if last_deployment
= user_avatar(user: last_deployment.user, size: 20)
%strong ##{last_deployment.id}
%span ##{last_deployment.iid}
- if last_deployment.user
by
= user_avatar(user: last_deployment.user, size: 20)
%td
- if last_deployment && last_deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, last_deployment.deployable], class: 'build-link' do
= "#{last_deployment.deployable.name} (##{last_deployment.deployable.id})"
%td
- if last_deployment
......@@ -20,5 +27,5 @@
- if last_deployment
#{time_ago_with_tooltip(last_deployment.created_at)}
%td
%td.hidden-xs
= render 'projects/deployments/actions', deployment: last_deployment
......@@ -9,25 +9,27 @@
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
- if @environments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
You don't have any environments right now.
%p.blank-state-text
Environments are places where code gets deployed, such as staging or production.
%br
= succeed "." do
= link_to "Read more about environments", help_page_path("ci/environments")
- if can?(current_user, :create_environment, @project)
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
- else
.table-holder
%table.table.builds.environments
%tbody
%th Environment
%th Last Deployment
%th Commit
%th
%th
= render @environments
.environments-container
- if @environments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
You don't have any environments right now.
%p.blank-state-text
Environments are places where code gets deployed, such as staging or production.
%br
= succeed "." do
= link_to "Read more about environments", help_page_path("ci/environments")
- if can?(current_user, :create_environment, @project)
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
- else
.table-holder
%table.table.builds.environments
%tbody
%th Environment
%th Last Deployment
%th Build
%th Commit
%th
%th.hidden-xs
= render @environments
......@@ -12,26 +12,27 @@
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
= link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
- if @deployments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
You don't have any deployments right now.
%p.blank-state-text
Define environments in the deploy stage(s) in
%code .gitlab-ci.yml
to track deployments here.
= link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- else
.table-holder
%table.table.builds.environments
%thead
%tr
%th ID
%th Commit
%th Build
%th
%th
.deployments-container
- if @deployments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
You don't have any deployments right now.
%p.blank-state-text
Define environments in the deploy stage(s) in
%code .gitlab-ci.yml
to track deployments here.
= link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- else
.table-holder
%table.table.builds.environments
%thead
%tr
%th ID
%th Commit
%th Build
%th
%th.hidden-xs
= render @deployments
= render @deployments
= paginate @deployments, theme: 'gitlab'
= paginate @deployments, theme: 'gitlab'
- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues"
%h3.page-title
Edit Issue ##{@issue.iid}
......
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_title "#{@issue.to_reference} #{@issue.title}", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
......
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
......
- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
......
......@@ -2,12 +2,11 @@ class ExpireBuildArtifactsWorker
include Sidekiq::Worker
def perform
Rails.logger.info 'Cleaning old build artifacts'
Rails.logger.info 'Scheduling removal of build artifacts'
builds = Ci::Build.with_expired_artifacts
builds.find_each(batch_size: 50).each do |build|
Rails.logger.debug "Removing artifacts build #{build.id}..."
build.erase_artifacts!
end
build_ids = Ci::Build.with_expired_artifacts.pluck(:id)
build_ids = build_ids.map { |build_id| [build_id] }
Sidekiq::Client.push_bulk('class' => ExpireBuildInstanceArtifactsWorker, 'args' => build_ids )
end
end
class ExpireBuildInstanceArtifactsWorker
include Sidekiq::Worker
def perform(build_id)
build = Ci::Build.with_expired_artifacts.reorder(nil).find_by(id: build_id)
return unless build
Rails.logger.info "Removing artifacts build #{build.id}..."
build.erase_artifacts!
end
end
Gitlab::Shell.new.generate_and_link_secret_token
Gitlab::Shell.ensure_secret_token!
......@@ -24,7 +24,10 @@ devise_scope :user do
end
constraints(UserUrlConstrainer.new) do
scope(path: ':username', as: :user, controller: :users) do
scope(path: ':username',
as: :user,
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
controller: :users) do
get '/', action: :show
end
end
......
......@@ -12,7 +12,9 @@ communication channel. For the consumer API see the
This API uses two types of authentication:
1. Unique Runner's token, which is the token assigned to the Runner after it
has been registered.
has been registered. This token can be found on the Runner's edit page (go to
**Project > Runners**, select one of the Runners listed under **Runners activated for
this project**).
2. Using Runners' registration token.
This is a token that can be found in project's settings.
......@@ -48,7 +50,7 @@ DELETE /ci/api/v1/runners/delete
| Attribute | Type | Required | Description |
| --------- | ------- | --------- | ----------- |
| `token` | string | yes | Runner's registration token |
| `token` | string | yes | Unique Runner's token |
Example request:
......
......@@ -148,7 +148,7 @@ PUT /projects/:id/labels
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer | yes | The ID of the project |
| `name` | string | yes | The name of the existing label |
| `new_name` | string | yes if `color` if not provided | The new name of the label |
| `new_name` | string | yes if `color` is not provided | The new name of the label |
| `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
| `description` | string | no | The new description of the label |
......
This diff is collapsed.
......@@ -34,6 +34,10 @@ request is up to one of our merge request "endbosses", denoted on the
## Having your code reviewed
Please keep in mind that code review is a process that can take multiple
iterations, and reviewers may spot things later that they may not have seen the
first time.
- The first reviewer of your code is _you_. Before you perform that first push
of your shiny new branch, read through the entire diff. Does it make sense?
Did you include something unrelated to the overall purpose of the changes? Did
......@@ -55,6 +59,7 @@ request is up to one of our merge request "endbosses", denoted on the
Understand why the change is necessary (fixes a bug, improves the user
experience, refactors the existing code). Then:
- Try to be thorough in your reviews to reduce the number of iterations.
- Communicate which ideas you feel strongly about and those you don't.
- Identify ways to simplify the code while still solving the problem.
- Offer alternative implementations, but assume the author already considered
......@@ -64,8 +69,10 @@ experience, refactors the existing code). Then:
someone else would be confused by it as well.
- After a round of line notes, it can be helpful to post a summary note such as
"LGTM :thumbsup:", or "Just a couple things to address."
- Avoid accepting a merge request before the build succeeds ("Merge when build
succeeds" is fine).
- Avoid accepting a merge request before the build succeeds. Of course, "Merge
When Build Succeeds" (MWBS) is fine.
- If you set the MR to "Merge When Build Succeeds", you should take over
subsequent revisions for anything that would be spotted after that.
## Credits
......
# Start using Git on the command line
If you want to start using a Git and GitLab, make sure that you have created an
account on GitLab.
If you want to start using Git and GitLab, make sure that you have created and/or signed into an account on GitLab.
## Open a shell
Depending on your operating system, find the shell of your preference. Here are some suggestions.
Depending on your operating system, you will need to use a shell of your preference. Here are some suggestions:
- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX
......@@ -22,19 +21,19 @@ Type the following command and then press enter:
git --version
```
You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
You should receive a message that will tell you which Git version you have on your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window.
After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed.
After you are finished installing, open a new shell and type "git --version" again to verify that it was correctly installed.
## Add your Git username and set your email
It is important because every Git commit that you create will use this information.
It is important to configure your Git username and email address as every Git commit will use this information to identify you as the author.
On your shell, type the following command to add your username:
```
git config --global user.name ADD YOUR USERNAME
git config --global user.name "YOUR_USERNAME"
```
Then verify that you have the correct username:
......@@ -44,7 +43,7 @@ git config --global user.name
To set your email address, type the following command:
```
git config --global user.email ADD YOUR EMAIL
git config --global user.email "your_email_address@example.com"
```
To verify that you entered your email correctly, type:
......@@ -52,7 +51,7 @@ To verify that you entered your email correctly, type:
git config --global user.email
```
You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project.
You'll need to do this only once as you are using the `--global` option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the `--global` option when you’re in that project.
## Check your information
......@@ -76,7 +75,7 @@ git pull REMOTE NAME-OF-BRANCH -u
(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
### Create a branch
Spaces won't be recognized, so you need to use a hyphen or underscore.
Spaces won't be recognized, so you will need to use a hyphen or underscore.
```
git checkout -b NAME-OF-BRANCH
```
......@@ -127,4 +126,3 @@ You need to be in the master branch.
git checkout master
git merge NAME-OF-BRANCH
```
......@@ -2,30 +2,52 @@
![backup banner](backup_hrz.png)
## Create a backup of the GitLab system
A backup creates an archive file that contains the database, all repositories and all attachments.
This archive will be saved in backup_path (see `config/gitlab.yml`).
The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup.
You can only restore a backup to exactly the same version of GitLab that you created it
on, for example 7.2.1. The best way to migrate your repositories from one server to
An application data backup creates an archive file that contains the database,
all repositories and all attachments.
This archive will be saved in `backup_path`, which is specified in the
`config/gitlab.yml` file.
The filename will be `[TIMESTAMP]_gitlab_backup.tar`, where `TIMESTAMP`
identifies the time at which each backup was created.
You can only restore a backup to exactly the same version of GitLab on which it
was created. The best way to migrate your repositories from one server to
another is through backup restore.
You need to keep separate copies of `/etc/gitlab/gitlab-secrets.json` and
`/etc/gitlab/gitlab.rb` (for omnibus packages) or
`/home/git/gitlab/config/secrets.yml` (for installations from source). This file
contains the database encryption keys used for two-factor authentication and CI
secret variables, among other things. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor authentication
enabled will lose access to your GitLab server.
To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json`
(for omnibus packages) or `/home/git/gitlab/.secret` (for installations
from source). This file contains the database encryption key and CI secret
variables used for two-factor authentication. If you fail to restore this
encryption key file along with the application data backup, users with two-factor
authentication enabled will lose access to your GitLab server.
## Create a backup of the GitLab system
Use this command if you've installed GitLab with the Omnibus package:
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
# if you've installed GitLab from source
```
Use this if you've installed GitLab from source:
```
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
You can specify that portions of the application data be skipped using the
environment variable `SKIP`. You can skip:
- `db` (database)
- `uploads` (attachments)
- `repositories` (Git repositories data)
- `builds` (CI build output logs)
- `artifacts` (CI build artifacts)
- `lfs` (LFS objects)
- `registry` (Container Registry images)
Separate multiple data types to skip using a comma. For example:
```
sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
```
Example output:
```
......@@ -55,35 +77,12 @@ Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING]
```
## Exclude specific directories from the backup
You can choose what should be backed up by adding the environment variable `SKIP`.
The available options are:
* `db`
* `uploads` (attachments)
* `repositories`
* `builds` (CI build output logs)
* `artifacts` (CI build artifacts)
* `lfs` (LFS objects)
* `pages` (pages content)
Use a comma to specify several options at the same time:
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
# if you've installed GitLab from source
sudo -u git -H bundle exec rake gitlab:backup:create SKIP=db,uploads RAILS_ENV=production
```
## Upload backups to remote (cloud) storage
Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates.
It uses the [Fog library](http://fog.io/) to perform the upload.
In the example below we use Amazon S3 for storage.
But Fog also lets you use [other storage providers](http://fog.io/storage/).
Fog also supports [other storage providers](http://fog.io/storage/).
For omnibus packages:
......@@ -175,7 +174,7 @@ with the name of your bucket:
### Uploading to locally mounted shares
You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by
using the [`Local`](https://github.com/fog/fog-local#usage) storage provider.
using the Fog [`Local`](https://github.com/fog/fog-local#usage) storage provider.
The directory pointed to by the `local_root` key **must** be owned by the `git`
user **when mounted** (mounting with the `uid=` of the `git` user for `CIFS` and
`SMB`) or the user that you are executing the backup tasks under (for omnibus
......@@ -242,7 +241,7 @@ of using encryption in the first place!
If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
If you have an installation from source, please consider backing up your `config/secrets.yml` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
If you installed from source, please consider backing up your `config/secrets.yml` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
At the very **minimum** you should backup `/etc/gitlab/gitlab.rb` and
`/etc/gitlab/gitlab-secrets.json` (Omnibus), or
......
This diff is collapsed.
......@@ -99,6 +99,10 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 7. Update configuration files
......
......@@ -116,6 +116,10 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 7. Update configuration files
......
......@@ -158,6 +158,10 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
......
......@@ -166,6 +166,10 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
......
......@@ -166,6 +166,10 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
......
......@@ -158,6 +158,10 @@ it where the 'public' directory of GitLab is.
cd /home/git/gitlab
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 8. Use Redis v2.8.0+
......
......@@ -98,6 +98,10 @@ We updated the init script for GitLab in order to set a specific PATH for gitlab
cd /home/git/gitlab
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 8. Start application
......
......@@ -119,6 +119,10 @@ via [/etc/default/gitlab].
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 8. Start application
......
......@@ -138,6 +138,10 @@ via [/etc/default/gitlab].
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
......
......@@ -127,6 +127,10 @@ via [/etc/default/gitlab].
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 8. Start application
......
......@@ -127,6 +127,10 @@ via [/etc/default/gitlab].
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 8. Start application
......
......@@ -156,6 +156,10 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
......
......@@ -156,6 +156,10 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
......
......@@ -98,6 +98,9 @@ As an Administrator, you can verify that the user is a member of the group or
project they're trying to have access to, and you can impersonate the user to
retry the failing build in order to verify that everything is correct.
You need to make sure that your installation has HTTPS cloning enabled.
HTTPS support is required by GitLab CI to clone all sources.
## Build triggers
[Build triggers][triggers] do not support the new permission model.
......
......@@ -25,7 +25,7 @@ module API
# Until CSRF protection is added to the API, disallow this method for
# state-changing endpoints
def find_user_from_warden
warden.try(:authenticate) if request.get? || request.head?
warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD'])
end
def find_user_by_private_token
......@@ -444,7 +444,7 @@ module API
end
def secret_token
File.read(Gitlab.config.gitlab_shell.secret_file).chomp
Gitlab::Shell.secret_token
end
def geo_token
......
......@@ -22,14 +22,25 @@ module API
# Example Request:
# GET /projects
get do
@projects = current_user.authorized_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
if params[:simple]
present @projects, with: Entities::BasicProjectDetails, user: current_user
else
present @projects, with: Entities::ProjectWithAccess, user: current_user
end
projects = current_user.authorized_projects
projects = filter_projects(projects)
projects = paginate projects
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
present projects, with: entity, user: current_user
end
# Get a list of visible projects for authenticated user
#
# Example Request:
# GET /projects/visible
get '/visible' do
projects = ProjectsFinder.new.execute(current_user)
projects = filter_projects(projects)
projects = paginate projects
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
present projects, with: entity, user: current_user
end
# Get an owned projects list for authenticated user
......@@ -37,10 +48,10 @@ module API
# Example Request:
# GET /projects/owned
get '/owned' do
@projects = current_user.owned_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::ProjectWithAccess, user: current_user
projects = current_user.owned_projects
projects = filter_projects(projects)
projects = paginate projects
present projects, with: Entities::ProjectWithAccess, user: current_user
end
# Gets starred project for the authenticated user
......@@ -48,10 +59,10 @@ module API
# Example Request:
# GET /projects/starred
get '/starred' do
@projects = current_user.viewable_starred_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::Project, user: current_user
projects = current_user.viewable_starred_projects
projects = filter_projects(projects)
projects = paginate projects
present projects, with: Entities::Project, user: current_user
end
# Get all projects for admin user
......@@ -60,10 +71,10 @@ module API
# GET /projects/all
get '/all' do
authenticated_as_admin!
@projects = Project.all
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::ProjectWithAccess, user: current_user
projects = Project.all
projects = filter_projects(projects)
projects = paginate projects
present projects, with: Entities::ProjectWithAccess, user: current_user
end
# Get a single project
......
......@@ -3,7 +3,7 @@ require 'erb'
module Banzai
module Filter
# Text filter that escapes these HTML entities: & " < >
class HTMLEntityFilter < HTML::Pipeline::TextFilter
class HtmlEntityFilter < HTML::Pipeline::TextFilter
def call
ERB::Util.html_escape(text)
end
......
......@@ -3,7 +3,7 @@ module Banzai
class SingleLinePipeline < GfmPipeline
def self.filters
@filters ||= FilterArray[
Filter::HTMLEntityFilter,
Filter::HtmlEntityFilter,
Filter::SanitizationFilter,
Filter::EmojiFilter,
......
......@@ -17,6 +17,18 @@ module Gitlab
end
class << self
def secret_token
@secret_token ||= begin
File.read(Gitlab.config.gitlab_shell.secret_file).chomp
end
end
def ensure_secret_token!
return if File.exist?(File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret'))
generate_and_link_secret_token
end
def version_required
@version_required ||= File.read(Rails.root.
join('GITLAB_SHELL_VERSION')).strip
......@@ -25,6 +37,25 @@ module Gitlab
def strip_key(key)
key.split(/ /)[0, 2].join(' ')
end
private
# Create (if necessary) and link the secret token file
def generate_and_link_secret_token
secret_file = Gitlab.config.gitlab_shell.secret_file
shell_path = Gitlab.config.gitlab_shell.path
unless File.size?(secret_file)
# Generate a new token of 16 random hexadecimal characters and store it in secret_file.
token = SecureRandom.hex(16)
File.write(secret_file, token)
end
link_path = File.join(shell_path, '.gitlab_shell_secret')
if File.exist?(shell_path) && !File.exist?(link_path)
FileUtils.symlink(secret_file, link_path)
end
end
end
# Init new repository
......
......@@ -2,7 +2,7 @@ module Gitlab
module Regex
extend self
NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze
NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(?<!\.git|\.atom)'.freeze
def namespace_regex
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
......@@ -10,7 +10,7 @@ module Gitlab
def namespace_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.'." \
"Cannot start with '-' or end in '.', '.git' or '.atom'." \
end
def namespace_name_regex
......
......@@ -111,7 +111,7 @@ module Gitlab
def write_secret
bytes = SecureRandom.random_bytes(SECRET_LENGTH)
File.open(secret_path, 'w:BINARY', 0600) do |f|
f.chmod(0600)
f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op.
f.write(Base64.strict_encode64(bytes))
end
end
......
......@@ -78,7 +78,7 @@ namespace :gitlab do
f.puts "PATH=#{ENV['PATH']}"
end
Gitlab::Shell.new.generate_and_link_secret_token
Gitlab::Shell.ensure_secret_token!
end
desc "GitLab | Setup gitlab-shell"
......
......@@ -5,7 +5,6 @@ describe Projects::BlobController do
let(:user) { create(:user) }
before do
user = create(:user)
project.team << [user, :master]
sign_in(user)
......
......@@ -44,6 +44,10 @@ feature 'Environments', feature: true do
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
end
scenario 'does show deployment internal id' do
expect(page).to have_content(deployment.iid)
end
context 'with build and manual actions' do
given(:pipeline) { create(:ci_pipeline, project: project) }
......@@ -61,6 +65,20 @@ feature 'Environments', feature: true do
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
end
scenario 'does show build name and id' do
expect(page).to have_link("#{build.name} (##{build.id})")
end
context 'with external_url' do
given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show an external link button' do
expect(page).to have_link(nil, href: environment.external_url)
end
end
end
end
end
......@@ -122,6 +140,16 @@ feature 'Environments', feature: true do
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
end
context 'with external_url' do
given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show an external link button' do
expect(page).to have_link(nil, href: environment.external_url)
end
end
end
end
end
......
......@@ -5,6 +5,12 @@ feature 'Group', feature: true do
login_as(:admin)
end
matcher :have_namespace_error_message do
match do |page|
page.has_content?("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.', '.git' or '.atom'.")
end
end
describe 'creating a group with space in group path' do
it 'renders new group form with validation errors' do
visit new_group_path
......@@ -13,7 +19,31 @@ feature 'Group', feature: true do
click_button 'Create group'
expect(current_path).to eq(groups_path)
expect(page).to have_content("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.'.")
expect(page).to have_namespace_error_message
end
end
describe 'creating a group with .atom at end of group path' do
it 'renders new group form with validation errors' do
visit new_group_path
fill_in 'Group path', with: 'atom_group.atom'
click_button 'Create group'
expect(current_path).to eq(groups_path)
expect(page).to have_namespace_error_message
end
end
describe 'creating a group with .git at end of group path' do
it 'renders new group form with validation errors' do
visit new_group_path
fill_in 'Group path', with: 'git_group.git'
click_button 'Create group'
expect(current_path).to eq(groups_path)
expect(page).to have_namespace_error_message
end
end
......
......@@ -63,28 +63,38 @@ describe IssuesHelper do
end
describe '#award_user_list' do
let!(:awards) { build_list(:award_emoji, 15) }
it "returns a comma-separated list of the first X users" do
user = build_stubbed(:user, name: 'Joe')
awards = Array.new(3, build_stubbed(:award_emoji, user: user))
it "returns a comma seperated list of 1-9 users" do
expect(award_user_list(awards.first(9), nil)).to eq(awards.first(9).map { |a| a.user.name }.to_sentence)
expect(award_user_list(awards, nil, limit: 3))
.to eq('Joe, Joe, and Joe')
end
it "displays the current user's name as 'You'" do
expect(award_user_list(awards.first(1), awards[0].user)).to eq('You')
end
user = build_stubbed(:user, name: 'Joe')
award = build_stubbed(:award_emoji, user: user)
it "truncates lists of larger than 9 users" do
expect(award_user_list(awards, nil)).to eq(awards.first(9).map { |a| a.user.name }.join(', ') + ", and 6 more.")
expect(award_user_list([award], user)).to eq('You')
expect(award_user_list([award], nil)).to eq 'Joe'
end
it "displays the current user in front of 0-9 other users" do
expect(award_user_list(awards, awards[0].user)).
to eq("You, " + awards[1..9].map { |a| a.user.name }.join(', ') + ", and 5 more.")
it "truncates lists" do
user = build_stubbed(:user, name: 'Jane')
awards = Array.new(5, build_stubbed(:award_emoji, user: user))
expect(award_user_list(awards, nil, limit: 3))
.to eq('Jane, Jane, Jane, and 2 more.')
end
it "displays the current user in front regardless of position in the list" do
expect(award_user_list(awards, awards[12].user)).
to eq("You, " + awards[0..8].map { |a| a.user.name }.join(', ') + ", and 5 more.")
it "displays the current user in front of other users" do
current_user = build_stubbed(:user)
my_award = build_stubbed(:award_emoji, user: current_user)
award = build_stubbed(:award_emoji, user: build_stubbed(:user, name: 'Jane'))
awards = Array.new(5, award).push(my_award)
expect(award_user_list(awards, current_user, limit: 2)).
to eq("You, Jane, and 4 more.")
end
end
......
......@@ -5,6 +5,8 @@
/*= require lib/utils/common_utils */
/*= require lib/utils/type_utility */
/*= require fuzzaldrin-plus */
/*= require turbolinks */
/*= require jquery.turbolinks */
(function() {
var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
......@@ -138,7 +140,7 @@
list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, projectIssuesPath, projectMRsPath);
});
return it('should not show category related menu if there is text in the input', function() {
it('should not show category related menu if there is text in the input', function() {
var link, list;
addBodyAttributes('project');
mockProjectOptions();
......@@ -148,6 +150,23 @@
link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']";
return expect(list.find(link).length).toBe(0);
});
return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
var ENTER = 13;
var DOWN = 40;
addBodyAttributes();
mockDashboardOptions(true);
var submitSpy = spyOnEvent('form', 'submit');
widget.searchInput.focus();
widget.wrap.trigger($.Event('keydown', { which: DOWN }));
var enterKeyEvent = $.Event('keydown', { which: ENTER });
widget.searchInput.trigger(enterKeyEvent);
// This does not currently catch failing behavior. For security reasons,
// browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses.
expect(submitSpy).not.toHaveBeenTriggered();
// Does a worse job at capturing the intent of the test, but works.
expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
});
});
}).call(this);
require 'spec_helper'
describe Banzai::Filter::HTMLEntityFilter, lib: true do
describe Banzai::Filter::HtmlEntityFilter, lib: true do
include FilterSpecHelper
let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' }
......
......@@ -5,7 +5,7 @@ describe Banzai::ObjectRenderer do
let(:user) { project.owner }
def fake_object(attrs = {})
object = double(attrs.merge("new_record?": true, "destroyed?": true))
object = double(attrs.merge("new_record?" => true, "destroyed?" => true))
allow(object).to receive(:markdown_cache_field_for).with(:note).and_return(:note_html)
allow(object).to receive(:banzai_render_context).with(:note).and_return(project: nil, author: nil)
allow(object).to receive(:update_column).with(:note_html, anything).and_return(true)
......
......@@ -22,15 +22,15 @@ describe Gitlab::Shell, lib: true do
it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
describe 'generate_and_link_secret_token' do
describe 'memoized secret_token' do
let(:secret_file) { 'tmp/tests/.secret_shell_test' }
let(:link_file) { 'tmp/tests/shell-secret-test/.gitlab_shell_secret' }
before do
allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test')
allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file)
allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test')
FileUtils.mkdir('tmp/tests/shell-secret-test')
gitlab_shell.generate_and_link_secret_token
Gitlab::Shell.ensure_secret_token!
end
after do
......@@ -39,7 +39,10 @@ describe Gitlab::Shell, lib: true do
end
it 'creates and links the secret token file' do
secret_token = Gitlab::Shell.secret_token
expect(File.exist?(secret_file)).to be(true)
expect(File.read(secret_file).chomp).to eq(secret_token)
expect(File.symlink?(link_file)).to be(true)
expect(File.readlink(link_file)).to eq(secret_file)
end
......
......@@ -126,6 +126,7 @@ describe Namespace, models: true do
it "cleans the path and makes sure it's available" do
expect(Namespace.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
expect(Namespace.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
end
end
end
......@@ -325,7 +325,9 @@ describe Project, models: true do
end
describe 'last_activity methods' do
let(:project) { create(:project, last_activity_at: 2.hours.ago) }
let(:timestamp) { 2.hours.ago }
# last_activity_at gets set to created_at upon creation
let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
describe 'last_activity' do
it 'alias last_activity to last_event' do
......@@ -339,6 +341,7 @@ describe Project, models: true do
it 'returns the creation date of the project\'s last event if present' do
new_event = create(:event, project: project, created_at: Time.now)
project.reload
expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
end
......
......@@ -97,12 +97,20 @@ describe Repository, models: true do
end
describe '#find_commits_by_message' do
subject { repository.find_commits_by_message('submodule').map{ |k| k.id } }
it 'returns commits with messages containing a given string' do
commit_ids = repository.find_commits_by_message('submodule').map(&:id)
it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { is_expected.to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
it { is_expected.to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') }
it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
expect(commit_ids).to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
expect(commit_ids).to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660')
expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e')
end
it 'is case insensitive' do
commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id)
expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
end
end
describe '#blob_at' do
......
require 'spec_helper'
describe ProjectPolicy, models: true do
let(:project) { create(:empty_project, :public) }
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
let(:dev) { create(:user) }
let(:master) { create(:user) }
let(:owner) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:empty_project, :public, namespace: owner.namespace) }
let(:users_ordered_by_permissions) do
[nil, guest, reporter, dev, master, owner, admin]
let(:guest_permissions) do
[
:read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label,
:read_milestone, :read_project_snippet, :read_project_member,
:read_merge_request, :read_note, :create_project, :create_issue, :create_note,
:upload_file
]
end
let(:users_permissions) do
users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size }
let(:reporter_permissions) do
[
:download_code, :fork_project, :create_project_snippet, :update_issue,
:admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build,
:read_container_image, :read_pipeline, :read_environment, :read_deployment
]
end
let(:team_member_reporter_permissions) do
[
:build_download_code, :build_read_container_image
]
end
let(:developer_permissions) do
[
:admin_merge_request, :update_merge_request, :create_commit_status,
:update_commit_status, :create_build, :update_build, :create_pipeline,
:update_pipeline, :create_merge_request, :create_wiki, :push_code,
:resolve_note, :create_container_image, :update_container_image,
:create_environment, :create_deployment
]
end
let(:master_permissions) do
[
:push_code_to_protected_branches, :update_project_snippet, :update_environment,
:update_deployment, :admin_milestone, :admin_project_snippet,
:admin_project_member, :admin_note, :admin_wiki, :admin_project,
:admin_commit_status, :admin_build, :admin_container_image,
:admin_pipeline, :admin_environment, :admin_deployment
]
end
let(:public_permissions) do
[
:download_code, :fork_project, :read_commit_status, :read_pipeline,
:read_container_image, :build_download_code, :build_read_container_image
]
end
let(:owner_permissions) do
[
:change_namespace, :change_visibility_level, :rename_project, :remove_project,
:archive_project, :remove_fork_project, :destroy_merge_request, :destroy_issue
]
end
before do
......@@ -22,16 +70,6 @@ describe ProjectPolicy, models: true do
project.team << [master, :master]
project.team << [dev, :developer]
project.team << [reporter, :reporter]
group = create(:group)
project.project_group_links.create(
group: group,
group_access: Gitlab::Access::MASTER)
group.add_owner(owner)
end
it 'returns increasing permissions for each level' do
expect(users_permissions).to eq(users_permissions.sort.uniq)
end
it 'does not include the read_issue permission when the issue author is not a member of the private project' do
......@@ -46,4 +84,81 @@ describe ProjectPolicy, models: true do
expect(Ability.allowed?(user, :read_issue, project)).to be_falsy
end
context 'abilities for non-public projects' do
let(:project) { create(:empty_project, namespace: owner.namespace) }
subject { described_class.abilities(current_user, project).to_set }
context 'with no user' do
let(:current_user) { nil }
it { is_expected.to be_empty }
end
context 'guests' do
let(:current_user) { guest }
it do
is_expected.to include(*guest_permissions)
is_expected.not_to include(*reporter_permissions)
is_expected.not_to include(*team_member_reporter_permissions)
is_expected.not_to include(*developer_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'reporter' do
let(:current_user) { reporter }
it do
is_expected.to include(*guest_permissions)
is_expected.to include(*reporter_permissions)
is_expected.to include(*team_member_reporter_permissions)
is_expected.not_to include(*developer_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'developer' do
let(:current_user) { dev }
it do
is_expected.to include(*guest_permissions)
is_expected.to include(*reporter_permissions)
is_expected.to include(*team_member_reporter_permissions)
is_expected.to include(*developer_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'master' do
let(:current_user) { master }
it do
is_expected.to include(*guest_permissions)
is_expected.to include(*reporter_permissions)
is_expected.to include(*team_member_reporter_permissions)
is_expected.to include(*developer_permissions)
is_expected.to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'owner' do
let(:current_user) { owner }
it do
is_expected.to include(*guest_permissions)
is_expected.to include(*reporter_permissions)
is_expected.not_to include(*team_member_reporter_permissions)
is_expected.to include(*developer_permissions)
is_expected.to include(*master_permissions)
is_expected.to include(*owner_permissions)
end
end
end
end
......@@ -175,6 +175,36 @@ describe API::API, api: true do
end
end
describe 'GET /projects/visible' do
let(:public_project) { create(:project, :public) }
before do
public_project
project
project2
project3
project4
end
it 'returns the projects viewable by the user' do
get api('/projects/visible', user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] }).
to contain_exactly(public_project.id, project.id, project2.id, project3.id)
end
it 'shows only public projects when the user only has access to those' do
get api('/projects/visible', user2)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] }).
to contain_exactly(public_project.id)
end
end
describe 'GET /projects/starred' do
let(:public_project) { create(:project, :public) }
......
......@@ -110,9 +110,21 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
context 'properly handles multiple stages' do
let(:ref) { mr_merge_if_green_enabled.source_branch }
let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') }
let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') }
let(:pipeline) { create(:ci_empty_pipeline, ref: mr_merge_if_green_enabled.source_branch, project: project) }
let(:sha) { project.commit(ref).id }
let(:pipeline) do
create(:ci_empty_pipeline, ref: ref, sha: sha, project: project)
end
let!(:build) do
create(:ci_build, :created, pipeline: pipeline, ref: ref,
name: 'build', stage: 'build')
end
let!(:test) do
create(:ci_build, :created, pipeline: pipeline, ref: ref,
name: 'test', stage: 'test')
end
before do
# This behavior of MergeRequest: we instantiate a new object
......@@ -121,14 +133,16 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
end
end
it "doesn't merge if some stages failed" do
it "doesn't merge if any of stages failed" do
expect(MergeWorker).not_to receive(:perform_async)
build.success
test.drop
end
it 'merge when all stages succeeded' do
it 'merges when all stages succeeded' do
expect(MergeWorker).to receive(:perform_async)
build.success
test.success
end
......
......@@ -57,6 +57,11 @@ RSpec.configure do |config|
example.run
Rails.cache = caching_store
end
config.after(:each) do
FileUtils.rm_rf("tmp/tests/repositories")
FileUtils.mkdir_p("tmp/tests/repositories")
end
end
FactoryGirl::SyntaxRunner.class_eval do
......
......@@ -5,65 +5,42 @@ describe ExpireBuildArtifactsWorker do
let(:worker) { described_class.new }
before { Sidekiq::Worker.clear_all }
describe '#perform' do
before { build }
subject! { worker.perform }
subject! do
Sidekiq::Testing.fake! { worker.perform }
end
context 'with expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) }
it 'does expire' do
expect(build.reload.artifacts_expired?).to be_truthy
end
it 'does remove files' do
expect(build.reload.artifacts_file.exists?).to be_falsey
end
it 'does nullify artifacts_file column' do
expect(build.reload.artifacts_file_identifier).to be_nil
it 'enqueues that build' do
expect(jobs_enqueued.size).to eq(1)
expect(jobs_enqueued[0]["args"]).to eq([build.id])
end
end
context 'with not yet expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) }
it 'does not expire' do
expect(build.reload.artifacts_expired?).to be_falsey
end
it 'does not remove files' do
expect(build.reload.artifacts_file.exists?).to be_truthy
end
it 'does not nullify artifacts_file column' do
expect(build.reload.artifacts_file_identifier).not_to be_nil
it 'does not enqueue that build' do
expect(jobs_enqueued.size).to eq(0)
end
end
context 'without expire date' do
let(:build) { create(:ci_build, :artifacts) }
it 'does not expire' do
expect(build.reload.artifacts_expired?).to be_falsey
end
it 'does not remove files' do
expect(build.reload.artifacts_file.exists?).to be_truthy
end
it 'does not nullify artifacts_file column' do
expect(build.reload.artifacts_file_identifier).not_to be_nil
it 'does not enqueue that build' do
expect(jobs_enqueued.size).to eq(0)
end
end
context 'for expired artifacts' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) }
it 'is still expired' do
expect(build.reload.artifacts_expired?).to be_truthy
end
def jobs_enqueued
Sidekiq::Queues.jobs_by_worker['ExpireBuildInstanceArtifactsWorker']
end
end
end
require 'spec_helper'
describe ExpireBuildInstanceArtifactsWorker do
include RepoHelpers
let(:worker) { described_class.new }
describe '#perform' do
before { build }
subject! { worker.perform(build.id) }
context 'with expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) }
it 'does expire' do
expect(build.reload.artifacts_expired?).to be_truthy
end
it 'does remove files' do
expect(build.reload.artifacts_file.exists?).to be_falsey
end
it 'does nullify artifacts_file column' do
expect(build.reload.artifacts_file_identifier).to be_nil
end
end
context 'with not yet expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) }
it 'does not expire' do
expect(build.reload.artifacts_expired?).to be_falsey
end
it 'does not remove files' do
expect(build.reload.artifacts_file.exists?).to be_truthy
end
it 'does not nullify artifacts_file column' do
expect(build.reload.artifacts_file_identifier).not_to be_nil
end
end
context 'without expire date' do
let(:build) { create(:ci_build, :artifacts) }
it 'does not expire' do
expect(build.reload.artifacts_expired?).to be_falsey
end
it 'does not remove files' do
expect(build.reload.artifacts_file.exists?).to be_truthy
end
it 'does not nullify artifacts_file column' do
expect(build.reload.artifacts_file_identifier).not_to be_nil
end
end
context 'for expired artifacts' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) }
it 'is still expired' do
expect(build.reload.artifacts_expired?).to be_truthy
end
end
end
end
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