Commit 6fea7c38 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into ci-permissions

# Conflicts:
#	app/views/projects/builds/index.html.haml
parents d231b6b9 e933a50b
...@@ -2,9 +2,11 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,9 +2,11 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu)
- Add "visibility" flag to GET /projects api endpoint - Add "visibility" flag to GET /projects api endpoint
- Ignore binary files in code search to prevent Error 500 (Stan Hu) - Ignore binary files in code search to prevent Error 500 (Stan Hu)
- Render sanitized SVG images (Stan Hu) - Render sanitized SVG images (Stan Hu)
- Support download access by PRIVATE-TOKEN header (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination - New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
...@@ -19,9 +21,15 @@ v 8.5.0 (unreleased) ...@@ -19,9 +21,15 @@ v 8.5.0 (unreleased)
- Update the ExternalIssue regex pattern (Blake Hitchcock) - Update the ExternalIssue regex pattern (Blake Hitchcock)
- Optimized performance of finding issues to be closed by a merge request - Optimized performance of finding issues to be closed by a merge request
- Revert "Add IP check against DNSBLs at account sign-up" - Revert "Add IP check against DNSBLs at account sign-up"
- Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Mark inline difference between old and new paths when a file is renamed - Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu)
- Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page
- Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg)
- In seach autocomplete show only groups and projects you are member of
v 8.4.3 v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger - Increase lfs_objects size column to 8-byte integer to allow files larger
...@@ -30,6 +38,9 @@ v 8.4.3 ...@@ -30,6 +38,9 @@ v 8.4.3
- Fix highlighting in blame view - Fix highlighting in blame view
- Update sentry-raven gem to prevent "Not a git repository" console output - Update sentry-raven gem to prevent "Not a git repository" console output
when running certain commands when running certain commands
- Add instrumentation to additional Gitlab::Git and Rugged methods for
performance monitoring
- Allow autosize textareas to also be manually resized
v 8.4.2 v 8.4.2
- Bump required gitlab-workhorse version to bring in a fix for missing - Bump required gitlab-workhorse version to bring in a fix for missing
...@@ -171,6 +182,7 @@ v 8.3.0 ...@@ -171,6 +182,7 @@ v 8.3.0
- Handle and report SSL errors in Web hook test (Stan Hu) - Handle and report SSL errors in Web hook test (Stan Hu)
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu) - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- WIP identifier on merge requests no longer requires trailing space
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission - Fix 500 error when update group member permission
- Fix: As an admin, cannot add oneself as a member to a group/project - Fix: As an admin, cannot add oneself as a member to a group/project
......
...@@ -177,6 +177,26 @@ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9. ...@@ -177,6 +177,26 @@ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
issues or chunks. You can simply not set the weight of a parent issue and set issues or chunks. You can simply not set the weight of a parent issue and set
weights to children issues. weights to children issues.
### Regression issues
Every monthly release has a corresponding issue on the CE issue tracker to keep
track of functionality broken by that release and any fixes that need to be
included in a patch release (see [8.3 Regressions] as an example).
As outlined in the issue description, the intended workflow is to post one note
with a reference to an issue describing the regression, and then to update that
note with a reference to the merge request that fixes it as it becomes available.
If you're a contributor who doesn't have the required permissions to update
other users' notes, please post a new note with a reference to both the issue
and the merge request.
The release manager will [update the notes] in the regression issue as fixes are
addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
## Merge requests ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
...@@ -257,6 +277,18 @@ Please ensure that your merge request meets the contribution acceptance criteria ...@@ -257,6 +277,18 @@ Please ensure that your merge request meets the contribution acceptance criteria
When having your code reviewed and when reviewing merge requests please take the [thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) into account. When having your code reviewed and when reviewing merge requests please take the [thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) into account.
## Changes for Stable Releases
Sometimes certain changes have to be added to an existing stable release.
Two examples are bug fixes and performance improvements. In these cases the
corresponding merge request should be updated to have the following:
1. A milestone indicating what release the merge request should be merged into.
1. The label "Pick into Stable"
This makes it easier for release managers to keep track of what still has to be
merged and where changes have to be merged into.
## Definition of done ## Definition of done
If you contribute to GitLab please know that changes involve more than just If you contribute to GitLab please know that changes involve more than just
......
...@@ -30,14 +30,15 @@ gem 'omniauth-github', '~> 1.1.1' ...@@ -30,14 +30,15 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.4.0' gem 'omniauth-saml', '~> 1.4.2'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1' gem 'rack-oauth2', '~> 1.2.1'
# reCAPTCHA protection # Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails' gem 'recaptcha', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0' gem 'devise-two-factor', '~> 2.0.0'
...@@ -49,7 +50,7 @@ gem "browser", '~> 1.0.0' ...@@ -49,7 +50,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.23' gem "gitlab_git", '~> 8.0.0'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -302,9 +303,6 @@ group :production do ...@@ -302,9 +303,6 @@ group :production do
gem "gitlab_meta", '7.0' gem "gitlab_meta", '7.0'
end end
gem "newrelic_rpm", '~> 3.9.4.245'
gem 'newrelic-grape'
gem 'octokit', '~> 3.8.0' gem 'octokit', '~> 3.8.0'
gem "mail_room", "~> 0.6.1" gem "mail_room", "~> 0.6.1"
......
...@@ -49,6 +49,7 @@ GEM ...@@ -49,6 +49,7 @@ GEM
addressable (2.3.8) addressable (2.3.8)
after_commit_queue (1.3.0) after_commit_queue (1.3.0)
activerecord (>= 3.0) activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.3) allocations (1.0.3)
annotate (2.6.10) annotate (2.6.10)
activerecord (>= 3.2, <= 4.3) activerecord (>= 3.2, <= 4.3)
...@@ -356,7 +357,7 @@ GEM ...@@ -356,7 +357,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.2.0) gitlab_emoji (0.2.0)
gemojione (~> 2.1) gemojione (~> 2.1)
gitlab_git (7.2.24) gitlab_git (8.0.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -478,10 +479,6 @@ GEM ...@@ -478,10 +479,6 @@ GEM
net-ldap (0.12.1) net-ldap (0.12.1)
net-ssh (3.0.1) net-ssh (3.0.1)
netrc (0.11.0) netrc (0.11.0)
newrelic-grape (2.1.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
nokogiri (1.6.7.2) nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2) mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7) nprogress-rails (0.1.6.7)
...@@ -534,9 +531,9 @@ GEM ...@@ -534,9 +531,9 @@ GEM
omniauth-oauth2 (1.3.1) omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0) oauth2 (~> 1.0)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-saml (1.4.1) omniauth-saml (1.4.2)
omniauth (~> 1.1) omniauth (~> 1.1)
ruby-saml (~> 1.0.0) ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.2.1)
...@@ -692,7 +689,7 @@ GEM ...@@ -692,7 +689,7 @@ GEM
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-progressbar (1.7.5) ruby-progressbar (1.7.5)
ruby-saml (1.0.0) ruby-saml (1.1.1)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3) uuid (~> 2.3)
ruby2ruby (2.2.0) ruby2ruby (2.2.0)
...@@ -884,6 +881,7 @@ DEPENDENCIES ...@@ -884,6 +881,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8) addressable (~> 2.3.8)
after_commit_queue after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
annotate (~> 2.6.0) annotate (~> 2.6.0)
asana (~> 0.4.0) asana (~> 0.4.0)
...@@ -934,7 +932,7 @@ DEPENDENCIES ...@@ -934,7 +932,7 @@ DEPENDENCIES
github-markup (~> 1.3.1) github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.2.0) gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.23) gitlab_git (~> 8.0.0)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
...@@ -961,8 +959,6 @@ DEPENDENCIES ...@@ -961,8 +959,6 @@ DEPENDENCIES
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
nested_form (~> 0.3.2) nested_form (~> 0.3.2)
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
nokogiri (= 1.6.7.2) nokogiri (= 1.6.7.2)
nprogress-rails (~> 0.1.6.7) nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
...@@ -976,7 +972,7 @@ DEPENDENCIES ...@@ -976,7 +972,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0) omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.4.0) omniauth-saml (~> 1.4.2)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
callback(namespaces) callback(namespaces)
# Return projects list. Filtered by query # Return projects list. Filtered by query
projects: (query, callback) -> projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path) url = Api.buildUrl(Api.projects_path)
$.ajax( $.ajax(
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
data: data:
private_token: gon.api_token private_token: gon.api_token
search: query search: query
order_by: order
per_page: 20 per_page: 20
dataType: "json" dataType: "json"
).done (projects) -> ).done (projects) ->
......
...@@ -65,8 +65,7 @@ class @DropzoneInput ...@@ -65,8 +65,7 @@ class @DropzoneInput
return return
success: (header, response) -> success: (header, response) ->
child = $(dropzone[0]).children("textarea") pasteText response.link.markdown
$(child).val $(child).val() + response.link.markdown + "\n"
return return
error: (temp, errorMessage) -> error: (temp, errorMessage) ->
...@@ -128,6 +127,7 @@ class @DropzoneInput ...@@ -128,6 +127,7 @@ class @DropzoneInput
beforeSelection = $(child).val().substring 0, caretStart beforeSelection = $(child).val().substring 0, caretStart
afterSelection = $(child).val().substring caretEnd, textEnd afterSelection = $(child).val().substring caretEnd, textEnd
$(child).val beforeSelection + text + afterSelection $(child).val beforeSelection + text + afterSelection
child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
form_textarea.trigger "input" form_textarea.trigger "input"
getFilename = (e) -> getFilename = (e) ->
......
...@@ -50,3 +50,19 @@ class @Project ...@@ -50,3 +50,19 @@ class @Project
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active' $(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active' $(@).parent().addClass 'active'
@projectSelectDropdown()
projectSelectDropdown: ->
new ProjectSelect()
$('.project-item-select').on 'click', (e) =>
@changeProject $(e.currentTarget).val()
$('.js-projects-dropdown-toggle').on 'click', (e) ->
e.preventDefault()
$('.js-projects-dropdown').select2('open')
changeProject: (url) ->
window.location = url
...@@ -3,6 +3,7 @@ class @ProjectSelect ...@@ -3,6 +3,7 @@ class @ProjectSelect
$('.ajax-project-select').each (i, select) -> $('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups') @includeGroups = $(select).data('include-groups')
@orderBy = $(select).data('order-by') || 'id'
placeholder = "Search for project" placeholder = "Search for project"
placeholder += " or group" if @includeGroups placeholder += " or group" if @includeGroups
...@@ -28,7 +29,7 @@ class @ProjectSelect ...@@ -28,7 +29,7 @@ class @ProjectSelect
if @groupId if @groupId
Api.groupProjects @groupId, query.term, projectsCallback Api.groupProjects @groupId, query.term, projectsCallback
else else
Api.projects query.term, projectsCallback Api.projects query.term, @orderBy, projectsCallback
id: (project) -> id: (project) ->
project.web_url project.web_url
......
...@@ -82,8 +82,7 @@ ...@@ -82,8 +82,7 @@
&.btn-success, &.btn-success,
&.btn-new, &.btn-new,
&.btn-create, &.btn-create,
&.btn-save, &.btn-save {
&.btn-green {
@include btn-green; @include btn-green;
} }
...@@ -159,7 +158,6 @@ ...@@ -159,7 +158,6 @@
.input-group-btn { .input-group-btn {
.btn { .btn {
@include btn-gray;
@include btn-middle; @include btn-middle;
&:hover { &:hover {
...@@ -186,8 +184,4 @@ ...@@ -186,8 +184,4 @@
border: 1px solid #c6cacf !important; border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important; background-color: #e4e7ed !important;
} }
.btn-green {
@include btn-green
}
} }
...@@ -376,11 +376,11 @@ table { ...@@ -376,11 +376,11 @@ table {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
} }
.new-project-item-select-holder { .project-item-select-holder {
display: inline-block; display: inline-block;
position: relative; position: relative;
.new-project-item-select { .project-item-select {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
......
...@@ -73,7 +73,6 @@ header { ...@@ -73,7 +73,6 @@ header {
.title { .title {
margin: 0; margin: 0;
overflow: hidden;
font-size: 19px; font-size: 19px;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
...@@ -88,6 +87,13 @@ header { ...@@ -88,6 +87,13 @@ header {
text-decoration: underline; text-decoration: underline;
} }
} }
.dropdown-toggle-caret {
position: relative;
top: -2px;
margin-left: 5px;
font-size: 10px;
}
} }
.navbar-collapse { .navbar-collapse {
......
...@@ -37,3 +37,89 @@ ...@@ -37,3 +37,89 @@
} }
} }
} }
.top-area {
@include clearfix;
border-bottom: 1px solid #EEE;
.nav-text {
padding-top: 16px;
padding-bottom: 11px;
display: inline-block;
width: 50%;
line-height: 28px;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-links {
display: inline-block;
width: 50%;
margin-bottom: 0px;
border-bottom: none;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-controls {
width: 50%;
display: inline-block;
float: right;
text-align: right;
padding: 11px 0;
margin-bottom: 0px;
> .dropdown {
margin-right: 10px;
display: inline-block;
}
> .btn {
display: inline-block;
}
input {
height: 34px;
display: inline-block;
position: relative;
top: 1px;
margin-right: 10px;
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 200px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 250px; }
&.input-short {
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 170px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 210px; }
}
}
/* Hide on extra small devices (phones) */
@media (max-width: $screen-xs-max) {
display: none;
}
/* Small devices (tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
width: 100%;
text-align: left;
input {
width: 300px;
}
}
}
}
...@@ -6,11 +6,3 @@ ...@@ -6,11 +6,3 @@
font-size: 30px; font-size: 30px;
} }
} }
.explore-trending-block {
.lead {
line-height: 32px;
font-size: 18px;
margin-top: 10px;
}
}
...@@ -21,3 +21,21 @@ ...@@ -21,3 +21,21 @@
height: 42px; height: 42px;
} }
} }
.group-row {
&.no-description {
.group-name {
line-height: 44px;
}
}
.stats {
float: right;
line-height: 44px;
color: $gl-gray;
span {
margin-right: 15px;
}
}
}
...@@ -281,36 +281,6 @@ ...@@ -281,36 +281,6 @@
margin-top: -1px; margin-top: -1px;
} }
.top-area {
border-bottom: 1px solid #EEE;
ul.nav-links {
display: inline-block;
width: 50%;
margin-bottom: 0px;
border-bottom: none;
}
.projects-search-form {
width: 50%;
display: inline-block;
float: right;
padding-top: 11px;
text-align: right;
.btn-green {
margin-left: 10px;
float: right;
}
}
@media (max-width: $screen-xs-max) {
.projects-search-form {
padding-top: 15px;
}
}
}
.fork-namespaces { .fork-namespaces {
.fork-thumbnail { .fork-thumbnail {
text-align: center; text-align: center;
...@@ -386,22 +356,6 @@ pre.light-well { ...@@ -386,22 +356,6 @@ pre.light-well {
border-color: #f1f1f1; border-color: #f1f1f1;
} }
.projects-search-form {
padding: $gl-padding 0;
padding-bottom: 0;
margin-bottom: 0px;
input {
display: inline-block;
width: calc(100% - 151px);
}
.btn {
display: inline-block;
width: 135px;
}
}
.git-empty { .git-empty {
margin: 0 7px 0 7px; margin: 0 7px 0 7px;
...@@ -461,6 +415,10 @@ pre.light-well { ...@@ -461,6 +415,10 @@ pre.light-well {
a:hover { a:hover {
text-decoration: none; text-decoration: none;
} }
> span {
margin-left: 10px;
}
} }
.project-description { .project-description {
...@@ -565,46 +523,6 @@ pre.light-well { ...@@ -565,46 +523,6 @@ pre.light-well {
margin-top: 2px; margin-top: 2px;
} }
/*
* Forks list rendered on Project's forks page
*/
.forks-top-block {
padding: 16px 0;
}
.projects-search-form {
.dropdown-toggle.btn {
margin-top: -3px;
}
&.fork-search-form {
margin: 0;
margin-top: -$gl-padding;
padding-bottom: 0;
input {
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) { width: 180px; }
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 350px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 400px; }
}
.sort-forks {
width: 160px;
}
.fork-link {
float: right;
margin-left: $gl-padding;
}
}
}
.private-forks-notice .private-fork-icon { .private-forks-notice .private-fork-icon {
i:nth-child(1) { i:nth-child(1) {
color: #2AA056; color: #2AA056;
......
...@@ -79,6 +79,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -79,6 +79,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:recaptcha_private_key, :recaptcha_private_key,
:sentry_enabled, :sentry_enabled,
:sentry_dsn, :sentry_dsn,
:akismet_enabled,
:akismet_api_key,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
) )
......
class Admin::SpamLogsController < Admin::ApplicationController
def index
@spam_logs = SpamLog.order(id: :desc).page(params[:page])
end
def destroy
spam_log = SpamLog.find(params[:id])
if params[:remove_user]
spam_log.remove_user
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
else
spam_log.destroy
render nothing: true
end
end
end
...@@ -60,6 +60,8 @@ class ApplicationController < ActionController::Base ...@@ -60,6 +60,8 @@ class ApplicationController < ActionController::Base
params[:authenticity_token].presence params[:authenticity_token].presence
elsif params[:private_token].presence elsif params[:private_token].presence
params[:private_token].presence params[:private_token].presence
elsif request.headers['PRIVATE-TOKEN'].present?
request.headers['PRIVATE-TOKEN']
end end
user = user_token && User.find_by_authentication_token(user_token.to_s) user = user_token && User.find_by_authentication_token(user_token.to_s)
......
...@@ -3,6 +3,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -3,6 +3,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def index def index
@projects = current_user.authorized_projects.sorted_by_activity.non_archived @projects = current_user.authorized_projects.sorted_by_activity.non_archived
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@last_push = current_user.recent_push @last_push = current_user.recent_push
......
...@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController ...@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController
before_action :project before_action :project
def show def show
@blob = @project.repository.blob_at_branch('master', @project.avatar_in_git) @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob if @blob
headers['X-Content-Type-Options'] = 'nosniff' headers['X-Content-Type-Options'] = 'nosniff'
send_data( headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
@blob.data, headers['Content-Disposition'] = 'inline'
type: @blob.mime_type, headers['Content-Type'] = @blob.content_type
disposition: 'inline', head :ok # 'render nothing: true' messes up the Content-Type
filename: @blob.name
)
else else
render_404 render_404
end end
......
...@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController
def edit def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository)
end end
def update def update
...@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
def preview def preview
@content = params[:content] @content = params[:content]
@blob.load_all_data!(@repository)
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1] diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines) diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
......
...@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController ...@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer? if @blob.lfs_pointer?
send_lfs_object send_lfs_object
else else
stream_data headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = get_blob_type
head :ok # 'render nothing: true' messes up the Content-Type
end end
else else
render_404 render_404
...@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController ...@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController
end end
end end
def stream_data
type = get_blob_type
send_data(
@blob.data,
type: type,
disposition: 'inline'
)
end
def send_lfs_object def send_lfs_object
lfs_object = find_lfs_object lfs_object = find_lfs_object
......
...@@ -23,6 +23,10 @@ module ApplicationSettingsHelper ...@@ -23,6 +23,10 @@ module ApplicationSettingsHelper
current_application_settings.user_oauth_applications current_application_settings.user_oauth_applications
end end
def askimet_enabled?
current_application_settings.akismet_enabled?
end
# Return a group of checkboxes that use Bootstrap's button plugin for a # Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect. # toggle button effect.
def restricted_level_checkboxes(help_block_id) def restricted_level_checkboxes(help_block_id)
......
...@@ -10,8 +10,19 @@ module ExploreHelper ...@@ -10,8 +10,19 @@ module ExploreHelper
options = exist_opts.merge(options) options = exist_opts.merge(options)
path = explore_projects_path path = if explore_controller?
explore_projects_path
elsif current_action?(:starred)
starred_dashboard_projects_path
else
dashboard_projects_path
end
path << "?#{options.to_param}" path << "?#{options.to_param}"
path path
end end
def explore_controller?
controller.class.name.split("::").first == "Explore"
end
end end
...@@ -53,15 +53,24 @@ module ProjectsHelper ...@@ -53,15 +53,24 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner)) link_to(simple_sanitize(owner.name), user_path(owner))
end end
project_link = link_to(simple_sanitize(project.name), project_path(project)) project_link = link_to project_path(project), { class: "project-item-select-holder #{"js-projects-dropdown-toggle" if current_user}" } do
link_output = simple_sanitize(project.name)
link_output += content_tag :span, nil, { class: "fa fa-chevron-down dropdown-toggle-caret" } if current_user
if current_user
link_output += project_select_tag :project_path,
class: "project-item-select js-projects-dropdown",
data: { include_groups: false, order_by: 'last_activity_at' }
end
link_output
end
full_title = namespace_link + ' / ' + project_link full_title = namespace_link + ' / ' + project_link
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
content_tag :span do
full_title full_title
end end
end
def remove_project_message(project) def remove_project_message(project)
"You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
......
...@@ -70,7 +70,7 @@ module SearchHelper ...@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups # Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5) def groups_autocomplete(term, limit = 5)
Group.search(term).limit(limit).map do |group| current_user.authorized_groups.search(term).limit(limit).map do |group|
{ {
label: "group: #{search_result_sanitize(group.name)}", label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group) url: group_path(group)
...@@ -80,7 +80,7 @@ module SearchHelper ...@@ -80,7 +80,7 @@ module SearchHelper
# Autocomplete results for the current user's projects # Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5) def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term). current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p| sorted_by_stars.non_archived.limit(limit).map do |p|
{ {
label: "project: #{search_result_sanitize(p.name_with_namespace)}", label: "project: #{search_result_sanitize(p.name_with_namespace)}",
......
...@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer ...@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer
subject: "[Rejected] #{@original_message.subject}" subject: "[Rejected] #{@original_message.subject}"
} }
headers['Message-ID'] = SecureRandom.hex headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
headers['In-Reply-To'] = @original_message.message_id headers['In-Reply-To'] = @original_message.message_id
headers['References'] = @original_message.message_id headers['References'] = @original_message.message_id
......
...@@ -88,6 +88,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -88,6 +88,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
if: :sentry_enabled if: :sentry_enabled
validates :akismet_api_key,
presence: true,
if: :akismet_enabled
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
...@@ -143,7 +147,9 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -143,7 +147,9 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false, require_two_factor_authentication: false,
two_factor_grace_period: 48 two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false
) )
end end
......
...@@ -205,7 +205,11 @@ module Ci ...@@ -205,7 +205,11 @@ module Ci
end end
def ci_yaml_file def ci_yaml_file
@ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data @ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
blob.data
end
rescue rescue
nil nil
end end
......
...@@ -258,7 +258,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -258,7 +258,7 @@ class MergeRequest < ActiveRecord::Base
end end
def work_in_progress? def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i) !!(title =~ /\A\[?WIP(\]|:| )/i)
end end
def mergeable? def mergeable?
...@@ -284,7 +284,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -284,7 +284,8 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user) def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) && !source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) && !source_project.root_ref?(source_branch) &&
Ability.abilities.allowed?(current_user, :push_code, source_project) Ability.abilities.allowed?(current_user, :push_code, source_project) &&
last_commit == source_project.commit(source_branch)
end end
def mr_and_commit_notes def mr_and_commit_notes
......
class SpamLog < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
def remove_user
user.block
user.destroy
end
end
class SpamReport < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end
...@@ -39,6 +39,8 @@ class Tree ...@@ -39,6 +39,8 @@ class Tree
git_repo = repository.raw_repository git_repo = repository.raw_repository
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
@readme.load_all_data!(git_repo)
@readme
end end
def trees def trees
......
...@@ -138,6 +138,7 @@ class User < ActiveRecord::Base ...@@ -138,6 +138,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
has_one :abuse_report, dependent: :destroy has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
......
class CreateSpamLogService < BaseService
def initialize(project, user, params)
super(project, user, params)
end
def execute
spam_params = params.merge({ user_id: @current_user.id,
project_id: @project.id } )
spam_log = SpamLog.new(spam_params)
spam_log.save
spam_log
end
end
...@@ -218,20 +218,37 @@ ...@@ -218,20 +218,37 @@
= f.label :recaptcha_enabled do = f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled = f.check_box :recaptcha_enabled
Enable reCAPTCHA Enable reCAPTCHA
%span.help-block#recaptcha_help_block Helps preventing bots from creating accounts %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
.form-group .form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2' = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :recaptcha_site_key, class: 'form-control' = f.text_field :recaptcha_site_key, class: 'form-control'
.help-block .help-block
Generate site and private keys here: Generate site and private keys at
%a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
.form-group .form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2' = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :recaptcha_private_key, class: 'form-control' = f.text_field :recaptcha_private_key, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :akismet_enabled do
= f.check_box :akismet_enabled
Enable Akismet
%span.help-block#akismet_help_block Helps prevent bots from creating issues
.form-group
= f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :akismet_api_key, class: 'form-control'
.help-block
Generate API key at
%a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
%fieldset %fieldset
%legend Error Reporting and Logging %legend Error Reporting and Logging
%p %p
......
- user = spam_log.user
%tr
%td
= time_ago_with_tooltip(spam_log.created_at)
%td
- if user
= link_to user.name, [:admin, user]
.light.small
Joined #{time_ago_with_tooltip(user.created_at)}
- else
(removed)
%td
= spam_log.source_ip
%td
= spam_log.via_api? ? 'Y' : 'N'
%td
= spam_log.noteable_type
%td
= spam_log.title
%td
= truncate(spam_log.description, length: 100)
%td
- if user
= link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
%td
- if user && !user.blocked?
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
- else
.btn.btn-xs.disabled
Already Blocked
= link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
- page_title "Spam Logs"
%h3.page-title Spam Logs
%hr
- if @spam_logs.present?
.table-holder
%table.table
%thead
%tr
%th Date
%th User
%th Source IP
%th API?
%th Type
%th Title
%th Description
%th Primary Action
%th
= render @spam_logs
= paginate @spam_logs
- else
%h4 There are no Spam Logs
%ul.nav-links .top-area
%ul.nav-links
= nav_link(page: dashboard_groups_path) do = nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do = link_to dashboard_groups_path, title: 'Your groups' do
Your Groups Your Groups
= nav_link(page: explore_groups_path) do = nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do = link_to explore_groups_path, title: 'Explore groups' do
Explore Groups Explore Groups
- if current_user.can_create_group?
.nav-controls
= link_to new_group_path, class: "btn btn-new" do
= icon('plus')
New Group
...@@ -8,13 +8,14 @@ ...@@ -8,13 +8,14 @@
= nav_link(page: starred_dashboard_projects_path) do = nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
Starred Projects Starred Projects
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore Projects Explore Projects
.projects-search-form .nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false = search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs input-short', spellcheck: false
= render 'explore/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-green' do = link_to new_project_path, class: 'btn btn-new' do
%i.fa.fa-plus = icon('plus')
New Project New Project
...@@ -2,15 +2,6 @@ ...@@ -2,15 +2,6 @@
- header_title "Groups", dashboard_groups_path - header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head' = render 'dashboard/groups_head'
.gray-content-block
- if current_user.can_create_group?
%span.pull-right.hidden-xs
= link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
.oneline
Group members have access to all group projects.
%ul.content-list %ul.content-list
- @group_members.each do |group_member| - @group_members.each do |group_member|
- group = group_member.group - group = group_member.group
......
...@@ -16,8 +16,5 @@ ...@@ -16,8 +16,5 @@
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
List all issues from all projects you have access to.
.prepend-top-default .prepend-top-default
= render 'shared/issues' = render 'shared/issues'
...@@ -7,8 +7,5 @@ ...@@ -7,8 +7,5 @@
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
List all merge requests from all projects you have access to.
.prepend-top-default .prepend-top-default
= render 'shared/merge_requests' = render 'shared/merge_requests'
- page_title "Milestones" - page_title "Milestones"
- header_title "Milestones", dashboard_milestones_path - header_title "Milestones", dashboard_milestones_path
.project-issuable-filter .top-area
.controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
= render 'shared/milestones_filter' = render 'shared/milestones_filter'
.gray-content-block .nav-controls
List all milestones from all projects you have access to. = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
.milestones .milestones
%ul.content-list %ul.content-list
......
...@@ -3,19 +3,13 @@ ...@@ -3,19 +3,13 @@
%span.light %span.light
- if @sort.present? - if @sort.present?
= sort_options_hash[@sort] = sort_options_hash[@sort]
- elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path)
Trending projects
- elsif current_page?(starred_explore_projects_path)
Most stars
- else - else
= sort_title_recently_created = sort_title_recently_updated
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu
%li %li
= link_to trending_explore_projects_path do = link_to explore_projects_filter_path(sort: sort_value_name) do
Trending projects = sort_title_name
= link_to starred_explore_projects_path do
Most stars
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do = link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created = sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f| = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group .form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
= hidden_field_tag :sort, @sort
.form-group .form-group
= button_tag 'Search', class: "btn" = button_tag 'Search', class: "btn"
...@@ -46,4 +47,3 @@ ...@@ -46,4 +47,3 @@
= link_to explore_projects_filter_path(tag: tag.name) do = link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag %i.fa.fa-tag
= tag.name = tag.name
= render 'explore/projects/dropdown'
%ul.nav-links
= nav_link(page: [trending_explore_projects_path, explore_root_path]) do
= link_to trending_explore_projects_path do
Trending
= nav_link(page: starred_explore_projects_path) do
= link_to starred_explore_projects_path do
Most stars
= nav_link(page: explore_projects_path) do
= link_to explore_projects_path do
All
...@@ -6,7 +6,11 @@ ...@@ -6,7 +6,11 @@
- else - else
= render 'explore/head' = render 'explore/head'
.gray-content-block.clearfix.second-block .top-area
= render 'explore/projects/nav'
.gray-content-block.second-block.clearfix
= render 'filter' = render 'filter'
= render 'projects', projects: @projects = render 'projects', projects: @projects
= paginate @projects, theme: "gitlab" = paginate @projects, theme: "gitlab"
...@@ -6,12 +6,6 @@ ...@@ -6,12 +6,6 @@
- else - else
= render 'explore/head' = render 'explore/head'
.explore-trending-block = render 'explore/projects/nav'
.gray-content-block.second-block = render 'projects', projects: @starred_projects
.pull-right = paginate @starred_projects, theme: 'gitlab'
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-star
See most starred projects
= render 'projects', projects: @starred_projects
= paginate @starred_projects, theme: 'gitlab'
...@@ -6,11 +6,5 @@ ...@@ -6,11 +6,5 @@
- else - else
= render 'explore/head' = render 'explore/head'
.explore-trending-block = render 'explore/projects/nav'
.gray-content-block.second-block = render 'projects', projects: @trending_projects
.pull-right
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-comments-o
See most discussed projects for last month
= render 'projects', projects: @trending_projects
.projects-list-holder .projects-list-holder.prepend-top-default
.projects-search-form
.input-group .input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group - if can? current_user, :create_projects, @group
%span.input-group-btn %span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new' do
%i.fa.fa-plus = icon('plus')
New Project New Project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
- page_title "Milestones" - page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group)) - header_title group_title(@group, "Milestones", group_milestones_path(@group))
.project-issuable-filter .top-area
.controls = render 'shared/milestones_filter'
.nav-controls
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
.pull-right
%span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do = link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus') = icon('plus')
New Milestone New Milestone
= render 'shared/milestones_filter'
.gray-content-block .gray-content-block
Only milestones from Only milestones from
%strong #{@group.name} %strong #{@group.name}
......
...@@ -138,8 +138,32 @@ ...@@ -138,8 +138,32 @@
%h2#navs Navigation %h2#navs Navigation
%h4
%code .top-area
%p Holder for top page navigation. Includes navigation, search field, sorting and button
.example
.top-area
%ul.nav-links
%li.active
%a Open
%li
%a Closed
.nav-controls
= text_field_tag 'sample', nil, class: 'form-control'
.dropdown
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span Sort by name
%b.caret
%ul.dropdown-menu
%li
%a Sort by date
= link_to 'New issue', '#', class: 'btn btn-new'
%h4 %h4
%code .nav-links %code .nav-links
%p Only nav links without button and search
.example .example
%ul.nav-links %ul.nav-links
%li.active %li.active
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do = link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw') = icon('dashboard fw')
%span %span
Overview Overview
...@@ -25,13 +25,13 @@ ...@@ -25,13 +25,13 @@
%span %span
Deploy Keys Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do = nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path do = link_to admin_runners_path, title: 'Runners' do
= icon('cog fw') = icon('cog fw')
%span %span
Runners Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all)) %span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do = nav_link path: 'builds#index' do
= link_to admin_builds_path do = link_to admin_builds_path, title: 'Builds' do
= icon('link fw') = icon('link fw')
%span %span
Builds Builds
...@@ -82,6 +82,14 @@ ...@@ -82,6 +82,14 @@
Abuse Reports Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all)) %span.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw')
%span
Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw') = icon('cogs fw')
......
.file-content.image_file .file-content.image_file
%img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"} %img{ src: namespace_project_raw_path(@project.namespace, @project, @id)}
- blob.load_all_data!(@repository)
- if markup?(blob.name) - if markup?(blob.name)
.file-content.wiki .file-content.wiki
= render_markup(blob.name, blob.data) = render_markup(blob.name, blob.data)
......
- page_title "Builds" - page_title "Builds"
= render "header_title" = render "header_title"
.project-issuable-filter .top-area
.controls
- if can?(current_user, :update_build, @project)
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
%ul.nav-links %ul.nav-links
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do = link_to project_builds_path(@project) do
...@@ -32,6 +21,16 @@ ...@@ -32,6 +21,16 @@
%span.badge.js-running-count %span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id)) = number_with_delimiter(@all_builds.finished.count(:id))
.nav-controls
- if can?(current_user, :update_build, @project)
- if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
.gray-content-block .gray-content-block
#{(@scope || 'running').capitalize} builds from this project #{(@scope || 'running').capitalize} builds from this project
......
.gray-content-block.top-block.clearfix.white.forks-top-block .top-area
.pull-left .nav-text
- public_count = @public_forks.size - public_count = @public_forks.size
- protected_count = @protected_forks.size - protected_count = @protected_forks.size
- full_count_title = "#{public_count} public and #{protected_count} private" - full_count_title = "#{public_count} public and #{protected_count} private"
== #{pluralize(@all_forks.size, 'fork')}: #{full_count_title} == #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
.pull-right .nav-controls
.projects-search-form.fork-search-form = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control input-short',
= search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control',
spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
.dropdown.inline.prepend-left-10 .dropdown
%button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort: %span.light sort:
- if @sort.present? - if @sort.present?
...@@ -30,13 +29,12 @@ ...@@ -30,13 +29,12 @@
= link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
= sort_title_oldest_updated = sort_title_oldest_updated
.fork-link.inline
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'pull-right btn btn-new' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
= icon('code-fork fw') = icon('code-fork fw')
Fork Fork
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'pull-right btn btn-new' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
= icon('code-fork fw') = icon('code-fork fw')
Fork Fork
......
- page_title "Labels" - page_title "Labels"
= render "header_title" = render "header_title"
.gray-content-block.top-block .top-area
.nav-text
Labels can be applied to issues and merge requests.
.nav-controls
- if can? current_user, :admin_label, @project - if can? current_user, :admin_label, @project
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
= icon('plus') = icon('plus')
New label New label
.oneline
Labels can be applied to issues and merge requests.
.labels .labels
- if @labels.present? - if @labels.present?
......
...@@ -2,17 +2,14 @@ ...@@ -2,17 +2,14 @@
= render "header_title" = render "header_title"
.project-issuable-filter .top-area
.controls
- if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
%i.fa.fa-plus
New Milestone
= render 'shared/milestones_filter' = render 'shared/milestones_filter'
.gray-content-block .nav-controls
Milestone allows you to group issues and set due date for it - if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
= icon('plus')
New Milestone
.milestones .milestones
%ul.content-list %ul.content-list
......
.project-issuable-filter .top-area
.controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
%i.fa.fa-plus
New Page
= render 'projects/wikis/new'
%ul.nav-links %ul.nav-links
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
...@@ -17,3 +9,11 @@ ...@@ -17,3 +9,11 @@
= 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
.nav-controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
= icon('plus')
New Page
= render 'projects/wikis/new'
.file-content.code.js-syntax-highlight .file-content.code.js-syntax-highlight
.line-numbers .line-numbers
- if blob.data.present? - if blob.data.present?
- blob.data.lines.each_index do |index| - blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1 - offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset - i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines. -# We're not using `link_to` because it is too slow once we get to thousands of lines.
......
.milestones-filters %ul.nav-links
%ul.nav-links
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
= link_to milestones_filter_path(state: 'opened') do = link_to milestones_filter_path(state: 'opened') do
Open Open
......
- if @projects.any? - if @projects.any?
.prepend-left-10.new-project-item-select-holder .prepend-left-10.project-item-select-holder
= project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] } = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }
%a.btn.btn-new.new-project-item-select-button %a.btn.btn-new.new-project-item-select-button
= icon('plus') = icon('plus')
= local_assigns[:label] = local_assigns[:label]
...@@ -8,12 +8,12 @@ ...@@ -8,12 +8,12 @@
:javascript :javascript
$('.new-project-item-select-button').on('click', function() { $('.new-project-item-select-button').on('click', function() {
$('.new-project-item-select').select2('open'); $('.project-item-select').select2('open');
}); });
var relativePath = '#{local_assigns[:path]}'; var relativePath = '#{local_assigns[:path]}';
$('.new-project-item-select').on('click', function() { $('.project-item-select').on('click', function() {
window.location = $(this).val() + '/' + relativePath; window.location = $(this).val() + '/' + relativePath;
}); });
......
- group_member = local_assigns[:group_member] - group_member = local_assigns[:group_member]
%li - css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if group.description.blank?
%li.group-row{ class: css_class }
- if group_member - if group_member
.controls.hidden-xs .controls.hidden-xs
- if can?(current_user, :admin_group, group) - if can?(current_user, :admin_group, group)
...@@ -9,6 +12,15 @@ ...@@ -9,6 +12,15 @@
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
%i.fa.fa-sign-out %i.fa.fa-sign-out
.stats
%span
= icon('home')
= number_with_delimiter(group.projects.count)
%span
= icon('users')
= number_with_delimiter(group.users.count)
= image_tag group_icon(group), class: "avatar s46 hidden-xs" = image_tag group_icon(group), class: "avatar s46 hidden-xs"
= link_to group, class: 'group-name' do = link_to group, class: 'group-name' do
%span.item-title= group.name %span.item-title= group.name
...@@ -17,5 +29,6 @@ ...@@ -17,5 +29,6 @@
as as
%span #{group_member.human_access} %span #{group_member.human_access}
%div.light - if group.description.present?
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} .light
= markdown(group.description, pipeline: :description)
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
.project-controls .project-controls
- if ci_commit - if ci_commit
%span
= render_ci_status(ci_commit) = render_ci_status(ci_commit)
&nbsp;
- if forks - if forks
%span %span
= icon('code-fork') = icon('code-fork')
......
<%= ENV['RAILS_ENV'] %>: <%= ENV['RAILS_ENV'] %>:
## Connection information
# Please be aware that the DATABASE_URL environment variable will take
# precedence over the following 6 parameters. For more information, see
# doc/administration/environment_variables.md
adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %> adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %>
encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %> database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %>
pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %> username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %>
password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %> password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %>
host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %> host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %>
port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %> port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %>
## Behavior information
# The following parameters will be used even if you're using the DATABASE_URL
# environment variable.
encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
...@@ -49,12 +49,14 @@ if Gitlab::Metrics.enabled? ...@@ -49,12 +49,14 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(Gitlab::Shell) config.instrument_instance_methods(Gitlab::Shell)
config.instrument_methods(Gitlab::Git) config.instrument_methods(Gitlab::Git)
config.instrument_instance_methods(Gitlab::Git::Repository)
Gitlab::Git.constants.each do |name| Gitlab::Git.constants.each do |name|
const = Gitlab::Git.const_get(name) const = Gitlab::Git.const_get(name)
config.instrument_methods(const) if const.is_a?(Module) next unless const.is_a?(Module)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end end
Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path| Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
...@@ -62,6 +64,16 @@ if Gitlab::Metrics.enabled? ...@@ -62,6 +64,16 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const) config.instrument_instance_methods(const)
end end
[
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
:Tag, :TagCollection, :Tree
].each do |name|
const = Rugged.const_get(name)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
end end
GC::Profiler.enable GC::Profiler.enable
......
# New Relic configuration file
#
# This file is here to make sure the New Relic gem stays
# quiet by default.
#
# To enable and configure New Relic, please use
# environment variables, e.g. NEW_RELIC_ENABLED=true
production:
enabled: false
development:
enabled: false
test:
enabled: false
...@@ -211,6 +211,8 @@ Rails.application.routes.draw do ...@@ -211,6 +211,8 @@ Rails.application.routes.draw do
end end
resources :abuse_reports, only: [:index, :destroy] resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
resources :applications resources :applications
resources :groups, constraints: { id: /[^\/]+/ } do resources :groups, constraints: { id: /[^\/]+/ } do
......
class AddAkismetToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
t.boolean :akismet_enabled, default: false
t.string :akismet_api_key
end
end
end
class CreateSpamLogs < ActiveRecord::Migration
def change
create_table :spam_logs do |t|
t.integer :user_id
t.string :source_ip
t.string :user_agent
t.boolean :via_api
t.integer :project_id
t.string :noteable_type
t.string :title
t.text :description
t.timestamps null: false
end
end
end
...@@ -64,6 +64,8 @@ ActiveRecord::Schema.define(version: 20160202164642) do ...@@ -64,6 +64,8 @@ ActiveRecord::Schema.define(version: 20160202164642) do
t.integer "metrics_sample_interval", default: 15 t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false t.boolean "sentry_enabled", default: false
t.string "sentry_dsn" t.string "sentry_dsn"
t.boolean "akismet_enabled", default: false
t.string "akismet_api_key"
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
...@@ -771,6 +773,19 @@ ActiveRecord::Schema.define(version: 20160202164642) do ...@@ -771,6 +773,19 @@ ActiveRecord::Schema.define(version: 20160202164642) do
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
create_table "spam_logs", force: :cascade do |t|
t.integer "user_id"
t.string "source_ip"
t.string "user_agent"
t.boolean "via_api"
t.integer "project_id"
t.string "noteable_type"
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subscriptions", force: :cascade do |t| create_table "subscriptions", force: :cascade do |t|
t.integer "user_id" t.integer "user_id"
t.integer "subscribable_id" t.integer "subscribable_id"
......
# Environment Variables # Environment Variables
## Introduction GitLab exposes certain environment variables which can be used to override
their defaults values.
Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package. People usually configure GitLab via `/etc/gitlab/gitlab.rb` for Omnibus
installations, or `gitlab.yml` for installations from source.
But if you prefer to use environment variables we allow that too. Below you will find the supported environment variables which you can use to
override certain values.
## Supported environment variables ## Supported environment variables
Variable | Type | Explanation Variable | Type | Description
-------- | ---- | ----------- -------- | ---- | -----------
GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation `GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
GITLAB_HOST | url | hostname of the GitLab server includes http or https `GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
RAILS_ENV | production / development / staging / test | Rails environment `RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 `DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab `GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab `GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab `GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
GITLAB_UNICORN_MEMORY_MIN | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
GITLAB_UNICORN_MEMORY_MAX | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
## Complete database variables ## Complete database variables
As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set: The recommended way of specifying your database connection information is to set
the `DATABASE_URL` environment variable. This variable only holds connection
- adapter information (`adapter`, `database`, `username`, `password`, `host` and `port`),
- database but not behavior information (`encoding`, `pool`). If you don't want to use
- username `DATABASE_URL` and/or want to set database behavior information, you will have
- password to either:
- host
- port - copy our template file: `cp config/database.yml.env config/database.yml`, or
- set a value for some `GITLAB_DATABASE_XXX` variables
To do so please `cp config/database.yml.env config/database.yml` and use the following variables:
The list of `GITLAB_DATABASE_XXX` variables that you can set is:
Variable | Default
--- | --- Variable | Default value | Overridden by `DATABASE_URL`?
GITLAB_DATABASE_ADAPTER | postgresql -------- | ------------- | -----------------------------
GITLAB_DATABASE_ENCODING | unicode `GITLAB_DATABASE_ADAPTER` | `postgresql` (for MySQL use `mysql2`) | Yes
GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV'] `GITLAB_DATABASE_DATABASE` | `gitlab_#{ENV['RAILS_ENV']` | Yes
GITLAB_DATABASE_POOL | 10 `GITLAB_DATABASE_USERNAME` | `root` | Yes
GITLAB_DATABASE_USERNAME | root `GITLAB_DATABASE_PASSWORD` | None | Yes
GITLAB_DATABASE_PASSWORD | `GITLAB_DATABASE_HOST` | `localhost` | Yes
GITLAB_DATABASE_HOST | localhost `GITLAB_DATABASE_PORT` | `5432` | Yes
GITLAB_DATABASE_PORT | 5432 `GITLAB_DATABASE_ENCODING` | `unicode` | No
`GITLAB_DATABASE_POOL` | `10` | No
## Adding more variables ## Adding more variables
We welcome merge requests to make more settings configurable via variables. We welcome merge requests to make more settings configurable via variables.
Please make changes in the file config/initializers/1_settings.rb Please make changes in the `config/initializers/1_settings.rb` file and stick
Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}". to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
## Omnibus configuration ## Omnibus configuration
It's possible to preconfigure the GitLab image by adding the environment variable: `GITLAB_OMNIBUS_CONFIG` to docker run command. It's possible to preconfigure the GitLab docker image by adding the environment
variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container). For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
...@@ -18,7 +18,7 @@ GET /ci/projects ...@@ -18,7 +18,7 @@ GET /ci/projects
Returns: Returns:
```json ```json
[ [
{ {
"id" : 271, "id" : 271,
"name" : "gitlabhq", "name" : "gitlabhq",
......
...@@ -97,7 +97,7 @@ image: php:5.6 ...@@ -97,7 +97,7 @@ image: php:5.6
before_script: before_script:
# Install dependencies # Install dependencies
- ci/docker_install.sh > /dev/null - bash ci/docker_install.sh > /dev/null
test:app: test:app:
script: script:
...@@ -112,7 +112,7 @@ with a different docker image version and the runner will do the rest: ...@@ -112,7 +112,7 @@ with a different docker image version and the runner will do the rest:
```yaml ```yaml
before_script: before_script:
# Install dependencies # Install dependencies
- ci/docker_install.sh > /dev/null - bash ci/docker_install.sh > /dev/null
# We test PHP5.6 # We test PHP5.6
test:5.6: test:5.6:
......
...@@ -393,8 +393,12 @@ The above script will: ...@@ -393,8 +393,12 @@ The above script will:
### artifacts ### artifacts
_**Note:** Introduced in GitLab Runner v0.7.0. Also, the Windows shell executor _**Note:** Introduced in GitLab Runner v0.7.0 for non-Windows platforms._
does not currently support artifact uploads._
_**Note:** Limited Windows support was added in GitLab Runner v.1.0.0.
Currently not all executors are supported._
_**Note:** Build artifacts are only collected for successful builds._
`artifacts` is used to specify list of files and directories which should be `artifacts` is used to specify list of files and directories which should be
attached to build after success. Below are some examples. attached to build after success. Below are some examples.
......
...@@ -355,7 +355,7 @@ GitLab Shell is an SSH access and repository management software developed speci ...@@ -355,7 +355,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse cd gitlab-workhorse
sudo -u git -H git checkout 0.6.2 sudo -u git -H git checkout 0.6.3
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
...@@ -15,6 +15,7 @@ See the documentation below for details on how to configure these services. ...@@ -15,6 +15,7 @@ See the documentation below for details on how to configure these services.
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation - [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users - [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
- [Akismet](akismet.md) Configure Akismet to stop spam
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
......
# Akismet
GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
GitLab uses Akismet to prevent users who are not members of a project from
creating spam via the GitLab API. Detected spam will be rejected, and
an entry in the "Spam Log" section in the Admin page will be created.
Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
adding a user to a project will disable the Akismet check and prevent this
from happening.
## Configuration
To use Akismet:
1. Go to the URL: https://akismet.com/account/
2. Sign-in or create a new account.
3. Click on "Show" to reveal the API key.
4. Go to Applications Settings on Admin Area (`admin/application_settings`)
5. Check the `Enable Akismet` checkbox
6. Fill in the API key from step 3.
7. Save the configuration.
![Screenshot of Akismet settings](img/akismet_settings.png)
## GitLab as OAuth2 authentication service provider # GitLab as OAuth2 authentication service provider
This document is about using GitLab as an OAuth authentication service provider to sign into other services. This document is about using GitLab as an OAuth authentication service provider
If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md) to sign in to other services.
OAuth2 provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. Or you can allow users to sign in to your application with their GitLab.com account. If you want to use other OAuth authentication service providers to sign in to
In fact OAuth allows to issue access token to third-party clients by an authorization server, GitLab, please see the [OAuth2 client documentation](../api/oauth2.md).
with the approval of the resource owner, or end-user.
Mostly, OAuth2 is using for SSO (Single sign-on). But you can find a lot of different usages for this functionality.
For example, our feature 'GitLab Importer' is using OAuth protocol to give an access to repositories without sharing user credentials to GitLab.com account.
Also GitLab.com application can be used for authentication to your GitLab instance if needed [GitLab OmniAuth](gitlab.md).
GitLab has two ways to add new OAuth2 application to an instance, you can add application as regular user and through admin area. So GitLab actually can have an instance-wide and a user-wide applications. There is no defferences between them except the different permission levels. ## Introduction to OAuth
### Adding application through profile [OAuth] provides to client applications a 'secure delegated access' to server
Go to your profile section 'Application' and press button 'New Application' resources on behalf of a resource owner. In fact, OAuth allows an authorization
server to issue access tokens to third-party clients with the approval of the
resource owner, or the end-user.
![applications](img/oauth_provider_user_wide_applications.png) OAuth is mostly used as a Single Sign-On service (SSO), but you can find a
lot of different uses for this functionality. For example, you can allow users
to sign in to your application with their GitLab.com account, or GitLab.com
can be used for authentication to your GitLab instance
(see [GitLab OmniAuth](gitlab.md)).
After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com. The 'GitLab Importer' feature is also using the OAuth protocol to give access
to repositories without sharing user credentials to your GitLab.com account.
![application_form](img/oauth_provider_application_form.png) ---
### Authorized application GitLab supports two ways of adding a new OAuth2 application to an instance. You
Every application you authorized will be shown in your "Authorized application" sections. can either add an application as a regular user or add it in the admin area.
What this means is that GitLab can actually have instance-wide and a user-wide
applications. There is no difference between them except for the different
permission levels they are set (user/admin).
![authorized_application](img/oauth_provider_authorized_application.png) ## Adding an application through the profile
At any time you can revoke access just clicking button "Revoke" In order to add a new application via your profile, navigate to
**Profile Settings > Applications** and select **New Application**.
### OAuth applications in admin area ![New OAuth application](img/oauth_provider_user_wide_applications.png)
If you want to create application that does not belong to certain user you can create it from admin area ---
![admin_application](img/oauth_provider_admin_application.png) In the application form, enter a **Name** (arbitrary), and make sure to set up
correctly the **Redirect URI** which is the URL where users will be sent after
they authorize with GitLab.
![New OAuth application form](img/oauth_provider_application_form.png)
---
When you hit **Submit** you will be provided with the application ID and
the application secret which you can then use with your application that
connects to GitLab.
![OAuth application ID and secret](img/oauth_provider_application_id_secret.png)
---
## OAuth applications in the admin area
To create an application that does not belong to a certain user, you can create
it from the admin area.
![OAuth admin_applications](img/oauth_provider_admin_application.png)
---
## Authorized applications
Every application you authorized to use your GitLab credentials will be shown
in the **Authorized applications** section under **Profile Settings > Applications**.
![Authorized_applications](img/oauth_provider_authorized_application.png)
---
As you can see, the default scope `api` is used, which is the only scope that
GitLab supports so far. At any time you can revoke any access by just clicking
**Revoke**.
[oauth]: http://oauth.net/2/ "OAuth website"
...@@ -14,6 +14,12 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are ...@@ -14,6 +14,12 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2. database migrations for GitLab 7.2.
## Stash changes
If you [deleted the vendors folder during your original installation](https://github.com/gitlabhq/gitlabhq/issues/4883#issuecomment-31108431), [you will get an error](https://gitlab.com/gitlab-org/gitlab-ce/issues/1494) when you attempt to rebuild the assets in step 7. To avoid this, stash the changes in your GitLab working copy before starting:
git stash
## 0. Stop server ## 0. Stop server
sudo service gitlab stop sudo service gitlab stop
......
Feature: Admin spam logs
Background:
Given I sign in as an admin
And spam logs exist
Scenario: Browse spam logs
When I visit spam logs page
Then I should see list of spam logs
class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
step 'I should see list of spam logs' do
expect(page).to have_content('Spam Logs')
expect(page).to have_content spam_log.source_ip
expect(page).to have_content spam_log.noteable_type
expect(page).to have_content 'N'
expect(page).to have_content spam_log.title
expect(page).to have_content truncate(spam_log.description)
expect(page).to have_link('Remove user')
expect(page).to have_link('Block user')
end
step 'spam logs exist' do
create(:spam_log)
end
def spam_log
@spam_log ||= SpamLog.first
end
def truncate(description)
"#{spam_log.description[0...97]}..."
end
end
...@@ -5,7 +5,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps ...@@ -5,7 +5,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
include RepoHelpers include RepoHelpers
step 'I see button to CI Lint' do step 'I see button to CI Lint' do
page.within('.controls') do page.within('.nav-controls') do
ci_lint_tool_link = page.find_link('CI Lint') ci_lint_tool_link = page.find_link('CI Lint')
expect(ci_lint_tool_link[:href]).to eq ci_lint_path expect(ci_lint_tool_link[:href]).to eq ci_lint_path
end end
......
...@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I should see raw file content' do step 'I should see raw file content' do
expect(source).to eq sample_blob.data expect(source).to eq '' # Body is filled in by gitlab-workhorse
end end
step 'I click button "Edit"' do step 'I click button "Edit"' do
......
...@@ -191,6 +191,10 @@ module SharedPaths ...@@ -191,6 +191,10 @@ module SharedPaths
visit admin_application_settings_path visit admin_application_settings_path
end end
step 'I visit spam logs page' do
visit admin_spam_logs_path
end
step 'I visit applications page' do step 'I visit applications page' do
visit admin_applications_path visit admin_applications_path
end end
......
...@@ -58,9 +58,11 @@ module API ...@@ -58,9 +58,11 @@ module API
commit = user_project.commit(ref) commit = user_project.commit(ref)
not_found! 'Commit' unless commit not_found! 'Commit' unless commit
blob = user_project.repository.blob_at(commit.sha, file_path) repo = user_project.repository
blob = repo.blob_at(commit.sha, file_path)
if blob if blob
blob.load_all_data!(repo)
status(200) status(200)
{ {
...@@ -72,7 +74,7 @@ module API ...@@ -72,7 +74,7 @@ module API
ref: ref, ref: ref,
blob_id: blob.id, blob_id: blob.id,
commit_id: commit.id, commit_id: commit.id,
last_commit_id: user_project.repository.last_commit_for_path(commit.sha, file_path).id last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id
} }
else else
not_found! 'File' not_found! 'File'
......
...@@ -30,7 +30,7 @@ module API ...@@ -30,7 +30,7 @@ module API
end end
def sudo_identifier() def sudo_identifier()
identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER] identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
# Regex for integers # Regex for integers
if !!(identifier =~ /^[0-9]+$/) if !!(identifier =~ /^[0-9]+$/)
...@@ -344,12 +344,22 @@ module API ...@@ -344,12 +344,22 @@ module API
def pagination_links(paginated_data) def pagination_links(paginated_data)
request_url = request.url.split('?').first request_url = request.url.split('?').first
request_params = params.clone
request_params[:per_page] = paginated_data.limit_value
links = [] links = []
links << %(<#{request_url}?page=#{paginated_data.current_page - 1}&per_page=#{paginated_data.limit_value}>; rel="prev") unless paginated_data.first_page?
links << %(<#{request_url}?page=#{paginated_data.current_page + 1}&per_page=#{paginated_data.limit_value}>; rel="next") unless paginated_data.last_page? request_params[:page] = paginated_data.current_page - 1
links << %(<#{request_url}?page=1&per_page=#{paginated_data.limit_value}>; rel="first") links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?
links << %(<#{request_url}?page=#{paginated_data.total_pages}&per_page=#{paginated_data.limit_value}>; rel="last")
request_params[:page] = paginated_data.current_page + 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?
request_params[:page] = 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
request_params[:page] = paginated_data.total_pages
links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
links.join(', ') links.join(', ')
end end
......
...@@ -3,6 +3,8 @@ module API ...@@ -3,6 +3,8 @@ module API
class Issues < Grape::API class Issues < Grape::API
before { authenticate! } before { authenticate! }
helpers ::Gitlab::AkismetHelper
helpers do helpers do
def filter_issues_state(issues, state) def filter_issues_state(issues, state)
case state case state
...@@ -19,6 +21,17 @@ module API ...@@ -19,6 +21,17 @@ module API
def filter_issues_milestone(issues, milestone) def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone) issues.includes(:milestone).where('milestones.title' => milestone)
end end
def create_spam_log(project, current_user, attrs)
params = attrs.merge({
source_ip: env['REMOTE_ADDR'],
user_agent: env['HTTP_USER_AGENT'],
noteable_type: 'Issue',
via_api: true
})
::CreateSpamLogService.new(project, current_user, params).execute
end
end end
resource :issues do resource :issues do
...@@ -114,7 +127,15 @@ module API ...@@ -114,7 +127,15 @@ module API
render_api_error!({ labels: errors }, 400) render_api_error!({ labels: errors }, 400)
end end
issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute project = user_project
text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
create_spam_log(project, current_user, attrs)
render_api_error!({ error: 'Spam detected' }, 400)
end
issue = ::Issues::CreateService.new(project, current_user, attrs).execute
if issue.valid? if issue.valid?
# Find or create labels and attach to issue. Labels are valid because # Find or create labels and attach to issue. Labels are valid because
......
...@@ -57,7 +57,7 @@ module API ...@@ -57,7 +57,7 @@ module API
not_found! "File" unless blob not_found! "File" unless blob
content_type 'text/plain' content_type 'text/plain'
present blob.data header *Gitlab::Workhorse.send_git_blob(repo, blob)
end end
# Get a raw blob contents by blob sha # Get a raw blob contents by blob sha
...@@ -83,7 +83,7 @@ module API ...@@ -83,7 +83,7 @@ module API
env['api.format'] = :txt env['api.format'] = :txt
content_type blob.mime_type content_type blob.mime_type
present blob.data header *Gitlab::Workhorse.send_git_blob(repo, blob)
end end
# Get a an archive of the repository # Get a an archive of the repository
......
...@@ -8,14 +8,7 @@ module Banzai ...@@ -8,14 +8,7 @@ module Banzai
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter class SanitizationFilter < HTML::Pipeline::SanitizationFilter
def whitelist def whitelist
# Descriptions are more heavily sanitized, allowing only a few elements.
# See http://git.io/vkuAN
if context[:inline_sanitization]
whitelist = LIMITED
whitelist[:elements] -= %w(pre code img ol ul li)
else
whitelist = super whitelist = super
end
customize_whitelist(whitelist) customize_whitelist(whitelist)
......
...@@ -4,9 +4,20 @@ module Banzai ...@@ -4,9 +4,20 @@ module Banzai
def self.transform_context(context) def self.transform_context(context)
super(context).merge( super(context).merge(
# SanitizationFilter # SanitizationFilter
inline_sanitization: true whitelist: whitelist
) )
end end
private
def self.whitelist
# Descriptions are more heavily sanitized, allowing only a few elements.
# See http://git.io/vkuAN
whitelist = Banzai::Filter::SanitizationFilter::LIMITED
whitelist[:elements] -= %w(pre code img ol ul li)
whitelist
end
end end
end end
end end
module Gitlab
module AkismetHelper
def akismet_enabled?
current_application_settings.akismet_enabled
end
def akismet_client
@akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
Gitlab.config.gitlab.url)
end
def check_for_spam?(project, user)
akismet_enabled? && !project.team.member?(user)
end
def is_spam?(environment, user, text)
client = akismet_client
ip_address = environment['REMOTE_ADDR']
user_agent = environment['HTTP_USER_AGENT']
params = {
type: 'comment',
text: text,
created_at: DateTime.now,
author: user.name,
author_email: user.email,
referrer: environment['HTTP_REFERER'],
}
begin
is_spam, is_blatant = client.check(ip_address, user_agent, params)
is_spam || is_blatant
rescue => e
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
false
end
end
end
end
...@@ -34,7 +34,8 @@ module Gitlab ...@@ -34,7 +34,8 @@ module Gitlab
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false, require_two_factor_authentication: false,
two_factor_grace_period: 48 two_factor_grace_period: 48,
akismet_enabled: false
) )
end end
......
...@@ -8,6 +8,7 @@ module Gitlab ...@@ -8,6 +8,7 @@ module Gitlab
blob = repository.blob_at(ref, file_name) blob = repository.blob_at(ref, file_name)
return [] unless blob return [] unless blob
blob.load_all_data!(repository)
highlight(file_name, blob.data).lines.map!(&:html_safe) highlight(file_name, blob.data).lines.map!(&:html_safe)
end end
......
...@@ -44,19 +44,19 @@ module Gitlab ...@@ -44,19 +44,19 @@ module Gitlab
def file_name_regex def file_name_regex
@file_name_regex ||= /\A[a-zA-Z0-9_\-\.]*\z/.freeze @file_name_regex ||= /\A[a-zA-Z0-9_\-\.\@]*\z/.freeze
end end
def file_name_regex_message def file_name_regex_message
"can contain only letters, digits, '_', '-' and '.'. " "can contain only letters, digits, '_', '-', '@' and '.'. "
end end
def file_path_regex def file_path_regex
@file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/]*\z/.freeze @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/\@]*\z/.freeze
end end
def file_path_regex_message def file_path_regex_message
"can contain only letters, digits, '_', '-' and '.'. Separate directories with a '/'. " "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. "
end end
......
require 'base64'
require 'json'
module Gitlab
class Workhorse
class << self
def send_git_blob(repository, blob)
params_hash = {
'RepoPath' => repository.path_to_repo,
'BlobId' => blob.id,
}
params = Base64.urlsafe_encode64(JSON.dump(params_hash))
[
'Gitlab-Workhorse-Send-Data',
"git-blob:#{params}",
]
end
end
end
end
...@@ -219,7 +219,7 @@ start_gitlab() { ...@@ -219,7 +219,7 @@ start_gitlab() {
echo "The Unicorn web server already running with pid $wpid, not restarting." echo "The Unicorn web server already running with pid $wpid, not restarting."
else else
# Remove old socket if it exists # Remove old socket if it exists
rm -f "$socket_path"/gitlab.socket 2>/dev/null rm -f "$rails_socket" 2>/dev/null
# Start the web server # Start the web server
RAILS_ENV=$RAILS_ENV bin/web start RAILS_ENV=$RAILS_ENV bin/web start
fi fi
......
require 'spec_helper'
describe Admin::SpamLogsController do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:first_spam) { create(:spam_log, user: user) }
let!(:second_spam) { create(:spam_log, user: user) }
before do
sign_in(admin)
end
describe '#index' do
it 'lists all spam logs' do
get :index
expect(response.status).to eq(200)
end
end
describe '#destroy' do
it 'removes only the spam log when removing log' do
expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1)
expect(User.find(user.id)).to be_truthy
expect(response.status).to eq(200)
end
it 'removes user and his spam logs when removing the user' do
delete :destroy, id: first_spam.id, remove_user: true
expect(flash[:notice]).to eq "User #{user.username} was successfully removed."
expect(response.status).to eq(302)
expect(SpamLog.count).to eq(0)
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :spam_log do
user
source_ip { FFaker::Internet.ip_v4_address }
noteable_type 'Issue'
title { FFaker::Lorem.sentence }
description { FFaker::Lorem.paragraph(5) }
end
end
...@@ -18,7 +18,7 @@ describe "Builds" do ...@@ -18,7 +18,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :running) visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') } it { expect(page).to have_selector('.nav-links li.active', text: 'Running') }
it { expect(page).to have_link 'Cancel running' } it { expect(page).to have_link 'Cancel running' }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
...@@ -31,7 +31,7 @@ describe "Builds" do ...@@ -31,7 +31,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') } it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') }
it { expect(page).to have_content 'No builds to show' } it { expect(page).to have_content 'No builds to show' }
it { expect(page).to have_link 'Cancel running' } it { expect(page).to have_link 'Cancel running' }
end end
...@@ -42,7 +42,7 @@ describe "Builds" do ...@@ -42,7 +42,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project) visit namespace_project_builds_path(@project.namespace, @project)
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name } it { expect(page).to have_content @build.name }
...@@ -57,7 +57,7 @@ describe "Builds" do ...@@ -57,7 +57,7 @@ describe "Builds" do
click_link "Cancel running" click_link "Cancel running"
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_content 'canceled' } it { expect(page).to have_content 'canceled' }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
......
...@@ -86,6 +86,25 @@ feature 'Project', feature: true do ...@@ -86,6 +86,25 @@ feature 'Project', feature: true do
end end
end end
describe 'project title' do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do
login_with(user)
project.team.add_user(user, Gitlab::Access::MASTER)
visit namespace_project_path(project.namespace, project)
end
it 'click toggle and show dropdown', js: true do
find('.js-projects-dropdown-toggle').click
wait_for_ajax
expect(page).to have_css('.select2-results li', count: 1)
end
end
def remove_with_confirm(button_text, confirm_with) def remove_with_confirm(button_text, confirm_with)
click_button button_text click_button button_text
fill_in 'confirm_name_input', with: confirm_with fill_in 'confirm_name_input', with: confirm_with
......
%h1.title
%a
GitLab Org
%a.project-item-select-holder.js-projects-dropdown-toggle{href: "/gitlab-org/gitlab-test"}
GitLab Test
%span.fa.fa-chevron-down.dropdown-toggle-caret
%input#project_path.project-item-select.js-projects-dropdown.ajax-project-select{type: "hidden", name: "project_path", "data-include-groups" => "false"}
[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}]
#= require select2
#= require api
#= require project_select
#= require project
window.gon = {}
window.gon.api_version = 'v3'
describe 'Project Title', ->
fixture.preload('project_title.html')
fixture.preload('projects.json')
beforeEach ->
fixture.load('project_title.html')
@project = new Project()
spyOn(@project, 'changeProject').and.callFake (url) ->
window.current_project_url = url
describe 'project list', ->
beforeEach =>
@projects_data = fixture.load('projects.json')[0]
spyOn(jQuery, 'ajax').and.callFake (req) =>
expect(req.url).toBe('/api/v3/projects.json')
d = $.Deferred()
d.resolve @projects_data
d.promise()
it 'to show on toggle click', =>
$('.js-projects-dropdown-toggle').click()
expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(true)
expect($('.ajax-project-dropdown li').length).toBe(@projects_data.length)
it 'hide dropdown', ->
$("#select2-drop-mask").click()
expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
it 'change project when clicking item', ->
$('.js-projects-dropdown-toggle').click()
$('.ajax-project-dropdown li:nth-child(2)').trigger('mouseup')
expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
expect(window.current_project_url).toBe('http://localhost:3000/h5bp/html5-boilerplate')
...@@ -177,26 +177,4 @@ describe Banzai::Filter::SanitizationFilter, lib: true do ...@@ -177,26 +177,4 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(act.to_html).to eq exp expect(act.to_html).to eq exp
end end
end end
context 'when inline_sanitization is true' do
it 'uses a stricter whitelist' do
doc = filter('<h1>Description</h1>', inline_sanitization: true)
expect(doc.to_html.strip).to eq 'Description'
end
%w(pre code img ol ul li).each do |elem|
it "removes '#{elem}' elements" do
act = "<#{elem}>Description</#{elem}>"
expect(filter(act, inline_sanitization: true).to_html.strip).
to eq 'Description'
end
end
%w(b i strong em a ins del sup sub p).each do |elem|
it "still allows '#{elem}' elements" do
exp = act = "<#{elem}>Description</#{elem}>"
expect(filter(act, inline_sanitization: true).to_html).to eq exp
end
end
end
end end
require 'rails_helper'
describe Banzai::Pipeline::DescriptionPipeline do
def parse(html)
# When we pass HTML to Redcarpet, it gets wrapped in `p` tags...
# ...except when we pass it pre-wrapped text. Rabble rabble.
unwrap = !html.start_with?('<p>')
output = described_class.to_html(html, project: spy)
output.gsub!(%r{\A<p>(.*)</p>(.*)\z}, '\1\2') if unwrap
output
end
it 'uses a limited whitelist' do
doc = parse('# Description')
expect(doc.strip).to eq 'Description'
end
%w(pre code img ol ul li).each do |elem|
it "removes '#{elem}' elements" do
act = "<#{elem}>Description</#{elem}>"
expect(parse(act).strip).to eq 'Description'
end
end
%w(b i strong em a ins del sup sub p).each do |elem|
it "still allows '#{elem}' elements" do
exp = act = "<#{elem}>Description</#{elem}>"
expect(parse(act).strip).to eq exp
end
end
end
require 'spec_helper'
describe Gitlab::AkismetHelper, type: :helper do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
current_application_settings.akismet_enabled = true
current_application_settings.akismet_api_key = '12345'
end
describe '#check_for_spam?' do
it 'returns true for non-member' do
expect(helper.check_for_spam?(project, user)).to eq(true)
end
it 'returns false for member' do
project.team << [user, :guest]
expect(helper.check_for_spam?(project, user)).to eq(false)
end
end
describe '#is_spam?' do
it 'returns true for spam' do
environment = {
'REMOTE_ADDR' => '127.0.0.1',
'HTTP_USER_AGENT' => 'Test User Agent'
}
allow_any_instance_of(::Akismet::Client).to receive(:check).and_return([true, true])
expect(helper.is_spam?(environment, user, 'Is this spam?')).to eq(true)
end
end
end
...@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do ...@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do
it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) } it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) } it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
end end
describe 'file name regex' do
it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
end
describe 'file path regex' do
it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
end
end end
...@@ -188,6 +188,11 @@ describe MergeRequest, models: true do ...@@ -188,6 +188,11 @@ describe MergeRequest, models: true do
expect(subject).to be_work_in_progress expect(subject).to be_work_in_progress
end end
it "detects the '[WIP]' prefix" do
subject.title = "[WIP]#{subject.title}"
expect(subject).to be_work_in_progress
end
it "doesn't detect WIP for words starting with WIP" do it "doesn't detect WIP for words starting with WIP" do
subject.title = "Wipwap #{subject.title}" subject.title = "Wipwap #{subject.title}"
expect(subject).not_to be_work_in_progress expect(subject).not_to be_work_in_progress
...@@ -226,9 +231,15 @@ describe MergeRequest, models: true do ...@@ -226,9 +231,15 @@ describe MergeRequest, models: true do
expect(subject.can_remove_source_branch?(user2)).to be_falsey expect(subject.can_remove_source_branch?(user2)).to be_falsey
end end
it "is can be removed in all other cases" do it "can be removed if the last commit is the head of the source branch" do
allow(subject.source_project).to receive(:commit).and_return(subject.last_commit)
expect(subject.can_remove_source_branch?(user)).to be_truthy expect(subject.can_remove_source_branch?(user)).to be_truthy
end end
it "cannot be removed if the last commit is not also the head of the source branch" do
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
end end
describe "#reset_merge_when_build_succeeds" do describe "#reset_merge_when_build_succeeds" do
......
require 'spec_helper'
describe SpamLog, models: true do
describe 'associations' do
it { is_expected.to belong_to(:user) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:user) }
end
describe '#remove_user' do
it 'blocks the user' do
spam_log = build(:spam_log)
expect { spam_log.remove_user }.to change { spam_log.user.blocked? }.to(true)
end
it 'removes the user' do
spam_log = build(:spam_log)
expect { spam_log.remove_user }.to change { User.count }.by(-1)
end
end
end
...@@ -91,6 +91,7 @@ describe User, models: true do ...@@ -91,6 +91,7 @@ describe User, models: true do
it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) } it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:identities).dependent(:destroy) } it { is_expected.to have_many(:identities).dependent(:destroy) }
it { is_expected.to have_one(:abuse_report) } it { is_expected.to have_one(:abuse_report) }
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
end end
describe 'validations' do describe 'validations' do
......
...@@ -46,10 +46,10 @@ describe API::API, api: true do ...@@ -46,10 +46,10 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['title']).to eq(issue.title)
end end
it "should add pagination headers" do it "should add pagination headers and keep query params" do
get api("/issues?per_page=3", user) get api("/issues?state=closed&per_page=3", user)
expect(response.headers['Link']).to eq( expect(response.headers['Link']).to eq(
'<http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="last"' '<http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="last"' % [user.private_token, user.private_token]
) )
end end
...@@ -241,6 +241,37 @@ describe API::API, api: true do ...@@ -241,6 +241,37 @@ describe API::API, api: true do
end end
end end
describe 'POST /projects/:id/issues with spam filtering' do
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:check_for_spam?).and_return(true)
allow(endpoint).to receive(:is_spam?).and_return(true)
end
end
let(:params) do
{
title: 'new issue',
description: 'content here',
labels: 'label, label2'
}
end
it "should not create a new project issue" do
expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
expect(response.status).to eq(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
spam_logs = SpamLog.all
expect(spam_logs.count).to eq(1)
expect(spam_logs[0].title).to eq('new issue')
expect(spam_logs[0].description).to eq('content here')
expect(spam_logs[0].user).to eq(user)
expect(spam_logs[0].noteable_type).to eq('Issue')
expect(spam_logs[0].project_id).to eq(project.id)
end
end
describe "PUT /projects/:id/issues/:issue_id to update only title" do describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.id}", user),
......
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