Commit e75300df authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce-upstream' into 'master'

CE upstream

Unresolved conflicts
```
both modified:   app/controllers/omniauth_callbacks_controller.rb
both modified:   app/views/shared/issuable/_sidebar.html.haml

```

See merge request !166
parents 97bc167b 23caf7b6
......@@ -2,26 +2,44 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased)
- 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
- Ignore binary files in code search to prevent Error 500 (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
- New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
set it up
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list
- Don't vendor minified JS
- Display 404 error on group not found
- Track project import failure
- Support Two-factor Authentication for LDAP users
- Display database type and version in Administration dashboard
- Fix visibility level text in admin area (Zeger-Jan van de Weg)
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock)
- Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson)
- Optimized performance of finding issues to be closed by a merge request
- 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/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues
- 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
- Fixed logo animation on Safari (Roman Rott)
- 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
- Fix: init.d script not working on OS X
v 8.4.4
- Update omniauth-saml gem to 1.4.2
v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger
......@@ -30,6 +48,9 @@ v 8.4.3
- Fix highlighting in blame view
- Update sentry-raven gem to prevent "Not a git repository" console output
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
- Bump required gitlab-workhorse version to bring in a fix for missing
......@@ -177,6 +198,7 @@ v 8.3.0
- Handle and report SSL errors in Web hook test (Stan Hu)
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- 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)
- Fix 500 error when update group member permission
- Fix: As an admin, cannot add oneself as a member to a group/project
......
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contribute to GitLab](#contribute-to-gitlab)
- [Contributor license agreement](#contributor-license-agreement)
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- [Helping others](#helping-others)
- [I want to contribute!](#i-want-to-contribute)
- [Issue tracker](#issue-tracker)
- [Feature proposals](#feature-proposals)
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done)
- [Style guides](#style-guides)
- [Code of conduct](#code-of-conduct)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Contribute to GitLab
Thank you for your interest in contributing to GitLab. This guide details how
......@@ -177,6 +203,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
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
We welcome merge requests with fixes and improvements to GitLab code, tests,
......@@ -214,15 +260,17 @@ request is as follows:
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are changing the README, some documentation or other things which
have no effect on the tests, add `[ci skip]` somewhere in the commit message
and make sure to read the [documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by
[squashing them][git-squash]
1. Push the commit(s) to your fork
1. Submit a merge request (MR) to the master branch
1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you
used to achieve it
used to achieve it, see the [merge request description format]
(#merge-request-description-format)
1. If the MR changes the UI it should include before and after screenshots
1. If the MR changes CSS classes please include the list of affected pages
1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R`
1. Link any relevant [issues][ce-tracker] in the merge request description and
leave a comment on them with a link back to the MR
......@@ -255,49 +303,30 @@ For examples of feedback on merge requests please look at already
request feel free to mention one of the Merge Marshalls of the [core team][].
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.
## Definition of done
### Merge request description format
If you contribute to GitLab please know that changes involve more than just
code. We have the following [definition of done][]. Please ensure you support
the feature you contribute through all of these steps.
Please submit merge requests using the following template in the merge request
description area. Copy-paste it to retain the markdown format.
1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
1. Documented in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
1. Merged by the project lead
1. Added to the release blog article
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.)
```
## What does this MR do?
If you add a dependency in GitLab (such as an operating system package) please
consider updating the following and note the applicability of each in your
merge request:
## Are there points in the code the reviewer needs to double check?
1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
## Why was this MR needed?
## Merge request description format
## What are the relevant issue numbers?
1. What does this MR do?
1. Are there points in the code the reviewer needs to double check?
1. Why was this MR needed?
1. What are the relevant issue numbers?
1. Screenshots (if relevant)
## Screenshots (if relevant)
```
## Contribution acceptance criteria
### Contribution acceptance criteria
1. The change is as small as possible (see the above paragraph for details)
1. The change is as small as possible
1. Include proper tests and make all tests pass (unless it contains a test
exposing a bug in existing code)
1. If you suspect a failing CI build is unrelated to your contribution, you may
......@@ -310,20 +339,62 @@ merge request:
1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine
things, send separate merge requests if needed)
1. Migrations should do only one thing (eg: either create a table, move data to
a new table or remove an old table) to aid retrying on failure
1. Migrations should do only one thing (e.g., either create a table, move data
to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is
over, before merging.
1. It conforms to the following style guides:
* If your change touches a line that does not follow the style, modify the
1. It conforms to the [style guides](#style-guides) and the following:
- If your change touches a line that does not follow the style, modify the
entire line to follow it. This prevents linting tools from generating warnings.
* Don't touch neighbouring lines. As an exception, automatic mass
- Don't touch neighbouring lines. As an exception, automatic mass
refactoring modifications may leave style non-compliant.
## 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
If you contribute to GitLab please know that changes involve more than just
code. We have the following [definition of done][]. Please ensure you support
the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
1. [Documented][doc-styleguide] in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
1. Merged by the project lead
1. Added to the release blog article
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.)
If you add a dependency in GitLab (such as an operating system package) please
consider updating the following and note the applicability of each in your
merge request:
1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
## Style guides
1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
......@@ -338,7 +409,7 @@ merge request:
contributors to enhance security
1. [Database Migrations](doc/development/migration_style_guide.md)
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
1. [Documentation styleguide](doc/development/doc_styleguide.md)
1. [Documentation styleguide][doc-styleguide]
1. Interface text should be written subjectively instead of objectively. It
should be the GitLab core team addressing a person. It should be written in
present time and never use past tense (has been/was). For example instead
......@@ -379,7 +450,7 @@ reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [Contributor Covenant][], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
[core team]: https://about.gitlab.com/core-team/
[core-team]: https://about.gitlab.com/core-team/
[getting help page]: https://about.gitlab.com/getting-help/
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
......@@ -400,3 +471,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[Contributor Covenant]: http://contributor-covenant.org
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
......@@ -30,15 +30,16 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0'
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-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'gssapi', group: :kerberos
gem 'rack-oauth2', '~> 1.2.1'
# reCAPTCHA protection
# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
......@@ -50,7 +51,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.23'
gem "gitlab_git", '~> 8.1'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
......@@ -309,9 +310,6 @@ group :production do
gem "gitlab_meta", '7.0'
end
gem "newrelic_rpm", '~> 3.9.4.245'
gem 'newrelic-grape'
gem 'octokit', '~> 3.8.0'
gem "mail_room", "~> 0.6.1"
......
......@@ -49,6 +49,7 @@ GEM
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.3)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
......@@ -378,7 +379,7 @@ GEM
gitlab-license (0.0.4)
gitlab_emoji (0.2.0)
gemojione (~> 2.1)
gitlab_git (7.2.24)
gitlab_git (8.1.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
......@@ -502,10 +503,6 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
newrelic-grape (2.1.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
......@@ -558,9 +555,9 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-saml (1.4.1)
omniauth-saml (1.4.2)
omniauth (~> 1.1)
ruby-saml (~> 1.0.0)
ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
......@@ -716,7 +713,7 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
ruby-saml (1.0.0)
ruby-saml (1.1.1)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.2.0)
......@@ -908,6 +905,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0)
......@@ -962,7 +960,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.23)
gitlab_git (~> 8.1)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
......@@ -991,8 +989,6 @@ DEPENDENCIES
nested_form (~> 0.3.2)
net-ldap
net-ssh (~> 3.0.1)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
nokogiri (= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
......@@ -1006,7 +1002,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.4.0)
omniauth-saml (~> 1.4.2)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
......
......@@ -11,7 +11,7 @@ class @AdminEmailSelect
group_result = Api.groups query.term, skip_ldap, (groups) ->
groups
project_result = Api.projects query.term, (projects) ->
project_result = Api.projects query.term, 'id', (projects) ->
projects
$.when(project_result, group_result).done (projects, groups) ->
......
......@@ -49,7 +49,7 @@
callback(namespaces)
# Return projects list. Filtered by query
projects: (query, callback) ->
projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path)
$.ajax(
......@@ -57,6 +57,7 @@
data:
private_token: gon.api_token
search: query
order_by: order
per_page: 20
dataType: "json"
).done (projects) ->
......@@ -96,17 +97,3 @@
dataType: "json"
).done (groups) ->
callback(groups)
# Return projects list. Filtered by query
projects: (query, callback) ->
project_url = Api.buildUrl(Api.projects_path)
project_query = $.ajax(
url: project_url
data:
private_token: gon.api_token
search: query
per_page: 20
dataType: "json"
).done (projects) ->
callback(projects)
......@@ -215,8 +215,89 @@ $ ->
$this.attr 'value', $this.val()
return
$(document).on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
$this.attr 'value', $this.val()
$(document)
.off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
$this.attr 'value', $this.val()
$(document)
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.gutter-toggle').find('i')
if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
$(document)
.off 'click', 'aside .gutter-toggle'
.on 'click', 'aside .gutter-toggle', (e) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon
.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
$this
.closest('aside')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
$thisIcon
.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
$this
.closest('aside')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
if $('.device-xs').is(':visible')
bootstrapBreakpoint = "xs"
else if $('.device-sm').is(':visible')
bootstrapBreakpoint = "sm"
else if $('.device-md').is(':visible')
bootstrapBreakpoint = "md"
else if $('.device-lg').is(':visible')
bootstrapBreakpoint = "lg"
setBootstrapBreakpoints = ->
if $('.device-xs').length
return
$("body")
.append('<div class="device-xs visible-xs"></div>'+
'<div class="device-sm visible-sm"></div>'+
'<div class="device-md visible-md"></div>'+
'<div class="device-lg visible-lg"></div>')
checkBootstrapBreakpoints()
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
checkBootstrapBreakpoints()
if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
.off "resize"
.on "resize", (e) ->
fitSidebarForSize()
setBootstrapBreakpoints()
checkInitialSidebarSize()
new Aside()
class @Dashboard
constructor: ->
new ProjectsList()
@Dashboard =
init: ->
this.initSearch()
initSearch: ->
@timer = null
$("#project-filter-form-field").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500)
)
filterResults: =>
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
form = $("#project-filter-form")
search = $("#project-filter-form-field").val()
project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('div.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
......@@ -58,7 +58,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
MergeRequests.init()
when 'dashboard:show', 'root:show'
new Dashboard()
Dashboard.init()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
......
......@@ -65,8 +65,7 @@ class @DropzoneInput
return
success: (header, response) ->
child = $(dropzone[0]).children("textarea")
$(child).val $(child).val() + response.link.markdown + "\n"
pasteText response.link.markdown
return
error: (temp, errorMessage) ->
......@@ -128,6 +127,7 @@ class @DropzoneInput
beforeSelection = $(child).val().substring 0, caretStart
afterSelection = $(child).val().substring caretEnd, textEnd
$(child).val beforeSelection + text + afterSelection
child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
form_textarea.trigger "input"
getFilename = (e) ->
......
......@@ -10,19 +10,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
$('.issuable-details').waitForImages ->
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
$('.issuable-affix').affix offset:
top: ->
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
$(".edit-link").click (e) ->
$(document).on "click",".edit-link", (e) ->
block = $(@).parents('.block')
block.find('.selectbox').show()
block.find('.value').hide()
......
......@@ -42,3 +42,8 @@ work = ->
$(document).on('page:fetch', start)
$(document).on('page:change', stop)
$ ->
# Make logo clickable
$('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click()
......@@ -50,3 +50,19 @@ class @Project
$('#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'
$(@).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
$('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups')
@orderBy = $(select).data('order-by') || 'id'
placeholder = "Search for project"
placeholder += " or group" if @includeGroups
......@@ -28,7 +29,7 @@ class @ProjectSelect
if @groupId
Api.groupProjects @groupId, query.term, projectsCallback
else
Api.projects query.term, projectsCallback
Api.projects query.term, @orderBy, projectsCallback
id: (project) ->
project.web_url
......
......@@ -22,5 +22,3 @@ class @ProjectsList
else
$(this).show()
uiBox.find("ul.projects-list li.bottom").hide()
......@@ -16,12 +16,31 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@replyWithSelectedText()
return false
)
Mousetrap.bind('j', =>
@prevIssue()
return false
)
Mousetrap.bind('k', =>
@nextIssue()
return false
)
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
else
@enabledHelp.push('.hidden-shortcut.issues')
prevIssue: ->
$prevBtn = $('.prev-btn')
if not $prevBtn.hasClass('disabled')
Turbolinks.visit($prevBtn.attr('href'))
nextIssue: ->
$nextBtn = $('.next-btn')
if not $nextBtn.hasClass('disabled')
Turbolinks.visit($nextBtn.attr('href'))
replyWithSelectedText: ->
if window.getSelection
selected = window.getSelection().toString()
......
......@@ -24,6 +24,7 @@
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s36 { width: 36px; height: 36px; margin-right: 10px; }
&.s40 { width: 40px; height: 40px; margin-right: 10px; }
&.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
......@@ -40,7 +41,8 @@
&.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; }
&.s32 { font-size: 22px; line-height: 32px; }
&.s32 { font-size: 20px; line-height: 32px; }
&.s40 { font-size: 16px; line-height: 40px; }
&.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
......
......@@ -2,7 +2,7 @@
@include border-radius(3px);
font-size: $gl-font-size;
font-weight: 500;
padding: $gl-vert-padding $gl-padding;
padding: $gl-vert-padding $gl-btn-padding;
&:focus,
&:active {
......@@ -82,8 +82,7 @@
&.btn-success,
&.btn-new,
&.btn-create,
&.btn-save,
&.btn-green {
&.btn-save {
@include btn-green;
}
......@@ -159,7 +158,6 @@
.input-group-btn {
.btn {
@include btn-gray;
@include btn-middle;
&:hover {
......@@ -186,8 +184,4 @@
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
}
.btn-green {
@include btn-green
}
}
......@@ -385,11 +385,11 @@ table {
margin-bottom: $gl-padding;
}
.new-project-item-select-holder {
.project-item-select-holder {
display: inline-block;
position: relative;
.new-project-item-select {
.project-item-select {
position: absolute;
top: 0;
right: 0;
......
......@@ -73,7 +73,6 @@ header {
.title {
margin: 0;
overflow: hidden;
font-size: 19px;
line-height: $header-height;
font-weight: normal;
......@@ -88,6 +87,22 @@ header {
text-decoration: underline;
}
}
.dropdown-toggle-caret {
position: relative;
top: -2px;
width: 12px;
line-height: 12px;
margin-left: 5px;
font-size: 10px;
text-align: center;
cursor: pointer;
}
.project-item-select {
right: auto;
left: 0;
}
}
.navbar-collapse {
......
......@@ -9,7 +9,7 @@
display: block;
float: left;
padding: 0 $gl-padding;
padding: 0 $gl-btn-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
......
......@@ -109,7 +109,6 @@ ul.content-list {
padding: 0;
> li {
padding: $gl-padding 0;
border-color: $table-border-color;
color: $gl-gray;
......
......@@ -116,7 +116,7 @@
display: none;
}
aside {
aside:not(.right-sidebar){
display: none;
}
......
......@@ -37,3 +37,93 @@
}
}
}
.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;
}
> form {
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;
}
}
}
}
......@@ -45,6 +45,19 @@
overflow: hidden;
transition-duration: .3s;
.home {
z-index: 1;
position: absolute;
left: 0px;
}
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
a {
float: left;
height: $header-height;
......@@ -70,7 +83,7 @@
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
margin-left: 50px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
......@@ -200,6 +213,14 @@
}
}
@mixin expanded-gutter {
padding-right: $gutter_width;
}
@mixin collapsed-gutter {
padding-right: $sidebar_collapsed_width;
}
@mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width;
......@@ -266,6 +287,7 @@
background: #f2f6f7;
}
// page is small enough
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
@include collapsed-sidebar;
......@@ -275,12 +297,32 @@
@include collapsed-sidebar;
}
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.collapse-nav {
display: none;
}
}
// page is large enough
@media(min-width: $screen-md-max) {
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.page-sidebar-collapsed {
@include collapsed-sidebar;
}
......@@ -288,4 +330,4 @@
.page-sidebar-expanded {
@include expanded-sidebar;
}
}
}
\ No newline at end of file
......@@ -12,6 +12,9 @@ $gl-font-size: 15px;
$list-font-size: 15px;
$sidebar_collapsed_width: 62px;
$sidebar_width: 230px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
......@@ -22,9 +25,10 @@ $header-height: 58px;
$fixed-layout-width: 1280px;
$gl-gray: #5a5a5a;
$gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top:10px;
$gl-avatar-size: 46px;
$gl-avatar-size: 40px;
$secondary-text: #7f8fa4;
$error-exclamation-point: #E62958;
......@@ -36,11 +40,12 @@ $white-light: #FFFFFF;
$white-normal: #ededed;
$white-dark: #ededed;
$gray-light: #f7f7f7;
$gray-normal: #ededed;
$gray-light: #faf9f9;
$gray-normal: #f5f5f5;
$gray-dark: #ededed;
$gray-darkest: #c9c9c9;
$green-light: #31AF64;
$green-light: #38ae67;
$green-normal: #2FAA60;
$green-dark: #2CA05B;
......@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB;
$blue-medium: #2F8EBF;
$blue-medium-dark: #2D86B4;
$orange-light: #FC6443;
$orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #E75E40;
$orange-dark: #CE5237;
......@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF;
$border-gray-light: #d1d1d1;
$border-gray-normal: #D6DAE2;
$border-gray-light: rgba(0, 0, 0, 0.06);
$border-gray-normal: rgba(0, 0, 0, 0.10);;
$border-gray-dark: #C6CACF;
$border-green-light: #2FAA60;
......@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8;
$border-blue-normal: #2897CE;
$border-blue-dark: #258DC1;
$border-orange-light: #ED5C3D;
$border-orange-light: #fc6d26;
$border-orange-normal: #CE5237;
$border-orange-dark: #C14E35;
......
......@@ -40,10 +40,6 @@
.avatar {
@include border-radius(50%);
}
.identicon {
line-height: 46px;
}
}
.dash-project-access-icon {
......
......@@ -4,7 +4,7 @@
*/
.event-item {
font-size: $gl-font-size;
padding: $gl-padding 0 $gl-padding ($gl-avatar-size + 15px);
padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color;
color: #7f8fa4;
......@@ -16,7 +16,7 @@
.event-title,
.event-item-timestamp {
line-height: 44px;
line-height: 40px;
}
}
......@@ -25,7 +25,7 @@
}
.avatar {
margin-left: -($gl-avatar-size + 15px);
margin-left: -($gl-avatar-size + $gl-padding-top);
}
.event-title {
......@@ -41,7 +41,6 @@
margin-right: 174px;
.event-note {
margin-top: 5px;
word-wrap: break-word;
.md {
......@@ -98,8 +97,6 @@
&:last-child { border:none }
.event_commits {
margin-top: 9px;
li {
&.commit {
background: transparent;
......
......@@ -6,11 +6,3 @@
font-size: 30px;
}
}
.explore-trending-block {
.lead {
line-height: 32px;
font-size: 18px;
margin-top: 10px;
}
}
......@@ -4,7 +4,7 @@
input[type='search'] {
width: 225px;
vertical-align: bottom;
@media (max-width: $screen-xs-max) {
width: 100px;
vertical-align: bottom;
......@@ -26,3 +26,21 @@
font-weight: 600;
color: #4c4e54;
}
.group-row {
&.no-description {
.group-name {
line-height: 44px;
}
}
.stats {
float: right;
line-height: 44px;
color: $gl-gray;
span {
margin-right: 15px;
}
}
}
......@@ -29,21 +29,8 @@
}
}
.project-issuable-filter {
.controls {
float: right;
margin-top: 11px;
}
.nav-links {
text-align: left;
}
}
.issuable-details {
section {
border-right: 1px solid $border-white-light;
.issuable-discussion {
margin-right: 1px;
}
......@@ -73,11 +60,35 @@
.block {
@include clearfix;
padding: $gl-padding 0;
border-bottom: 1px solid #F0F0F0;
border-bottom: 1px solid $border-gray-light;
// This prevents the mess when resizing the sidebar
// of elements repositioning themselves..
width: $gutter_inner_width;
overflow-x: hidden;
// --
&:first-child {
padding-top: 5px;
}
&:last-child {
border: none;
}
span {
margin-top: 7px;
display: inline-block;
}
.issuable-count {
}
.gutter-toggle {
margin-left: 20px;
border-left: 1px solid $border-gray-light;
padding-left: 10px;
}
}
.title {
......@@ -133,3 +144,98 @@
margin-right: 2px;
}
}
.right-sidebar {
position: fixed;
top: 58px;
right: 0;
height: 100%;
transition-duration: .3s;
background: $gray-light;
overflow: scroll;
padding: 10px 20px;
&.right-sidebar-expanded {
width: $gutter_width;
hr {
display: none;
}
}
.subscribe-button {
span {
margin-top: 0;
}
}
&.right-sidebar-collapsed {
width: $sidebar_collapsed_width;
padding-top: 0;
overflow-x: hidden;
hr {
margin: 0;
color: $gray-normal;
border-color: $gray-normal;
width: 62px;
margin-left: -20px
}
.block {
border-bottom: none;
padding: 15px 0 0 0;
}
}
.btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
}
&.right-sidebar-collapsed {
.issuable-count,
.issuable-nav,
.assignee > *,
.milestone > *,
.labels > *,
.participants > *,
.light > *,
.project-reference > * {
display: none;
}
.gutter-toggle {
margin-left: -$gutter_inner_width + 4;
}
.sidebar-collapsed-icon {
display: block;
float: left;
width: 62px;
text-align: center;
margin-left: -19px;
padding-bottom: 10px;
color: #999999;
span {
display: block;
margin-top: 0;
}
}
}
&.right-sidebar-expanded {
.sidebar-collapsed-icon {
display: none;
}
}
}
.detail-page-description {
small {
color: $gray-darkest;
}
}
\ No newline at end of file
......@@ -65,10 +65,6 @@ form.edit-issue {
width: 3em;
}
.merge-request-info {
padding-left: 5px;
}
.merge-request-status {
color: $gl-gray;
font-size: 15px;
......@@ -143,4 +139,4 @@ form.edit-issue {
.issue-closed-by-widget {
color: $secondary-text;
margin-left: 52px;
}
\ No newline at end of file
}
......@@ -281,36 +281,6 @@
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-thumbnail {
text-align: center;
......@@ -386,22 +356,6 @@ pre.light-well {
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 {
margin: 0 7px 0 7px;
......@@ -437,12 +391,11 @@ pre.light-well {
@include basic-list;
.project-row {
padding: $gl-padding 0;
border-color: $table-border-color;
&.no-description {
.project {
line-height: 44px;
line-height: 40px;
}
}
......@@ -455,12 +408,16 @@ pre.light-well {
.project-controls {
float: right;
color: $gl-gray;
line-height: 45px;
line-height: 40px;
color: #7f8fa4;
a:hover {
text-decoration: none;
}
> span {
margin-left: 10px;
}
}
.project-description {
......@@ -559,52 +516,12 @@ pre.light-well {
}
}
.cannot-be-merged,
.cannot-be-merged,
.cannot-be-merged:hover {
color: #E62958;
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 {
i:nth-child(1) {
color: #2AA056;
......
......@@ -81,6 +81,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:recaptcha_private_key,
:sentry_enabled,
:sentry_dsn,
:akismet_enabled,
:akismet_api_key,
restricted_visibility_levels: [],
import_sources: []
)
......
......@@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
before_action :finder, only: [:edit, :update, :destroy]
def index
@broadcast_messages = BroadcastMessage.reorder("starts_at ASC").page(params[:page])
@broadcast_messages = BroadcastMessage.reorder("ends_at DESC").page(params[:page])
@broadcast_message = BroadcastMessage.new
end
......
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
params[:authenticity_token].presence
elsif params[:private_token].presence
params[:private_token].presence
elsif request.headers['PRIVATE-TOKEN'].present?
request.headers['PRIVATE-TOKEN']
end
user = user_token && User.find_by_authentication_token(user_token.to_s)
......@@ -279,9 +281,10 @@ class ApplicationController < ActionController::Base
}
end
def view_to_html_string(partial)
def view_to_html_string(partial, locals = {})
render_to_string(
partial,
locals: locals,
layout: false,
formats: [:html]
)
......
......@@ -3,7 +3,16 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def index
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace)
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push
respond_to do |format|
......@@ -13,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
load_events
render layout: false
end
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end
......@@ -20,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = current_user.starred_projects
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push
@groups = []
......@@ -27,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
format.html
format.json do
load_events
pager_json("events/_events", @events.count)
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end
......
......@@ -11,14 +11,14 @@ class Explore::ProjectsController < Explore::ApplicationController
end
def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.non_archived
@trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE)
@projects = TrendingProjectsFinder.new.execute(current_user)
@projects = @projects.non_archived
@projects = @projects.page(params[:page]).per(PER_PAGE)
end
def starred
@starred_projects = ProjectsFinder.new.execute(current_user)
@starred_projects = @starred_projects.reorder('star_count DESC')
@starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE)
@projects = ProjectsFinder.new.execute(current_user)
@projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page]).per(PER_PAGE)
end
end
......@@ -41,6 +41,7 @@ class GroupsController < Groups::ApplicationController
def show
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@shared_projects = @group.shared_projects
......
......@@ -30,7 +30,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Do additional LDAP checks for the user filter and EE features
if ldap_user.allowed?
if @user.otp_required_for_login?
if @user.two_factor_enabled?
prompt_for_two_factor(@user)
else
log_audit_event(@user, with: :ldap)
......
......@@ -28,6 +28,11 @@ class Projects::ApplicationController < ApplicationController
private
def apply_diff_view_cookie!
view = params[:view] || cookies[:diff_view]
cookies.permanent[:diff_view] = params[:view] = view if view
end
def builds_enabled
return render_404 unless @project.builds_enabled?
end
......
......@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController
before_action :project
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
headers['X-Content-Type-Options'] = 'nosniff'
send_data(
@blob.data,
type: @blob.mime_type,
disposition: 'inline',
filename: @blob.name
)
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = @blob.content_type
head :ok # 'render nothing: true' messes up the Content-Type
else
render_404
end
......
......@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController
def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository)
end
def update
......@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
def preview
@content = params[:content]
@blob.load_all_data!(@repository)
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
......
......@@ -13,6 +13,8 @@ class Projects::CommitController < Projects::ApplicationController
def show
return git_not_found! unless @commit
apply_diff_view_cookie!
@line_notes = commit.notes.inline
@note = @project.build_commit_note(commit)
@notes = commit.notes.not_inline.fresh
......
......@@ -57,6 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def diffs
apply_diff_view_cookie!
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
......
......@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer?
send_lfs_object
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
else
render_404
......@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController
end
end
def stream_data
type = get_blob_type
send_data(
@blob.data,
type: type,
disposition: 'inline'
)
end
def send_lfs_object
lfs_object = find_lfs_object
......
......@@ -4,8 +4,9 @@ class UsersController < ApplicationController
def show
@contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
@projects = PersonalProjectsFinder.new(@user).execute(current_user)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@groups = @user.groups.order_id_desc
......
......@@ -172,18 +172,6 @@ module ApplicationHelper
Gitlab.config.extra
end
def search_placeholder
if @project && @project.persisted?
'Search'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
'Search in this group'
else
'Search'
end
end
# Render a `time` element with Javascript-based relative date and tooltip
#
# time - Time object
......@@ -296,6 +284,76 @@ module ApplicationHelper
end
end
def issuable_link_next(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_link_prev(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_count(entity, project)
if project.nil?
0
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
end
def next_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def has_next_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def prev_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def has_prev_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def state_filters_text_for(entity, project)
titles = {
opened: "Open"
......
......@@ -27,6 +27,10 @@ module ApplicationSettingsHelper
current_application_settings.user_oauth_applications
end
def askimet_enabled?
current_application_settings.akismet_enabled?
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
......
......@@ -10,8 +10,19 @@ module ExploreHelper
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
end
def explore_controller?
controller.class.name.split("::").first == "Explore"
end
end
......@@ -7,6 +7,8 @@ module LabelsHelper
# project - Project object which will be used as the context for the label's
# link. If omitted, defaults to `@project`, or the label's own
# project.
# type - The type of item the link will point to (:issue or
# :merge_request). If omitted, defaults to :issue.
# block - An optional block that will be passed to `link_to`, forming the
# body of the link element. If omitted, defaults to
# `render_colored_label`.
......@@ -23,14 +25,19 @@ module LabelsHelper
# # Force the generated link to use a provided project
# link_to_label(label, project: Project.last)
#
# # Force the generated link to point to merge requests instead of issues
# link_to_label(label, type: :merge_request)
#
# # Customize link body with a block
# link_to_label(label) { "My Custom Label Text" }
#
# Returns a String
def link_to_label(label, project: nil, &block)
def link_to_label(label, project: nil, type: :issue, &block)
project ||= @project || label.project
link = namespace_project_issues_path(project.namespace, project,
label_name: label.name)
link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
project,
label_name: label.name)
if block_given?
link_to link, &block
......
......@@ -3,6 +3,18 @@ module NavHelper
cookies[:collapsed_nav] == 'true'
end
def sidebar_gutter_collapsed_class
if cookies[:collapsed_gutter] == 'true'
"right-sidebar-collapsed"
else
"right-sidebar-expanded"
end
end
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def nav_sidebar_class
if nav_menu_collapsed?
"sidebar-collapsed"
......@@ -19,6 +31,19 @@ module NavHelper
end
end
def page_gutter_class
if current_path?('merge_requests#show') ||
current_path?('merge_requests#diffs') ||
current_path?('merge_requests#commits') ||
current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed"
else
"page-gutter right-sidebar-expanded"
end
end
end
def nav_header_class
if nav_menu_collapsed?
"header-collapsed"
......
......@@ -20,6 +20,12 @@ module ProjectsHelper
end
end
def link_to_member_avatar(author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
end
def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
......@@ -53,14 +59,23 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
project_link = link_to(simple_sanitize(project.name), project_path(project))
project_link = link_to project_path(project), { class: "project-item-select-holder" } do
link_output = simple_sanitize(project.name)
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
project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user
full_title = namespace_link + ' / ' + project_link
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
content_tag :span do
full_title
end
full_title
end
def remove_project_message(project)
......
......@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups
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)}",
url: group_path(group)
......@@ -80,7 +80,7 @@ module SearchHelper
# Autocomplete results for the current user's projects
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|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
......
......@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer
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['References'] = @original_message.message_id
......
......@@ -89,6 +89,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :sentry_enabled
validates :akismet_api_key,
presence: true,
if: :akismet_enabled
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
......@@ -144,7 +148,9 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
two_factor_grace_period: 48
two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false
)
end
......
......@@ -205,7 +205,11 @@ module Ci
end
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
nil
end
......
......@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
where(project_id: projects.select(:id).reorder(nil)).recent
where(project_id: projects.map(&:id)).recent
end
scope :with_associations, -> { includes(project: :namespace) }
......
......@@ -262,7 +262,7 @@ class MergeRequest < ActiveRecord::Base
end
def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i)
!!(title =~ /\A\[?WIP(\]|:| )/i)
end
def mergeable?
......@@ -288,7 +288,8 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(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
def mr_and_commit_notes
......
......@@ -208,8 +208,11 @@ class Repository
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
number_commits_behind = commits_between(branch.target, root_ref_hash).size
number_commits_ahead = commits_between(root_ref_hash, branch.target).size
number_commits_behind = raw_repository.
count_commits_between(branch.target, root_ref_hash)
number_commits_ahead = raw_repository.
count_commits_between(root_ref_hash, branch.target)
{ behind: number_commits_behind, ahead: number_commits_ahead }
end
......
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
git_repo = repository.raw_repository
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
@readme.load_all_data!(git_repo)
@readme
end
def trees
......
......@@ -140,6 +140,7 @@ class User < ActiveRecord::Base
has_many :approvals, dependent: :destroy
has_many :approvers, dependent: :destroy
has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
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
......@@ -231,20 +231,37 @@
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
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
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_site_key, class: 'form-control'
.help-block
Generate site and private keys here:
%a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha
Generate site and private keys at
%a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10
= 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
%legend Error Reporting and Logging
%p
......
......@@ -34,4 +34,4 @@
= link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs'
= link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger'
= paginate @broadcast_messages
= paginate @broadcast_messages, theme: 'gitlab'
.project-issuable-filter
.controls
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do
......@@ -20,7 +15,11 @@
Finished
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
.gray-content-block
.nav-controls
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.gray-content-block.second-block
#{(@scope || 'running').capitalize} builds
%ul.content-list
......
......@@ -92,6 +92,11 @@
Rails
%span.pull-right
#{Rails::VERSION::STRING}
%p
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%hr
.row
.col-sm-4
......
- 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
= nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do
Your Groups
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do
Explore Groups
.top-area
%ul.nav-links
= nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups' do
Your Groups
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore groups' do
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,15 @@
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
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
Explore Projects
.projects-search-form
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short', spellcheck: false, id: 'project-filter-form-field'
= render 'explore/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-green' do
%i.fa.fa-plus
= link_to new_project_path, class: 'btn btn-new' do
= icon('plus')
New Project
......@@ -2,15 +2,6 @@
- header_title "Groups", dashboard_groups_path
= 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
- @group_members.each do |group_member|
- group = group_member.group
......
......@@ -4,20 +4,15 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
.project-issuable-filter
.controls
.pull-left
- if current_user
.hidden-xs.pull-left
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
List all issues from all projects you have access to.
= render 'shared/issuable/filter', type: :issues
.prepend-top-default
= render 'shared/issues'
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
.project-issuable-filter
.controls
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
List all merge requests from all projects you have access to.
= render 'shared/issuable/filter', type: :merge_requests
.prepend-top-default
= render 'shared/merge_requests'
- page_title "Milestones"
- header_title "Milestones", dashboard_milestones_path
.project-issuable-filter
.controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
.top-area
= render 'shared/milestones_filter'
.gray-content-block
List all milestones from all projects you have access to.
.nav-controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
.milestones
%ul.content-list
......
.projects-list-holder
= render 'shared/projects/list', projects: @projects, ci: true
:javascript
Dashboard.init()
......@@ -3,8 +3,8 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.1"] do
= image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:''
= cache [event, current_application_settings, "v2.2"] do
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
......
......@@ -3,19 +3,13 @@
%span.light
- if @sort.present?
= 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
= sort_title_recently_created
= sort_title_recently_updated
%b.caret
%ul.dropdown-menu
%li
= link_to trending_explore_projects_path do
Trending projects
= link_to starred_explore_projects_path do
Most stars
= link_to explore_projects_filter_path(sort: sort_value_name) do
= sort_title_name
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
......
......@@ -2,6 +2,7 @@
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
= 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
= button_tag 'Search', class: "btn"
......@@ -46,4 +47,3 @@
= link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag
= 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,10 @@
- else
= render 'explore/head'
.gray-content-block.clearfix.second-block
.top-area
= render 'explore/projects/nav'
.gray-content-block.second-block.clearfix
= render 'filter'
= render 'projects', projects: @projects
= paginate @projects, theme: "gitlab"
......@@ -6,12 +6,5 @@
- else
= render 'explore/head'
.explore-trending-block
.gray-content-block.second-block
.pull-right
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-star
See most starred projects
= render 'projects', projects: @starred_projects
= paginate @starred_projects, theme: 'gitlab'
= render 'explore/projects/nav'
= render 'projects', projects: @projects
......@@ -6,11 +6,5 @@
- else
= render 'explore/head'
.explore-trending-block
.gray-content-block.second-block
.pull-right
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-comments-o
See most discussed projects for last month
= render 'projects', projects: @trending_projects
= render 'explore/projects/nav'
= render 'projects', projects: @projects
.projects-list-holder
.projects-search-form
.input-group
= 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
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
%i.fa.fa-plus
New Project
.projects-list-holder.prepend-top-default
.input-group
= 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
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new' do
= icon('plus')
New Project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
......@@ -4,17 +4,15 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
.project-issuable-filter
.controls
.pull-left
- if current_user
.hidden-xs.pull-left
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
Only issues from
......
- page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
.project-issuable-filter
.controls
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
Only merge requests from
......
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
.project-issuable-filter
.controls
- 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
= icon('plus')
New Milestone
.top-area
= render 'shared/milestones_filter'
.nav-controls
- if can?(current_user, :admin_milestones, @group)
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus')
New Milestone
.gray-content-block
Only milestones from
%strong #{@group.name}
......
......@@ -40,10 +40,6 @@
%td.shortcut
.key enter
%td Open Selection
%tr
%td.shortcut
.key t
%td Go to finding file
%tbody
%tr
%th
......@@ -161,6 +157,10 @@
.key s
%td
Go to snippets
%tr
%td.shortcut
.key t
%td Go to finding file
.col-lg-4
%table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' }
......
......@@ -138,8 +138,32 @@
%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
%code .nav-links
%p Only nav links without button and search
.example
%ul.nav-links
%li.active
......
.page-with-sidebar{ class: page_sidebar_class }
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
%a#logo
= brand_header_logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
......
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control", spellcheck: false
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
......
......@@ -2,8 +2,9 @@
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
%a#logo
= brand_header_logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
......
%ul.nav.nav-sidebar
= 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')
%span
Overview
......@@ -25,13 +25,13 @@
%span
Deploy Keys
= 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')
%span
Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
= link_to admin_builds_path do
= link_to admin_builds_path, title: 'Builds' do
= icon('link fw')
%span
Builds
......@@ -92,6 +92,14 @@
Abuse Reports
%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
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
......
.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)
.file-content.wiki
= render_markup(blob.name, blob.data)
......
- page_title "Builds"
= render "header_title"
.project-issuable-filter
.controls
- if can?(current_user, :manage_builds, @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
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do
......@@ -32,6 +21,16 @@
%span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id))
.nav-controls
- if can?(current_user, :manage_builds, @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
#{(@scope || 'running').capitalize} builds from this project
......
.gray-content-block.top-block.clearfix.white.forks-top-block
.pull-left
.top-area
.nav-text
- public_count = @public_forks.size
- protected_count = @protected_forks.size
- full_count_title = "#{public_count} public and #{protected_count} private"
== #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
.pull-right
.projects-search-form.fork-search-form
= search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control',
spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
.nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control input-short',
spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
- else
.dropdown
%button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
- excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
= link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do
= sort_title_recently_created
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
- excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
= link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do
= sort_title_recently_created
= link_to page_filter_path(sort: sort_value_oldest_created, without: excluded_filters) do
= sort_title_oldest_created
= link_to page_filter_path(sort: sort_value_recently_updated, without: excluded_filters) do
= sort_title_recently_updated
= link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
= sort_title_oldest_updated
= link_to page_filter_path(sort: sort_value_oldest_created, without: excluded_filters) do
= sort_title_oldest_created
= link_to page_filter_path(sort: sort_value_recently_updated, without: excluded_filters) do
= sort_title_recently_updated
= link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
= sort_title_oldest_updated
.fork-link.inline
- 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
= icon('code-fork fw')
Fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'pull-right btn btn-new' do
= icon('code-fork fw')
Fork
- 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: 'btn btn-new' do
= icon('code-fork fw')
Fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
= icon('code-fork fw')
Fork
.projects-list-holder
......
......@@ -5,22 +5,19 @@
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
.project-issuable-filter
.controls
.pull-left
- if current_user
.hidden-xs.pull-left
= link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
%i.fa.fa-rss
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
= link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
= icon('rss')
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
%i.fa.fa-plus
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
= icon('plus')
New Issue
= render 'shared/issuable/filter', type: :issues
= render 'shared/issuable/filter', type: :issues
.issues-holder
= render "issues"
......@@ -15,11 +15,6 @@
opened by #{link_to_member(@project, @issue.author, size: 24)}
&middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
.pull-right
- if can?(current_user, :create_issue, @project)
......@@ -46,6 +41,10 @@
= markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field
= @issue.description
- if @issue.updated_at != @issue.created_at
%small
Edited
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests
= render 'merge_requests'
......@@ -54,11 +53,8 @@
= render 'votes/votes_block', votable: @issue
.row
%section.col-md-9
%section.col-md-12
.issuable-discussion
= render 'projects/issues/discussion'
%aside.col-md-3
= render 'shared/issuable/sidebar', issuable: @issue
= render 'shared/show_aside'
= render 'shared/issuable/sidebar', issuable: @issue
\ No newline at end of file
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}");
$('.issuable-sidebar').parent().effect('highlight')
new Issue();
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('aside.right-sidebar').effect('highlight');
new Issue();
\ No newline at end of file
- page_title "Labels"
= render "header_title"
.gray-content-block.top-block
- if can? current_user, :admin_label, @project
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
= icon('plus')
New label
.oneline
.top-area
.nav-text
Labels can be applied to issues and merge requests.
.nav-controls
- if can? current_user, :admin_label, @project
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
= icon('plus')
New label
.labels
- if @labels.present?
......
......@@ -53,7 +53,7 @@
- if merge_request.labels.any?
&nbsp;
- merge_request.labels.each do |label|
= link_to_label(label, project: merge_request.project)
= link_to_label(label, project: merge_request.project, type: 'merge_request')
- if merge_request.tasks?
&nbsp;
%span.task-status
......
......@@ -70,12 +70,9 @@
= render 'votes/votes_block', votable: @merge_request
.row
%section.col-md-9
%section.col-md-12
.issuable-discussion
= render "projects/merge_requests/discussion"
%aside.col-md-3
= render 'shared/issuable/sidebar', issuable: @merge_request
= render 'shared/show_aside'
#commits.commits.tab-pane
- # This tab is always loaded via AJAX
......@@ -87,6 +84,8 @@
.mr-loading-status
= spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
:javascript
var merge_request;
......
......@@ -2,16 +2,19 @@
= render "header_title"
= render 'projects/last_push'
.project-issuable-filter
.controls
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project
.pull-left.hidden-xs
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
%i.fa.fa-plus
New Merge Request
= render 'shared/issuable/filter', type: :merge_requests
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
= icon('plus')
New Merge Request
= render 'shared/issuable/filter', type: :merge_requests
.merge-requests-holder
= render 'merge_requests'
......@@ -10,3 +10,8 @@
= markdown(@merge_request.description, cache_key: [@merge_request, "description"])
%textarea.hidden.js-task-list-field
= @merge_request.description
- if @merge_request.updated_at != @merge_request.created_at
%small
Edited
= time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
......@@ -8,11 +8,6 @@
opened by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot;
= time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
.issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request)
......
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}");
$('.issuable-sidebar').parent().effect('highlight')
$('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('aside.right-sidebar').effect('highlight')
merge_request = new MergeRequest();
......@@ -2,17 +2,14 @@
= render "header_title"
.project-issuable-filter
.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
.top-area
= render 'shared/milestones_filter'
.gray-content-block
Milestone allows you to group issues and set due date for it
.nav-controls
- 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
%ul.content-list
......
......@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project)
xml.updated @events[0].updated_at.xmlschema if @events[0?
xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
......
.project-issuable-filter
.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'
.top-area
%ul.nav-links
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
......@@ -17,3 +9,11 @@
= nav_link(path: 'wikis#git_access') do
= link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
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
.line-numbers
- 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
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
......
.milestones-filters
%ul.nav-links
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
= link_to milestones_filter_path(state: 'opened') do
Open
%li{class: ("active" if params[:state] == 'closed')}
= link_to milestones_filter_path(state: 'closed') do
Closed
%li{class: ("active" if params[:state] == 'all')}
= link_to milestones_filter_path(state: 'all') do
All
%ul.nav-links
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
= link_to milestones_filter_path(state: 'opened') do
Open
%li{class: ("active" if params[:state] == 'closed')}
= link_to milestones_filter_path(state: 'closed') do
Closed
%li{class: ("active" if params[:state] == 'all')}
= link_to milestones_filter_path(state: 'all') do
All
- if @projects.any?
.prepend-left-10.new-project-item-select-holder
= project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
.prepend-left-10.project-item-select-holder
= 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
= icon('plus')
= local_assigns[:label]
......@@ -8,12 +8,12 @@
:javascript
$('.new-project-item-select-button').on('click', function() {
$('.new-project-item-select').select2('open');
$('.project-item-select').select2('open');
});
var relativePath = '#{local_assigns[:path]}';
$('.new-project-item-select').on('click', function() {
$('.project-item-select').on('click', function() {
window.location = $(this).val() + '/' + relativePath;
});
......
- 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
.controls.hidden-xs
- if can?(current_user, :admin_group, group)
......@@ -9,7 +12,16 @@
= 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
= image_tag group_icon(group), class: "avatar s46 hidden-xs"
.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 s40 hidden-xs"
= link_to group, class: 'group-name' do
%span.item-title= group.name
......@@ -17,5 +29,6 @@
as
%span #{group_member.human_access}
%div.light
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
- if group.description.present?
.light
= markdown(group.description, pipeline: :description)
.issues-filters
.issues-state-filters
%ul.nav-links
- if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests'
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
.issues-details-filters.gray-content-block
.issues-details-filters.gray-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
......
%ul.nav-links.issues-state-filters
- if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests'
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
.block.participants
.sidebar-collapsed-icon
= icon('users')
%span
= participants.count
.title
= pluralize participants.count, "participant"
- participants.each do |participant|
......
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
.append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input', spellcheck: false }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :author_id, params['author_id']
= hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id']
= form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :author_id, params['author_id']
= hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id']
.issuable-sidebar.issuable-affix
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
.title
%label
Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
%aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar
.block
%span.issuable-count.pull-left
= issuable.iid
of
= issuable_count(:all, @project)
%span.pull-right
%a.gutter-toggle{href: '#'}
- if sidebar_gutter_collapsed?
= icon('angle-double-left')
- else
= icon('angle-double-right')
.issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if has_prev_issuable?(@project, issuable.id)
= link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default prev-btn'
- else
.light None
.selectbox
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
.block.milestone
.title
%label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.milestone
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
%a.btn.btn-default.disabled{href: '#'}
Prev
- if has_next_issuable?(@project, issuable.id)
= link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default next-btn'
- else
.light None
.selectbox
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
%a.btn.btn-default.disabled{href: '#'}
Next
- if issuable.project.labels.any?
.block
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
.sidebar-collapsed-icon
- if issuable.assignee
= link_to_member_avatar(issuable.assignee, size: 24)
- else
= icon('user')
.title
%label Labels
%label
Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value.issuable-show-labels
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label)
.value
- if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
- else
.light None
.selectbox
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
- if issuable.respond_to?(:weight)
.block.weight
.block.milestone
.sidebar-collapsed-icon
= icon('balance-scale')
%span
- if issuable.milestone
= issuable.milestone.title
- else
No
.title
%label Weight
%label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.weight
= icon('balance-scale')
= issuable.weight
- if issuable.milestone
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
- else
.light None
.selectbox
= f.select :weight, projects_weight_options(issuable.weight), { include_blank: true },
{ class: 'select2 js-select2', data: { placeholder: "Select weight" }}
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if issuable.project.labels.any?
.block.labels
.sidebar-collapsed-icon
= icon('tags')
%span
= issuable.labels.count
.title
%label Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value.issuable-show-labels
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
- else
.light None
.selectbox
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
- if current_user
- subscribed = issuable.subscribed?(current_user)
.block.light
.title
%label.light Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
%button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
- if issuable.respond_to?(:weight)
.block.weight
.title
%label Weight
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.weight
= icon('balance-scale')
= issuable.weight
- else
.light None
.selectbox
= f.select :weight, projects_weight_options(issuable.weight), { include_blank: true },
{ class: 'select2 js-select2', data: { placeholder: "Select weight" }}
- project_ref = cross_project_reference(@project, issuable)
.block
.title
.cross-project-reference
%span
Reference:
%cite{title: project_ref}
= project_ref
= clipboard_button(clipboard_text: project_ref)
= render "shared/issuable/participants", participants: issuable.participants(current_user)
%hr
- if current_user
- subscribed = issuable.subscribed?(current_user)
.block.light
.sidebar-collapsed-icon
= icon('rss')
.title
%label.light Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
%button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
- project_ref = cross_project_reference(@project, issuable)
.block.project-reference
.sidebar-collapsed-icon
= icon('clipboard')
.title
.cross-project-reference
%span
Reference:
%cite{title: project_ref}
= project_ref
= clipboard_button(clipboard_text: project_ref)
:javascript
new Subscription("#{toggle_subscription_path(issuable)}");
new IssuableContext();
:javascript
new Subscription("#{toggle_subscription_path(issuable)}");
new IssuableContext();
......@@ -8,18 +8,22 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
%ul.projects-list
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description
- if projects.any?
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description
- if projects.size > projects_limit
%li.bottom.center
.light
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
= link_to '#', class: 'js-expand' do
Show all
- if projects.size > projects_limit && projects.kind_of?(Array)
%li.bottom.center
.light
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
= link_to '#', class: 'js-expand' do
Show all
= paginate projects, theme: "gitlab" if !projects.kind_of?(Array)
- else
%h3 No projects found
:javascript
new ProjectsList();
......@@ -16,9 +16,9 @@
- if avatar
.dash-project-avatar
- if use_creator_avatar
= image_tag avatar_icon(project.creator.email, 46), class: "avatar s46", alt:''
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s46')
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace && !skip_namespace
......@@ -29,8 +29,8 @@
.project-controls
- if ci_commit
= render_ci_status(ci_commit)
&nbsp;
%span
= render_ci_status(ci_commit)
- if forks
%span
= icon('code-fork')
......
<%= 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' %>
encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %>
pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %>
password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %>
host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %>
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?
config.instrument_instance_methods(Gitlab::Shell)
config.instrument_methods(Gitlab::Git)
config.instrument_instance_methods(Gitlab::Git::Repository)
Gitlab::Git.constants.each do |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
Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
......@@ -62,6 +64,16 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const)
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
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
......@@ -217,6 +217,8 @@ Rails.application.routes.draw do
resources :git_hooks, only: [:index, :update]
resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
resources :applications
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
class RemoveDotAtomPathEndingOfProjects < ActiveRecord::Migration
include Gitlab::ShellAdapter
class ProjectPath
attr_reader :old_path, :id, :namespace_path
def initialize(old_path, id, namespace_path, namespace_id)
@old_path = old_path
@id = id
@namespace_path = namespace_path
@namespace_id = namespace_id
end
def clean_path
@_clean_path ||= PathCleaner.clean(@old_path, @namespace_id)
end
end
class PathCleaner
def initialize(path, namespace_id)
@namespace_id = namespace_id
@path = path
end
def self.clean(*args)
new(*args).clean
end
def clean
path = cleaned_path
count = 0
while path_exists?(path)
path = "#{cleaned_path}#{count}"
count += 1
end
path
end
private
def cleaned_path
@_cleaned_path ||= @path.gsub(/\.atom\z/, '-atom')
end
def path_exists?(path)
Project.find_by_path_and_namespace_id(path, @namespace_id)
end
end
def projects_with_dot_atom
select_all("SELECT p.id, p.path, n.path as namespace_path, n.id as namespace_id FROM projects p inner join namespaces n on n.id = p.namespace_id WHERE lower(p.path) LIKE '%.atom'")
end
def up
projects_with_dot_atom.each do |project|
project_path = ProjectPath.new(project['path'], project['id'], project['namespace_path'], project['namespace_id'])
clean_path(project_path) if rename_project_repo(project_path)
end
end
private
def clean_path(project_path)
execute "UPDATE projects SET path = #{sanitize(project_path.clean_path)} WHERE id = #{project_path.id}"
end
def rename_project_repo(project_path)
old_path_with_namespace = File.join(project_path.namespace_path, project_path.old_path)
new_path_with_namespace = File.join(project_path.namespace_path, project_path.clean_path)
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
rescue
false
end
def sanitize(value)
ActiveRecord::Base.connection.quote(value)
end
end
......@@ -77,6 +77,8 @@ ActiveRecord::Schema.define(version: 20160204190809) do
t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
t.boolean "akismet_enabled", default: false
t.string "akismet_api_key"
end
create_table "approvals", force: :cascade do |t|
......@@ -882,6 +884,19 @@ ActiveRecord::Schema.define(version: 20160204190809) do
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
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|
t.integer "user_id"
t.integer "subscribable_id"
......
# 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
Variable | Type | Explanation
Variable | Type | Description
-------- | ---- | -----------
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
RAILS_ENV | production / development / staging / test | Rails environment
DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab
GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab
GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab
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_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-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` | 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_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
## 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:
- adapter
- database
- username
- password
- host
- port
To do so please `cp config/database.yml.env config/database.yml` and use the following variables:
Variable | Default
--- | ---
GITLAB_DATABASE_ADAPTER | postgresql
GITLAB_DATABASE_ENCODING | unicode
GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV']
GITLAB_DATABASE_POOL | 10
GITLAB_DATABASE_USERNAME | root
GITLAB_DATABASE_PASSWORD |
GITLAB_DATABASE_HOST | localhost
GITLAB_DATABASE_PORT | 5432
The recommended way of specifying your database connection information is to set
the `DATABASE_URL` environment variable. This variable only holds connection
information (`adapter`, `database`, `username`, `password`, `host` and `port`),
but not behavior information (`encoding`, `pool`). If you don't want to use
`DATABASE_URL` and/or want to set database behavior information, you will have
to either:
- copy our template file: `cp config/database.yml.env config/database.yml`, or
- set a value for some `GITLAB_DATABASE_XXX` variables
The list of `GITLAB_DATABASE_XXX` variables that you can set is:
Variable | Default value | Overridden by `DATABASE_URL`?
-------- | ------------- | -----------------------------
`GITLAB_DATABASE_ADAPTER` | `postgresql` (for MySQL use `mysql2`) | Yes
`GITLAB_DATABASE_DATABASE` | `gitlab_#{ENV['RAILS_ENV']` | Yes
`GITLAB_DATABASE_USERNAME` | `root` | Yes
`GITLAB_DATABASE_PASSWORD` | None | Yes
`GITLAB_DATABASE_HOST` | `localhost` | Yes
`GITLAB_DATABASE_PORT` | `5432` | Yes
`GITLAB_DATABASE_ENCODING` | `unicode` | No
`GITLAB_DATABASE_POOL` | `10` | No
## Adding more variables
We welcome merge requests to make more settings configurable via variables.
Please make changes in the file config/initializers/1_settings.rb
Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
Please make changes in the `config/initializers/1_settings.rb` file and stick
to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
## 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).
# Adding deploy keys to multiple projects
If you want to easily add the same deploy key to multiple projects in the same group, this can be achieved quite easily with the API.
If you want to easily add the same deploy key to multiple projects in the same
group, this can be achieved quite easily with the API.
First, find the ID of the projects you're interested in, by either listing all projects:
First, find the ID of the projects you're interested in, by either listing all
projects:
```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects
curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects
```
Or finding the id of a group and then listing all projects in that group:
Or finding the ID of a group and then listing all projects in that group:
```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups
curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups
# For group 1234:
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups/1234
curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234
```
With those IDs, add the same deploy key to all:
```
for project_id in 321 456 987; do
curl -X POST --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects/${project_id}/keys
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \
--data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/keys
done
```
# Session
Login to get private token
You can login with both GitLab and LDAP credentials in order to obtain the
private token.
```
POST /session
```
Parameters:
| Attribute | Type | Required | Description |
| ---------- | ------- | -------- | -------- |
| `login` | string | yes | The username of the user|
| `email` | string | yes if login is not provided | The email of the user |
| `password` | string | yes | The password of the user |
- `login` (required) - The login of user
- `email` (required if login missing) - The email of user
- `password` (required) - Valid password
**You can login with both GitLab and LDAP credentials now**
```bash
curl -X POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd"
```
Example response:
```json
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"private_token": "dd34asd13as",
"blocked": false,
"created_at": "2012-05-23T08:00:58Z",
"username": "john_smith",
"id": 32,
"state": "active",
"avatar_url": null,
"created_at": "2015-01-29T21:07:19.440Z",
"is_admin": true,
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"dark_scheme": false,
"email": "john@example.com",
"theme_id": 1,
"is_admin": false,
"color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": "2015-07-07T07:10:58.392Z",
"identities": [],
"can_create_group": true,
"can_create_team": true,
"can_create_project": true
"can_create_project": true,
"two_factor_enabled": false,
"private_token": "9koXpg98eAheJpvBs5tK"
}
```
......@@ -18,7 +18,7 @@ GET /ci/projects
Returns:
```json
[
[
{
"id" : 271,
"name" : "gitlabhq",
......
......@@ -97,7 +97,7 @@ image: php:5.6
before_script:
# Install dependencies
- ci/docker_install.sh > /dev/null
- bash ci/docker_install.sh > /dev/null
test:app:
script:
......@@ -112,7 +112,7 @@ with a different docker image version and the runner will do the rest:
```yaml
before_script:
# Install dependencies
- ci/docker_install.sh > /dev/null
- bash ci/docker_install.sh > /dev/null
# We test PHP5.6
test:5.6:
......
......@@ -393,8 +393,12 @@ The above script will:
### artifacts
_**Note:** Introduced in GitLab Runner v0.7.0. Also, the Windows shell executor
does not currently support artifact uploads._
_**Note:** Introduced in GitLab Runner v0.7.0 for non-Windows platforms._
_**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
attached to build after success. Below are some examples.
......
......@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that
For example:
```
git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
```
will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
......
......@@ -120,6 +120,17 @@ Inside the document:
`http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate.
## Installation guide
- **Ruby:**
In [step 2 of the installation guide](../install/installation.md#2-ruby),
we install Ruby from source. Whenever there is a new version that needs to
be updated, remember to change it throughout the codeblock and also replace
the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
website).
[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
## API
Here is a list of must-have items. Use them in the exact order that appears
......
......@@ -124,7 +124,7 @@ Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz
echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.2.4.tar.gz' | shasum -c - && tar xzf ruby-2.2.4.tar.gz
echo 'b6eff568b48e0fda76e5a36333175df049b204e91217aa32a65153cc0cdcb761 ruby-2.2.4.tar.gz' | sha256sum -c - && tar xzf ruby-2.2.4.tar.gz
cd ruby-2.2.4
./configure --disable-install-rdoc
make
......@@ -267,6 +267,9 @@ sudo usermod -aG redis git
sudo chmod -R u+rwX tmp/pids/
sudo chmod -R u+rwX tmp/sockets/
# Create the public/uploads/ directory
sudo -u git -H mkdir public/uploads/
# Make sure GitLab can write to the public/uploads/ directory
sudo chmod -R u+rwX public/uploads
......@@ -358,7 +361,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
sudo -u git -H git checkout 0.6.2
sudo -u git -H git checkout 0.6.3
sudo -u git -H make
### Initialize Database and Activate Advanced Features
......
......@@ -66,7 +66,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
- 1GB RAM + 1GB swap supports up to 100 users but it will be slow
- **2GB RAM** is the **recommended** memory size and supports up to 100 users
- 4GB RAM supports up to 1,000 users
......
......@@ -17,6 +17,7 @@ See the documentation below for details on how to configure these services.
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [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
- [Akismet](akismet.md) Configure Akismet to stop spam
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.
If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md)
This document is about using GitLab as an OAuth authentication service provider
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.
In fact OAuth allows to issue access token to third-party clients by an authorization server,
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).
If you want to use other OAuth authentication service providers to sign in to
GitLab, please see the [OAuth2 client documentation](../api/oauth2.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
Go to your profile section 'Application' and press button 'New Application'
[OAuth] provides to client applications a 'secure delegated access' to server
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
Every application you authorized will be shown in your "Authorized application" sections.
GitLab supports two ways of adding a new OAuth2 application to an instance. You
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"
......@@ -424,24 +424,24 @@ will point the link to `wikis/style` when the link is inside of a wiki markdown
Here's our logo (hover to see the title text):
Inline-style:
![alt text](assets/logo-white.png)
![alt text](assets/logo.svg)
Reference-style:
![alt text1][logo]
[logo]: assets/logo-white.png
[logo]: assets/logo.svg
Here's our logo:
Inline-style:
![alt text](/assets/logo-white.png)
![alt text](/assets/logo.svg)
Reference-style:
![alt text][logo]
[logo]: /assets/logo-white.png
[logo]: /assets/logo.svg
## Blockquotes
......
# GitLab JIRA integration
_**Note:**
Full JIRA integration was previously exclusive to GitLab Enterprise Edition.
With [GitLab 8.3 forward][8_3_post], this feature in now [backported][jira-ce]
to GitLab Community Edition as well._
---
GitLab can be configured to interact with [JIRA Core] either using an
on-premises instance or the SaaS solution that Atlassian offers. Configuration
happens via username and password on a per-project basis. Connecting to a JIRA
......@@ -210,3 +217,5 @@ You can see from the above image that there are four references to GitLab:
[services-templates]: ../project_services/services_templates.md "Services templates documentation"
[JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website"
[jira-ce]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2146 "MR - Backport JIRA service"
[8_3_post]: https://about.gitlab.com/2015/12/22/gitlab-8-3-released/ "GitLab 8.3 release post"
......@@ -5,6 +5,12 @@
An SSH key allows you to establish a secure connection between your
computer and GitLab. Before generating an SSH key in your shell, check if your system
already has one by running the following command:
**Windows Command Line:**
```bash
type %userprofile%\.ssh\id_rsa.pub
```
**GNU/Linux/Mac/PowerShell:**
```bash
cat ~/.ssh/id_rsa.pub
```
......@@ -25,6 +31,12 @@ press enter to use the default. If you use a different name, the key will not
be used automatically.
Use the command below to show your public key:
**Windows Command Line:**
```bash
type %userprofile%\.ssh\id_rsa.pub
```
**GNU/Linux/Mac/PowerShell:**
```bash
cat ~/.ssh/id_rsa.pub
```
......@@ -36,9 +48,14 @@ with your username and host.
To copy your public key to the clipboard, use the code below. Depending on your
OS you'll need to use a different command:
**Windows:**
**Windows Command Line:**
```bash
type %userprofile%\.ssh\id_rsa.pub | clip
```
**Windows PowerShell:**
```bash
clip < ~/.ssh/id_rsa.pub
cat ~/.ssh/id_rsa.pub | clip
```
**Mac:**
......
......@@ -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
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
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
......@@ -13,7 +13,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
end
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')
expect(ci_lint_tool_link[:href]).to eq ci_lint_path
end
......
......@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
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
step 'I click button "Edit"' do
......
......@@ -211,6 +211,10 @@ module SharedPaths
visit admin_application_settings_path
end
step 'I visit spam logs page' do
visit admin_spam_logs_path
end
step 'I visit applications page' do
visit admin_applications_path
end
......
......@@ -58,9 +58,11 @@ module API
commit = user_project.commit(ref)
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
blob.load_all_data!(repo)
status(200)
{
......@@ -72,7 +74,7 @@ module API
ref: ref,
blob_id: blob.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
not_found! 'File'
......
......@@ -30,7 +30,7 @@ module API
end
def sudo_identifier()
identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
# Regex for integers
if !!(identifier =~ /^[0-9]+$/)
......@@ -344,12 +344,22 @@ module API
def pagination_links(paginated_data)
request_url = request.url.split('?').first
request_params = params.clone
request_params[:per_page] = paginated_data.limit_value
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?
links << %(<#{request_url}?page=1&per_page=#{paginated_data.limit_value}>; rel="first")
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="prev") unless paginated_data.first_page?
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(', ')
end
......
......@@ -3,6 +3,8 @@ module API
class Issues < Grape::API
before { authenticate! }
helpers ::Gitlab::AkismetHelper
helpers do
def filter_issues_state(issues, state)
case state
......@@ -19,6 +21,17 @@ module API
def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone)
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
resource :issues do
......@@ -114,7 +127,15 @@ module API
render_api_error!({ labels: errors }, 400)
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?
# Find or create labels and attach to issue. Labels are valid because
......
......@@ -57,7 +57,7 @@ module API
not_found! "File" unless blob
content_type 'text/plain'
present blob.data
header *Gitlab::Workhorse.send_git_blob(repo, blob)
end
# Get a raw blob contents by blob sha
......@@ -83,7 +83,7 @@ module API
env['api.format'] = :txt
content_type blob.mime_type
present blob.data
header *Gitlab::Workhorse.send_git_blob(repo, blob)
end
# Get a an archive of the repository
......
module Backup
class Manager
def pack
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
# saving additional informations
s = {}
s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
......
......@@ -8,14 +8,7 @@ module Banzai
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
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
end
whitelist = super
customize_whitelist(whitelist)
......
......@@ -4,9 +4,20 @@ module Banzai
def self.transform_context(context)
super(context).merge(
# SanitizationFilter
inline_sanitization: true
whitelist: whitelist
)
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
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
......@@ -61,7 +61,7 @@ module Gitlab
# new_path - new project path with namespace
#
# Ex.
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new")
#
def mv_repository(path, new_path)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
......
......@@ -7,8 +7,8 @@ module Gitlab
settings = nil
if connect_to_db?
settings = ApplicationSetting.current
settings ||= ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
settings = ::ApplicationSetting.current
settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
end
settings || fake_application_settings
......@@ -34,7 +34,8 @@ module Gitlab
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
two_factor_grace_period: 48
two_factor_grace_period: 48,
akismet_enabled: false
)
end
......
module Gitlab
module Database
def self.adapter_name
connection.adapter_name
end
def self.mysql?
ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2'
adapter_name.downcase == 'mysql2'
end
def self.postgresql?
ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
adapter_name.downcase == 'postgresql'
end
def self.version
database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end
def true_value
case ActiveRecord::Base.connection.adapter_name.downcase
when 'postgresql'
if self.class.postgresql?
"'t'"
else
1
......@@ -18,12 +25,31 @@ module Gitlab
end
def false_value
case ActiveRecord::Base.connection.adapter_name.downcase
when 'postgresql'
if self.class.postgresql?
"'f'"
else
0
end
end
private
def self.connection
ActiveRecord::Base.connection
end
def self.database_version
row = connection.execute("SELECT VERSION()").first
if postgresql?
row['version']
else
row.first
end
end
def connection
self.class.connection
end
end
end
......@@ -8,6 +8,7 @@ module Gitlab
blob = repository.blob_at(ref, file_name)
return [] unless blob
blob.load_all_data!(repository)
highlight(file_name, blob.data).lines.map!(&:html_safe)
end
......
......@@ -34,29 +34,29 @@ module Gitlab
def project_path_regex
@project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/.freeze
@project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git|\.atom)\z/.freeze
end
def project_path_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.git'" \
"Cannot start with '-', end in '.git' or end in '.atom'" \
end
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
def file_name_regex_message
"can contain only letters, digits, '_', '-' and '.'. "
"can contain only letters, digits, '_', '-', '@' and '.'. "
end
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
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
......
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
......@@ -38,7 +38,7 @@ web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
mail_room_enabled=false
mail_room_pid_path="$pid_path/mail_room.pid"
gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd)
gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse 2> /dev/null && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
......@@ -49,7 +49,7 @@ test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script.
if [ `whoami` != "$app_user" ]; then
eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit;
eval su - "$app_user" -c $(echo \")$shell_path -l -c \'$0 "$@"\'$(echo \"); exit;
fi
# Switch to the gitlab path, exit on failure.
......@@ -219,7 +219,7 @@ start_gitlab() {
echo "The Unicorn web server already running with pid $wpid, not restarting."
else
# 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
RAILS_ENV=$RAILS_ENV bin/web start
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
......@@ -188,7 +188,7 @@ describe Projects::MergeRequestsController do
expect(response).to render_template('diffs')
end
end
context 'as json' do
it 'renders the diffs template to a string' do
go format: 'json'
......@@ -199,6 +199,32 @@ describe Projects::MergeRequestsController do
end
end
describe 'GET diffs with view' do
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid
}
get :diffs, params.merge(extra_params)
end
it 'saves the preferred diff view in a cookie' do
go view: 'parallel'
expect(response.cookies['diff_view']).to eq('parallel')
end
it 'assigns :view param based on cookie' do
request.cookies['diff_view'] = 'parallel'
go
expect(controller.params[:view]).to eq 'parallel'
end
end
describe 'GET commits' do
def go(format: 'html')
get :commits,
......
......@@ -86,6 +86,14 @@ describe ProjectsController do
end
end
end
context "when the url contains .atom" do
let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
it 'expect an error creating the project' do
expect(public_project_with_dot_atom).not_to be_valid
end
end
end
describe "#destroy" do
......
# == Schema Information
#
# Table name: builds
#
# id :integer not null, primary key
# project_id :integer
# status :string(255)
# finished_at :datetime
# trace :text
# created_at :datetime
# updated_at :datetime
# started_at :datetime
# runner_id :integer
# commit_id :integer
# coverage :float
# commands :text
# job_id :integer
# name :string(255)
# deploy :boolean default(FALSE)
# options :text
# allow_failure :boolean default(FALSE), not null
# stage :string(255)
# trigger_request_id :integer
#
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
......@@ -65,5 +38,20 @@ FactoryGirl.define do
build.trace = 'BUILD TRACE'
end
end
trait :artifacts do
after(:create) do |build, _|
build.artifacts_file =
fixture_file_upload(Rails.root +
'spec/fixtures/ci_build_artifacts.zip',
'application/zip')
build.artifacts_metadata =
fixture_file_upload(Rails.root +
'spec/fixtures/ci_build_artifacts_metadata.gz',
'application/x-gzip')
build.save!
end
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 'Admin Builds' do
visit admin_builds_path
expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all'
end
......@@ -28,7 +28,7 @@ describe 'Admin Builds' do
it 'shows a message' do
visit admin_builds_path
expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all'
end
......@@ -44,7 +44,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page.find('.build-link')).to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
......@@ -58,7 +58,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all'
end
......@@ -74,7 +74,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page.find('.build-link')).not_to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).to have_content(build3.id)
......@@ -88,7 +88,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No builds to show'
expect(page).to have_link 'Cancel all'
end
......
......@@ -18,7 +18,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
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_content @build.short_sha }
it { expect(page).to have_content @build.ref }
......@@ -31,7 +31,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
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_link 'Cancel running' }
end
......@@ -42,7 +42,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project)
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.ref }
it { expect(page).to have_content @build.name }
......@@ -57,7 +57,7 @@ describe "Builds" do
click_link "Cancel running"
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 @build.short_sha }
it { expect(page).to have_content @build.ref }
......
......@@ -82,7 +82,26 @@ feature 'Project', feature: true do
it 'click project-settings and find leave project' do
find('#project-settings-button').click
expect(page).to have_link('Leave Project')
expect(page).to have_link('Leave Project')
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
......
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe LabelsHelper do
describe 'link_to_label' do
let(:project) { create(:empty_project) }
let(:label) { create(:label, project: project) }
let(:label) { create(:label, project: project) }
context 'with @project set' do
before do
......@@ -11,34 +11,31 @@ describe LabelsHelper do
end
it 'uses the instance variable' do
expect(label).not_to receive(:project)
link_to_label(label)
expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
end
end
context 'without @project set' do
it "uses the label's project" do
expect(label).to receive(:project).and_return(project)
link_to_label(label)
expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
end
end
context 'with a named project argument' do
it 'uses the provided project' do
arg = double('project')
expect(arg).to receive(:namespace).and_return('foo')
expect(arg).to receive(:to_param).and_return('foo')
context 'with a project argument' do
let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') }
link_to_label(label, project: arg)
it 'links to merge requests page' do
expect(link_to_label(label, project: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name=#{label.name}">.*</a>}
end
end
it 'takes precedence over other types' do
@project = project
expect(@project).not_to receive(:namespace)
expect(label).not_to receive(:project)
arg = double('project', namespace: 'foo', to_param: 'foo')
link_to_label(label, project: arg)
context 'with a type argument' do
['issue', :issue, 'merge_request', :merge_request].each do |type|
context "set to #{type}" do
it 'links to correct page' do
expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name=#{label.name}">.*</a>}
end
end
end
end
......
......@@ -42,9 +42,9 @@ describe SearchHelper do
expect(search_autocomplete_opts(project.name).size).to eq(1)
end
it "includes the public group" do
it "should not include the public group" do
group = create(:group)
expect(search_autocomplete_opts(group.name).size).to eq(1)
expect(search_autocomplete_opts(group.name).size).to eq(0)
end
context "with a current project" do
......
%h1.title
%a
GitLab Org
%a.project-item-select-holder{href: "/gitlab-org/gitlab-test"}
GitLab Test
%input#project_path.project-item-select.js-projects-dropdown.ajax-project-select{type: "hidden", name: "project_path", "data-include-groups" => "false"}
%i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle
[{"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
expect(act.to_html).to eq exp
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
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
......@@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do
it { is_expected.to satisfy { |val| val == true || val == false } }
end
describe '.version' do
context "on mysql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("5.7.12-standard")
expect(described_class.version).to eq '5.7.12-standard'
end
end
context "on postgresql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
expect(described_class.version).to eq '9.4.4'
end
end
end
end
......@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do
it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
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
......@@ -188,6 +188,11 @@ describe MergeRequest, models: true do
expect(subject).to be_work_in_progress
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
subject.title = "Wipwap #{subject.title}"
expect(subject).not_to be_work_in_progress
......@@ -250,9 +255,15 @@ describe MergeRequest, models: true do
expect(subject.can_remove_source_branch?(user2)).to be_falsey
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
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
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
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_one(:abuse_report) }
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
end
describe 'validations' do
......
......@@ -46,10 +46,10 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
it "should add pagination headers" do
get api("/issues?per_page=3", user)
it "should add pagination headers and keep query params" do
get api("/issues?state=closed&per_page=3", user)
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
......@@ -241,6 +241,37 @@ describe API::API, api: true do
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
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
......
......@@ -151,8 +151,8 @@ describe Ci::API::API do
context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
let(:commit) { create(:ci_commit, project: project) }
let(:build) { create(:ci_build, commit: commit, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
......@@ -160,12 +160,10 @@ describe Ci::API::API do
let(:headers) { { "GitLab-Workhorse" => "1.0" } }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
before { build.run! }
describe "POST /builds/:id/artifacts/authorize" do
context "should authorize posting artifact to running build" do
before do
build.run!
end
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
expect(response.status).to eq(200)
......@@ -180,10 +178,6 @@ describe Ci::API::API do
end
context "should fail to post too large artifact" do
before do
build.run!
end
it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0)
post authorize_url, { token: build.token, filesize: 100 }, headers
......@@ -197,8 +191,8 @@ describe Ci::API::API do
end
end
context "should get denied" do
it do
context 'token is invalid' do
it 'should respond with forbidden'do
post authorize_url, { token: 'invalid', filesize: 100 }
expect(response.status).to eq(403)
end
......@@ -206,17 +200,13 @@ describe Ci::API::API do
end
describe "POST /builds/:id/artifacts" do
context "Disable sanitizer" do
context "disable sanitizer" do
before do
# by configuring this path we allow to pass temp file from any path
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
end
context "should post artifact to running build" do
before do
build.run!
end
it "uses regual file post" do
upload_artifacts(file_upload, headers_with_token, false)
expect(response.status).to eq(201)
......@@ -244,10 +234,7 @@ describe Ci::API::API do
let(:stored_artifacts_file) { build.reload.artifacts_file.file }
let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
before do
build.run!
post(post_url, post_data, headers_with_token)
end
before { post(post_url, post_data, headers_with_token) }
context 'post data accelerated by workhorse is correct' do
let(:post_data) do
......@@ -257,11 +244,8 @@ describe Ci::API::API do
'metadata.name' => metadata.original_filename }
end
it 'responds with valid status' do
expect(response.status).to eq(201)
end
it 'stores artifacts and artifacts metadata' do
expect(response.status).to eq(201)
expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
end
......@@ -282,56 +266,42 @@ describe Ci::API::API do
end
end
context "should fail to post too large artifact" do
before do
build.run!
end
it do
context "artifacts file is too large" do
it "should fail to post too large artifact" do
stub_application_setting(max_artifacts_size: 0)
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(413)
end
end
context "should fail to post artifacts without file" do
before do
build.run!
end
it do
context "artifacts post request does not contain file" do
it "should fail to post artifacts without file" do
post post_url, {}, headers_with_token
expect(response.status).to eq(400)
end
end
context "should fail to post artifacts without GitLab-Workhorse" do
before do
build.run!
end
it do
context 'GitLab Workhorse is not configured' do
it "should fail to post artifacts without GitLab-Workhorse" do
post post_url, { token: build.token }, {}
expect(response.status).to eq(403)
end
end
end
context "should fail to post artifacts for outside of tmp path" do
context "artifacts are being stored outside of tmp path" do
before do
# by configuring this path we allow to pass file from @tmpdir only
# but all temporary files are stored in system tmp directory
@tmpdir = Dir.mktmpdir
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
build.run!
end
after do
FileUtils.remove_entry @tmpdir
end
it do
it "should fail to post artifacts for outside of tmp path" do
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(400)
end
......@@ -349,33 +319,37 @@ describe Ci::API::API do
end
end
describe "DELETE /builds/:id/artifacts" do
before do
build.run!
post delete_url, token: build.token, file: file_upload
end
describe 'DELETE /builds/:id/artifacts' do
let(:build) { create(:ci_build, :artifacts) }
before { delete delete_url, token: build.token }
it "should delete artifact build" do
build.success
delete delete_url, token: build.token
it 'should remove build artifacts' do
expect(response.status).to eq(200)
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
end
end
describe "GET /builds/:id/artifacts" do
before do
build.run!
end
describe 'GET /builds/:id/artifacts' do
before { get get_url, token: build.token }
it "should download artifact" do
build.update_attributes(artifacts_file: file_upload)
get get_url, token: build.token
expect(response.status).to eq(200)
context 'build has artifacts' do
let(:build) { create(:ci_build, :artifacts) }
let(:download_headers) do
{ 'Content-Transfer-Encoding'=>'binary',
'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
end
it 'should download artifact' do
expect(response.status).to eq(200)
expect(response.headers).to include download_headers
end
end
it "should fail to download if no artifact uploaded" do
get get_url, token: build.token
expect(response.status).to eq(404)
context 'build does not has artifacts' do
it 'should respond with not found' do
expect(response.status).to eq(404)
end
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment