Commit 44558c72 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch 'master' into all-skipped-equals-success

* master: (103 commits)
  Fixes sidebar navigation.
  Convert "SSH Keys" Spinach features to RSpec
  Enable import/export back for non-admins
  Update gitlab-shell to 3.6.3
  Updated artwork of empty group state.
  Better empty state for Groups view.
  Members::RequestAccessService is tricter on permissions
  Add a /wip slash command
  Link to the "What requires downtime?" page from the Migration Style Guide
  Fix RuboCop failure in app/services/notification_service.rb
  Add word-wrap to issue title on issue and milestone boards
  Fix page scrolling to top on sidebar toggle
  Changed zero padded days to no padded days in date_format
  GrapeDSL for Keys endpoint
  Remove duplicate test
  Add a spec to verify comparison context inclusion in path when a version is chosen to compare against
  Add flash containers and broadcast messages below subnav
  Add white background to create MR banner
  Move create MR banner below subnav
  Remove contianer from last push widget
  ...
parents afc0ae5c 8c5701b6
image: "ruby:2.3.1" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1"
cache: cache:
key: "ruby-231" key: "ruby-231"
paths: paths:
- vendor/apt
- vendor/ruby - vendor/ruby
variables: variables:
...@@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack ...@@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.1 # Execute all testing suites against Ruby 2.1
.ruby-21: &ruby-21 .ruby-21: &ruby-21
image: "ruby:2.1" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1"
<<: *use-db <<: *use-db
only: only:
- master - master
cache: cache:
key: "ruby21" key: "ruby21"
paths: paths:
- vendor/apt
- vendor/ruby - vendor/ruby
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21 .rspec-knapsack-ruby21: &rspec-knapsack-ruby21
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.13.0 (unreleased) v 8.13.0 (unreleased)
- Add link from system note to compare with previous version
- Use gitlab-shell v3.6.2 (GIT TRACE logging) - Use gitlab-shell v3.6.2 (GIT TRACE logging)
- Fix centering of custom header logos (Ashley Dumaine)
- AbstractReferenceFilter caches project_refs on RequestStore when active - 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)
- Speed-up group milestones show page - Speed-up group milestones show page
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Add more tests for calendar contribution (ClemMakesApps) - Add more tests for calendar contribution (ClemMakesApps)
- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
- Fix permission for setting an issue's due date - Fix permission for setting an issue's due date
- Expose expires_at field when sharing project on API - Expose expires_at field when sharing project on API
- Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell)
- Allow the Koding integration to be configured through the API
- Added soft wrap button to repository file/blob editor
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Use a ConnectionPool for Rails.cache on Sidekiq servers - Use a ConnectionPool for Rails.cache on Sidekiq servers
- Replace `alias_method_chain` with `Module#prepend`
- Enable GitLab Import/Export for non-admin users.
- Preserve label filters when sorting !6136 (Joseph Frazier)
- Only update issuable labels if they have been changed - Only update issuable labels if they have been changed
- Take filters in account in issuable counters. !6496
- Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*)
- Revoke button in Applications Settings underlines on hover. - Revoke button in Applications Settings underlines on hover.
- Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska)
- Fix Long commit messages overflow viewport in file tree - Fix Long commit messages overflow viewport in file tree
- Revert avoid touching file system on Build#artifacts?
- Add broadcast messages and alerts below sub-nav
- Better empty state for Groups view
- Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe)
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
- Add organization field to user profile - Add organization field to user profile
- Fix resolved discussion display in side-by-side diff view !6575 - Fix resolved discussion display in side-by-side diff view !6575
- Optimize GitHub importing for speed and memory - Optimize GitHub importing for speed and memory
- API: expose pipeline data in builds API (!6502, Guilherme Salazar) - API: expose pipeline data in builds API (!6502, Guilherme Salazar)
- Notify the Merger about merge after successful build (Dimitris Karakasilis)
- Fix broken repository 500 errors in project list - Fix broken repository 500 errors in project list
- Close todos when accepting merge requests via the API !6486 (tonygambone)
v 8.12.4 (unreleased)
v 8.12.3
- Update Gitlab Shell to support low IO priority for storage moves
v 8.12.2 (unreleased) v 8.12.2 (unreleased)
- Added University content to doc/university
- Fix Import/Export not recognising correctly the imported services. - Fix Import/Export not recognising correctly the imported services.
- Fix snippets pagination - Fix snippets pagination
- Fix "Create project" button layout when visibility options are restricted
- Fix List-Unsubscribe header in emails - Fix List-Unsubscribe header in emails
- Fix IssuesController#show degradation including project on loaded notes - Fix IssuesController#show degradation including project on loaded notes
- Fix an issue with the "Commits" section of the cycle analytics summary. !6513 - Fix an issue with the "Commits" section of the cycle analytics summary. !6513
- Fix errors importing project feature and milestone models using GitLab project import - Fix errors importing project feature and milestone models using GitLab project import
- Make JWT messages Docker-compatible - Make JWT messages Docker-compatible
- Fix duplicate branch entry in the merge request version compare dropdown - Fix duplicate branch entry in the merge request version compare dropdown
- Respect the fork_project permission when forking projects
- Only update issuable labels if they have been changed
- 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
v 8.12.1 v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
- Fix issue with search filter labels not displaying - Fix issue with search filter labels not displaying
- Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv)
v 8.12.0 v 8.12.0
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
...@@ -52,7 +81,6 @@ v 8.12.0 ...@@ -52,7 +81,6 @@ v 8.12.0
- Filter tags by name !6121 - Filter tags by name !6121
- Update gitlab shell secret file also when it is empty. !3774 (glensc) - Update gitlab shell secret file also when it is empty. !3774 (glensc)
- Give project selection dropdowns responsive width, make non-wrapping. - Give project selection dropdowns responsive width, make non-wrapping.
- Fix resolve discussion buttons endpoint path
- Fix note form hint showing slash commands supported for commits. - Fix note form hint showing slash commands supported for commits.
- Make push events have equal vertical spacing. - Make push events have equal vertical spacing.
- API: Ensure invitees are not returned in Members API. - API: Ensure invitees are not returned in Members API.
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
// submitted textarea // submitted textarea
})(this)); })(this));
this.initModePanesAndLinks(); this.initModePanesAndLinks();
this.initSoftWrap();
new BlobLicenseSelectors({ new BlobLicenseSelectors({
editor: this.editor editor: this.editor
}); });
...@@ -50,6 +51,7 @@ ...@@ -50,6 +51,7 @@
this.$editModePanes.hide(); this.$editModePanes.hide();
currentPane.fadeIn(200); currentPane.fadeIn(200);
if (paneId === "#preview") { if (paneId === "#preview") {
this.$toggleButton.hide();
return $.post(currentLink.data("preview-url"), { return $.post(currentLink.data("preview-url"), {
content: this.editor.getValue() content: this.editor.getValue()
}, function(response) { }, function(response) {
...@@ -57,10 +59,23 @@ ...@@ -57,10 +59,23 @@
return currentPane.syntaxHighlight(); return currentPane.syntaxHighlight();
}); });
} else { } else {
this.$toggleButton.show();
return this.editor.focus(); return this.editor.focus();
} }
}; };
EditBlob.prototype.initSoftWrap = function() {
this.isSoftWrapped = false;
this.$toggleButton = $('.soft-wrap-toggle');
this.$toggleButton.on('click', this.toggleSoftWrap.bind(this));
};
EditBlob.prototype.toggleSoftWrap = function(e) {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
};
return EditBlob; return EditBlob;
})(); })();
......
...@@ -23,8 +23,9 @@ ...@@ -23,8 +23,9 @@
selectable: true, selectable: true,
filterable: true, filterable: true,
filterByText: true, filterByText: true,
fieldName: $dropdown.attr('name'), toggleLabel: true,
filterInput: 'input[type="text"]', fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]',
renderRow: function(ref) { renderRow: function(ref) {
var link; var link;
if (ref.header != null) { if (ref.header != null) {
......
...@@ -10,12 +10,13 @@ ...@@ -10,12 +10,13 @@
ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>'; COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file) { function SingleFileDiff(file) {
this.file = file; this.file = file;
this.toggleDiff = bind(this.toggleDiff, this); this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file); this.content = $('.diff-content', this.file);
this.$toggleIcon = $('.diff-toggle-caret', this.file);
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
this.isOpen = !this.diffForPath; this.isOpen = !this.diffForPath;
if (this.diffForPath) { if (this.diffForPath) {
...@@ -23,18 +24,22 @@ ...@@ -23,18 +24,22 @@
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
this.content = null; this.content = null;
this.collapsedContent.after(this.loadingContent); this.collapsedContent.after(this.loadingContent);
this.$toggleIcon.addClass('fa-caret-right');
} else { } else {
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
this.content.after(this.collapsedContent); this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down');
} }
this.collapsedContent.on('click', this.toggleDiff); $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
$('.file-title > a', this.file).on('click', this.toggleDiff);
} }
SingleFileDiff.prototype.toggleDiff = function(e) { SingleFileDiff.prototype.toggleDiff = function(e) {
var $target = $(e.target);
if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) { if (!this.isOpen && !this.hasError) {
this.content.hide(); this.content.hide();
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
this.collapsedContent.show(); this.collapsedContent.show();
if (typeof DiffNotesApp !== 'undefined') { if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents(); DiffNotesApp.compileComponents();
...@@ -42,10 +47,12 @@ ...@@ -42,10 +47,12 @@
} else if (this.content) { } else if (this.content) {
this.collapsedContent.hide(); this.collapsedContent.hide();
this.content.show(); this.content.show();
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
if (typeof DiffNotesApp !== 'undefined') { if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents(); DiffNotesApp.compileComponents();
} }
} else { } else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML(); return this.getContentHTML();
} }
}; };
......
...@@ -19,10 +19,8 @@ ...@@ -19,10 +19,8 @@
&.diff-collapsed { &.diff-collapsed {
padding: 5px; padding: 5px;
cursor: pointer; .click-to-expand {
cursor: pointer;
&:hover {
background-color: $row-hover;
} }
} }
} }
......
...@@ -26,6 +26,15 @@ ...@@ -26,6 +26,15 @@
padding: 10px $gl-padding; padding: 10px $gl-padding;
word-wrap: break-word; word-wrap: break-word;
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
cursor: pointer;
&:hover {
background-color: $dark-background-color;
}
.diff-toggle-caret {
padding-right: 6px;
}
&.file-title-clear { &.file-title-clear {
padding-left: 0; padding-left: 0;
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
margin: 0; margin: 0;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
font-size: 14px; font-size: 14px;
position: relative;
z-index: 1;
.flash-notice { .flash-notice {
@extend .alert; @extend .alert;
...@@ -33,6 +35,12 @@ ...@@ -33,6 +35,12 @@
} }
} }
.content-wrapper {
.flash-notice .container-fluid {
background-color: transparent;
}
}
@media (max-width: $screen-md-min) { @media (max-width: $screen-md-min) {
ul.notes { ul.notes {
.flash-container.timeline-content { .flash-container.timeline-content {
......
...@@ -112,11 +112,15 @@ header { ...@@ -112,11 +112,15 @@ header {
.header-logo { .header-logo {
position: absolute; position: absolute;
left: 50%; left: 50%;
margin-left: -18px;
top: 7px; top: 7px;
transition-duration: .3s; transition-duration: .3s;
z-index: 999; z-index: 999;
#logo {
position: relative;
left: -50%;
}
svg, img { svg, img {
height: 36px; height: 36px;
} }
...@@ -126,8 +130,12 @@ header { ...@@ -126,8 +130,12 @@ header {
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
right: 25px; right: 20px;
left: auto; left: auto;
#logo {
left: auto;
}
} }
} }
......
...@@ -142,6 +142,7 @@ ...@@ -142,6 +142,7 @@
transition-duration: .3s; transition-duration: .3s;
position: absolute; position: absolute;
top: 0; top: 0;
cursor: pointer;
&:hover, &:hover,
&:focus { &:focus {
......
...@@ -197,6 +197,7 @@ lex ...@@ -197,6 +197,7 @@ lex
a { a {
color: inherit; color: inherit;
word-wrap: break-word;
} }
} }
......
...@@ -107,10 +107,14 @@ ...@@ -107,10 +107,14 @@
.block { .block {
width: 100%; width: 100%;
}
.block-first { &.coverage {
padding: 5px 16px 11px; padding: 0 16px 11px;
}
.btn-group-justified {
margin-top: 5px;
}
} }
.js-build-variable { .js-build-variable {
...@@ -214,6 +218,9 @@ ...@@ -214,6 +218,9 @@
.build-detail-row { .build-detail-row {
margin-bottom: 5px; margin-bottom: 5px;
&:last-of-type {
margin-bottom: 0;
}
} }
.build-light-text { .build-light-text {
......
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
} }
.encoding-selector, .encoding-selector,
.soft-wrap-toggle,
.license-selector, .license-selector,
.gitignore-selector, .gitignore-selector,
.gitlab-ci-yml-selector { .gitlab-ci-yml-selector {
...@@ -67,6 +68,24 @@ ...@@ -67,6 +68,24 @@
font-family: $regular_font; font-family: $regular_font;
} }
.soft-wrap-toggle {
margin: 0 $btn-side-margin;
.soft-wrap {
display: block;
}
.no-wrap {
display: none;
}
&.soft-wrap-active {
.soft-wrap {
display: none;
}
.no-wrap {
display: block;
}
}
}
.gitignore-selector, .license-selector, .gitlab-ci-yml-selector { .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown { .dropdown {
line-height: 21px; line-height: 21px;
......
...@@ -57,7 +57,6 @@ ...@@ -57,7 +57,6 @@
} }
.groups-header { .groups-header {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.nav-links { .nav-links {
width: 35%; width: 35%;
...@@ -68,3 +67,38 @@ ...@@ -68,3 +67,38 @@
} }
} }
} }
.groups-empty-state {
padding: 50px 100px;
overflow: hidden;
@media (max-width: $screen-md-min) {
padding: 50px 0;
}
svg {
float: right;
@media (max-width: $screen-md-min) {
float: none;
display: block;
width: 250px;
position: relative;
left: 50%;
margin-left: -125px;
}
}
.text-content {
float: left;
width: 460px;
margin-top: 120px;
@media (max-width: $screen-md-min) {
float: none;
margin-top: 60px;
width: auto;
text-align: center;
}
}
}
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
// Issue title // Issue title
span a { span a {
color: $gl-text-color; color: $gl-text-color;
word-wrap: break-word;
} }
} }
} }
......
...@@ -743,6 +743,62 @@ pre.light-well { ...@@ -743,6 +743,62 @@ pre.light-well {
.dropdown-menu { .dropdown-menu {
width: 300px; width: 300px;
} }
&.from .compare-dropdown-toggle {
width: 237px;
}
&.to .compare-dropdown-toggle {
width: 254px;
}
.dropdown-toggle-text {
display: block;
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
}
.compare-ellipsis {
display: inline;
}
@media (max-width: $screen-xs-max) {
.compare-form-group {
.input-group {
width: 100%;
& > .compare-dropdown-toggle {
width: 100%;
}
}
.dropdown-menu {
width: 100%;
}
}
.compare-switch-container {
text-align: center;
padding: 0 0 $gl-padding;
.commits-compare-switch {
float: none;
}
}
.compare-ellipsis {
display: block;
text-align: center;
padding: 0 0 $gl-padding;
}
.commits-compare-btn {
width: 100%;
}
} }
.clearable-input { .clearable-input {
...@@ -779,4 +835,4 @@ pre.light-well { ...@@ -779,4 +835,4 @@ pre.light-well {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
} }
\ No newline at end of file
...@@ -10,7 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -10,7 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController
def show def show
@members = @group.members.order("access_level DESC").page(params[:members_page]) @members = @group.members.order("access_level DESC").page(params[:members_page])
@requesters = @group.requesters @requesters = AccessRequestsFinder.new(@group).execute(current_user)
@projects = @group.projects.page(params[:projects_page]) @projects = @group.projects.page(params[:projects_page])
end end
......
...@@ -22,7 +22,7 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -22,7 +22,7 @@ class Admin::ProjectsController < Admin::ApplicationController
end end
@project_members = @project.members.page(params[:project_members_page]) @project_members = @project.members.page(params[:project_members_page])
@requesters = @project.requesters @requesters = AccessRequestsFinder.new(@project).execute(current_user)
end end
def transfer def transfer
......
...@@ -14,6 +14,7 @@ module Ci ...@@ -14,6 +14,7 @@ module Ci
@config_processor = Ci::GitlabCiYamlProcessor.new(@content) @config_processor = Ci::GitlabCiYamlProcessor.new(@content)
@stages = @config_processor.stages @stages = @config_processor.stages
@builds = @config_processor.builds @builds = @config_processor.builds
@jobs = @config_processor.jobs
end end
rescue rescue
@error = 'Undefined error' @error = 'Undefined error'
......
...@@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
@members = @members.order('access_level DESC').page(params[:page]).per(50) @members = @members.order('access_level DESC').page(params[:page]).per(50)
@requesters = @group.requesters if can?(current_user, :admin_group, @group) @requesters = AccessRequestsFinder.new(@group).execute(current_user)
@group_member = @group.group_members.new @group_member = @group.group_members.new
end end
......
class Import::GitlabProjectsController < Import::BaseController class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled before_action :verify_gitlab_project_import_enabled
before_action :authenticate_admin!
def new def new
@namespace_id = project_params[:namespace_id] @namespace_id = project_params[:namespace_id]
...@@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file :path, :namespace_id, :file
) )
end end
def authenticate_admin!
render_404 unless current_user.is_admin?
end
end end
...@@ -33,7 +33,7 @@ module Projects ...@@ -33,7 +33,7 @@ module Projects
def issue def issue
@issue ||= @issue ||=
IssuesFinder.new(current_user, project_id: project.id, state: 'all') IssuesFinder.new(current_user, project_id: project.id)
.execute .execute
.where(iid: params[:id]) .where(iid: params[:id])
.first! .first!
......
...@@ -18,6 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -18,6 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :define_commit_vars, only: [:diffs] before_action :define_commit_vars, only: [:diffs]
before_action :define_diff_comment_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines]
before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
# Allow read any merge_request # Allow read any merge_request
before_action :authorize_read_merge_request! before_action :authorize_read_merge_request!
...@@ -275,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -275,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def remove_wip def remove_wip
MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request) MergeRequests::UpdateService.new(project, current_user, wip_event: 'unwip').execute(@merge_request)
redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request),
notice: "The merge request can now be merged." notice: "The merge request can now be merged."
...@@ -308,8 +309,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -308,8 +309,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return return
end end
TodoService.new.merge_merge_request(merge_request, current_user)
@merge_request.update(merge_error: nil) @merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? if params[:merge_when_build_succeeds].present?
...@@ -418,10 +417,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -418,10 +417,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def validates_merge_request def validates_merge_request
# If source project was removed and merge request for some reason
# wasn't close (Ex. mr from fork to origin)
return invalid_mr if !@merge_request.source_project && @merge_request.open?
# Show git not found page # Show git not found page
# if there is no saved commits between source & target branch # if there is no saved commits between source & target branch
if @merge_request.commits.blank? if @merge_request.commits.blank?
...@@ -496,7 +491,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -496,7 +491,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def invalid_mr def invalid_mr
# Render special view for MR with removed source or target branch # Render special view for MR with removed target branch
render 'invalid' render 'invalid'
end end
...@@ -538,4 +533,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -538,4 +533,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diff_notes_disabled = !@merge_request_diff.latest? @diff_notes_disabled = !@merge_request_diff.latest?
@diffs = @merge_request_diff.diffs(diff_options) @diffs = @merge_request_diff.diffs(diff_options)
end end
def close_merge_request_without_source_project
if !@merge_request.source_project && @merge_request.open?
@merge_request.close
end
end
end end
...@@ -29,7 +29,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -29,7 +29,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_members = @group_members.order('access_level DESC') @group_members = @group_members.order('access_level DESC')
end end
@requesters = @project.requesters if can?(current_user, :admin_project, @project) @requesters = AccessRequestsFinder.new(@project).execute(current_user)
@project_member = @project.project_members.new @project_member = @project.project_members.new
@project_group_links = @project.project_group_links @project_group_links = @project.project_group_links
......
...@@ -137,10 +137,10 @@ class ProjectsController < Projects::ApplicationController ...@@ -137,10 +137,10 @@ class ProjectsController < Projects::ApplicationController
noteable = noteable =
case params[:type] case params[:type]
when 'Issue' when 'Issue'
IssuesFinder.new(current_user, project_id: @project.id, state: 'all'). IssuesFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id]) execute.find_by(iid: params[:type_id])
when 'MergeRequest' when 'MergeRequest'
MergeRequestsFinder.new(current_user, project_id: @project.id, state: 'all'). MergeRequestsFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id]) execute.find_by(iid: params[:type_id])
when 'Commit' when 'Commit'
@project.commit(params[:type_id]) @project.commit(params[:type_id])
......
class AccessRequestsFinder
attr_accessor :source
# Arguments:
# source - a Group or Project
def initialize(source)
@source = source
end
def execute(*args)
execute!(*args)
rescue Gitlab::Access::AccessDeniedError
[]
end
def execute!(current_user)
raise Gitlab::Access::AccessDeniedError unless can_see_access_requests?(current_user)
source.requesters
end
private
def can_see_access_requests?(current_user)
source && Ability.allowed?(current_user, :"admin_#{source.class.to_s.underscore}", source)
end
end
...@@ -183,17 +183,12 @@ class IssuableFinder ...@@ -183,17 +183,12 @@ class IssuableFinder
end end
def by_state(items) def by_state(items)
case params[:state] params[:state] ||= 'all'
when 'closed'
items.closed if items.respond_to?(params[:state])
when 'merged' items.public_send(params[:state])
items.respond_to?(:merged) ? items.merged : items.closed
when 'all'
items
when 'opened'
items.opened
else else
raise 'You must specify default state' items
end end
end end
......
...@@ -280,32 +280,6 @@ module ApplicationHelper ...@@ -280,32 +280,6 @@ module ApplicationHelper
end end
end end
def state_filters_text_for(entity, project)
titles = {
opened: "Open"
}
entity_title = titles[entity] || entity.to_s.humanize
count =
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.visible_to_user(current_user).send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
html = content_tag :span, entity_title
if count.present?
html += " "
html += content_tag :span, number_with_delimiter(count), class: 'badge'
end
html.html_safe
end
def truncate_first_line(message, length = 50) def truncate_first_line(message, length = 50)
truncate(message.each_line.first.chomp, length: length) if message truncate(message.each_line.first.chomp, length: length) if message
end end
......
...@@ -94,6 +94,24 @@ module IssuablesHelper ...@@ -94,6 +94,24 @@ module IssuablesHelper
label_names.join(', ') label_names.join(', ')
end end
def issuables_state_counter_text(issuable_type, state)
titles = {
opened: "Open"
}
state_title = titles[state] || state.to_s.humanize
count =
Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
issuables_count_for_state(issuable_type, state)
end
html = content_tag(:span, state_title)
html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
html.html_safe
end
private private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
...@@ -111,4 +129,22 @@ module IssuablesHelper ...@@ -111,4 +129,22 @@ module IssuablesHelper
issuable.open? ? :opened : :closed issuable.open? ? :opened : :closed
end end
end end
def issuables_count_for_state(issuable_type, state)
issuables_finder = public_send("#{issuable_type}_finder")
issuables_finder.params[:state] = state
issuables_finder.execute.page(1).total_count
end
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
def issuables_state_counter_cache_key(issuable_type, state)
opts = params.with_indifferent_access
opts[:state] = state
opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
end
end end
...@@ -373,7 +373,7 @@ module Ci ...@@ -373,7 +373,7 @@ module Ci
end end
def artifacts? def artifacts?
!artifacts_expired? && self[:artifacts_file].present? !artifacts_expired? && artifacts_file.exists?
end end
def artifacts_metadata? def artifacts_metadata?
......
...@@ -8,9 +8,6 @@ module AccessRequestable ...@@ -8,9 +8,6 @@ module AccessRequestable
extend ActiveSupport::Concern extend ActiveSupport::Concern
def request_access(user) def request_access(user)
members.create( Members::RequestAccessService.new(self, user).execute
access_level: Gitlab::Access::DEVELOPER,
user: user,
requested_at: Time.now.utc)
end end
end end
...@@ -102,40 +102,44 @@ class Group < Namespace ...@@ -102,40 +102,44 @@ class Group < Namespace
self[:lfs_enabled] self[:lfs_enabled]
end end
def add_users(user_ids, access_level, current_user: nil, expires_at: nil) def add_users(users, access_level, current_user: nil, expires_at: nil)
user_ids.each do |user_id| GroupMember.add_users_to_group(
Member.add_user( self,
self.group_members, users,
user_id, access_level,
access_level, current_user: current_user,
current_user: current_user, expires_at: expires_at
expires_at: expires_at )
)
end
end end
def add_user(user, access_level, current_user: nil, expires_at: nil) def add_user(user, access_level, current_user: nil, expires_at: nil)
add_users([user], access_level, current_user: current_user, expires_at: expires_at) GroupMember.add_user(
self,
user,
access_level,
current_user: current_user,
expires_at: expires_at
)
end end
def add_guest(user, current_user = nil) def add_guest(user, current_user = nil)
add_user(user, Gitlab::Access::GUEST, current_user: current_user) add_user(user, :guest, current_user: current_user)
end end
def add_reporter(user, current_user = nil) def add_reporter(user, current_user = nil)
add_user(user, Gitlab::Access::REPORTER, current_user: current_user) add_user(user, :reporter, current_user: current_user)
end end
def add_developer(user, current_user = nil) def add_developer(user, current_user = nil)
add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user) add_user(user, :developer, current_user: current_user)
end end
def add_master(user, current_user = nil) def add_master(user, current_user = nil)
add_user(user, Gitlab::Access::MASTER, current_user: current_user) add_user(user, :master, current_user: current_user)
end end
def add_owner(user, current_user = nil) def add_owner(user, current_user = nil)
add_user(user, Gitlab::Access::OWNER, current_user: current_user) add_user(user, :owner, current_user: current_user)
end end
def has_owner?(user) def has_owner?(user)
......
...@@ -80,49 +80,70 @@ class Member < ActiveRecord::Base ...@@ -80,49 +80,70 @@ class Member < ActiveRecord::Base
find_by(invite_token: invite_token) find_by(invite_token: invite_token)
end end
# This method is used to find users that have been entered into the "Add members" field. def add_user(source, user, access_level, current_user: nil, expires_at: nil)
# These can be the User objects directly, their IDs, their emails, or new emails to be invited. user = retrieve_user(user)
def user_for_id(user_id) access_level = retrieve_access_level(access_level)
return user_id if user_id.is_a?(User)
user = User.find_by(id: user_id)
user ||= User.find_by(email: user_id)
user ||= user_id
user
end
def add_user(members, user_id, access_level, current_user: nil, expires_at: nil)
user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited # `user` can be either a User object or an email to be invited
if user.is_a?(User) member =
member = members.find_or_initialize_by(user_id: user.id) if user.is_a?(User)
source.members.find_by(user_id: user.id) ||
source.requesters.find_by(user_id: user.id) ||
source.members.build(user_id: user.id)
else
source.members.build(invite_email: user)
end
return member unless can_update_member?(current_user, member)
member.attributes = {
created_by: member.created_by || current_user,
access_level: access_level,
expires_at: expires_at
}
if member.request?
::Members::ApproveAccessRequestService.new(source, current_user, id: member.id).execute
else else
member = members.build member.save
member.invite_email = user
end end
if can_update_member?(current_user, member) || project_creator?(member, access_level) member
member.created_by ||= current_user end
member.access_level = access_level
member.expires_at = expires_at
member.save def access_levels
end Gitlab::Access.sym_options
end end
private private
# This method is used to find users that have been entered into the "Add members" field.
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
def retrieve_user(user)
return user if user.is_a?(User)
User.find_by(id: user) || User.find_by(email: user) || user
end
def retrieve_access_level(access_level)
access_levels.fetch(access_level) { access_level.to_i }
end
def can_update_member?(current_user, member) def can_update_member?(current_user, member)
# There is no current user for bulk actions, in which case anything is allowed # There is no current user for bulk actions, in which case anything is allowed
!current_user || !current_user || current_user.can?(:"update_#{member.type.underscore}", member)
current_user.can?(:update_group_member, member) ||
current_user.can?(:update_project_member, member)
end end
def project_creator?(member, access_level) def add_users_to_source(source, users, access_level, current_user: nil, expires_at: nil)
member.new_record? && member.owner? && users.each do |user|
access_level.to_i == ProjectMember::MASTER add_user(
source,
user,
access_level,
current_user: current_user,
expires_at: expires_at
)
end
end end
end end
......
...@@ -12,6 +12,22 @@ class GroupMember < Member ...@@ -12,6 +12,22 @@ class GroupMember < Member
Gitlab::Access.options_with_owner Gitlab::Access.options_with_owner
end end
def self.access_levels
Gitlab::Access.sym_options_with_owner
end
def self.add_users_to_group(group, users, access_level, current_user: nil, expires_at: nil)
self.transaction do
add_users_to_source(
group,
users,
access_level,
current_user: current_user,
expires_at: expires_at
)
end
end
def group def group
source source
end end
......
...@@ -34,36 +34,20 @@ class ProjectMember < Member ...@@ -34,36 +34,20 @@ class ProjectMember < Member
# :master # :master
# ) # )
# #
def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil) def add_users_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil)
access_level = if roles_hash.has_key?(access) self.transaction do
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
access
else
raise "Non valid access"
end
users = user_ids.map { |user_id| Member.user_for_id(user_id) }
ProjectMember.transaction do
project_ids.each do |project_id| project_ids.each do |project_id|
project = Project.find(project_id) project = Project.find(project_id)
users.each do |user| add_users_to_source(
Member.add_user( project,
project.project_members, users,
user, access_level,
access_level, current_user: current_user,
current_user: current_user, expires_at: expires_at
expires_at: expires_at )
)
end
end end
end end
true
rescue
false
end end
def truncate_teams(project_ids) def truncate_teams(project_ids)
...@@ -84,13 +68,15 @@ class ProjectMember < Member ...@@ -84,13 +68,15 @@ class ProjectMember < Member
truncate_teams [project.id] truncate_teams [project.id]
end end
def roles_hash
Gitlab::Access.sym_options
end
def access_level_roles def access_level_roles
Gitlab::Access.options Gitlab::Access.options
end end
private
def can_update_member?(current_user, member)
super || (member.owner? && member.new_record?)
end
end end
def access_field def access_field
......
...@@ -155,6 +155,20 @@ class MergeRequest < ActiveRecord::Base ...@@ -155,6 +155,20 @@ class MergeRequest < ActiveRecord::Base
where("merge_requests.id IN (#{union.to_sql})") where("merge_requests.id IN (#{union.to_sql})")
end end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def self.work_in_progress?(title)
!!(title =~ WIP_REGEX)
end
def self.wipless_title(title)
title.sub(WIP_REGEX, "")
end
def self.wip_title(title)
work_in_progress?(title) ? title : "WIP: #{title}"
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
...@@ -389,14 +403,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -389,14 +403,16 @@ class MergeRequest < ActiveRecord::Base
@closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def work_in_progress? def work_in_progress?
!!(title =~ WIP_REGEX) self.class.work_in_progress?(title)
end end
def wipless_title def wipless_title
self.title.sub(WIP_REGEX, "") self.class.wipless_title(self.title)
end
def wip_title
self.class.wip_title(self.title)
end end
def mergeable?(skip_ci_check: false) def mergeable?(skip_ci_check: false)
......
...@@ -158,7 +158,7 @@ class Milestone < ActiveRecord::Base ...@@ -158,7 +158,7 @@ class Milestone < ActiveRecord::Base
end end
def title=(value) def title=(value)
write_attribute(:title, Sanitize.clean(value.to_s)) if value.present? write_attribute(:title, sanitize_title(value)) if value.present?
end end
# Sorts the issues for the given IDs. # Sorts the issues for the given IDs.
...@@ -204,4 +204,8 @@ class Milestone < ActiveRecord::Base ...@@ -204,4 +204,8 @@ class Milestone < ActiveRecord::Base
iid iid
end end
end end
def sanitize_title(value)
CGI.unescape_html(Sanitize.clean(value.to_s))
end
end end
...@@ -146,6 +146,7 @@ class Project < ActiveRecord::Base ...@@ -146,6 +146,7 @@ class Project < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
delegate :add_user, to: :team
# Validations # Validations
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
...@@ -1016,10 +1017,6 @@ class Project < ActiveRecord::Base ...@@ -1016,10 +1017,6 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user) project_members.find_by(user_id: user)
end end
def add_user(user, access_level, current_user: nil, expires_at: nil)
team.add_user(user, access_level, current_user: current_user, expires_at: expires_at)
end
def default_branch def default_branch
@default_branch ||= repository.root_ref if repository.exists? @default_branch ||= repository.root_ref if repository.exists?
end end
......
...@@ -33,18 +33,24 @@ class ProjectTeam ...@@ -33,18 +33,24 @@ class ProjectTeam
member member
end end
def add_users(users, access, current_user: nil, expires_at: nil) def add_users(users, access_level, current_user: nil, expires_at: nil)
ProjectMember.add_users_to_projects( ProjectMember.add_users_to_projects(
[project.id], [project.id],
users, users,
access, access_level,
current_user: current_user, current_user: current_user,
expires_at: expires_at expires_at: expires_at
) )
end end
def add_user(user, access, current_user: nil, expires_at: nil) def add_user(user, access_level, current_user: nil, expires_at: nil)
add_users([user], access, current_user: current_user, expires_at: expires_at) ProjectMember.add_user(
project,
user,
access_level,
current_user: current_user,
expires_at: expires_at
)
end end
# Remove all users from project team # Remove all users from project team
......
module Members
class RequestAccessService < BaseService
attr_accessor :source
def initialize(source, current_user)
@source = source
@current_user = current_user
end
def execute
raise Gitlab::Access::AccessDeniedError unless can_request_access?(source)
source.members.create(
access_level: Gitlab::Access::DEVELOPER,
user: current_user,
requested_at: Time.now.utc)
end
private
def can_request_access?(source)
source && can?(current_user, :request_access, source)
end
end
end
...@@ -5,16 +5,17 @@ module MergeRequests ...@@ -5,16 +5,17 @@ module MergeRequests
end end
def create_title_change_note(issuable, old_title) def create_title_change_note(issuable, old_title)
removed_wip = old_title =~ MergeRequest::WIP_REGEX && !issuable.work_in_progress? removed_wip = MergeRequest.work_in_progress?(old_title) && !issuable.work_in_progress?
added_wip = old_title !~ MergeRequest::WIP_REGEX && issuable.work_in_progress? added_wip = !MergeRequest.work_in_progress?(old_title) && issuable.work_in_progress?
changed_title = MergeRequest.wipless_title(old_title) != issuable.wipless_title
if removed_wip if removed_wip
SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user) SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user)
elsif added_wip elsif added_wip
SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user) SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user)
else
super
end end
super if changed_title
end end
def hook_data(merge_request, action, oldrev = nil) def hook_data(merge_request, action, oldrev = nil)
......
...@@ -7,6 +7,7 @@ module MergeRequests ...@@ -7,6 +7,7 @@ module MergeRequests
class PostMergeService < MergeRequests::BaseService class PostMergeService < MergeRequests::BaseService
def execute(merge_request) def execute(merge_request)
close_issues(merge_request) close_issues(merge_request)
todo_service.merge_merge_request(merge_request, current_user)
merge_request.mark_as_merged merge_request.mark_as_merged
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
......
...@@ -16,7 +16,7 @@ module MergeRequests ...@@ -16,7 +16,7 @@ module MergeRequests
end end
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
handle_wip_event(merge_request)
update(merge_request) update(merge_request)
end end
...@@ -81,5 +81,18 @@ module MergeRequests ...@@ -81,5 +81,18 @@ module MergeRequests
def after_update(issuable) def after_update(issuable)
issuable.cache_merge_request_closes_issues!(current_user) issuable.cache_merge_request_closes_issues!(current_user)
end end
private
def handle_wip_event(merge_request)
if wip_event = params.delete(:wip_event)
# We update the title that is provided in the params or we use the mr title
title = params[:title] || merge_request.title
params[:title] = case wip_event
when 'wip' then MergeRequest.wip_title(title)
when 'unwip' then MergeRequest.wipless_title(title)
end
end
end
end end
end end
...@@ -134,7 +134,8 @@ class NotificationService ...@@ -134,7 +134,8 @@ class NotificationService
merge_request, merge_request,
merge_request.target_project, merge_request.target_project,
current_user, current_user,
:merged_merge_request_email :merged_merge_request_email,
skip_current_user: !merge_request.merge_when_build_succeeds?
) )
end end
...@@ -514,9 +515,16 @@ class NotificationService ...@@ -514,9 +515,16 @@ class NotificationService
end end
end end
def close_resource_email(target, project, current_user, method) def close_resource_email(target, project, current_user, method, skip_current_user: true)
action = method == :merged_merge_request_email ? "merge" : "close" action = method == :merged_merge_request_email ? "merge" : "close"
recipients = build_recipients(target, project, current_user, action: action)
recipients = build_recipients(
target,
project,
current_user,
action: action,
skip_current_user: skip_current_user
)
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, current_user.id).deliver_later mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
...@@ -557,7 +565,7 @@ class NotificationService ...@@ -557,7 +565,7 @@ class NotificationService
end end
end end
def build_recipients(target, project, current_user, action: nil, previous_assignee: nil) def build_recipients(target, project, current_user, action: nil, previous_assignee: nil, skip_current_user: true)
custom_action = build_custom_key(action, target) custom_action = build_custom_key(action, target)
recipients = target.participants(current_user) recipients = target.participants(current_user)
...@@ -586,7 +594,8 @@ class NotificationService ...@@ -586,7 +594,8 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target) recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target) recipients = reject_users_without_access(recipients, target)
recipients.delete(current_user) recipients.delete(current_user) if skip_current_user
recipients.uniq recipients.uniq
end end
......
...@@ -214,6 +214,18 @@ module SlashCommands ...@@ -214,6 +214,18 @@ module SlashCommands
@updates[:due_date] = nil @updates[:due_date] = nil
end end
desc do
"Toggle the Work In Progress status"
end
condition do
issuable.persisted? &&
issuable.respond_to?(:work_in_progress?) &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :wip do
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
end
# This is a dummy command, so that it appears in the autocomplete commands # This is a dummy command, so that it appears in the autocomplete commands
desc 'CC' desc 'CC'
params '@user' params '@user'
......
...@@ -24,6 +24,7 @@ module SystemNoteService ...@@ -24,6 +24,7 @@ module SystemNoteService
body = "Added #{commits_text}:\n\n" body = "Added #{commits_text}:\n\n"
body << existing_commit_summary(noteable, existing_commits, oldrev) body << existing_commit_summary(noteable, existing_commits, oldrev)
body << new_commit_summary(new_commits).join("\n") body << new_commit_summary(new_commits).join("\n")
body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
create_note(noteable: noteable, project: project, author: author, note: body) create_note(noteable: noteable, project: project, author: author, note: body)
end end
...@@ -254,8 +255,7 @@ module SystemNoteService ...@@ -254,8 +255,7 @@ module SystemNoteService
# #
# "Started branch `201-issue-branch-button`" # "Started branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch) def new_issue_branch(issue, project, author, branch)
h = Gitlab::Routing.url_helpers link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "Started branch [`#{branch}`](#{link})" body = "Started branch [`#{branch}`](#{link})"
create_note(noteable: issue, project: project, author: author, note: body) create_note(noteable: issue, project: project, author: author, note: body)
...@@ -466,4 +466,20 @@ module SystemNoteService ...@@ -466,4 +466,20 @@ module SystemNoteService
def escape_html(text) def escape_html(text)
Rack::Utils.escape_html(text) Rack::Utils.escape_html(text)
end end
def url_helpers
@url_helpers ||= Gitlab::Routing.url_helpers
end
def diff_comparison_url(merge_request, project, oldrev)
diff_id = merge_request.merge_request_diff.id
url_helpers.diffs_namespace_project_merge_request_url(
project.namespace,
project,
merge_request.iid,
diff_id: diff_id,
start_sha: oldrev
)
end
end end
.scrolling-tabs-container.sub-nav-scroll = content_for :sub_nav do
= render 'shared/nav_scroll' .scrolling-tabs-container.sub-nav-scroll
.nav-links.sub-nav.scrolling-tabs = render 'shared/nav_scroll'
%ul{ class: (container_class) } .nav-links.sub-nav.scrolling-tabs
= nav_link(controller: :system_info) do %ul{ class: (container_class) }
= link_to admin_system_info_path, title: 'System Info' do = nav_link(controller: :system_info) do
%span = link_to admin_system_info_path, title: 'System Info' do
System Info %span
= nav_link(controller: :background_jobs) do System Info
= link_to admin_background_jobs_path, title: 'Background Jobs' do = nav_link(controller: :background_jobs) do
%span = link_to admin_background_jobs_path, title: 'Background Jobs' do
Background Jobs %span
= nav_link(controller: :logs) do Background Jobs
= link_to admin_logs_path, title: 'Logs' do = nav_link(controller: :logs) do
%span = link_to admin_logs_path, title: 'Logs' do
Logs %span
= nav_link(controller: :health_check) do Logs
= link_to admin_health_check_path, title: 'Health Check' do = nav_link(controller: :health_check) do
%span = link_to admin_health_check_path, title: 'Health Check' do
Health Check %span
= nav_link(controller: :requests_profiles) do Health Check
= link_to admin_requests_profiles_path, title: 'Requests Profiles' do = nav_link(controller: :requests_profiles) do
%span = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
Requests Profiles %span
Requests Profiles
.scrolling-tabs-container.sub-nav-scroll = content_for :sub_nav do
= render 'shared/nav_scroll' .scrolling-tabs-container.sub-nav-scroll
.nav-links.sub-nav.scrolling-tabs = render 'shared/nav_scroll'
%ul{ class: (container_class) } .nav-links.sub-nav.scrolling-tabs
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do %ul{ class: (container_class) }
= link_to admin_root_path, title: 'Overview' do = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
%span = link_to admin_root_path, title: 'Overview' do
Overview %span
= nav_link(controller: [:admin, :projects]) do Overview
= link_to admin_namespaces_projects_path, title: 'Projects' do = nav_link(controller: [:admin, :projects]) do
%span = link_to admin_namespaces_projects_path, title: 'Projects' do
Projects %span
= nav_link(controller: :users) do Projects
= link_to admin_users_path, title: 'Users' do = nav_link(controller: :users) do
%span = link_to admin_users_path, title: 'Users' do
Users %span
= nav_link(controller: :groups) do Users
= link_to admin_groups_path, title: 'Groups' do = nav_link(controller: :groups) do
%span = link_to admin_groups_path, title: 'Groups' do
Groups %span
= nav_link path: 'builds#index' do Groups
= link_to admin_builds_path, title: 'Builds' do = nav_link path: 'builds#index' do
%span = link_to admin_builds_path, title: 'Builds' do
Builds %span
= nav_link path: ['runners#index', 'runners#show'] do Builds
= link_to admin_runners_path, title: 'Runners' do = nav_link path: ['runners#index', 'runners#show'] do
%span = link_to admin_runners_path, title: 'Runners' do
Runners %span
Runners
...@@ -21,13 +21,16 @@ ...@@ -21,13 +21,16 @@
%br %br
%b Tag list: %b Tag list:
= build[:tags] = build[:tag_list].to_a.join(", ")
%br %br
%b Refs only: %b Refs only:
= build[:only] && build[:only].join(", ") = @jobs[build[:name].to_sym][:only].to_a.join(", ")
%br %br
%b Refs except: %b Refs except:
= build[:except] && build[:except].join(", ") = @jobs[build[:name].to_sym][:except].to_a.join(", ")
%br
%b Environment:
= build[:environment]
%br %br
%b When: %b When:
= build[:when] = build[:when]
......
.groups-empty-state
= custom_icon("icon_empty_groups")
.text-content
%h4 A group is a collection of several projects.
%p If you organize your projects under a group, it works like a folder.
%p You can manage your group member’s permissions and access to each project in the group.
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
- header_title "Groups", dashboard_groups_path - header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head' = render 'dashboard/groups_head'
%ul.content-list - if @group_members.empty?
- @group_members.each do |group_member| = render 'empty_state'
- group = group_member.group - else
= render 'shared/groups/group', group: group, group_member: group_member %ul.content-list
- @group_members.each do |group_member|
- group = group_member.group
= render 'shared/groups/group', group: group, group_member: group_member
= paginate @group_members, theme: 'gitlab' = paginate @group_members, theme: 'gitlab'
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
- else - else
Any Any
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu.dropdown-menu-align-right
%li %li
= link_to filter_projects_path(visibility_level: nil) do = link_to filter_projects_path(visibility_level: nil) do
Any Any
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
- else - else
Any Any
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu.dropdown-menu-align-right
%li %li
= link_to filter_projects_path(tag: nil) do = link_to filter_projects_path(tag: nil) do
Any Any
......
.flash-container.flash-container-page .flash-container.flash-container-page
- if alert - if alert
.flash-alert .flash-alert
= alert %div{ class: (container_class) }
%span= alert
- elsif notice - elsif notice
.flash-notice .flash-notice
= notice %div{ class: (container_class) }
%span= notice
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll .sidebar-wrapper.nicescroll
.sidebar-action-buttons .sidebar-action-buttons
= link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do .nav-header-btn.toggle-nav-collapse{ title: "Open/Close" }
%span.sr-only Toggle navigation %span.sr-only Toggle navigation
= icon('bars') = icon('bars')
= link_to '#', class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
%div{ class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: { placement: 'right', container: 'body' } }
%span.sr-only Toggle navigation pinning %span.sr-only Toggle navigation pinning
= icon('fw thumb-tack') = icon('fw thumb-tack')
...@@ -20,6 +21,7 @@ ...@@ -20,6 +21,7 @@
.container-fluid .container-fluid
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
.content-wrapper{ class: "#{layout_nav_class}" } .content-wrapper{ class: "#{layout_nav_class}" }
= yield :sub_nav
= render "layouts/broadcast" = render "layouts/broadcast"
= render "layouts/flash" = render "layouts/flash"
= yield :flash_message = yield :flash_message
......
.nav-block.activity-filter-block - @no_container = true
- if current_user
.controls
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
%i.fa.fa-rss
= render 'shared/event_filter' %div{ class: container_class }
.nav-block.activity-filter-block
- if current_user
.controls
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
= icon('rss')
.content_list.project-activity{:"data-href" => activity_project_path(@project)} = render 'shared/event_filter'
= spinner
.content_list.project-activity{:"data-href" => activity_project_path(@project)}
= spinner
:javascript :javascript
var activity = new Activities(); var activity = new Activities();
......
- if event = last_push_event - if event = last_push_event
- if show_last_push_widget?(event) - if show_last_push_widget?(event)
.row-content-block.top-block.clear-block.hidden-xs .row-content-block.top-block.hidden-xs.white
%div{ class: container_class } %div{ class: container_class }
.event-last-push .event-last-push
.event-last-push-text .event-last-push-text
......
...@@ -21,6 +21,13 @@ ...@@ -21,6 +21,13 @@
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
= dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
= button_tag class: 'soft-wrap-toggle btn', type: 'button' do
%span.no-wrap
= custom_icon('icon_no_wrap')
No wrap
%span.soft-wrap
= custom_icon('icon_soft_wrap')
Soft wrap
.encoding-selector .encoding-selector
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" } %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
= icon('angle-double-right') = icon('angle-double-right')
- if @build.coverage - if @build.coverage
.block.block-first .block.coverage
.title .title
Test coverage Test coverage
%p.build-detail-row %p.build-detail-row
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
- @build.trigger_request.variables.each do |key, value| - @build.trigger_request.variables.each do |key, value|
.hide.js-build .hide.js-build
.js-build-variable= key .js-build-variable= key
.js-build-value= value .js-build-value= value
.block .block
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
- builds.select{|build| build.status == build_status}.each do |build| - builds.select{|build| build.status == build_status}.each do |build|
.build-job{class: ('active' if build == @build), data: {stage: build.stage}} .build-job{class: ('active' if build == @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do = link_to namespace_project_build_path(@project.namespace, @project, build) do
= icon('check') = icon('right-arrow')
= ci_icon_for_status(build.status) = ci_icon_for_status(build.status)
%span %span
- if build.name - if build.name
......
.scrolling-tabs-container.sub-nav-scroll = content_for :sub_nav do
= render 'shared/nav_scroll' .scrolling-tabs-container.sub-nav-scroll
.nav-links.sub-nav.scrolling-tabs = render 'shared/nav_scroll'
%ul{ class: (container_class) } .nav-links.sub-nav.scrolling-tabs
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do %ul{ class: (container_class) }
= link_to project_files_path(@project) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
Files = link_to project_files_path(@project) do
Files
= nav_link(controller: [:commit, :commits]) do = nav_link(controller: [:commit, :commits]) do
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
Commits Commits
= nav_link(controller: %w(network)) do = nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref) do = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
Network Network
= nav_link(controller: :compare) do = nav_link(controller: :compare) do
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
Compare Compare
= nav_link(html_options: {class: branches_tab_class}) do = nav_link(html_options: {class: branches_tab_class}) do
= link_to namespace_project_branches_path(@project.namespace, @project) do = link_to namespace_project_branches_path(@project.namespace, @project) do
Branches Branches
= nav_link(controller: [:tags, :releases]) do = nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do = link_to namespace_project_tags_path(@project.namespace, @project) do
Tags Tags
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
= render "head" = content_for :sub_nav do
= render "head"
%div{ class: container_class } %div{ class: container_class }
.row-content-block.second-block.content-component-block .row-content-block.second-block.content-component-block
......
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
.clearfix .clearfix
- if params[:to] && params[:from] - if params[:to] && params[:from]
= link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'} .compare-switch-container
.form-group.dropdown.compare-form-group.js-compare-from-dropdown = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
.form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon from %span.input-group-addon from
= text_field_tag :from, params[:from], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from].presence } = hidden_field_tag :from, params[:from]
= button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text= params[:from] || 'Select branch/tag'
= render "ref_dropdown" = render "ref_dropdown"
= "..." .compare-ellipsis ...
.form-group.dropdown.compare-form-group.js-compare-to-dropdown .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon to %span.input-group-addon to
= text_field_tag :to, params[:to], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to].presence } = hidden_field_tag :to, params[:to]
= button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
.dropdown-toggle-text= params[:to] || 'Select branch/tag'
= render "ref_dropdown" = render "ref_dropdown"
&nbsp; &nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag "Compare", class: "btn btn-create commits-compare-btn"
......
.dropdown-menu.dropdown-menu-selectable .dropdown-menu.dropdown-menu-selectable
= dropdown_title "Select branch/tag" = dropdown_title "Select branch/tag"
= dropdown_filter "Filter by branch/tag"
= dropdown_content = dropdown_content
= dropdown_loading = dropdown_loading
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
- elsif diff_file.collapsed? - elsif diff_file.collapsed?
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path)) - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path))
.nothing-here-block.diff-collapsed{data: { diff_for_path: url } } .nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
This diff is collapsed. Click to expand it. This diff is collapsed.
%a.click-to-expand
Click to expand it.
- elsif diff_file.diff_lines.length > 0 - elsif diff_file.diff_lines.length > 0
- if diff_view == :parallel - if diff_view == :parallel
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
......
%i.fa.diff-toggle-caret
- if defined?(blob) && blob && diff_file.submodule? - if defined?(blob) && blob && diff_file.submodule?
%span %span
= icon('archive fw') = icon('archive fw')
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
or a or a
= link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link' = link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link'
to this project. to this project.
%p
You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected.
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%div{ class: container_class } %div{ class: container_class }
......
.scrolling-tabs-container.sub-nav-scroll = content_for :sub_nav do
= render 'shared/nav_scroll' .scrolling-tabs-container.sub-nav-scroll
.nav-links.sub-nav.scrolling-tabs = render 'shared/nav_scroll'
%ul{ class: (container_class) } .nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/chart.js') = page_specific_javascript_tag('lib/chart.js')
= page_specific_javascript_tag('graphs/graphs_bundle.js') = page_specific_javascript_tag('graphs/graphs_bundle.js')
= nav_link(action: :show) do = nav_link(action: :show) do
= link_to 'Contributors', namespace_project_graph_path = link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do = nav_link(action: :commits) do
= link_to 'Commits', commits_namespace_project_graph_path = link_to 'Commits', commits_namespace_project_graph_path
= nav_link(action: :languages) do = nav_link(action: :languages) do
= link_to 'Languages', languages_namespace_project_graph_path = link_to 'Languages', languages_namespace_project_graph_path
- if @project.feature_available?(:builds, current_user) - if @project.feature_available?(:builds, current_user)
= nav_link(action: :ci) do = nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do = link_to ci_namespace_project_graph_path do
Continuous Integration Continuous Integration
.scrolling-tabs-container.sub-nav-scroll = content_for :sub_nav do
= render 'shared/nav_scroll' .scrolling-tabs-container.sub-nav-scroll
.nav-links.sub-nav.scrolling-tabs = render 'shared/nav_scroll'
%ul{ class: (container_class) } .nav-links.sub-nav.scrolling-tabs
- if project_nav_tab?(:issues) && !current_controller?(:merge_requests) %ul{ class: (container_class) }
= nav_link(controller: :issues) do - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
= link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do = nav_link(controller: :issues) do
%span = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
Issues %span
Issues
= nav_link(controller: :boards) do = nav_link(controller: :boards) do
= link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
%span %span
Board Board
- if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
= nav_link(controller: :merge_requests) do = nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
%span %span
Merge Requests Merge Requests
- if project_nav_tab? :labels - if project_nav_tab? :labels
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
%span %span
Labels Labels
- if project_nav_tab? :milestones - if project_nav_tab? :milestones
= nav_link(controller: :milestones) do = nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
%span %span
Milestones Milestones
\ No newline at end of file
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
- page_title "Issues" - page_title "Issues"
- new_issue_email = @project.new_issue_address(current_user) - new_issue_email = @project.new_issue_address(current_user)
= render "projects/issues/head" = content_for :sub_nav do
= render "projects/issues/head"
= content_for :meta_tags do = content_for :meta_tags do
- if current_user - if current_user
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
= link_to "#", class: 'btn js-toggle-button import_git' do = link_to "#", class: 'btn js-toggle-button import_git' do
= icon('git', text: 'Repo by URL') = icon('git', text: 'Repo by URL')
%div{ class: 'import_gitlab_project' } %div{ class: 'import_gitlab_project' }
- if gitlab_project_import_enabled? && current_user.is_admin? - if gitlab_project_import_enabled?
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export') = icon('gitlab', text: 'GitLab export')
......
.scrolling-tabs-container.sub-nav-scroll = content_for :sub_nav do
= render 'shared/nav_scroll' .scrolling-tabs-container.sub-nav-scroll
.nav-links.sub-nav.scrolling-tabs = render 'shared/nav_scroll'
%ul{ class: (container_class) } .nav-links.sub-nav.scrolling-tabs
- if project_nav_tab? :pipelines %ul{ class: (container_class) }
= nav_link(controller: :pipelines) do - if project_nav_tab? :pipelines
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do = nav_link(controller: :pipelines) do
%span = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
Pipelines %span
Pipelines
- if project_nav_tab? :builds - if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do = nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
%span %span
Builds Builds
- if project_nav_tab? :environments - if project_nav_tab? :environments
= nav_link(controller: %w(environments)) do = nav_link(controller: %w(environments)) do
= link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
%span %span
Environments Environments
- if can?(current_user, :read_cycle_analytics, @project) - if can?(current_user, :read_cycle_analytics, @project)
= nav_link(controller: %w(cycle_analytics)) do = nav_link(controller: %w(cycle_analytics)) do
= link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do
%span %span
Cycle Analytics Cycle Analytics
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
= content_for :meta_tags do = content_for :meta_tags do
- if current_user - if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
= render 'projects/last_push'
= render "projects/commits/head" = render "projects/commits/head"
= render 'projects/last_push'
%div{ class: container_class } %div{ class: container_class }
.tree-controls .tree-controls
......
.scrolling-tabs-container.sub-nav-scroll = content_for :sub_nav do
= render 'shared/nav_scroll' .scrolling-tabs-container.sub-nav-scroll
.nav-links.sub-nav.scrolling-tabs = render 'shared/nav_scroll'
%ul{ class: (container_class) } .nav-links.sub-nav.scrolling-tabs
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do %ul{ class: (container_class) }
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
= nav_link(path: 'wikis#pages') do = nav_link(path: 'wikis#pages') do
= link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
= nav_link(path: 'wikis#git_access') do = nav_link(path: 'wikis#git_access') do
= link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
Git Access Git Access
= render 'projects/wikis/new' = render 'projects/wikis/new'
...@@ -8,26 +8,26 @@ ...@@ -8,26 +8,26 @@
%b.caret %b.caret
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li %li
= link_to page_filter_path(sort: sort_value_priority) do = link_to page_filter_path(sort: sort_value_priority, label: true) do
= sort_title_priority = sort_title_priority
= link_to page_filter_path(sort: sort_value_recently_created) do = link_to page_filter_path(sort: sort_value_recently_created, label: true) do
= sort_title_recently_created = sort_title_recently_created
= link_to page_filter_path(sort: sort_value_oldest_created) do = link_to page_filter_path(sort: sort_value_oldest_created, label: true) do
= sort_title_oldest_created = sort_title_oldest_created
= link_to page_filter_path(sort: sort_value_recently_updated) do = link_to page_filter_path(sort: sort_value_recently_updated, label: true) do
= sort_title_recently_updated = sort_title_recently_updated
= link_to page_filter_path(sort: sort_value_oldest_updated) do = link_to page_filter_path(sort: sort_value_oldest_updated, label: true) do
= sort_title_oldest_updated = sort_title_oldest_updated
= link_to page_filter_path(sort: sort_value_milestone_soon) do = link_to page_filter_path(sort: sort_value_milestone_soon, label: true) do
= sort_title_milestone_soon = sort_title_milestone_soon
= link_to page_filter_path(sort: sort_value_milestone_later) do = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do
= sort_title_milestone_later = sort_title_milestone_later
- if controller.controller_name == 'issues' || controller.action_name == 'issues' - if controller.controller_name == 'issues' || controller.action_name == 'issues'
= link_to page_filter_path(sort: sort_value_due_date_soon) do = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do
= sort_title_due_date_soon = sort_title_due_date_soon
= link_to page_filter_path(sort: sort_value_due_date_later) do = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do
= sort_title_due_date_later = sort_title_due_date_later
= link_to page_filter_path(sort: sort_value_upvotes) do = link_to page_filter_path(sort: sort_value_upvotes, label: true) do
= sort_title_upvotes = sort_title_upvotes
= link_to page_filter_path(sort: sort_value_downvotes) do = link_to page_filter_path(sort: sort_value_downvotes, label: true) do
= sort_title_downvotes = sort_title_downvotes
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- if can_change_visibility_level - if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- else - else
.col-sm-10 %div
%span.info %span.info
= visibility_level_icon(visibility_level) = visibility_level_icon(visibility_level)
%strong %strong
......
...@@ -10,6 +10,6 @@ ...@@ -10,6 +10,6 @@
.option-descr .option-descr
= visibility_level_description(level, form_model) = visibility_level_description(level, form_model)
- unless restricted_visibility_levels.empty? - unless restricted_visibility_levels.empty?
.col-sm-10 %div
%span.info %span.info
Some visibility level settings have been restricted by the administrator. Some visibility level settings have been restricted by the administrator.
<svg width="249" height="368" viewBox="891 156 249 368" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="131" height="162" rx="10"/><mask id="e" x="0" y="0" width="131" height="162" fill="#fff"><use xlink:href="#a"/></mask><path d="M223.616 127.958V108.96c0-4.416-3.584-8-8.005-8h-23.985c-2.778 0-5.98 2.014-7.18 4.5l-5.07 10.5h-49.763c-5.527 0-9.996 4.475-9.996 9.997v53.005c0 5.513 4.475 9.997 9.996 9.997h84.01c5.525 0 9.994-4.477 9.994-9.998v-51.004z" id="b"/><mask id="f" x="0" y="0" width="104" height="88" fill="#fff"><use xlink:href="#b"/></mask><path d="M47 25h.996C53.52 25 58 29.472 58 34.99v20.02C58 60.526 53.52 65 47.996 65H10.004C4.48 65 0 60.528 0 55.01V34.99C0 29.474 4.48 25 10.004 25H11v-7c0-9.94 8.06-18 18-18s18 8.06 18 18v7zm-6 0H17v-7c0-6.627 5.373-12 12-12s12 5.373 12 12v7z" id="c"/><mask id="g" x="0" y="0" width="58" height="65" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 10.008C0 4.48 4.476 0 10 0h218c5.523 0 10 4.473 10 10.008v140.94c0 5.53-4.062 11.882-9.08 14.196l-100.84 46.5c-5.015 2.31-13.142 2.312-18.16 0l-100.84-46.5C4.064 162.832 0 156.484 0 150.95V10.007z" id="d"/><mask id="h" x="0" y="0" width="238" height="213.417" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(891 156)"><g transform="rotate(8 -266.528 490.3)"><use stroke="#E5E5E5" mask="url(#e)" stroke-width="8" fill="#FFF" xlink:href="#a"/><rect fill="#FC8A51" x="20" y="31" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="60" y="31" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="36" y="31" width="20" height="4" rx="2"/><rect fill="#6B4FBB" x="20" y="65" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="44" y="65" width="20" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="80" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="80" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="48" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="60" y="80" width="12" height="4" rx="2"/><rect fill="#6B4FBB" x="52" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="68" y="48" width="12" height="4" rx="2"/></g><use stroke="#B5A7DD" mask="url(#f)" stroke-width="8" fill="#FFF" transform="rotate(5 171.616 144.96)" xlink:href="#b"/><path d="M58 132c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#C1E7D0"/><path d="M90.143 132c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M74.686 133.875l-3.18-3.18c-.29-.29-.77-.296-1.06-.005l-1.55 1.55c-.287.287-.29.766.004 1.06l4.92 4.92c.504.504 1.32.504 1.823 0l.654-.653 7.804-7.804c.3-.3.29-.77-.005-1.067l-1.578-1.58c-.302-.3-.775-.298-1.068-.004l-6.764 6.763z" fill="#31AF64"/><path d="M4 66c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18S4 75.94 4 66z" fill="#D5ECF7"/><path d="M36.143 66c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M22 55.714c5.68 0 10.286 4.605 10.286 10.286 0 5.68-4.605 10.286-10.286 10.286-3.45 0-6.505-1.7-8.37-4.307L22 66V55.714z" fill="#2D9FD8"/><g transform="rotate(-8 748.533 18.147)"><use stroke="#FDE5D8" mask="url(#g)" stroke-width="8" fill="#FFF" xlink:href="#c"/><path d="M31 46.584c1.766-.772 3-2.534 3-4.584 0-2.76-2.24-5-5-5s-5 2.24-5 5c0 2.05 1.234 3.812 3 4.584v3.42c0 1.1.895 1.996 2 1.996 1.112 0 2-.894 2-1.997v-3.42z" fill="#FC8A51"/></g><g transform="translate(0 154)"><use stroke="#E5E5E5" mask="url(#h)" stroke-width="8" fill="#FFF" xlink:href="#d"/><g opacity=".3"><path d="M141.837 104.53l-2.56-7.993-5.074-15.843c-.26-.815-1.398-.815-1.66 0l-5.074 15.843h-16.85l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33 22.16-16.33c.61-.452.866-1.25.632-1.98" fill="#A1A1A1"/><path fill="#5C5C5C" d="M119.044 122.84l8.425-26.303h-16.85l8.424 26.304"/><path fill="#787878" d="M119.044 122.84l-8.425-26.303H98.81l20.232 26.304"/><path fill="#787878" d="M119.044 122.84l8.425-26.303h11.807l-20.233 26.304"/><path d="M98.812 96.537l-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33L98.81 96.538z" fill="#A1A1A1"/><path d="M98.812 96.537h11.807l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843z" fill="#5C5C5C"/><path d="M139.277 96.537l2.56 7.993c.234.73-.022 1.528-.634 1.98l-22.16 16.33 20.234-26.303z" fill="#A1A1A1"/><path d="M139.277 96.537H127.47l5.074-15.843c.26-.815 1.398-.815 1.66 0l5.073 15.843z" fill="#5C5C5C"/></g><path d="M57 18.29c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H41c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H77c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm17 24.693c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm202 32.923c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm202 32.923c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm-202 0c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm202 32.922c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm179.023 19.555c-.988.452-1.388 1.55-.894 2.454.493.904 1.694 1.27 2.682.82l14.31-6.545c.99-.452 1.39-1.55.896-2.454-.494-.902-1.696-1.27-2.684-.817l-14.31 6.544zm-32.2 14.723c-.987.452-1.388 1.55-.894 2.454.493.904 1.695 1.27 2.683.818l14.31-6.544c.99-.45 1.39-1.55.895-2.454-.494-.903-1.695-1.27-2.683-.818l-14.31 6.544zm-32.2 14.724c-.987.45-1.387 1.55-.893 2.454.494.903 1.695 1.27 2.683.818l14.31-6.544c.99-.452 1.39-1.55.896-2.454-.495-.904-1.697-1.27-2.685-.818l-14.31 6.544zm-23.67-2.023l-12.186-5.57c-.987-.452-2.19-.086-2.683.817-.494.904-.093 2.003.895 2.454l12.185 5.573c.754.345 1.57.645 2.438.898 1.052.307 2.177-.224 2.513-1.187.335-.962-.246-1.99-1.298-2.298-.677-.197-1.302-.426-1.864-.684zM62.57 168.437c-.988-.452-2.19-.086-2.683.818-.494.903-.094 2.002.894 2.454l14.31 6.544c.988.45 2.19.085 2.683-.818.494-.904.094-2.003-.894-2.454l-14.312-6.544zm-32.2-14.723c-.988-.452-2.19-.086-2.683.818-.494.904-.093 2.003.895 2.454l14.31 6.544c.988.452 2.19.086 2.684-.818.494-.903.093-2.002-.895-2.454l-14.312-6.543z" fill="#EEE"/></g><g><path d="M104 18c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#FADFD9"/><path d="M136.143 18c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M119.43 8.994c0-.707.57-1.28 1.283-1.28h2.574c.71 0 1.284.57 1.284 1.28v10.298c0 .706-.57 1.28-1.283 1.28h-2.574c-.71 0-1.284-.57-1.284-1.28V8.994zm0 15.433c0-.71.57-1.284 1.283-1.284h2.574c.71 0 1.284.57 1.284 1.284V27c0 .71-.57 1.286-1.283 1.286h-2.574c-.71 0-1.284-.57-1.284-1.285v-2.573z" fill="#E75E40"/></g><g><path d="M213 89c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#F6D4DC"/><path d="M245.143 89c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M231 86.348l-3.603-3.602c-.288-.29-.766-.286-1.063.01l-1.578 1.578c-.3.302-.3.773-.01 1.063L228.348 89l-3.602 3.603c-.29.288-.286.766.01 1.063l1.578 1.578c.302.3.773.3 1.063.01L231 91.652l3.603 3.602c.288.29.766.286 1.063-.01l1.578-1.578c.3-.302.3-.773.01-1.063L233.652 89l3.602-3.603c.29-.288.286-.766-.01-1.063l-1.578-1.578c-.302-.3-.773-.3-1.063-.01L231 86.348z" fill="#D22852"/></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="m6 11h-4.509c-.263 0-.491.226-.491.505v.991c0 .291.22.505.491.505h4.509v.679c0 .301.194.413.454.236l2.355-1.607c.251-.171.259-.442 0-.619l-2.355-1.607c-.251-.171-.454-.07-.454.236v.681m-5-7.495c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991m10 8c0-.279.215-.505.49-.505h3.02c.271 0 .49.214.49.505v.991c0 .279-.215.505-.49.505h-3.02c-.271 0-.49-.214-.49-.505v-.991m-10-4c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="m12 11h-2v-.681c0-.307-.203-.407-.454-.236l-2.355 1.607c-.259.177-.251.448 0 .619l2.355 1.607c.259.177.454.065.454-.236v-.679h2c0 0 0 0 0 0 1.657 0 3-1.343 3-3 0-.828-.336-1.578-.879-2.121-.543-.543-1.293-.879-2.121-.879-.001 0-.002 0-.002 0h-10.497c-.271 0-.5.226-.5.505v.991c0 .291.224.505.5.505h10.497c.001 0 .002 0 .002 0 .552 0 1 .448 1 1 0 .276-.112.526-.293.707-.181.181-.431.293-.707.293m-11-7.495c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991m0 8c0-.279.215-.505.49-.505h3.02c.271 0 .49.214.49.505v.991c0 .279-.215.505-.49.505h-3.02c-.271 0-.49-.214-.49-.505v-.991"/>
</svg>
- type = local_assigns.fetch(:type, :issues)
- page_context_word = type.to_s.humanize(capitalize: false)
- issuables = @issues || @merge_requests
%ul.nav-links.issues-state-filters %ul.nav-links.issues-state-filters
- if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests'
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')} %li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)} #{issuables_state_counter_text(type, :opened)}
- if defined?(type) && type == :merge_requests - if type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')} %li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)} #{issuables_state_counter_text(type, :merged)}
%li{class: ("active" if params[:state] == 'closed')} %li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)} #{issuables_state_counter_text(type, :closed)}
- else - else
%li{class: ("active" if params[:state] == 'closed')} %li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)} #{issuables_state_counter_text(type, :closed)}
%li{class: ("active" if params[:state] == 'all')} %li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)} #{issuables_state_counter_text(type, :all)}
# Make sure we initialize a Redis connection pool before Sidekiq starts
# multi-threaded execution.
Gitlab::Redis.with { nil }
module AttrEncrypted module AttrEncrypted
module Adapters module Adapters
module ActiveRecord module ActiveRecord
def attribute_instance_methods_as_symbols_with_no_db_connection module DBConnectionQuerier
# Use with_connection so the connection doesn't stay pinned to the thread. def attribute_instance_methods_as_symbols
connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false # Use with_connection so the connection doesn't stay pinned to the thread.
connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false
if connected
# Call version from AttrEncrypted::Adapters::ActiveRecord if connected
attribute_instance_methods_as_symbols_without_no_db_connection # Call version from AttrEncrypted::Adapters::ActiveRecord
else super
# Call version from AttrEncrypted, i.e., `super` with regards to AttrEncrypted::Adapters::ActiveRecord else
AttrEncrypted.instance_method(:attribute_instance_methods_as_symbols).bind(self).call # Call version from AttrEncrypted, i.e., `super` with regards to AttrEncrypted::Adapters::ActiveRecord
AttrEncrypted.instance_method(:attribute_instance_methods_as_symbols).bind(self).call
end
end end
end end
prepend DBConnectionQuerier
alias_method_chain :attribute_instance_methods_as_symbols, :no_db_connection
end end
end end
end end
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
module LimitFilter
def add_column(table_name, column_name, type, options = {})
options.delete(:limit) if type == :text
super(table_name, column_name, type, options)
end
def change_column(table_name, column_name, type, options = {})
options.delete(:limit) if type == :text
super(table_name, column_name, type, options)
end
end
prepend ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::LimitFilter
class TableDefinition class TableDefinition
def text(*args) def text(*args)
options = args.extract_options! options = args.extract_options!
...@@ -9,18 +23,5 @@ if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) ...@@ -9,18 +23,5 @@ if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
column_names.each { |name| column(name, type, options) } column_names.each { |name| column(name, type, options) }
end end
end end
def add_column_with_limit_filter(table_name, column_name, type, options = {})
options.delete(:limit) if type == :text
add_column_without_limit_filter(table_name, column_name, type, options)
end
def change_column_with_limit_filter(table_name, column_name, type, options = {})
options.delete(:limit) if type == :text
change_column_without_limit_filter(table_name, column_name, type, options)
end
alias_method_chain :add_column, :limit_filter
alias_method_chain :change_column, :limit_filter
end end
end end
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
Group.all.each do |group| Group.all.each do |group|
User.all.sample(4).each do |user| User.all.sample(4).each do |user|
if group.add_users([user.id], Gitlab::Access.values.sample) if group.add_user(user, Gitlab::Access.values.sample).persisted?
print '.' print '.'
else else
print 'F' print 'F'
......
...@@ -101,7 +101,7 @@ Once you have your token, pass it to the API using either the `private_token` ...@@ -101,7 +101,7 @@ Once you have your token, pass it to the API using either the `private_token`
parameter or the `PRIVATE-TOKEN` header. parameter or the `PRIVATE-TOKEN` header.
### Session cookie ### Session Cookie
When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is
set. The API will use this cookie for authentication if it is present, but using set. The API will use this cookie for authentication if it is present, but using
......
# GitLab as an OAuth2 client # GitLab as an OAuth2 provider
This document covers using the OAuth2 protocol to access GitLab. This document covers using the OAuth2 protocol to access GitLab.
...@@ -112,7 +112,7 @@ You can do POST request to `/oauth/token` with parameters: ...@@ -112,7 +112,7 @@ You can do POST request to `/oauth/token` with parameters:
{ {
"grant_type" : "password", "grant_type" : "password",
"username" : "user@example.com", "username" : "user@example.com",
"password" : "sekret" "password" : "secret"
} }
``` ```
...@@ -130,8 +130,8 @@ For testing you can use the oauth2 ruby gem: ...@@ -130,8 +130,8 @@ For testing you can use the oauth2 ruby gem:
``` ```
client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com") client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com")
access_token = client.password.get_token('user@example.com', 'sekret') access_token = client.password.get_token('user@example.com', 'secret')
puts access_token.token puts access_token.token
``` ```
[personal access tokens]: ./README.md#personal-access-tokens [personal access tokens]: ./README.md#personal-access-tokens
\ No newline at end of file
...@@ -41,7 +41,9 @@ Example response: ...@@ -41,7 +41,9 @@ Example response:
"gravatar_enabled" : true, "gravatar_enabled" : true,
"sign_in_text" : null, "sign_in_text" : null,
"container_registry_token_expire_delay": 5, "container_registry_token_expire_delay": 5,
"repository_storage": "default" "repository_storage": "default",
"koding_enabled": false,
"koding_url": null
} }
``` ```
...@@ -72,7 +74,9 @@ PUT /application/settings ...@@ -72,7 +74,9 @@ PUT /application/settings
| `after_sign_out_path` | string | no | Where to redirect users after logout | | `after_sign_out_path` | string | no | Where to redirect users after logout |
| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
| `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml | | `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. | `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |
```bash ```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
...@@ -103,6 +107,8 @@ Example response: ...@@ -103,6 +107,8 @@ Example response:
"user_oauth_applications": true, "user_oauth_applications": true,
"after_sign_out_path": "", "after_sign_out_path": "",
"container_registry_token_expire_delay": 5, "container_registry_token_expire_delay": 5,
"repository_storage": "default" "repository_storage": "default",
"koding_enabled": false,
"koding_url": null
} }
``` ```
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
> **Note**: > **Note**:
GitLab 8.12 has a completely redesigned build permissions system. GitLab 8.12 has a completely redesigned build permissions system.
Read all about the [new model and its implications][../../user/project/new_ci_build_permissions_model.md#build-triggers]. Read all about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#build-triggers).
Triggers can be used to force a rebuild of a specific branch, tag or commit, Triggers can be used to force a rebuild of a specific branch, tag or commit,
with an API call. with an API call.
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
- [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations - [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations
- [Testing standards and style guidelines](testing.md) - [Testing standards and style guidelines](testing.md)
- [UI guide](ui_guide.md) for building GitLab with existing CSS styles and elements - [UI guide](ui_guide.md) for building GitLab with existing CSS styles and elements
- [Frontend guidelines](frontend.md)
- [SQL guidelines](sql.md) for SQL guidelines - [SQL guidelines](sql.md) for SQL guidelines
## Process ## Process
......
This diff is collapsed.
...@@ -9,10 +9,10 @@ a big burden for most organizations. For this reason it is important that your ...@@ -9,10 +9,10 @@ a big burden for most organizations. For this reason it is important that your
migrations are written carefully, can be applied online and adhere to the style guide below. migrations are written carefully, can be applied online and adhere to the style guide below.
Migrations should not require GitLab installations to be taken offline unless Migrations should not require GitLab installations to be taken offline unless
_absolutely_ necessary. If a migration requires downtime this should be _absolutely_ necessary - see the ["What Requires Downtime?"](what_requires_downtime.md)
clearly mentioned during the review process as well as being documented in the page. If a migration requires downtime, this should be clearly mentioned during
monthly release post. For more information see the "Downtime Tagging" section the review process, as well as being documented in the monthly release post. For
below. more information, see the "Downtime Tagging" section below.
When writing your migrations, also consider that databases might have stale data When writing your migrations, also consider that databases might have stale data
or inconsistencies and guard for that. Try to make as little assumptions as possible or inconsistencies and guard for that. Try to make as little assumptions as possible
......
...@@ -108,7 +108,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ...@@ -108,7 +108,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby ## 2. Ruby
_**Note:** The current supported Ruby versions are 2.1.x and 2.3.x. 2.3.x is preferred, and support for 2.1.x will be dropped in the future. **Note:** The current supported Ruby versions are 2.1.x and 2.3.x. 2.3.x is preferred, and support for 2.1.x will be dropped in the future.
The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
in production, frequently leads to hard to diagnose problems. For example, in production, frequently leads to hard to diagnose problems. For example,
...@@ -268,9 +268,9 @@ sudo usermod -aG redis git ...@@ -268,9 +268,9 @@ sudo usermod -aG redis git
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-12-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-13-stable gitlab
**Note:** You can change `8-12-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `8-13-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
......
...@@ -22,7 +22,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ...@@ -22,7 +22,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
### 3. Update Ruby ### 3. Update Ruby
If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. We will continue supporting Ruby < 2.3 for the time being but we recommend you
upgrade to Ruby 2.3 if you're running a source installation, as this is the same
version that ships with our Omnibus package.
You can check which version you are running with `ruby -v`. You can check which version you are running with `ruby -v`.
......
...@@ -22,7 +22,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ...@@ -22,7 +22,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
### 3. Update Ruby ### 3. Update Ruby
If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. We will continue supporting Ruby < 2.3 for the time being but we recommend you
upgrade to Ruby 2.3 if you're running a source installation, as this is the same
version that ships with our Omnibus package.
You can check which version you are running with `ruby -v`. You can check which version you are running with `ruby -v`.
......
# From 8.12 to 8.13
Make sure you view this update guide from the tag (version) of GitLab you would
like to install. In most cases this should be the highest numbered production
tag (without rc in it). You can select the tag in the version dropdown at the
top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the
[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
guide links by version.
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Update Ruby
We will continue supporting Ruby < 2.3 for the time being but we recommend you
upgrade to Ruby 2.3 if you're running a source installation, as this is the same
version that ships with our Omnibus package.
You can check which version you are running with `ruby -v`.
Download and compile Ruby:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz
cd ruby-2.3.1
./configure --disable-install-rdoc
make
sudo make install
```
Install Bundler:
```bash
sudo gem install bundler --no-ri --no-rdoc
```
### 4. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 8-13-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-13-stable-ee
```
### 5. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v3.6.3
```
### 6. Update gitlab-workhorse
Install and compile gitlab-workhorse. This requires
[Go 1.5](https://golang.org/dl) which should already be on your system from
GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.8.2
sudo -u git -H make
```
### 7. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 8. Update configuration files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-12-stable:config/gitlab.yml.example origin/8-13-stable:config/gitlab.yml.example
```
#### Git configuration
Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
the GitLab server during `git gc`.
```sh
sudo -u git -H git config --global repack.writeBitmaps true
```
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh
# For HTTPS configurations
git diff origin/8-12-stable:lib/support/nginx/gitlab-ssl origin/8-13-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-12-stable:lib/support/nginx/gitlab origin/8-13-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/lib/support/init.d/gitlab.default.example#L38
#### SMTP configuration
If you're installing from source and use SMTP to deliver mail, you will need to add the following line
to config/initializers/smtp_settings.rb:
```ruby
ActionMailer::Base.delivery_method = :smtp
```
See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
### 9. Start application
sudo service gitlab start
sudo service nginx restart
### 10. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.12)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 8.11 to 8.12](8.11-to-8.12.md), except for the
database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
...@@ -23,6 +23,7 @@ The following table depicts the various user permission levels in a project. ...@@ -23,6 +23,7 @@ The following table depicts the various user permission levels in a project.
| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| View wiki pages | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull project code | | ✓ | ✓ | ✓ | ✓ | | Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ | | Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ |
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
> that of the exporter. > that of the exporter.
> - For existing installations, the project import option has to be enabled in > - For existing installations, the project import option has to be enabled in
> application settings (`/admin/application_settings`) under 'Import sources'. > application settings (`/admin/application_settings`) under 'Import sources'.
> You will have to be an administrator to enable and use the import functionality. > Ask your administrator if you don't see the **GitLab export** button when
> creating a new project.
> - You can find some useful raketasks if you are an administrator in the > - You can find some useful raketasks if you are an administrator in the
> [import_export](../../../administration/raketasks/project_import_export.md) > [import_export](../../../administration/raketasks/project_import_export.md)
> raketask. > raketask.
......
...@@ -27,4 +27,5 @@ do. ...@@ -27,4 +27,5 @@ do.
| `/subscribe` | Subscribe | | `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe | | `/unsubscribe` | Unsubscribe |
| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date | | <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
| `/remove_due_date` | Remove due date | | `/remove_due_date` | Remove due date |
| `/wip` | Toggle the Work In Progress status |
@profile
Feature: Profile SSH Keys
Background:
Given I sign in as a user
And I have ssh key "ssh-rsa Work"
And I visit profile keys page
Scenario: I should see ssh keys
Then I should see my ssh keys
Scenario: Add new ssh key
Given I should see new ssh key form
And I submit new ssh key "Laptop"
Then I should see new ssh key "Laptop"
Scenario: Remove ssh key
Given I click link "Work"
And I click link "Remove"
Then I visit profile keys page
And I should not see "Work" ssh key
...@@ -20,6 +20,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps ...@@ -20,6 +20,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
expect(page).to have_link('GitLab.com') expect(page).to have_link('GitLab.com')
expect(page).to have_link('Google Code') expect(page).to have_link('Google Code')
expect(page).to have_link('Repo by URL') expect(page).to have_link('Repo by URL')
expect(page).to have_link('GitLab export')
end end
step 'I click on "Import project from GitHub"' do step 'I click on "Import project from GitHub"' do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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