Commit a3f5e7af authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'ce/master' into ce-to-ee

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 07fa43bd 96684317
......@@ -15,36 +15,45 @@ v 8.10.0 (unreleased)
- Make images fit to the size of the viewport !4810
- Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix MR-auto-close text added to description. !4836
- Add Spring EmojiOne updates.
- Fix pagination when sorting by columns with lots of ties (like priority)
- Updated project header design
- Exclude email check from the standard health check
- Fix changing issue state columns in milestone view
- Add notification settings dropdown for groups
- Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Todos !3188 (Robert Schilling)
- Add "Enabled Git access protocols" to Application Settings
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- PipelinesFinder uses git cache data
- Throttle the update of `project.pushes_since_gc` to 1 minute.
- Check for conflicts with existing Project's wiki path when creating a new project.
- Don't instantiate a git tree on Projects show default view
- Bump Rinku to 2.0.0
- Remove unused front-end variable -> default_issues_tracker
- Better caching of git calls on ProjectsController#show.
- Add API endpoint for a group issues !4520 (mahcsig)
- Add Bugzilla integration !4930 (iamtjg)
- Instrument Rinku usage
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
- Set import_url validation to be more strict
- Add basic system information like memory and disk usage to the admin panel
- Don't garbage collect commits that have related DB records like comments
- More descriptive message for git hooks and file locks
- Handle custom Git hook result in GitLab UI
- Allow '?', or '&' for label names
v 8.9.4 (unreleased)
- Ensure references to private repos aren't shown to logged-out users
v 8.9.5 (unreleased)
- Improve the request / withdraw access button. !4860
- Fix assigning shared runners as admins. !4961
- Show "locked" label for locked runners on runners admin. !4961
- Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq
- Add index on the user and emoji name on AwardEmoji table !5061
- Fixes issues importing events in Import/Export. Import/Export version bumped to 0.1.1
- Fix import button disabled when import process fail due to the namespace already been taken.
- Fix diff comments not showing up in activity feed. !5069
- Security: Update RedCloth to 4.3.2 (Takuya Noguchi)
v 8.9.4
......
......@@ -19,7 +19,7 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 4.0'
gem 'doorkeeper', '~> 3.1'
gem 'doorkeeper', '~> 4.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
......@@ -233,7 +233,7 @@ gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.6.1'
gem 'gitlab_emoji', '~> 0.3.0'
gem 'gemojione', '~> 2.6'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
......
......@@ -171,8 +171,8 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
doorkeeper (3.1.0)
railties (>= 3.2)
doorkeeper (4.0.0)
railties (>= 4.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
elasticsearch (1.0.15)
......@@ -268,7 +268,7 @@ GEM
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.24.0b13)
gemojione (2.2.1)
gemojione (2.6.1)
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
......@@ -296,8 +296,6 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-license (0.0.4)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.2.3)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
......@@ -602,7 +600,7 @@ GEM
listen (~> 3.0)
responders (2.1.1)
railties (>= 4.2.0, < 5.1)
rinku (1.7.3)
rinku (2.0.0)
rotp (2.1.2)
rouge (1.11.0)
rqrcode (0.7.0)
......@@ -867,7 +865,7 @@ DEPENDENCIES
devise (~> 4.0)
devise-two-factor (~> 3.0.0)
diffy (~> 3.0.3)
doorkeeper (~> 3.1)
doorkeeper (~> 4.0)
dropzonejs-rails (~> 0.7.1)
elasticsearch-model
elasticsearch-rails
......@@ -888,12 +886,12 @@ DEPENDENCIES
foreman
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 2.6)
github-linguist (~> 4.7.0)
github-markup (~> 1.3.1)
gitlab-elasticsearch-git (~> 0.0.15)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.3.0)
gitlab_git (~> 10.2)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
......
app/assets/images/emoji.png

257 KB | W: | H:

app/assets/images/emoji.png

1000 KB | W: | H:

app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/emoji@2x.png

673 KB | W: | H:

app/assets/images/emoji@2x.png

2.38 MB | W: | H:

app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -70,12 +70,12 @@ class @DropzoneInput
pasteText response.link.markdown
return
error: (temp, errorMessage) ->
error: (temp) ->
errorAlert = $(form).find('.error-alert')
checkIfMsgExists = errorAlert.children().length
if checkIfMsgExists is 0
errorAlert.append divAlert
$(".div-dropzone-alert").append btnAlert + errorMessage
$(".div-dropzone-alert").append "#{btnAlert}Attaching the file failed."
return
totaluploadprogress: (totalUploadProgress) ->
......
......@@ -190,7 +190,7 @@ GitLab.GfmAutoComplete =
callbacks:
beforeSave: (merges) ->
sanitizeLabelTitle = (title)->
if /\w+\s+\w+/g.test(title)
if /[\w\?&]+\s+[\w\?&]+/g.test(title)
"\"#{sanitize(title)}\""
else
sanitize(title)
......
......@@ -11,11 +11,11 @@ issuable_created = false
initTemplates: ->
Issuable.labelRow = _.template(
'<% _.each(labels, function(label){ %>
<span class="label-row btn-group" role="group" aria-label="<%= _.escape(label.title) %>" style="color: <%= label.text_color %>;">
<a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%= label.color %>;" title="<%= _.escape(label.description) %>" data-container="body">
<%= _.escape(label.title) %>
<span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;">
<a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body">
<%- label.title %>
</a>
<button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%= label.color %>;" data-label="<%= _.escape(label.title) %>">
<button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>">
<i class="fa fa-times"></i>
</button>
</span>
......
......@@ -32,9 +32,9 @@ class @LabelsSelect
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%= _.escape(label.title) %>">
<span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;">
<%= _.escape(label.title) %>
<a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>">
<span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">
<%- label.title %>
</span>
</a>
<% }); %>'
......@@ -261,7 +261,7 @@ class @LabelsSelect
$a.attr('data-label-id', label.id)
$a.addClass(selectedClass.join(' '))
.html("#{colorEl} #{_.escape(label.title)}")
.html("#{colorEl} #{label.title}")
# Return generated html
$li.html($a).prop('outerHTML')
......@@ -288,7 +288,7 @@ class @LabelsSelect
fieldName: $dropdown.data('field-name')
id: (label) ->
if $dropdown.hasClass("js-filter-submit") and not label.isAny?
_.escape label.title
label.title
else
label.id
......
......@@ -49,8 +49,9 @@
insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
if document.queryCommandSupported('insertText')
document.execCommand 'insertText', false, insertText
else
inserted = document.execCommand 'insertText', false, insertText
unless inserted
try
document.execCommand("ms-beginUndoUnit")
......
......@@ -24,14 +24,14 @@ class @MilestoneSelect
if issueUpdateURL
milestoneLinkTemplate = _.template(
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>" class="bold has-tooltip" data-container="body" title="<%= remaining %>"><%= _.escape(title) %></a>'
'<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'
)
milestoneLinkNoneTemplate = '<span class="no-value">None</span>'
collapsedSidebarLabelTemplate = _.template(
'<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
<%= _.escape(title) %>
'<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left">
<%- title %>
</span>'
)
......
......@@ -100,13 +100,40 @@ class @Notes
$('.note .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.note .js-task-list-container'
keydownNoteText: (e) ->
$this = $(this)
if $this.val() is '' and e.which is 38 and not isMetaKey e
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote])
keydownNoteText: (e) =>
return if isMetaKey e
$textarea = $(e.target)
# Edit previous note when UP arrow is hit
switch e.which
when 38
return unless $textarea.val() is ''
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote])
# Cancel creating diff note or editing any note when ESCAPE is hit
when 27
discussionNoteForm = $textarea.closest('.js-discussion-note-form')
if discussionNoteForm.length
if $textarea.val() isnt ''
return unless confirm('Are you sure you want to cancel creating this comment?')
@removeDiscussionNoteForm(discussionNoteForm)
return
editNote = $textarea.closest('.note')
if editNote.length
originalText = $textarea.closest('form').data('original-note')
newText = $textarea.val()
if originalText isnt newText
return unless confirm('Are you sure you want to cancel editing this comment?')
@removeNoteEditForm(editNote)
isMetaKey = (e) ->
(e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
......@@ -401,9 +428,12 @@ class @Notes
Hides edit form and restores the original note text to the editor textarea.
###
cancelEdit: (e) ->
cancelEdit: (e) =>
e.preventDefault()
note = $(this).closest(".note")
note = $(e.target).closest('.note')
@removeNoteEditForm(note)
removeNoteEditForm: (note) ->
form = note.find(".current-note-edit-form")
note.removeClass "is-editting"
form.removeClass("current-note-edit-form")
......
......@@ -61,8 +61,8 @@ class @UsersSelect
collapsedAssigneeTemplate = _.template(
'<% if( avatar ) { %>
<a class="author_link" href="/u/<%= username %>">
<img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>">
<a class="author_link" href="/u/<%- username %>">
<img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>">
<span class="author">Toni Boehm</span>
</a>
<% } else { %>
......@@ -72,13 +72,13 @@ class @UsersSelect
assigneeTemplate = _.template(
'<% if (username) { %>
<a class="author_link bold" href="/u/<%= username %>">
<a class="author_link bold" href="/u/<%- username %>">
<% if( avatar ) { %>
<img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
<img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>">
<% } %>
<span class="author"><%= name %></span>
<span class="author"><%- name %></span>
<span class="username">
@<%= username %>
@<%- username %>
</span>
</a>
<% } else { %>
......
......@@ -281,3 +281,21 @@
color: $gl-icon-color;
}
}
.clone-dropdown-btn a {
color: $dropdown-link-color;
&:hover {
text-decoration: none;
}
}
.btn-static {
background-color: $background-color !important;
border: 1px solid lightgrey;
cursor: default;
&:active {
-moz-box-shadow: inset 0 0 0 white;
-webkit-box-shadow: inset 0 0 0 white;
box-shadow: inset 0 0 0 white;
}
}
......@@ -20,17 +20,6 @@
.sidebar-wrapper {
background: $color-darker;
.sidebar-user {
background: $color-darker;
color: $color-light;
&:hover {
background-color: $color-dark;
color: $white-light;
text-decoration: none;
}
}
}
.nav-sidebar li {
......
......@@ -40,32 +40,16 @@
}
}
.sidebar-user {
padding: 15px;
position: absolute;
left: 0;
bottom: 0;
width: $sidebar_width;
overflow: hidden;
font-size: 16px;
line-height: 36px;
transition: width $sidebar-transition-duration, padding $sidebar-transition-duration;
@media (min-width: $sidebar-breakpoint) {
bottom: 50px;
}
}
.nav-sidebar {
position: absolute;
top: 50px;
bottom: 65px;
bottom: 0;
width: $sidebar_width;
overflow-y: auto;
overflow-x: hidden;
@media (min-width: $sidebar-breakpoint) {
bottom: 115px;
bottom: 50px;
}
&.navbar-collapse {
......
......@@ -167,7 +167,8 @@
.commit {
margin: 0;
padding: 2px 0;
padding-top: 2px;
padding-bottom: 2px;
list-style: none;
&:hover {
background: none;
......
......@@ -64,86 +64,49 @@
}
.project-home-panel {
background: $white-light;
text-align: left;
padding: 24px 0;
padding-top: 24px;
padding-bottom: 24px;
.container-fluid {
position: relative;
@media (min-width: $screen-lg-min) {
.row {
display: flex;
-ms-flex-align: center;
-webkit-align-items: center;
-webkit-box-align: center;
}
}
@media (min-width: $screen-sm-min) {
border-bottom: 1px solid $border-color;
}
.cover-controls {
.project-settings-dropdown {
margin-left: 10px;
display: inline-block;
.project-avatar {
float: none;
margin-left: auto;
margin-right: auto;
.dropdown-menu {
left: auto;
width: auto;
right: 0;
max-width: 240px;
}
&.identicon {
border-radius: 50%;
}
}
.cover-title {
margin-bottom: 0;
}
.project-image-container {
@include make-sm-column(1);
max-width: 86px;
min-width: 86px;
padding-right: 0;
@media (max-width: $screen-md-max) {
padding-left: 0;
margin: 0 0 10px;
max-width: none;
min-width: none;
.project-title {
margin-top: 10px;
margin-bottom: 10px;
font-size: 24px;
font-weight: 400;
line-height: 1;
.avatar.s70 {
margin: auto;
}
.fa {
margin-left: 2px;
font-size: 12px;
vertical-align: middle;
}
}
.project-info {
@include make-sm-column(10);
h1 {
font-size: 24px;
font-weight: normal;
margin: 0;
}
.project-home-desc {
margin-left: auto;
margin-right: auto;
margin-bottom: 15px;
max-width: 480px;
.project-home-desc {
p {
margin: 0;
}
> p {
margin-bottom: 0;
}
}
.identicon {
float: left;
@include border-radius(50%);
}
.avatar {
float: none;
}
.notifications-btn {
.fa-bell,
.fa-spinner {
margin-right: 6px;
......@@ -153,127 +116,106 @@
margin-left: 6px;
}
}
}
.project-repo-buttons {
font-size: 0;
.btn {
@include btn-gray;
padding: 3px 10px;
text-transform: none;
background-color: $background-color;
.project-repo-buttons {
font-size: 0;
.fa {
color: $layout-link-gray;
}
.btn {
@include btn-gray;
padding: 3px 10px;
.fa-caret-down {
margin-left: 3px;
}
.fa {
color: $layout-link-gray;
}
form {
margin-left: 10px;
.fa-caret-down {
margin-left: 3px;
}
}
.count-buttons {
display: inline-block;
vertical-align: top;
margin-top: 16px;
}
.project-repo-btn-group,
.notification-dropdown {
margin-left: 10px;
}
.project-clone-holder {
display: inline-block;
margin-top: 16px;
.count-buttons {
display: inline-block;
vertical-align: top;
}
input {
height: 29px;
}
.project-clone-holder {
display: inline-block;
input {
height: 29px;
}
}
.count-with-arrow {
display: inline-block;
position: relative;
margin-left: 4px;
.arrow {
&:before {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 0;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: #dce0e5;
}
&:after {
content: '';
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 1px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: #fff;
}
}
.count {
@include btn-gray;
.count-with-arrow {
display: inline-block;
position: relative;
margin-left: 4px;
.arrow {
&:before {
content: '';
display: inline-block;
background: white;
border-radius: 2px;
border-width: 1px;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
font-size: 13px;
font-weight: 600;
line-height: 13px;
padding: $gl-vert-padding $gl-padding;
letter-spacing: .4px;
padding: 7px 14px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
white-space: nowrap;
margin: 0 10px 0 4px;
a {
color: inherit;
}
&:hover {
background: #fff;
}
top: 50%;
left: 0;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: #dce0e5;
pointer-events: none;
}
}
}
.project-right-buttons {
position: absolute;
right: 16px;
bottom: 0;
@media (max-width: $screen-md-max) {
top: 0;
&:after {
content: '';
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 1px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: #fff;
pointer-events: none;
}
}
}
@media (max-width: $screen-md-max) {
text-align: center;
.count {
@include btn-gray;
display: inline-block;
background: white;
border-radius: 2px;
border-width: 1px;
border-style: solid;
font-size: 13px;
font-weight: 600;
line-height: 13px;
padding: $gl-vert-padding $gl-padding;
letter-spacing: .4px;
padding: 7px 14px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
background-image: none;
white-space: nowrap;
margin: 0 10px 0 4px;
a {
color: inherit;
}
.project-info,
.project-image-container {
width: 100%;
&:hover {
background: #fff;
}
}
}
}
......@@ -421,36 +363,42 @@ a.deploy-project-label {
}
.project-stats {
margin-top: $gl-padding;
margin-bottom: 0;
padding: 0;
background-color: $white-light;
font-size: 0;
border-bottom: 1px solid $border-color;
ul.nav {
display: inline-block;
.nav {
padding-top: 12px;
padding-bottom: 12px;
}
.nav li {
.nav > li {
display: inline-block;
margin: 16px 0;
margin-right: 16px;
&:not(:last-child) {
margin-right: $gl-padding;
}
&.project-repo-buttons-right {
margin-top: 10px;
@media (min-width: $screen-md-min) {
float: right;
margin-top: 0;
}
}
}
.nav > li > a {
padding: 0;
background-color: transparent;
padding: 5px 10px;
font-size: 15px;
line-height: 29px;
color: $notes-light-color;
}
li {
display: inline;
}
a {
float: left;
font-size: 17px;
&:hover,
&:focus {
color: darken($notes-light-color, 15%);
}
}
li.missing {
......@@ -458,6 +406,8 @@ a.deploy-project-label {
border-radius: $border-radius-default;
a {
padding-left: 10px;
padding-right: 10px;
color: $notes-light-color;
display: block;
}
......@@ -466,10 +416,6 @@ a.deploy-project-label {
background-color: $gray-normal;
}
}
&.row-content-block.second-block {
margin-top: 0;
}
}
pre.light-well {
......@@ -557,8 +503,32 @@ pre.light-well {
}
.project-last-commit {
@media (min-width: $screen-sm-min) {
margin-top: $gl-padding;
}
&.container-fluid {
padding-top: 12px;
padding-bottom: 12px;
background-color: $background-color;
border: 1px solid $border-color;
border-right-width: 0;
border-left-width: 0;
@media (min-width: $screen-sm-min) {
border-right-width: 1px;
border-left-width: 1px;
}
}
&.container-limited {
@media (min-width: 1281px) {
border-radius: $border-radius-base;
}
}
.ci-status {
margin-right: 16px;
margin-right: $gl-padding;
}
.commit-row-message {
......@@ -566,19 +536,12 @@ pre.light-well {
}
.commit_short_id {
margin: 0 5px;
margin-right: 5px;
color: $gl-link-color;
font-weight: 600;
}
.commit-author-link {
margin-left: 7px;
text-decoration: none;
.avatar {
float: none;
margin-right: 4px;
}
.commit-author-name {
font-weight: 600;
}
......@@ -601,15 +564,10 @@ pre.light-well {
}
.git-clone-holder {
width: 498px;
width: 380px;
.btn-clipboard {
border: 1px solid $border-color;
padding: 6px $gl-padding;
}
.project-home-dropdown + & {
margin-right: 45px;
}
.clone-options {
......
......@@ -118,6 +118,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:elasticsearch_host,
:elasticsearch_port,
:repository_storage,
:enabled_git_access_protocol,
restricted_visibility_levels: [],
import_sources: [],
disabled_oauth_sign_in_sources: []
......
......@@ -19,6 +19,8 @@ class Projects::GitHttpController < Projects::ApplicationController
render_ok
elsif receive_pack? && receive_pack_allowed?
render_ok
elsif http_blocked?
render_not_allowed
else
render_not_found
end
......@@ -154,6 +156,10 @@ class Projects::GitHttpController < Projects::ApplicationController
render plain: 'Not Found', status: :not_found
end
def render_not_allowed
render plain: download_access.message, status: :forbidden
end
def ci?
@ci.present?
end
......@@ -162,12 +168,28 @@ class Projects::GitHttpController < Projects::ApplicationController
return false unless Gitlab.config.gitlab_shell.upload_pack
if user
Gitlab::GitAccess.new(user, project).download_access_check.allowed?
download_access.allowed?
else
ci? || project.public?
end
end
def access
return @access if defined?(@access)
@access = Gitlab::GitAccess.new(user, project, 'http')
end
def download_access
return @download_access if defined?(@download_access)
@download_access = access.check('git-upload-pack')
end
def http_blocked?
!access.protocol_allowed?
end
def receive_pack_allowed?
return false unless Gitlab.config.gitlab_shell.receive_pack
......
......@@ -35,6 +35,28 @@ module ApplicationSettingsHelper
current_application_settings.akismet_enabled?
end
def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present?
end
def enabled_protocol
case current_application_settings.enabled_git_access_protocol
when 'http'
gitlab_config.protocol
when 'ssh'
'ssh'
end
end
def enabled_project_button(project, protocol)
case protocol
when 'ssh'
ssh_clone_button(project, 'bottom', append_link: false)
else
http_clone_button(project, 'bottom', append_link: false)
end
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
......
......@@ -12,7 +12,7 @@ module BranchesHelper
def can_push_branch?(project, branch_name)
return false unless project.repository.branch_exists?(branch_name)
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(branch_name)
end
def project_branches
......
......@@ -40,33 +40,33 @@ module ButtonHelper
type: :button
end
def http_clone_button(project)
def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector'
klass << ' has-tooltip' if current_user.try(:require_password?)
protocol = gitlab_config.protocol.upcase
content_tag :a, protocol,
content_tag (append_link ? :a : :span), protocol,
class: klass,
href: project.http_url_to_repo,
href: (project.http_url_to_repo if append_link),
data: {
html: true,
placement: 'right',
placement: placement,
container: 'body',
title: "Set a password on your account<br>to pull or push via #{protocol}"
}
end
def ssh_clone_button(project)
def ssh_clone_button(project, placement = 'right', append_link: true)
klass = 'ssh-selector'
klass << ' has-tooltip' if current_user.try(:require_ssh_key?)
content_tag :a, 'SSH',
content_tag (append_link ? :a : :span), 'SSH',
class: klass,
href: project.ssh_url_to_repo,
href: (project.ssh_url_to_repo if append_link),
data: {
html: true,
placement: 'right',
placement: placement,
container: 'body',
title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
}
......
......@@ -118,7 +118,7 @@ module IssuesHelper
end
def emoji_icon(name, unicode = nil, aliases = [], sprite: true)
unicode ||= Emoji.emoji_filename(name) rescue ""
unicode ||= Gitlab::Emoji.emoji_filename(name) rescue ""
data = {
aliases: aliases.join(" "),
......
......@@ -12,17 +12,6 @@ module MembersHelper
can?(current_user, action_member_permission(:admin, member), member.source)
end
def can_see_request_access_button?(source)
source_parent = source.respond_to?(:group) && source.group
return false if source_parent && source.group.members.exists?(user_id: current_user.id)
return false if source_parent && source.group.requesters.exists?(user_id: current_user.id)
return false if source.members.exists?(user_id: current_user.id)
return true if source.requesters.exists?(user_id: current_user.id)
true
end
def remove_member_message(member, user: nil)
user = current_user if defined?(current_user)
......
......@@ -208,12 +208,21 @@ module ProjectsHelper
end
def default_clone_protocol
<<<<<<< HEAD
if alternative_kerberos_url? && current_user
"krb5"
elsif !current_user || current_user.require_ssh_key?
gitlab_config.protocol
=======
if allowed_protocols_present?
enabled_protocol
>>>>>>> ce/master
else
"ssh"
if !current_user || current_user.require_ssh_key?
gitlab_config.protocol
else
'ssh'
end
end
end
......
......@@ -171,10 +171,11 @@ class Ability
# Push abilities on the users team role
rules.push(*project_team_rules(project.team, user))
if project.owner == user ||
(project.group && project.group.has_owner?(user)) ||
user.admin?
owner = user.admin? ||
project.owner == user ||
(project.group && project.group.has_owner?(user))
if owner
rules.push(*project_owner_rules)
end
......@@ -183,6 +184,10 @@ class Ability
# Allow to read builds for internal projects
rules << :read_build if project.public_builds?
unless owner || project.team.member?(user) || project_group_member?(project, user)
rules << :request_access
end
end
if project.archived?
......@@ -364,8 +369,11 @@ class Ability
rules = []
rules << :read_group if can_read_group?(user, group)
owner = user.admin? || group.has_owner?(user)
master = owner || group.has_master?(user)
# Only group masters and group owners can create new projects
if group.has_master?(user) || group.has_owner?(user) || user.admin?
if master
rules += [
:create_projects,
:admin_milestones
......@@ -373,7 +381,7 @@ class Ability
end
# Only group owner and administrators can admin group
if group.has_owner?(user) || user.admin?
if owner
rules += [
:admin_group,
:admin_namespace,
......@@ -386,6 +394,10 @@ class Ability
end
end
if group.public? || (group.internal? && !user.external?)
rules << :request_access unless group.users.include?(user)
end
rules.flatten
end
......@@ -587,5 +599,13 @@ class Ability
rules
end
def project_group_member?(project, user)
project.group &&
(
project.group.members.exists?(user_id: user.id) ||
project.group.requesters.exists?(user_id: user.id)
)
end
end
end
......@@ -67,6 +67,9 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates :enabled_git_access_protocol,
inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
......
......@@ -8,7 +8,7 @@ class AwardEmoji < ActiveRecord::Base
belongs_to :user
validates :awardable, :user, presence: true
validates :name, presence: true, inclusion: { in: Emoji.emojis_names }
validates :name, presence: true, inclusion: { in: Gitlab::Emoji.emojis_names }
validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] }
participant :user
......
......@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base
elsif issue? || issue_note?
Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target)
else
((merge_request? || note?) && target) || milestone?
((merge_request? || note?) && target.present?) || milestone?
end
end
......@@ -141,7 +141,7 @@ class Event < ActiveRecord::Base
end
def note?
target_type == "Note"
target.is_a?(Note)
end
def issue?
......
......@@ -99,7 +99,7 @@ class Group < Namespace
end
def avatar_url(size = nil)
if avatar.present?
if self[:avatar].present?
[gitlab_config.url, avatar.url].join
end
end
......
......@@ -20,10 +20,10 @@ class Label < ActiveRecord::Base
validates :color, color: true, allow_blank: false
validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow '?', '&', and ',' for label titles
# Don't allow ',' for label titles
validates :title,
presence: true,
format: { with: /\A[^&\?,]+\z/ },
format: { with: /\A[^,]+\z/ },
uniqueness: { scope: :project_id }
before_save :nullify_priority
......@@ -58,8 +58,8 @@ class Label < ActiveRecord::Base
(?:
(?<label_id>\d+) | # Integer-based label ID, or
(?<label_name>
[A-Za-z0-9_-]+ | # String-based single-word label title, or
"[^&\?,]+" # String-based multi-word label surrounded in quotes
[A-Za-z0-9_\-\?&]+ | # String-based single-word label title, or
"[^,]+" # String-based multi-word label surrounded in quotes
)
)
}x
......@@ -114,7 +114,7 @@ class Label < ActiveRecord::Base
end
def title=(value)
write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
write_attribute(:title, sanitize_title(value)) if value.present?
end
private
......@@ -132,4 +132,8 @@ class Label < ActiveRecord::Base
def nullify_priority
self.priority = nil if priority.blank?
end
def sanitize_title(value)
CGI.unescapeHTML(Sanitize.clean(value.to_s))
end
end
......@@ -548,7 +548,7 @@ class MergeRequest < ActiveRecord::Base
end
def can_be_merged_by?(user)
::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
::Gitlab::GitAccess.new(user, project, 'web').can_push_to_branch?(target_branch)
end
def mergeable_ci_state?
......
......@@ -168,9 +168,7 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
url: { protocols: %w(ssh git http https) },
if: :external_import?
validates :import_url, addressable_url: true, if: :external_import?
validates :import_url, presence: true, if: :mirror?
validate :import_url_availability, if: :import_url_changed?
validates :mirror_user, presence: true, if: :mirror?
......@@ -496,6 +494,8 @@ class Project < ActiveRecord::Base
end
def import_url=(value)
return super(value) unless Gitlab::UrlSanitizer.valid?(value)
import_url = Gitlab::UrlSanitizer.new(value)
create_or_update_import_data(credentials: import_url.credentials)
super(import_url.sanitized_url)
......@@ -805,7 +805,7 @@ class Project < ActiveRecord::Base
end
def avatar_url
if avatar.present?
if self[:avatar].present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
......
......@@ -686,7 +686,7 @@ class User < ActiveRecord::Base
end
def avatar_url(size = nil, scale = 2)
if avatar.present?
if self[:avatar].present?
[gitlab_config.url, avatar.url].join
else
GravatarService.new.execute(email, size, scale)
......
......@@ -23,7 +23,7 @@ module Commits
private
def check_push_permissions
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch)
unless allowed
raise ValidationError.new('You are not allowed to push into this branch')
......
......@@ -43,7 +43,7 @@ module Files
end
def validate
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch)
unless allowed
raise_error("You are not allowed to push into this branch")
......
......@@ -27,7 +27,7 @@ module Projects
GitlabShellOneShotWorker.perform_async(:gc, @project.repository_storage_path, @project.path_with_namespace)
ensure
Gitlab::Metrics.measure(:reset_pushes_since_gc) do
@project.update_column(:pushes_since_gc, 0)
update_pushes_since_gc(0)
end
end
......@@ -37,12 +37,18 @@ module Projects
def increment!
Gitlab::Metrics.measure(:increment_pushes_since_gc) do
@project.increment!(:pushes_since_gc)
update_pushes_since_gc(@project.pushes_since_gc + 1)
end
end
private
def update_pushes_since_gc(new_value)
if Gitlab::ExclusiveLease.new("project_housekeeping:update_pushes_since_gc:#{project.id}", timeout: 60).try_obtain
@project.update_column(:pushes_since_gc, new_value)
end
end
def try_obtain_lease
Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
......
......@@ -38,6 +38,8 @@ module Projects
end
def cleanup_and_notify
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
FileUtils.rm_rf(@shared.export_path)
notify_error
......@@ -45,6 +47,8 @@ module Projects
end
def notify_success
Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported")
notification_service.project_exported(@project, @current_user)
end
......
# AddressableUrlValidator
#
# Custom validator for URLs. This is a stricter version of UrlValidator - it also checks
# for using the right protocol, but it actually parses the URL checking for any syntax errors.
# The regex is also different from `URI` as we use `Addressable::URI` here.
#
# By default, only URLs for http, https, ssh, and git protocols will be considered valid.
# Provide a `:protocols` option to configure accepted protocols.
#
# Example:
#
# class User < ActiveRecord::Base
# validates :personal_url, addressable_url: true
#
# validates :ftp_url, addressable_url: { protocols: %w(ftp) }
#
# validates :git_url, addressable_url: { protocols: %w(http https ssh git) }
# end
#
class AddressableUrlValidator < ActiveModel::EachValidator
DEFAULT_OPTIONS = { protocols: %w(http https ssh git) }
def validate_each(record, attribute, value)
unless valid_url?(value)
record.errors.add(attribute, "must be a valid URL")
end
end
private
def valid_url?(value)
return false unless value
valid_protocol?(value) && valid_uri?(value)
end
def valid_uri?(value)
Gitlab::UrlSanitizer.valid?(value)
end
def valid_protocol?(value)
options = DEFAULT_OPTIONS.merge(self.options)
value =~ /\A#{URI.regexp(options[:protocols])}\z/
end
end
......@@ -43,6 +43,12 @@
= link_to "(?)", help_page_path("integration", "bitbucket")
and GitLab.com
= link_to "(?)", help_page_path("integration", "gitlab")
.form-group
%label.control-label.col-sm-2 Enabled Git access protocols
.col-sm-10
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
%span.help-block#clone-protocol-help
Allow only the selected protocols to be used for Git access.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
......
......@@ -8,11 +8,6 @@
- else
= render 'layouts/nav/explore'
- if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
= link_to '#', class: "nav-header-btn text-center 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
%span.sr-only Toggle navigation pinning
= icon('thumb-tack')
......
......@@ -49,7 +49,9 @@
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li
= link_to "Profile", current_user
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Profile Settings", profile_path
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", title: 'Sign out'
......
- empty_repo = @project.empty_repo?
.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
%div{ class: container_class }
.row
.project-image-container
= project_icon(@project, alt: '', class: 'project-avatar avatar s70')
.project-info
.cover-title.project-home-desc
%h1
= @project.name
%span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
= visibility_level_icon(@project.visibility_level, fw: false)
- if @project.description.present?
.cover-desc.project-home-desc
= markdown(@project.description, pipeline: :description)
- if forked_from_project = @project.forked_from_project
.cover-desc
Forked from
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
= project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70')
%h1.project-title
= @project.name
%span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
= visibility_level_icon(@project.visibility_level, fw: false)
.project-home-desc
- if @project.description.present?
= markdown(@project.description, pipeline: :description)
- if forked_from_project = @project.forked_from_project
%p
Forked from
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
<<<<<<< HEAD
- if @project.mirror?
- import_url = @project.safe_import_url
%p
......@@ -44,6 +41,15 @@
= render "projects/buttons/download"
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
=======
.project-repo-buttons.project-action-buttons
.count-buttons
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
.project-clone-holder
= render "shared/clone_panel"
>>>>>>> ce/master
:javascript
new Star();
.project-last-commit
- if commit.status
= link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
= ci_icon_for_status(commit.status)
= ci_label_for_status(commit.status)
- if commit.status
= link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
= ci_icon_for_status(commit.status)
= ci_label_for_status(commit.status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
&middot;
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
= commit_author_link(commit, avatar: true, size: 24)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
&middot;
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
= commit_author_link(commit, avatar: true, size: 24)
......@@ -14,6 +14,5 @@
Fork
%div.count-with-arrow
%span.arrow
%span.count
= link_to namespace_project_forks_path(@project.namespace, @project) do
= @project.forks_count
= link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
= @project.forks_count
......@@ -12,60 +12,67 @@
= render 'projects/last_push'
= render "home_panel"
.project-stats.row-content-block.second-block
%div{ class: container_class }
%ul.nav
%li
= link_to project_files_path(@project) do
Files (#{repository_size})
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
#{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%nav.project-stats{ class: (container_class) }
%ul.nav
%li
= link_to project_files_path(@project) do
Files (#{repository_size})
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
#{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
#{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if default_project_view != 'readme' && @repository.readme
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
#{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
= link_to 'Readme', readme_path(@project)
- if default_project_view != 'readme' && @repository.readme
%li
= link_to 'Readme', readme_path(@project)
- if @repository.changelog
%li
= link_to 'Changelog', changelog_path(@project)
- if @repository.changelog
%li
= link_to 'Changelog', changelog_path(@project)
- if @repository.license_blob
%li
= link_to license_short_name(@project), license_path(@project)
- if @repository.license_blob
%li
= link_to license_short_name(@project), license_path(@project)
- if @repository.contribution_guide
%li
= link_to 'Contribution guide', contribution_guide_path(@project)
- if @repository.contribution_guide
%li
= link_to 'Contribution guide', contribution_guide_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- unless @repository.license_blob
%li.missing
= link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- unless @repository.gitlab_ci_yml
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
%li.project-repo-buttons-right
.project-repo-buttons.project-right-buttons
- if current_user
= render 'shared/members/access_request_buttons', source: @project
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- unless @repository.license_blob
%li.missing
= link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- unless @repository.gitlab_ci_yml
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
.btn-group.project-repo-btn-group
= render "projects/buttons/download"
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
.content-block.second-block.white
%div{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, project: @project
.project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, project: @project
%div{ class: container_class }
- if @project.archived?
......
......@@ -2,6 +2,7 @@
.git-clone-holder.input-group
.input-group-btn
<<<<<<< HEAD
%a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'}
%span
= default_clone_protocol.upcase
......@@ -14,6 +15,22 @@
- if alternative_kerberos_url?
%li
= kerberos_clone_button(project)
=======
-if allowed_protocols_present?
.clone-dropdown-btn.btn.btn-static
%span
= enabled_project_button(project, enabled_protocol)
- else
%a#clone-dropdown.clone-dropdown-btn.btn{href: '#', data: { toggle: 'dropdown' }}
%span
= default_clone_protocol.upcase
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
%li
= ssh_clone_button(project)
%li
= http_clone_button(project)
>>>>>>> ce/master
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn
......
- labels.each do |label|
%span.label-row.btn-group{ role: "group", aria: { label: escape_once(label.name) }, style: "color: #{text_color_for_bg(label.color)}" }
= link_to label_filter_path(@project, label, type: controller.controller_name),
%span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" }
= link_to label.name, label_filter_path(@project, label, type: controller.controller_name),
class: "btn btn-transparent has-tooltip",
style: "background-color: #{label.color};",
title: escape_once(label.description),
data: { container: "body" } do
= escape_once label.name
data: { container: "body" }
%button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } }
= icon("times")
- if can_see_request_access_button?(source)
- if can?(current_user, :request_access, source)
- if requester = source.requesters.find_by(user_id: current_user.id)
= link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]),
method: :delete,
......
......@@ -135,6 +135,8 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(Rouge::Plugins::Redcarpet)
config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
config.instrument_methods(Rinku)
end
GC::Profiler.enable
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable all
class AddEnabledGitAccessProtocolsToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_column :application_settings, :enabled_git_access_protocol, :string
end
end
# Updates project records containing invalid URLs using the AddressableUrlValidator.
# This is optimized assuming the number of invalid records is low, but
# we still need to loop through all the projects with an +import_url+
# so we use batching for the latter.
#
# This migration is non-reversible as we would have to keep the old data.
class FixNoValidatableImportUrl < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
class SqlBatches
attr_reader :results, :query
def initialize(batch_size: 100, query:)
@offset = 0
@batch_size = batch_size
@query = query
@results = []
end
def next?
@results = ActiveRecord::Base.connection.exec_query(batched_sql)
@offset += @batch_size
@results.any?
end
private
def batched_sql
"#{@query} LIMIT #{@batch_size} OFFSET #{@offset}"
end
end
# AddressableValidator - Snapshot of AddressableUrlValidator
module AddressableUrlValidatorSnap
extend self
def valid_url?(value)
return false unless value
valid_uri?(value) && valid_protocol?(value)
rescue Addressable::URI::InvalidURIError
false
end
def valid_uri?(value)
Addressable::URI.parse(value).is_a?(Addressable::URI)
end
def valid_protocol?(value)
value =~ /\A#{URI.regexp(%w(http https ssh git))}\z/
end
end
def up
unless defined?(Addressable::URI::InvalidURIError)
say('Skipping cleaning up invalid import URLs as class from Addressable is missing')
return
end
say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.')
invalid_import_url_project_ids.each { |project_id| cleanup_import_url(project_id) }
end
def invalid_import_url_project_ids
ids = []
batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL")
while batches.next?
batches.results.each do |result|
ids << result['id'] unless valid_url?(result['import_url'])
end
end
ids
end
def valid_url?(url)
AddressableUrlValidatorSnap.valid_url?(url)
end
def cleanup_import_url(project_id)
execute("UPDATE projects SET import_url = NULL WHERE id = #{project_id}")
end
end
# rubocop:disable all
# Migration type: online without errors
class AddIndexOnAwardEmojiUserAndName < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def change
add_concurrent_index(:award_emoji, [:user_id, :name])
end
end
class RemoveRequestersThatAreOwners < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def up
# Delete requesters that are owner of their projects and actually requested
# access to it
execute <<-SQL
DELETE FROM members
WHERE members.source_type = 'Project'
AND members.type = 'ProjectMember'
AND members.requested_at IS NOT NULL
AND members.user_id = (
SELECT namespaces.owner_id
FROM namespaces
JOIN projects ON namespaces.id = projects.namespace_id
WHERE namespaces.type IS NULL
AND projects.id = members.source_id
AND namespaces.owner_id = members.user_id);
SQL
# Delete requesters that are owner of their project's group and actually requested
# access to it
execute <<-SQL
DELETE FROM members
WHERE members.source_type = 'Project'
AND members.type = 'ProjectMember'
AND members.requested_at IS NOT NULL
AND members.user_id = (
SELECT namespaces.owner_id
FROM namespaces
JOIN projects ON namespaces.id = projects.namespace_id
WHERE namespaces.type = 'Group'
AND projects.id = members.source_id
AND namespaces.owner_id = members.user_id);
SQL
end
def down
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160705111606) do
ActiveRecord::Schema.define(version: 20160705163108) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -93,6 +93,7 @@ ActiveRecord::Schema.define(version: 20160705111606) do
t.string "elasticsearch_host", default: "localhost"
t.string "elasticsearch_port", default: "9200"
t.string "repository_storage", default: "default"
t.string "enabled_git_access_protocol"
end
create_table "approvals", force: :cascade do |t|
......@@ -137,6 +138,7 @@ ActiveRecord::Schema.define(version: 20160705111606) do
end
add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree
add_index "award_emoji", ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree
add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree
create_table "broadcast_messages", force: :cascade do |t|
......
......@@ -25,6 +25,7 @@
## Administrator documentation
- [Audit Events](administration/audit_events.md) Check how user access changed in projects and groups.
- [Access restrictions](administration/access_restrictions.md) Define which Git access protocols can be used to talk to GitLab
- [Authentication/Authorization](administration/auth/README.md) Configure
external authentication with LDAP, SAML, CAS and additional Omniauth providers.
- [Changing the appearance of the login page](customization/branded_login_page.md) Make the login page branded for your GitLab instance.
......
# Access Restrictions
> **Note:** This feature is only available on versions 8.10 and above.
With GitLab's Access restrictions you can choose which Git access protocols you
want your users to use to communicate with GitLab. This feature can be enabled
via the `Application Settings` in the Admin interface.
The setting is called `Enabled Git access protocols`, and it gives you the option
to choose between:
- Both SSH and HTTP(S)
- Only SSH
- Only HTTP(s)
![Settings Overview](img/access_restrictions.png)
## Enabled Protocol
When both SSH and HTTP(S) are enabled, GitLab will behave as usual, it will give
your users the option to choose which protocol they would like to use.
When you choose to allow only one of the protocols, a couple of things will happen:
- The project page will only show the allowed protocol's URL, with no option to
change it.
- A tooltip will be shown when you hover over the URL's protocol, if an action
on the user's part is required, e.g. adding an SSH key, or setting a password.
![Project URL with SSH only access](img/restricted_url.png)
On top of these UI restrictions, GitLab will deny all Git actions on the protocol
not selected.
> **Note:** Please keep in mind that disabling an access protocol does not actually
block access to the server itself. The ports used for the protocol, be it SSH or
HTTP, will still be accessible. What GitLab does is restrict access on the
application level.
\ No newline at end of file
# Custom Git Hooks
>
**Note:** Custom Git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not
have filesystem access. For a user configurable Push Rules interface, please see
[GitLab Enterprise Edition Push Rules](http://docs.gitlab.com/ee/push_rules/push_rules.html).**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
See [Git SCM Server-Side Hooks][hooks] for more information about each hook type.
As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
administrators can add custom git hooks to any GitLab project.
## Setup
Normally, Git hooks are placed in the repository or project's `hooks` directory.
GitLab creates a symlink from each project's `hooks` directory to the
gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell
upgrades. As such, custom hooks are implemented a little differently. Behavior
is exactly the same once the hook is created, though.
Follow the steps below to set up a custom hook:
1. Pick a project that needs a custom Git hook.
1. On the GitLab server, navigate to the project's repository directory.
For an installation from source the path is usually
`/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is
usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`.
1. Create a new directory in this location called `custom_hooks`.
1. Inside the new `custom_hooks` directory, create a file with a name matching
the hook type. For a pre-receive hook the file name should be `pre-receive`
with no extension.
1. Make the hook file executable and make sure it's owned by git.
1. Write the code to make the Git hook function as expected. Hooks can be
in any language. Ensure the 'shebang' at the top properly reflects the language
type. For example, if the script is in Ruby the shebang will probably be
`#!/usr/bin/env ruby`.
That's it! Assuming the hook code is properly implemented the hook will fire
as appropriate.
## Custom error messages
>**Note:**
This feature was [introduced][5073] in GitLab 8.10.
If the commit is declined or an error occurs during the Git hook check,
the STDERR and/or SDOUT message of the hook will be present in GitLab's UI.
![Custom message from custom Git hook](img/custom_hooks_error_msg.png)
[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks
[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073
......@@ -68,6 +68,7 @@ PUT /application/settings
| `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 |
| `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.
```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
......
# Custom Git Hooks
**Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Push Rules interface, please see [GitLab Enterprise Edition Push Rules](http://docs.gitlab.com/ee/push_rules/push_rules.html).**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
See
[Git SCM Server-Side Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks)
for more information about each hook type.
As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
administrators can add custom git hooks to any GitLab project.
## Setup
Normally, git hooks are placed in the repository or project's `hooks` directory.
GitLab creates a symlink from each project's `hooks` directory to the
gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell
upgrades. As such, custom hooks are implemented a little differently. Behavior
is exactly the same once the hook is created, though. Follow these steps to
set up a custom hook.
1. Pick a project that needs a custom git hook.
1. On the GitLab server, navigate to the project's repository directory.
For an installation from source the path is usually
`/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is
usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`.
1. Create a new directory in this location called `custom_hooks`.
1. Inside the new `custom_hooks` directory, create a file with a name matching
the hook type. For a pre-receive hook the file name should be `pre-receive` with
no extension.
1. Make the hook file executable and make sure it's owned by git.
1. Write the code to make the git hook function as expected. Hooks can be
in any language. Ensure the 'shebang' at the top properly reflects the language
type. For example, if the script is in Ruby the shebang will probably be
`#!/usr/bin/env ruby`.
That's it! Assuming the hook code is properly implemented the hook will fire
as appropriate.
This document was moved to [administration/custom_hooks.md](../administration/custom_hooks.md).
......@@ -36,7 +36,7 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
# Grant the GitLab user necessary permissions on the database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session
mysql> \q
......
......@@ -269,9 +269,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ee.git -b 8-9-stable-ee gitlab
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ee.git -b 8-10-stable-ee gitlab
**Note:** You can change `8-9-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server!
**Note:** You can change `8-10-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
......@@ -401,7 +401,7 @@ If you are not using Linux you may have to run `gmake` instead of
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
sudo -u git -H git checkout v0.7.5
sudo -u git -H git checkout v0.7.7
sudo -u git -H make
### Initialize Database and Activate Advanced Features
......
......@@ -2,7 +2,7 @@
This documentation is for enabling shibboleth with omnibus-gitlab package.
In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however I did not found way to easily configure Nginx that is bundled in omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled NIGNX provided in the omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
To enable the Shibboleth OmniAuth provider you must:
......
......@@ -62,7 +62,23 @@ sudo -u git -H git checkout v0.7.5
sudo -u git -H make
```
### 6. Install libs, migrations, etc.
### 6. Update MySQL permissions
If you are using MySQL you need to grant the GitLab user the necessary
permissions on the database:
```bash
# Login to MySQL
mysql -u root -p
# Grant the GitLab user the REFERENCES permission on the database
GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session
mysql> \q
```
### 7. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
......@@ -84,7 +100,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
```
### 7. Update configuration files
### 8. Update configuration files
#### New configuration options for `gitlab.yml`
......@@ -141,12 +157,12 @@ Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
### 8. Start application
### 9. Start application
sudo service gitlab start
sudo service nginx restart
### 9. Check application status
### 10. Check application status
Check if GitLab and its environment are configured correctly:
......
# From 8.9 to 8.10
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. 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-10-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-10-stable-ee
```
### 4. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v3.1.0
```
### 5. 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.7.7
sudo -u git -H make
```
### 6. Update MySQL permissions
If you are using MySQL you need to grant the GitLab user the necessary
permissions on the database:
```bash
# Login to MySQL
mysql -u root -p
# Grant the GitLab user the REFERENCES permission on the database
GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session
mysql> \q
```
### 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-9-stable:config/gitlab.yml.example origin/8-10-stable:config/gitlab.yml.example
```
#### Git configuration
Disable `git gc --auto` because GitLab runs `git gc` for us already.
```sh
sudo -u git -H git config --global gc.auto 0
```
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh
# For HTTPS configurations
git diff origin/8-9-stable:lib/support/nginx/gitlab-ssl origin/8-10-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-9-stable:lib/support/nginx/gitlab origin/8-10-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-10-stable/lib/support/init.d/gitlab.default.example#L37
#### 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/v8.9.0/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.9)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 8.8 to 8.9](8.8-to-8.9.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.
......@@ -155,8 +155,11 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I click on my profile picture' do
find(:css, '.side-nav-toggle').click
find(:css, '.sidebar-user').click
find(:css, '.header-user-dropdown-toggle').click
page.within ".header-user" do
click_link "Profile"
end
end
step 'I should see my user page' do
......
This diff is collapsed.
......@@ -13,6 +13,7 @@ module API
# action - git action (git-upload-pack or git-receive-pack)
# ref - branch name
# forced_push - forced_push
# protocol - Git access protocol being used, e.g. HTTP or SSH
#
helpers do
......@@ -46,11 +47,13 @@ module API
User.find_by(id: params[:user_id])
end
protocol = params[:protocol]
access =
if wiki?
Gitlab::GitAccessWiki.new(actor, project)
Gitlab::GitAccessWiki.new(actor, project, protocol)
else
Gitlab::GitAccess.new(actor, project)
Gitlab::GitAccess.new(actor, project, protocol)
end
access_status = access.check(params[:action], params[:changes])
......
......@@ -61,7 +61,7 @@ module Banzai
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
@emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
@emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
end
def emoji_pattern
......@@ -69,7 +69,7 @@ module Banzai
end
def emoji_filename(name)
"#{Emoji.emoji_filename(name)}.png"
"#{Gitlab::Emoji.emoji_filename(name)}.png"
end
end
end
......
......@@ -13,13 +13,13 @@ module Banzai
end
def self.references_in(text, pattern = Label.reference_pattern)
text.gsub(pattern) do |match|
unescape_html_entities(text).gsub(pattern) do |match|
yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~
end
end
def references_in(text, pattern = Label.reference_pattern)
text.gsub(pattern) do |match|
unescape_html_entities(text).gsub(pattern) do |match|
label = find_label($~[:project], $~[:label_id], $~[:label_name])
if label
......@@ -66,6 +66,10 @@ module Banzai
LabelsHelper.render_colored_cross_project_label(object)
end
end
def unescape_html_entities(text)
CGI.unescapeHTML(text.to_s)
end
end
end
end
module Gitlab
module Emoji
extend self
def emojis
Gemojione.index.instance_variable_get(:@emoji_by_name)
end
def emojis_by_moji
Gemojione.index.instance_variable_get(:@emoji_by_moji)
end
def emojis_names
emojis.keys.sort
end
def emoji_filename(name)
emojis[name]["unicode"]
end
end
end
module Gitlab
module Git
class Hook
GL_PROTOCOL = 'web'.freeze
attr_reader :name, :repo_path, :path
def initialize(name, repo_path)
......@@ -34,7 +35,8 @@ module Gitlab
vars = {
'GL_ID' => gl_id,
'PWD' => repo_path
'PWD' => repo_path,
'GL_PROTOCOL' => GL_PROTOCOL
}
options = {
......
......@@ -6,11 +6,12 @@ module Gitlab
PUSH_COMMANDS = %w{ git-receive-pack }
GIT_ANNEX_COMMANDS = %w{ git-annex-shell }
attr_reader :actor, :project
attr_reader :actor, :project, :protocol
def initialize(actor, project)
def initialize(actor, project, protocol)
@actor = actor
@project = project
@protocol = protocol
end
def user
......@@ -60,6 +61,8 @@ module Gitlab
end
def check(cmd, changes = nil)
return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed?
unless actor
return build_status_object(false, "No user or key was provided.")
end
......@@ -200,6 +203,10 @@ module Gitlab
Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
end
def protocol_allowed?
Gitlab::ProtocolAccess.allowed?(protocol)
end
def path_locks_check(user, project, ref, oldrev, newrev)
unless project.path_locks.any? && newrev && oldrev
return build_status_object(true)
......
......@@ -28,7 +28,8 @@ module Gitlab
end
def execute(cmd)
_output, status = Gitlab::Popen.popen(cmd)
output, status = Gitlab::Popen.popen(cmd)
@shared.error(output.to_s) unless status.zero?
status.zero?
end
......
......@@ -17,6 +17,7 @@ module Gitlab
Rails.logger.info("Saved project export #{archive_file}")
archive_file
else
@shared.error("Unable to save #{archive_file} into #{@shared.export_path}")
false
end
rescue => e
......
......@@ -2,11 +2,21 @@ module Gitlab
module Metrics
module Subscribers
# Class for tracking the total time spent in Rails cache calls
# http://guides.rubyonrails.org/active_support_instrumentation.html
class RailsCache < ActiveSupport::Subscriber
attach_to :active_support
def cache_read(event)
increment(:cache_read, event.duration)
return unless current_transaction
return if event.payload[:super_operation] == :fetch
if event.payload[:hit]
current_transaction.increment(:cache_read_hit_count, 1)
else
current_transaction.increment(:cache_read_miss_count, 1)
end
end
def cache_write(event)
......@@ -21,6 +31,18 @@ module Gitlab
increment(:cache_exists, event.duration)
end
def cache_fetch_hit(event)
return unless current_transaction
current_transaction.increment(:cache_read_hit_count, 1)
end
def cache_generate(event)
return unless current_transaction
current_transaction.increment(:cache_read_miss_count, 1)
end
def increment(key, duration)
return unless current_transaction
......
module Gitlab
module ProtocolAccess
def self.allowed?(protocol)
if protocol == 'web'
true
elsif current_application_settings.enabled_git_access_protocol.blank?
true
else
protocol == current_application_settings.enabled_git_access_protocol
end
end
end
end
......@@ -29,11 +29,11 @@ module Gitlab
"in #{GRACE_TIME} seconds"
sleep(GRACE_TIME)
Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"
Process.kill('SIGTERM', Process.pid)
Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
"#{SHUTDOWN_SIGNAL} to PID #{Process.pid}"
"#{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"
sleep(SHUTDOWN_WAIT)
Sidekiq.logger.warn "sending #{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"
......
......@@ -6,8 +6,16 @@ module Gitlab
content.gsub(regexp) { |url| new(url).masked_url }
end
def self.valid?(url)
Addressable::URI.parse(url.strip)
true
rescue Addressable::URI::InvalidURIError
false
end
def initialize(url, credentials: nil)
@url = Addressable::URI.parse(url)
@url = Addressable::URI.parse(url.strip)
@credentials = credentials
end
......
......@@ -13,7 +13,7 @@ namespace :gemojione do
aliases[real_name] << alias_name
end
AwardEmoji.emojis.map do |name, emoji_hash|
Gitlab::AwardEmoji.emojis.map do |name, emoji_hash|
fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
digest = Digest::SHA256.file(fpath).hexdigest
......
require 'rails_helper'
feature 'Admin disables Git access protocol', feature: true do
let(:project) { create(:empty_project, :empty_repo) }
let(:admin) { create(:admin) }
background do
login_as(admin)
end
context 'with HTTP disabled' do
background do
disable_http_protocol
end
scenario 'shows only SSH url' do
visit_project
expect(page).to have_content("git clone #{project.ssh_url_to_repo}")
expect(page).not_to have_selector('#clone-dropdown')
end
end
context 'with SSH disabled' do
background do
disable_ssh_protocol
end
scenario 'shows only HTTP url' do
visit_project
expect(page).to have_content("git clone #{project.http_url_to_repo}")
expect(page).not_to have_selector('#clone-dropdown')
end
end
context 'with nothing disabled' do
background do
create(:personal_key, user: admin)
end
scenario 'shows default SSH url and protocol selection dropdown' do
visit_project
expect(page).to have_content("git clone #{project.ssh_url_to_repo}")
expect(page).to have_selector('#clone-dropdown')
end
end
def visit_project
visit namespace_project_path(project.namespace, project)
end
def disable_http_protocol
visit admin_application_settings_path
find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[2]').select_option
click_on 'Save'
end
def disable_ssh_protocol
visit admin_application_settings_path
find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[3]').select_option
click_on 'Save'
end
end
......@@ -144,9 +144,7 @@ describe "Admin::Users", feature: true do
before { click_link 'Impersonate' }
it 'logs in as the user when impersonate is clicked' do
page.within '.sidebar-wrapper' do
expect(page.find('.sidebar-user')['data-user']).to eql(another_user.username)
end
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
end
it 'sees impersonation log out icon' do
......@@ -158,9 +156,7 @@ describe "Admin::Users", feature: true do
it 'can log out of impersonated user back to original user' do
find(:css, 'li.impersonation a').click
page.within '.sidebar-wrapper' do
expect(page.find('.sidebar-user')['data-user']).to eql(@user.username)
end
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username)
end
it 'is redirected back to the impersonated users page in the admin after stopping' do
......
require 'spec_helper'
feature 'Groups > Members > Member cannot request access to his project', feature: true do
let(:member) { create(:user) }
let(:group) { create(:group) }
background do
group.add_developer(member)
login_as(member)
visit group_path(group)
end
scenario 'member does not see the request access button' do
expect(page).not_to have_content 'Request Access'
end
end
......@@ -19,12 +19,12 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do
find('.js-gitlab-ci-yml-selector').click
wait_for_ajax
within '.gitlab-ci-yml-selector' do
find('.dropdown-input-field').set('jekyll')
find('.dropdown-content li', text: 'jekyll').click
find('.dropdown-input-field').set('Jekyll')
find('.dropdown-content li', text: 'Jekyll').click
end
wait_for_ajax
expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'jekyll')
expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Jekyll')
expect(page).to have_content('This file is a template, and might need editing before it works on your project')
expect(page).to have_content('jekyll build -d test')
end
......
......@@ -5,9 +5,6 @@ feature 'Projects > Members > Group member cannot request access to his group pr
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
background do
end
scenario 'owner does not see the request access button' do
group.add_owner(user)
login_and_visit_project_page(user)
......
require 'spec_helper'
feature 'Projects > Members > Member cannot request access to his project', feature: true do
let(:member) { create(:user) }
let(:project) { create(:project) }
background do
project.team << [member, :developer]
login_as(member)
visit namespace_project_path(project.namespace, project)
end
scenario 'member does not see the request access button' do
expect(page).not_to have_content 'Request Access'
end
end
require 'spec_helper'
feature 'Projects > Members > Owner cannot request access to his project', feature: true do
let(:owner) { create(:user) }
let(:project) { create(:project) }
background do
project.team << [owner, :owner]
login_as(owner)
visit namespace_project_path(project.namespace, project)
end
scenario 'owner does not see the request access button' do
expect(page).not_to have_content 'Request Access'
end
end
......@@ -57,72 +57,6 @@ describe MembersHelper do
end
end
describe '#can_see_request_access_button?' do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'source is a group' do
context 'current_user is not a member' do
it 'returns true' do
expect(helper.can_see_request_access_button?(group)).to be_truthy
end
end
context 'current_user is a member' do
it 'returns false' do
group.add_owner(user)
expect(helper.can_see_request_access_button?(group)).to be_falsy
end
end
context 'current_user is a requester' do
it 'returns true' do
group.request_access(user)
expect(helper.can_see_request_access_button?(group)).to be_truthy
end
end
end
context 'source is a project' do
context 'current_user is not a member' do
it 'returns true' do
expect(helper.can_see_request_access_button?(project)).to be_truthy
end
end
context 'current_user is a group member' do
it 'returns false' do
group.add_owner(user)
expect(helper.can_see_request_access_button?(project)).to be_falsy
end
end
context 'current_user is a group requester' do
it 'returns false' do
group.request_access(user)
expect(helper.can_see_request_access_button?(project)).to be_falsy
end
end
context 'current_user is a member' do
it 'returns false' do
project.team << [user, :master]
expect(helper.can_see_request_access_button?(project)).to be_falsy
end
end
end
end
describe '#remove_member_message' do
let(:requester) { build(:user) }
let(:project) { create(:project) }
......
......@@ -104,6 +104,31 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
end
context 'String-based single-word references with special characters' do
let(:label) { create(:label, name: '?gfm&', project: project) }
let(:reference) { "#{Label.reference_prefix}#{label.name}" }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See ?gfm&'
end
it 'links with adjacent text' do
doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>\?gfm&amp;</span></a>\.\)))
end
it 'ignores invalid label names' do
act = "Label #{Label.reference_prefix}#{label.name.reverse}"
exp = "Label #{Label.reference_prefix}&amp;mfg?"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'String-based multi-word references in quotes' do
let(:label) { create(:label, name: 'gfm references', project: project) }
let(:reference) { label.to_reference(format: :name) }
......@@ -128,6 +153,31 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
end
context 'String-based multi-word references with special characters in quotes' do
let(:label) { create(:label, name: 'gfm & references?', project: project) }
let(:reference) { label.to_reference(format: :name) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm & references?'
end
it 'links with adjacent text' do
doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>gfm &amp; references\?</span></a>\.\)))
end
it 'ignores invalid label names' do
act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
exp = %(Label #{Label.reference_prefix}"?secnerefer &amp; mfg\")
expect(reference_filter(act).to_html).to eq exp
end
end
describe 'edge cases' do
it 'gracefully handles non-references matching the pattern' do
exp = act = '(format nil "~0f" 3.0) ; 3.0'
......
require 'spec_helper'
describe Gitlab::GitAccess, lib: true do
let(:access) { Gitlab::GitAccess.new(actor, project) }
let(:access) { Gitlab::GitAccess.new(actor, project, 'web') }
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:actor) { user }
......@@ -72,6 +72,43 @@ describe Gitlab::GitAccess, lib: true do
end
end
describe '#check with single protocols allowed' do
def disable_protocol(protocol)
settings = ::ApplicationSetting.create_from_defaults
settings.update_attribute(:enabled_git_access_protocol, protocol)
end
context 'ssh disabled' do
before do
disable_protocol('ssh')
@acc = Gitlab::GitAccess.new(actor, project, 'ssh')
end
it 'blocks ssh git push' do
expect(@acc.check('git-receive-pack').allowed?).to be_falsey
end
it 'blocks ssh git pull' do
expect(@acc.check('git-upload-pack').allowed?).to be_falsey
end
end
context 'http disabled' do
before do
disable_protocol('http')
@acc = Gitlab::GitAccess.new(actor, project, 'http')
end
it 'blocks http push' do
expect(@acc.check('git-receive-pack').allowed?).to be_falsey
end
it 'blocks http git pull' do
expect(@acc.check('git-upload-pack').allowed?).to be_falsey
end
end
end
describe 'download_access_check' do
describe 'master permissions' do
before { project.team << [user, :master] }
......
require 'spec_helper'
describe Gitlab::GitAccessWiki, lib: true do
let(:access) { Gitlab::GitAccessWiki.new(user, project) }
let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') }
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
......
......@@ -13,6 +13,61 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
subscriber.cache_read(event)
end
context 'with a transaction' do
before do
allow(subscriber).to receive(:current_transaction).
and_return(transaction)
end
context 'with hit event' do
let(:event) { double(:event, duration: 15.2, payload: { hit: true }) }
it 'increments the cache_read_hit count' do
expect(transaction).to receive(:increment).
with(:cache_read_hit_count, 1)
expect(transaction).to receive(:increment).
with(any_args).at_least(1) # Other calls
subscriber.cache_read(event)
end
context 'when super operation is fetch' do
let(:event) { double(:event, duration: 15.2, payload: { hit: true, super_operation: :fetch }) }
it 'does not increment cache read miss' do
expect(transaction).not_to receive(:increment).
with(:cache_read_hit_count, 1)
subscriber.cache_read(event)
end
end
end
context 'with miss event' do
let(:event) { double(:event, duration: 15.2, payload: { hit: false }) }
it 'increments the cache_read_miss count' do
expect(transaction).to receive(:increment).
with(:cache_read_miss_count, 1)
expect(transaction).to receive(:increment).
with(any_args).at_least(1) # Other calls
subscriber.cache_read(event)
end
context 'when super operation is fetch' do
let(:event) { double(:event, duration: 15.2, payload: { hit: false, super_operation: :fetch }) }
it 'does not increment cache read miss' do
expect(transaction).not_to receive(:increment).
with(:cache_read_miss_count, 1)
subscriber.cache_read(event)
end
end
end
end
end
describe '#cache_write' do
......@@ -42,6 +97,54 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
end
end
describe '#cache_fetch_hit' do
context 'without a transaction' do
it 'returns' do
expect(transaction).not_to receive(:increment)
subscriber.cache_fetch_hit(event)
end
end
context 'with a transaction' do
before do
allow(subscriber).to receive(:current_transaction).
and_return(transaction)
end
it 'increments the cache_read_hit count' do
expect(transaction).to receive(:increment).
with(:cache_read_hit_count, 1)
subscriber.cache_fetch_hit(event)
end
end
end
describe '#cache_generate' do
context 'without a transaction' do
it 'returns' do
expect(transaction).not_to receive(:increment)
subscriber.cache_generate(event)
end
end
context 'with a transaction' do
before do
allow(subscriber).to receive(:current_transaction).
and_return(transaction)
end
it 'increments the cache_fetch_miss count' do
expect(transaction).to receive(:increment).
with(:cache_read_miss_count, 1)
subscriber.cache_generate(event)
end
end
end
describe '#increment' do
context 'without a transaction' do
it 'returns' do
......
......@@ -46,6 +46,22 @@ describe Event, models: true do
it { expect(@event.author).to eq(@user) }
end
describe '#note?' do
subject { Event.new(project: target.project, target: target) }
context 'issue note event' do
let(:target) { create(:note_on_issue) }
it { is_expected.to be_note }
end
context 'merge request diff note event' do
let(:target) { create(:note_on_merge_request_diff) }
it { is_expected.to be_note }
end
end
describe '#visible_to_user?' do
let(:project) { create(:empty_project, :public) }
let(:non_member) { create(:user) }
......@@ -89,7 +105,7 @@ describe Event, models: true do
end
end
context 'note event' do
context 'issue note event' do
context 'on non confidential issues' do
let(:target) { note_on_issue }
......@@ -112,6 +128,20 @@ describe Event, models: true do
it { expect(event.visible_to_user?(admin)).to eq true }
end
end
context 'merge request diff note event' do
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) }
let(:note_on_merge_request) { create(:note_on_merge_request_diff, noteable: merge_request, project: project) }
let(:target) { note_on_merge_request }
it { expect(event.visible_to_user?(non_member)).to eq true }
it { expect(event.visible_to_user?(author)).to eq true }
it { expect(event.visible_to_user?(assignee)).to eq true }
it { expect(event.visible_to_user?(member)).to eq true }
it { expect(event.visible_to_user?(guest)).to eq true }
it { expect(event.visible_to_user?(admin)).to eq true }
end
end
describe '.limit_recent' do
......
......@@ -32,21 +32,20 @@ describe Label, models: true do
it 'should validate title' do
expect(label).not_to allow_value('G,ITLAB').for(:title)
expect(label).not_to allow_value('G?ITLAB').for(:title)
expect(label).not_to allow_value('G&ITLAB').for(:title)
expect(label).not_to allow_value('').for(:title)
expect(label).to allow_value('GITLAB').for(:title)
expect(label).to allow_value('gitlab').for(:title)
expect(label).to allow_value('G?ITLAB').for(:title)
expect(label).to allow_value('G&ITLAB').for(:title)
expect(label).to allow_value("customer's request").for(:title)
end
end
describe "#title" do
let(:label) { create(:label, title: "<b>test</b>") }
it "sanitizes title" do
expect(label.title).to eq("test")
describe '#title' do
it 'sanitizes title' do
label = described_class.new(title: '<b>foo & bar?</b>')
expect(label.title).to eq('foo & bar?')
end
end
......
......@@ -131,6 +131,18 @@ describe Project, models: true do
expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
end
end
it 'should not allow an invalid URI as import_url' do
project2 = build(:project, import_url: 'invalid://')
expect(project2).not_to be_valid
end
it 'should allow a valid URI as import_url' do
project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
expect(project2).to be_valid
end
end
describe 'default_scope' 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.
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