Commit e93523ed authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge commit '79190fe0' into merge-79190fe0-7.3.0-pre

Conflicts:
	Gemfile
	README.md
	VERSION
	db/schema.rb
	doc/install/installation.md
	lib/gitlab/git_access.rb
	lib/gitlab/ldap/user.rb
	spec/lib/gitlab/ldap/access_spec.rb
parents 405a5acb 79190fe0
......@@ -5,6 +5,8 @@ targets:
debian-7: &wheezy
build_dependencies:
- libicu-dev
- cmake
- pkg-config
dependencies:
- libicu48
- libpcre3
......@@ -13,6 +15,8 @@ targets:
ubuntu-14.04:
build_dependencies:
- libicu-dev
- cmake
- pkg-config
dependencies:
- libicu52
- libpcre3
......@@ -20,6 +24,8 @@ targets:
centos-6:
build_dependencies:
- libicu-devel
- cmake
- pkgconfig
dependencies:
- libicu
- pcre
......
language: ruby
env:
global:
- TRAVIS=true
matrix:
- TASK=spinach_project DB=mysql
- TASK=spinach_other DB=mysql
- TASK=spec:api DB=mysql
- TASK=spec:feature DB=mysql
- TASK=spec:other DB=mysql
- TASK=jasmine:ci DB=mysql
- TASK=spinach_project DB=postgresql
- TASK=spinach_other DB=postgresql
- TASK=spec:api DB=postgresql
- TASK=spec:feature DB=postgresql
- TASK=spec:other DB=postgresql
- TASK=jasmine:ci DB=postgresql
before_install:
- sudo apt-get install libicu-dev -y
install:
- "travis_retry bundle install --deployment --without production --retry 5"
branches:
only:
- 'master'
rvm:
- 2.0.0
services:
- redis-server
before_script:
- "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml"
- "bundle exec rake db:setup"
- "bundle exec rake db:seed_fu"
script: "bundle exec rake $TASK --trace"
notifications:
email: false
v 7.3.0
- Always set the 'origin' remote in satellite actions
- Write authorized_keys in tmp/ during tests
- Expire Rack sessions after 1 week
- Cleaner signin/signup pages
- Improved comments UI
- Better search with filtering, pagination etc
- Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
- Prevent project stars duplication when fork project
- Use the default Unicorn socket backlog value of 1024
- Support Unix domain sockets for Redis
- Store session Redis keys in 'session:gitlab:' namespace
- Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
- Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
- Keyboard shortcuts for productivity (Robert Schilling)
- API: filter issues by state (Julien Bianchi)
- API: filter issues by labels (Julien Bianchi)
- Add system hook for ssh key changes
- Add blob permalink link (Ciro Santilli)
- Create annotated tags through UI and API (Sean Edge)
- Snippets search (Charles Bushong)
- Comment new push to existing MR
- Add 'ci' to the blacklist of forbidden names
- Improve text filtering on issues page
- Comment & Close button
- Process git push --all much faster
- Don't allow edit of system notes
- Project wiki search (Ralf Seidler)
- Enabled Shibboleth authentication support (Matus Banas)
- Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
- Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
v 7.2.1
- Delete orphaned labels during label migration (James Brooks)
- Security: prevent XSS with stricter MIME types for raw repo files
v 7.2.0
- Explore page
- Add project stars (Ciro Santilli)
......
......@@ -62,6 +62,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code
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
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
1. Submit a merge request (MR) to the master branch
......
......@@ -27,6 +27,7 @@ gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
gem 'omniauth-shibboleth'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
......@@ -36,7 +37,7 @@ gem "gitlab_git", '~> 6.0'
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap"
gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
gem 'net-ldap'
# Git Wiki
......@@ -83,7 +84,7 @@ gem "seed-fu"
gem "github-markup"
# Required markup gems by github-markdown
gem 'redcarpet', '~> 2.2.2'
gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby'
......@@ -157,6 +158,9 @@ gem "rack-attack"
# Ace editor
gem 'ace-rails-ap'
# Keyboard shortcuts
gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar
gem 'semantic-ui-sass', '~> 0.16.1.0'
......@@ -232,7 +236,7 @@ group :development, :test do
gem 'jasmine', '2.0.2'
gem "spring", '1.1.1'
gem "spring", '1.1.3'
gem "spring-commands-rspec", '1.0.1'
gem "spring-commands-spinach", '1.0.0'
end
......
......@@ -168,7 +168,7 @@ GEM
multi_json
gitlab-grack (2.0.0.pre)
rack (~> 1.5.1)
gitlab-grit (2.6.10)
gitlab-grit (2.6.11)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
......@@ -186,8 +186,8 @@ GEM
gitlab-linguist (~> 3.0)
rugged (~> 0.21.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.0.4)
net-ldap (~> 0.3.1)
gitlab_omniauth-ldap (1.1.0)
net-ldap (~> 0.7.0)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
......@@ -287,11 +287,12 @@ GEM
mime-types (1.25.1)
mini_portile (0.6.0)
minitest (5.3.5)
mousetrap-rails (1.4.6)
multi_json (1.10.1)
multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.16)
net-ldap (0.3.1)
net-ldap (0.7.0)
net-scp (1.1.2)
net-ssh (>= 2.6.5)
net-ssh (2.8.0)
......@@ -320,6 +321,8 @@ GEM
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
omniauth-shibboleth (1.1.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
......@@ -391,7 +394,7 @@ GEM
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (2.2.2)
redcarpet (3.1.2)
redis (3.0.6)
redis-actionpack (4.0.0)
actionpack (~> 4)
......@@ -489,7 +492,7 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
spring (1.1.1)
spring (1.1.3)
spring-commands-rspec (1.0.1)
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
......@@ -615,7 +618,7 @@ DEPENDENCIES
gitlab_emoji (~> 0.0.1.1)
gitlab_git (~> 6.0)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.0.4)
gitlab_omniauth-ldap (= 1.1.0)
gollum-lib (~> 3.0.0)
gon (~> 5.0.0)
grape (~> 0.6.1)
......@@ -636,12 +639,14 @@ DEPENDENCIES
launchy
letter_opener
minitest (~> 5.3.0)
mousetrap-rails
mysql2
net-ldap
nprogress-rails
omniauth (~> 1.1.3)
omniauth-github
omniauth-google-oauth2
omniauth-shibboleth
omniauth-twitter
org-ruby
pg
......@@ -658,7 +663,7 @@ DEPENDENCIES
rb-fsevent
rb-inotify
rdoc (~> 3.6)
redcarpet (~> 2.2.2)
redcarpet (~> 3.1.2)
redis-rails
request_store
rspec-rails
......@@ -677,7 +682,7 @@ DEPENDENCIES
slack-notifier (~> 0.3.2)
slim
spinach-rails
spring (= 1.1.1)
spring (= 1.1.3)
spring-commands-rspec (= 1.0.1)
spring-commands-spinach (= 1.0.0)
stamp
......
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do
guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
......@@ -19,7 +19,7 @@ guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_p
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
end
guard 'spinach' do
guard 'spinach', command_prefix: 'spring' do
watch(%r|^features/(.*)\.feature|)
watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
"features/#{m[1]}#{m[2]}.feature"
......
......@@ -87,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea
### Issue fixed in newer version
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request
......
# GitLab
# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
## Open source software to collaborate on code
![logo](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/gitlab_logo.png)
![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif)
![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
......@@ -21,7 +19,9 @@
- [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
......@@ -29,14 +29,14 @@
## Website
On [www.gitlab.com](https://www.gitlab.com/) you can find more information about:
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
- [Subscriptions](https://www.gitlab.com/subscription/)
- [Consultancy](https://www.gitlab.com/consultancy/)
- [Community](https://www.gitlab.com/community/)
- [Hosted GitLab.com](https://www.gitlab.com/gitlab-com/) use GitLab as a free service
- [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://www.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
- [Subscriptions](https://about.gitlab.com/subscription/)
- [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Third-party applications
......@@ -61,15 +61,11 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation
We recommend the [GitLab Enterprise Edition packages](https://gitlab.com/subscribers/gitlab-ee/blob/master/doc/install/packages.md).
You can also use the [Chef cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) or the [manual installation](doc/install/installation.md).
Other options are listed on the [installation page on the GitLab website](https://www.gitlab.com/installation/) but most of these assume GitLab Community Edition.
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
### New versions
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://www.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
### Upgrading
......@@ -89,7 +85,8 @@ Please login with `root` / `5iveL!fe`
## Install a development environment
We recommend setting up your development environment with [the cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md#installation). If you do not use the cookbook you might need to copy the example development unicorn configuration file
We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
If you do not use the development kit you might need to copy the example development unicorn configuration file
cp config/unicorn.rb.example.development config/unicorn.rb
......@@ -130,7 +127,7 @@ All documentation can be found on [doc.gitlab.com/ee/](http://doc.gitlab.com/ee/
## Getting help
Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help.
Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help.
## Is it any good?
......
......@@ -15,6 +15,7 @@
#= require jquery.atwho
#= require jquery.scrollTo
#= require jquery.blockUI
#= require jquery.sticky
#= require turbolinks
#= require jquery.turbolinks
#= require bootstrap
......@@ -25,12 +26,20 @@
#= require branch-graph
#= require highlight.pack
#= require ace/ace
#= require ace/ext-searchbox
#= require d3
#= require underscore
#= require nprogress
#= require nprogress-turbolinks
#= require dropzone
#= require semantic-ui/sidebar
#= require mousetrap
#= require mousetrap/pause
#= require shortcuts
#= require shortcuts_navigation
#= require shortcuts_dashboard_navigation
#= require shortcuts_issueable
#= require shortcuts_network
#= require_tree .
window.slugify = (text) ->
......@@ -117,6 +126,13 @@ $ ->
# Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
# Close select2 on escape
$('.js-select2').bind 'select2-close', ->
setTimeout ( ->
$('.select2-container-active').removeClass('select2-container-active')
$(':focus').blur()
), 1
# Initialize tooltips
$('.has_tooltip').tooltip()
......@@ -149,20 +165,6 @@ $ ->
# Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(@).toggleClass('hover')
# Focus search field by pressing 's' key
$(document).keypress (e) ->
# Don't do anything if typing in an input
return if $(e.target).is(":input")
switch e.which
when 115
$("#search").focus()
e.preventDefault()
when 63
new Shortcuts()
e.preventDefault()
# Commit show suppressed diff
$(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show()
......
$ ->
# Toggle line wrapping in diff.
#
# %div.diff-file
# %input.js-toggle-diff-line-wrap
# %td.line_content
#
$("body").on "click", ".js-toggle-diff-line-wrap", (e) ->
diffFile = $(@).closest(".diff-file")
if $(@).is(":checked")
diffFile.addClass("diff-wrap-lines")
else
diffFile.removeClass("diff-wrap-lines")
class BranchGraph
class @BranchGraph
constructor: (@element, @options) ->
@preparedCommits = {}
@mtime = 0
......@@ -120,23 +120,32 @@ class BranchGraph
@top.toFront()
bindEvents: ->
drag = {}
element = @element
$(element).scroll (event) =>
@renderPartialGraph()
$(window).on
keydown: (event) =>
# left
element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37
# top
element.scrollTop element.scrollTop() - 50 if event.keyCode is 38
# right
element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39
# bottom
element.scrollTop element.scrollTop() + 50 if event.keyCode is 40
@renderPartialGraph()
scrollDown: =>
@element.scrollTop @element.scrollTop() + 50
@renderPartialGraph()
scrollUp: =>
@element.scrollTop @element.scrollTop() - 50
@renderPartialGraph()
scrollLeft: =>
@element.scrollLeft @element.scrollLeft() - 50
@renderPartialGraph()
scrollRight: =>
@element.scrollLeft @element.scrollLeft() + 50
@renderPartialGraph()
scrollBottom: =>
@element.scrollTop @element.find('svg').height()
scrollTop: =>
@element.scrollTop 0
appendLabel: (x, y, commit) ->
return unless commit.refs
......@@ -325,5 +334,3 @@ Raphael::textWrap = (t, width) ->
b = t.getBBox()
h = Math.abs(b.y2) - Math.abs(b.y) + 1
t.attr y: b.y + h
@BranchGraph = BranchGraph
......@@ -34,7 +34,8 @@ class Diff
$.get(link, params, (response) =>
target.parent().replaceWith(response)
)
)
).ready =>
$(".diff-header").sticky {responsiveWidth:true, getWidthFrom: ".diff-file"}
lineNumbers: (line) ->
return ([0, 0]) unless line.children().length
......
......@@ -15,49 +15,87 @@ class Dispatcher
return false
path = page.split(':')
shortcut_handler = null
switch page
when 'projects:issues:index'
Issues.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when 'projects:milestones:show'
new Milestone()
when 'projects:issues:new'
when 'projects:issues:new','projects:issues:edit'
GitLab.GfmAutoComplete.setup()
when 'projects:merge_requests:new'
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
GitLab.GfmAutoComplete.setup()
new Diff()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when "projects:merge_requests:diffs"
new Diff()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
when 'dashboard:show'
new Dashboard()
new Activities()
when 'projects:commit:show'
new Commit()
new Diff()
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
when 'groups:show', 'projects:show'
new Activities()
when 'projects:new', 'projects:edit'
shortcut_handler = new ShortcutsNavigation()
when 'projects:new'
new Project()
when 'projects:edit'
new Project()
shortcut_handler = new ShortcutsNavigation()
when 'projects:teams:members:index'
new TeamMembers()
when 'groups:members'
new GroupMembers()
when 'projects:tree:show'
new TreeView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show'
new BlobView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
when 'projects:network:show'
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
shortcut_handler = true
switch path.first()
when 'admin' then new Admin()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'projects'
new Wikis() if path[1] == 'wikis'
switch path[1]
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
when 'snippets', 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
# If we haven't installed a custom shortcut handler, install the default one
if not shortcut_handler
new Shortcuts()
initSearch: ->
opts = $('.search-autocomplete-opts')
......
......@@ -43,25 +43,31 @@
$(".selected_issue").bind "change", Issues.checkChanged
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
form = $("#issue_search_form")
last_terms = ""
@timer = null
$("#issue_search").keyup ->
terms = $(this).val()
unless terms is last_terms
last_terms = terms
if terms.length >= 2 or terms.length is 0
$.ajax
type: "GET"
url: location.href
data: "issue_search=" + terms
complete: ->
$(".loading").hide()
success: (data) ->
$('.issues-holder').html(data.html)
Issues.reload()
dataType: "json"
clearTimeout(@timer);
@timer = setTimeout(Issues.filterResults, 500)
filterResults: =>
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.issues-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '? '+ form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.issues-holder').css("opacity", '1.0')
success: (data) ->
$('.issues-holder').html(data.html)
# Change url so if user reload a page - search results are saved
History.replaceState {page: issues_url}, document.title, issues_url
Issues.reload()
dataType: "json"
checkChanged: ->
checked_issues = $(".selected_issue:checked")
......
......@@ -27,7 +27,7 @@ $(document).ready ->
dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
dictDefaultMessage: ""
clickable: true
clickable: false
paramName: "markdown_img"
maxFilesize: 10
uploadMultiple: false
......
class Network
class @Network
constructor: (opts) ->
$("#filter_ref").click ->
$(this).closest('form').submit()
branch_graph = new BranchGraph($(".network-graph"), opts)
@branch_graph = new BranchGraph($(".network-graph"), opts)
vph = $(window).height() - 250
$('.network-graph').css 'height': (vph + 'px')
@Network = Network
......@@ -16,13 +16,19 @@ class Notes
$(document).on "ajax:success", ".js-main-target-form", @addNote
$(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
# change note in UI after update
# change note in UI after update
$(document).on "ajax:success", "form.edit_note", @updateNote
# Edit note link
$(document).on "click", ".js-note-edit", @showEditForm
$(document).on "click", ".note-edit-cancel", @cancelEdit
# Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-note-target-reopen", @targetReopen
$(document).on "click", ".js-note-target-close", @targetClose
$(document).on "click", ".js-comment-button", @updateCloseButton
$(document).on "keyup", ".js-note-text", @updateTargetButtons
# remove a note (in general)
$(document).on "click", ".js-note-delete", @removeNote
......@@ -78,7 +84,9 @@ class Notes
$(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange"
$(document).off "keypress", @notes_forms
$(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
initRefresh: ->
clearInterval(Notes.interval)
......@@ -406,30 +414,6 @@ class Notes
form.find(".js-note-text").focus()
form.addClass "js-discussion-note-form"
###
General note form setup.
deactivates the submit button when text is empty
hides the preview button when text is empty
setup GFM auto complete
show the form
###
setupNoteForm: (form) =>
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
form.removeClass "js-new-note-form"
form.removeClass "js-new-note-form"
GitLab.GfmAutoComplete.setup()
# setup preview buttons
previewButton = form.find(".js-note-preview-button")
form.find(".js-note-text").on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
else
previewButton.removeClass("turn-on").addClass "turn-off"
form.show()
###
Called when clicking on the "add a comment" button on the side of a diff line.
......@@ -502,4 +486,33 @@ class Notes
visibilityChange: =>
@refresh()
targetReopen: (e) =>
@submitNoteForm($(e.target).parents('form'))
targetClose: (e) =>
@submitNoteForm($(e.target).parents('form'))
submitNoteForm: (form) =>
noteText = form.find(".js-note-text").val()
if noteText.trim().length > 0
form.submit()
updateCloseButton: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
form.find('.js-note-target-close').text('Close')
updateTargetButtons: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
if textarea.val().trim().length > 0
form.find('.js-note-target-reopen').text('Comment & reopen')
form.find('.js-note-target-close').text('Comment & close')
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
@Notes = Notes
......@@ -55,7 +55,7 @@ $ ->
$(@).parents('.no-ssh-key-message').hide()
e.preventDefault()
$('.project-side .star').on 'ajax:success', (e, data, status, xhr) ->
$('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
class Shortcuts
class @Shortcuts
constructor: ->
@enabledHelp = []
Mousetrap.reset()
Mousetrap.bind('?', @selectiveHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
selectiveHelp: (e) =>
Shortcuts.showHelp(e, @enabledHelp)
@showHelp: (e, location) ->
if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show')
else
$.ajax(
url: '/help/shortcuts',
dataType: "script"
dataType: 'script',
success: (e) ->
if location and location.length > 0
for l in location
$(l).show()
else
$('.hidden-shortcut').show()
$('.js-more-help-button').remove()
)
e.preventDefault()
@Shortcuts = Shortcuts
@focusSearch: (e) ->
$('#search').focus()
e.preventDefault()
#= require shortcuts
class @ShortcutsDashboardNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-activity'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-projects'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-merge_requests'))
@findAndollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
#= require shortcuts_navigation
class @ShortcutsIssueable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
Mousetrap.bind('a', ->
$('.js-assignee').select2('open')
return false
)
Mousetrap.bind('m', ->
$('.js-milestone').select2('open')
return false
)
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_reuests')
else
@enabledHelp.push('.hidden-shortcut.issues')
#= require shortcuts
class @ShortcutsNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndollowLink('.shortcuts-project'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndollowLink('.shortcuts-snippets'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
#= require shortcuts_navigation
class @ShortcutsNetwork extends ShortcutsNavigation
constructor: (@graph) ->
super()
Mousetrap.bind(['left', 'h'], @graph.scrollLeft)
Mousetrap.bind(['right', 'l'], @graph.scrollRight)
Mousetrap.bind(['up', 'k'], @graph.scrollUp)
Mousetrap.bind(['down', 'j'], @graph.scrollDown)
Mousetrap.bind(['shift+up', 'shift+k'], @graph.scrollTop)
Mousetrap.bind(['shift+down', 'shift+j'], @graph.scrollBottom)
@enabledHelp.push('.hidden-shortcut.network')
class @ZenMode
@fullscreen_prefix = 'fullscreen_'
@ESC = 27
constructor: ->
@active_zen_area = null
@active_checkbox = null
$('body').on 'change', '.zennable input[type=checkbox]', (e) =>
checkbox = e.currentTarget;
if checkbox.checked
Mousetrap.pause()
@udpateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) =>
console.log("esc")
if e.keyCode is ZenMode.ESC
@exitZenMode()
$(window).on 'hashchange', @updateZenModeFromLocationHash
udpateActiveZenArea: (checkbox) =>
@active_checkbox = $(checkbox)
@active_checkbox.prop('checked', true)
@active_zen_area = @active_checkbox.parent().find('textarea')
@active_zen_area.focus()
window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id')
exitZenMode: =>
if @active_zen_area isnt null
Mousetrap.unpause()
@active_checkbox.prop('checked', false)
@active_zen_area = null
@active_checkbox = null
window.location.hash = ''
checkboxFromLocationHash: (e) ->
id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, ''))
if id
return $('.zennable input[type=checkbox]#' + id)[0]
else
return null
updateZenModeFromLocationHash: (e) =>
checkbox = @checkboxFromLocationHash()
if checkbox
@udpateActiveZenArea(checkbox)
else
@exitZenMode()
......@@ -128,7 +128,7 @@ p.time {
}
.highlight_word {
border-bottom: 2px solid #F90;
background: #fafe3d;
}
.thin_area{
......
......@@ -83,3 +83,140 @@ label {
.form-control {
@include box-shadow(none);
}
.issuable-description {
margin-top: 35px;
}
.zennable {
position: relative;
input {
display: none;
}
.collapse {
display: none;
opacity: 0.5;
&:before {
content: '\f066';
font-family: FontAwesome;
color: #000;
font-size: 28px;
position: relative;
padding: 30px 40px 0 0;
}
&:hover {
opacity: 0.8;
}
}
.expand {
opacity: 0.5;
&:before {
content: '\f065';
font-family: FontAwesome;
color: #000;
font-size: 14px;
line-height: 14px;
padding-right: 20px;
position: relative;
vertical-align: middle;
}
&:hover {
opacity: 0.8;
}
}
input:checked ~ .zen-backdrop .expand {
display: none;
}
input:checked ~ .zen-backdrop .collapse {
display: block;
position: absolute;
top: 0;
}
label {
position: absolute;
top: -26px;
right: 0;
font-variant: small-caps;
text-transform: uppercase;
font-size: 10px;
padding: 4px;
font-weight: 500;
letter-spacing: 1px;
&:before {
display: inline-block;
width: 10px;
height: 14px;
}
}
input:checked ~ .zen-backdrop {
background-color: white;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1031;
textarea {
border: none;
box-shadow: none;
border-radius: 0;
color: #000;
font-size: 20px;
line-height: 26px;
padding: 30px;
display: block;
outline: none;
resize: none;
height: 100vh;
max-width: 900px;
margin: 0 auto;
}
}
.zen-backdrop textarea::-webkit-input-placeholder {
color: white;
}
.zen-backdrop textarea:-moz-placeholder {
color: white;
}
.zen-backdrop textarea::-moz-placeholder {
color: white;
}
.zen-backdrop textarea:-ms-input-placeholder {
color: white;
}
input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
color: #999;
}
input:checked ~ .zen-backdrop textarea:-moz-placeholder {
color: #999;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea::-moz-placeholder {
color: #999;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
color: #999;
}
}
......@@ -88,6 +88,10 @@
.description {
padding: 0 15px 10px 15px;
code {
white-space: pre-wrap;
}
}
.title, .context, .description {
......
......@@ -39,7 +39,7 @@
&:hover {
background: $hover;
border-bottom: 1px solid #ADF;
border-bottom: 1px solid darken($hover, 10%);
}
&:last-child {
......
.timeline {
list-style: none;
padding: 20px 0 20px;
position: relative;
&:before {
top: 0;
bottom: 0;
position: absolute;
content: " ";
width: 3px;
background-color: #eeeeee;
margin-left: 29px;
}
.timeline-entry {
position: relative;
margin-top: 5px;
margin-left: 30px;
margin-bottom: 10px;
clear: both;
&:target {
.timeline-entry-inner .timeline-content {
-webkit-animation:target-note 2s linear;
background: $hover;
}
}
.timeline-entry-inner {
position: relative;
margin-left: -20px;
&:before, &:after {
content: " ";
display: table;
}
.timeline-icon {
margin-top: 2px;
background: #fff;
color: #737881;
float: left;
@include border-radius(40px);
@include box-shadow(0 0 0 3px #EEE);
overflow: hidden;
.avatar {
margin: 0;
padding: 0;
}
}
.timeline-content {
position: relative;
background: #f5f5f6;
padding: 10px 15px;
margin-left: 60px;
&:after {
content: '';
display: block;
position: absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 9px 9px 9px 0;
border-color: transparent #f5f5f6 transparent transparent;
left: 0;
top: 10px;
margin-left: -9px;
}
}
}
}
}
......@@ -40,7 +40,7 @@ a {
outline: none;
color: $link_color;
&:hover {
text-decoration: none;
text-decoration: underline;
color: $link_hover_color;
}
......@@ -89,6 +89,8 @@ a:focus {
.wiki {
@include md-typography;
word-wrap: break-word;
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
......
......@@ -16,3 +16,4 @@ body {
.container .content {
margin: 0 0;
}
......@@ -2,13 +2,13 @@
* General Colors
*/
$style_color: #474D57;
$hover: #D9EDF7;
$hover: #FFECDB;
/*
* Link colors
*/
$link_color: #446e9b;
$link_hover_color: #2FA0BB;
$link_hover_color: darken($link-color, 10%);
$btn-border: 1px solid #ccc;
......
......@@ -60,12 +60,13 @@
}
.project-row, .group-row {
padding: 8px 15px !important;
padding: 0 !important;
font-size: 14px;
line-height: 24px;
a {
display: block;
padding: 8px 15px;
}
.project-name, .group-name {
......@@ -99,14 +100,9 @@
margin-right: 15px;
font-size: 20px;
margin-bottom: 15px;
border: 1px solid #EEE;
padding: 8px 12px;
border-radius: 50px;
background: #f5f5f5;
text-align: center;
i {
color: #BBB;
color: #888;
}
}
......
......@@ -125,8 +125,6 @@
}
.line_content {
display: block;
white-space: pre;
height: 18px;
margin: 0px;
padding: 0px 0.5em;
border: none;
......@@ -341,3 +339,12 @@
margin: 0;
border: none;
}
.diff-file .line_content {
white-space: pre;
}
.diff-wrap-lines .line_content {
white-space: pre-wrap;
}
......@@ -17,3 +17,56 @@
}
}
}
.shortcut-mappings {
font-size: 12px;
color: #555;
tbody:first-child tr:first-child {
padding-top: 0
}
th {
padding-top: 15px;
font-size: 14px;
line-height: 1.5;
color: #333;
text-align: left
}
td {
padding-top: 3px;
padding-bottom: 3px;
vertical-align: top;
line-height: 20px
}
.shortcut {
padding-right: 10px;
color: #999;
text-align: right;
white-space: nowrap
}
.key {
@extend .label;
@extend .label-inverse;
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
padding: 3px 5px;
}
}
.modal-body {
position: relative;
overflow-y: auto;
padding: 15px;
}
body.modal-open {
overflow: hidden;
}
.modal .modal-dialog {
width: 860px;
}
......@@ -6,6 +6,21 @@
}
.login-box{
padding: 0 15px;
.login-heading h3 {
font-weight: 300;
line-height: 2;
}
.login-footer {
margin-top: 10px;
}
.btn {
padding: 12px !important;
@extend .btn-block;
}
}
.brand-image {
......@@ -19,7 +34,7 @@
}
}
.login-logo{
.login-logo {
margin: 10px 0 30px 0;
display: block;
}
......@@ -64,4 +79,8 @@
color: #a00;
}
}
.brand-holder {
border-right: 1px solid #EEE;
}
}
......@@ -35,39 +35,31 @@
width: 1%;
&.active {
a {
color: #333;
color: $link_color;
font-weight: bold;
&:after {
content: '';
display: block;
position: relative;
bottom: 8px;
left: 50%;
width: 0;
height: 0;
border-color: transparent transparent #333 transparent;
bottom: -1px;
border-color: $link_color;
border-style: solid;
border-width: 6px;
margin-left: -6px;
border-width: 2px;
}
}
}
&:hover {
a {
color: $link_color;
color: $link_hover_color;
&:after {
content: '';
display: block;
position: relative;
bottom: 8px;
left: 50%;
width: 0;
height: 0;
border-color: transparent transparent $link_color transparent;
bottom: -1px;
border-color: $link_hover_color;
border-style: solid;
border-width: 6px;
margin-left: -6px;
border-width: 2px;
}
}
}
......@@ -90,7 +82,6 @@
line-height: 34px;
color: #777;
text-shadow: 0 1px 1px white;
padding: 0 10px;
text-decoration: none;
padding-top: 2px;
}
......
......@@ -17,7 +17,6 @@ ul.notes {
.discussion-header,
.note-header {
@extend .cgray;
padding-top: 5px;
padding-bottom: 15px;
.avatar {
......@@ -43,34 +42,19 @@ ul.notes {
}
.discussion {
padding: 10px 0;
overflow: hidden;
display: block;
position:relative;
border-bottom: 1px solid #EEE;
.discussion-body {
margin-left: 50px;
}
}
.note {
padding: 8px 0;
overflow: hidden;
display: block;
position:relative;
border-bottom: 1px solid #eee;
p { color: $style_color; }
.avatar {
margin-top: 3px;
}
.attachment {
font-size: 14px;
}
.note-body {
@include md-typography;
margin-left: 43px;
}
.note-header {
padding-bottom: 3px;
......@@ -80,11 +64,6 @@ ul.notes {
border-bottom: none;
}
}
.note:target {
-webkit-animation:target-note 2s linear;
background: #fffff0;
}
}
.diff-file .notes_holder {
......@@ -99,7 +78,7 @@ ul.notes {
&.notes_line {
text-align: center;
padding: 10px 0;
background: #eee;
background: #FFF;
}
&.notes_line2 {
text-align: center;
......@@ -111,6 +90,9 @@ ul.notes {
border-width: 1px 0;
padding-top: 0;
vertical-align: top;
&.parallel{
border-width: 1px;
}
}
}
}
......@@ -362,3 +344,7 @@ ul.notes {
border-top: 1px solid #DDD;
}
}
.discussion-notes-count {
font-size: 16px;
}
......@@ -15,62 +15,64 @@
}
.project-home-panel {
border-bottom: 1px solid #DDD;
padding-bottom: 15px;
margin-bottom: 30px;
margin-bottom: 15px;
&.empty-project {
border-bottom: 0px;
padding-bottom: 15px;
margin-bottom: 0px;
border-bottom: 0px;
padding-bottom: 15px;
margin-bottom: 0px;
}
.project-home-title {
font-size: 18px;
color: #444;
margin: 0;
line-height: 32px;
}
.project-home-dropdown {
margin-left: 10px;
float: right;
}
.project-home-extra {
margin-top: 15px;
.project-home-row {
@extend .clearfix;
margin-bottom: 15px;
.project-home-desc {
float: left;
color: #777;
margin-bottom: 10px;
color: #666;
font-size: 16px;
}
.project-home-links {
.star-fork-buttons {
float: right;
a {
margin-left: 10px;
font-weight: 500;
width: 200px;
font-size: 14px;
font-weight: bold;
.star-buttons, .fork-buttons {
float: right;
margin-left: 20px;
.count {
margin-left: 5px;
}
}
}
}
.visibility-level-label {
font-size: 17px;
background: #f1f1f1;
border-radius: 4px;
color: #444;
position: absolute;
margin-left: -55px;
text-shadow: 0 1px 1px #FFF;
width: 40px;
text-align: center;
padding: 6px;
color: #555;
font-weight: bold;
i {
color: inherit;
}
}
}
.project-home-links {
padding: 10px 0px;
float: right;
a {
margin-left: 10px;
font-weight: 500;
}
}
.git-clone-holder {
.project-home-dropdown + & {
margin-right: 45px;
......@@ -159,6 +161,7 @@ ul.nav.nav-projects-tabs {
li {
.project-info {
margin-bottom: 10px;
overflow: hidden;
}
.access-icon {
......@@ -195,8 +198,8 @@ ul.nav.nav-projects-tabs {
white-space: normal;
text-align: left;
padding: 10px 15px;
background-color: #F1f1f1;
border-color: #EEE;
background-color: #F9F9F9;
border-color: #DDD;
&:hover {
background-color: #eee;
......
.search-results {
.search-result-row {
border-bottom: 1px solid #EEE;
padding-bottom: 10px;
margin-bottom: 10px;
}
}
......@@ -17,9 +17,17 @@ class Projects::BranchesController < Projects::ApplicationController
end
def create
@branch = CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user)
redirect_to project_tree_path(@project, @branch.name)
result = CreateBranchService.new.execute(project,
params[:branch_name],
params[:ref],
current_user)
if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
else
@error = result[:message]
render action: 'new'
end
end
def destroy
......
......@@ -31,7 +31,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true)
@diff = Gitlab::DiffParser.new(diffy.diff.scan(/.*\n/))
@diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false
end
......
......@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
terms = params['issue_search']
@issues = issues_filtered
@issues = @issues.where("title LIKE ? OR description LIKE ?", "%#{terms}%", "%#{terms}%") if terms.present?
@issues = @issues.full_search(terms) if terms.present?
@issues = @issues.page(params[:page]).per(20)
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
......
......@@ -52,7 +52,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' }
format.js { render nothing: true }
format.js
end
end
......
......@@ -30,8 +30,10 @@ class Projects::NotesController < Projects::ApplicationController
end
def update
note.update_attributes(note_params)
note.reset_events_cache
if note.editable?
note.update_attributes(note_params)
note.reset_events_cache
end
respond_to do |format|
format.json { render_note_json(note) }
......@@ -40,8 +42,10 @@ class Projects::NotesController < Projects::ApplicationController
end
def destroy
note.destroy
note.reset_events_cache
if note.editable?
note.destroy
note.reset_events_cache
end
respond_to do |format|
format.js { render nothing: true }
......
......@@ -29,12 +29,10 @@ class Projects::RawController < Projects::ApplicationController
private
def get_blob_type
if @blob.mime_type =~ /html|javascript/
if @blob.text?
'text/plain; charset=utf-8'
elsif @blob.name =~ /(?:msi|exe|rar|r0\d|7z|7zip|zip)$/
'application/octet-stream'
else
@blob.mime_type
'application/octet-stream'
end
end
end
......
......@@ -63,7 +63,7 @@ class Projects::SnippetsController < Projects::ApplicationController
def raw
send_data(
@snippet.content,
type: "text/plain",
type: 'text/plain; charset=utf-8',
disposition: 'inline',
filename: @snippet.file_name
)
......
......@@ -13,10 +13,16 @@ class Projects::TagsController < Projects::ApplicationController
end
def create
@tag = CreateTagService.new.execute(@project, params[:tag_name],
params[:ref], current_user)
redirect_to project_tags_path(@project)
result = CreateTagService.new.execute(@project, params[:tag_name],
params[:ref], params[:message],
current_user)
if result[:status] == :success
@tag = result[:tag]
redirect_to project_tags_path(@project)
else
@error = result[:message]
render action: 'new'
end
end
def destroy
......@@ -28,7 +34,7 @@ class Projects::TagsController < Projects::ApplicationController
respond_to do |format|
format.html { redirect_to project_tags_path }
format.js { render nothing: true }
format.js
end
end
end
......@@ -103,7 +103,15 @@ class ProjectsController < ApplicationController
::Projects::DestroyService.new(@project, current_user, {}).execute
respond_to do |format|
format.html { redirect_to root_path }
format.html do
flash[:alert] = "Project deleted."
if request.referer.include?("/admin")
redirect_to admin_projects_path
else
redirect_to projects_dashboard_path
end
end
end
end
......
......@@ -4,14 +4,33 @@ class SearchController < ApplicationController
def show
@project = Project.find_by(id: params[:project_id]) if params[:project_id].present?
@group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
if @project
return access_denied! unless can?(current_user, :download_code, @project)
@search_results = if @project
return access_denied! unless can?(current_user, :download_code, @project)
@search_results = Search::ProjectService.new(@project, current_user, params).execute
else
@search_results = Search::GlobalService.new(current_user, params).execute
end
unless %w(blobs notes issues merge_requests wiki_blobs).
include?(@scope)
@scope = 'blobs'
end
Search::ProjectService.new(@project, current_user, params).execute
elsif @show_snippets
unless %w(snippet_blobs snippet_titles).include?(@scope)
@scope = 'snippet_blobs'
end
Search::SnippetService.new(current_user, params).execute
else
unless %w(projects issues merge_requests).include?(@scope)
@scope = 'projects'
end
Search::GlobalService.new(current_user, params).execute
end
@objects = @search_results.objects(@scope, params[:page])
end
def autocomplete
......
......@@ -86,7 +86,7 @@ class SnippetsController < ApplicationController
def raw
send_data(
@snippet.content,
type: "text/plain",
type: 'text/plain; charset=utf-8',
disposition: 'inline',
filename: @snippet.file_name
)
......
# BaseFinder
# IssuableFinder
#
# Used to filter Issues and MergeRequests collections by set of params
#
......@@ -16,7 +16,9 @@
# label_name: string
# sort: string
#
class BaseFinder
require_relative 'projects_finder'
class IssuableFinder
attr_accessor :current_user, :params
def execute(current_user, params)
......
......@@ -15,7 +15,7 @@
# label_name: string
# sort: string
#
class IssuesFinder < BaseFinder
class IssuesFinder < IssuableFinder
def klass
Issue
end
......
......@@ -15,7 +15,7 @@
# label_name: string
# sort: string
#
class MergeRequestsFinder < BaseFinder
class MergeRequestsFinder < IssuableFinder
def klass
MergeRequest
end
......
......@@ -178,6 +178,8 @@ module ApplicationHelper
def search_placeholder
if @project && @project.persisted?
"Search in this project"
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
"Search in this group"
else
......
......@@ -16,38 +16,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer))
end
def each_diff_line(diff, index)
Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
.each do |full_line, type, line_code, line_new, line_old|
yield(full_line, type, line_code, line_new, line_old)
end
end
def each_diff_line_near(diff, index, expected_line_code)
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
each_diff_line(diff, index) do |full_line, type, line_code, line_new, line_old|
line = [full_line, type, line_code, line_new, line_old]
if line_code != expected_line_code
if type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines.push(line)
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
else
yield(prev_match_line) if !prev_match_line.nil?
prev_lines.each { |ln| yield(ln) }
yield(line)
break
end
end
end
def image_diff_class(diff)
if diff.deleted_file
"deleted"
......@@ -63,14 +31,6 @@ module CommitsHelper
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
# Breadcrumb links for a Project and, if applicable, a tree path
def commits_breadcrumbs
return unless @project && @ref
......@@ -105,82 +65,6 @@ module CommitsHelper
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
end
def parallel_diff_lines(project, commit, diff, file)
old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
deleted_lines = {}
added_lines = {}
each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old|
if type == "old"
deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
elsif type == "new"
added_lines[line_new] = { line_code: line_code, type: type, line: line }
end
end
max_length = old_file ? [old_file.loc, file.loc].max : file.loc
offset1 = 0
offset2 = 0
old_lines = []
new_lines = []
max_length.times do |line_index|
line_index1 = line_index - offset1
line_index2 = line_index - offset2
deleted_line = deleted_lines[line_index1 + 1]
added_line = added_lines[line_index2 + 1]
old_line = old_file.lines[line_index1] if old_file
new_line = file.lines[line_index2]
if deleted_line && added_line
elsif deleted_line
new_line = nil
offset2 += 1
elsif added_line
old_line = nil
offset1 += 1
end
old_lines[line_index] = DiffLine.new
new_lines[line_index] = DiffLine.new
# old
if line_index == 0 && diff.new_file
old_lines[line_index].type = :file_created
old_lines[line_index].content = 'File was created'
elsif deleted_line
old_lines[line_index].type = :deleted
old_lines[line_index].content = old_line
old_lines[line_index].num = line_index1 + 1
old_lines[line_index].code = deleted_line[:line_code]
elsif old_line
old_lines[line_index].type = :no_change
old_lines[line_index].content = old_line
old_lines[line_index].num = line_index1 + 1
else
old_lines[line_index].type = :added
end
# new
if line_index == 0 && diff.deleted_file
new_lines[line_index].type = :file_deleted
new_lines[line_index].content = "File was deleted"
elsif added_line
new_lines[line_index].type = :added
new_lines[line_index].num = line_index2 + 1
new_lines[line_index].content = new_line
new_lines[line_index].code = added_line[:line_code]
elsif new_line
new_lines[line_index].type = :no_change
new_lines[line_index].num = line_index2 + 1
new_lines[line_index].content = new_line
else
new_lines[line_index].type = :deleted
end
end
return old_lines, new_lines
end
def link_to_browse_code(project, commit)
if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path)
......@@ -229,14 +113,6 @@ module CommitsHelper
end
end
def diff_file_mode_changed?(diff)
diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def view_file_btn(commit_sha, diff, project)
link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file' do
......
module DiffHelper
def safe_diff_files(diffs)
def allowed_diff_size
if diff_hard_limit_enabled?
diffs.first(Commit::DIFF_HARD_LIMIT_FILES)
Commit::DIFF_HARD_LIMIT_FILES
else
diffs.first(Commit::DIFF_SAFE_FILES)
Commit::DIFF_SAFE_FILES
end
end
def safe_diff_files(diffs)
diffs.first(allowed_diff_size).map do |diff|
Gitlab::Diff::File.new(diff)
end
end
def show_diff_size_warninig?(diffs)
safe_diff_files(diffs).size < diffs.size
def show_diff_size_warning?(diffs)
diffs.size > allowed_diff_size
end
def diff_hard_limit_enabled?
......@@ -19,4 +25,96 @@ module DiffHelper
false
end
end
def generate_line_code(file_path, line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def parallel_diff(diff_file, index)
lines = []
skip_next = false
# Building array of lines
#
# [
# left_type, left_line_number, left_line_content, left_line_code,
# right_line_type, right_line_number, right_line_content, right_line_code
# ]
#
diff_file.diff_lines.each do |line|
full_line = line.text
type = line.type
line_code = generate_line_code(diff_file.file_path, line)
line_new = line.new_pos
line_old = line.old_pos
next_line = diff_file.next_line(line.index)
if next_line
next_line_code = generate_line_code(diff_file.file_path, next_line)
next_type = next_line.type
next_line = next_line.text
end
if type == 'match' || type.nil?
# line in the right panel is the same as in the left one
line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code]
lines.push(line)
elsif type == 'old'
if next_type == 'new'
# Left side has text removed, right side has text added
line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code]
lines.push(line)
skip_next = true
elsif next_type == 'old' || next_type.nil?
# Left side has text removed, right side doesn't have any change
# No next line code, no new line number, no new line text
line = [type, line_old, full_line, line_code, next_type, nil, "&nbsp;", nil]
lines.push(line)
end
elsif type == 'new'
if skip_next
# Change has been already included in previous line so no need to do it again
skip_next = false
next
else
# Change is only on the right side, left side has no change
line = [nil, nil, "&nbsp;", line_code, type, line_new, full_line, line_code]
lines.push(line)
end
end
end
lines
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
def line_comments
@line_comments ||= @line_notes.group_by(&:line_code)
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
comments_left = comments_right = nil
unless type_left.nil? && type_right == 'new'
comments_left = line_comments[line_code_left]
end
unless type_left.nil? && type_right.nil?
comments_right = line_comments[line_code_right]
end
[comments_left, comments_right]
end
end
......@@ -52,6 +52,8 @@ module EventsHelper
"#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
elsif event.membership_changed?
"#{event.author_name} #{event.action_name} #{event.project_name}"
elsif event.note? && event.note_commit?
"#{event.author_name} commented on #{event.note_target_type} #{event.note_short_commit_id} at #{event.project_name}"
elsif event.note?
"#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}"
else
......@@ -64,6 +66,8 @@ module EventsHelper
project_issue_url(event.project, event.issue)
elsif event.merge_request?
project_merge_request_url(event.project, event.merge_request)
elsif event.note? && event.note_commit?
project_commit_url(event.project, event.note_target)
elsif event.note?
if event.note_target
if event.note_commit?
......@@ -94,6 +98,8 @@ module EventsHelper
render "events/event_push", event: event
elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request
elsif event.push?
render "events/event_push", event: event
elsif event.note?
render "events/event_note", note: event.note
end
......
......@@ -123,7 +123,7 @@ module ProjectsHelper
end
def link_to_toggle_star(title, starred, signed_in)
cls = 'btn btn-block'
cls = 'star-btn'
cls += ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do
......@@ -151,7 +151,7 @@ module ProjectsHelper
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to toggle_star_project_path(@project), link_opts do
toggle_html + count_html
toggle_html + ' ' + count_html
end
end
end
......@@ -261,4 +261,10 @@ module ProjectsHelper
project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name))
end
end
def hidden_pass_url(original_url)
result = URI(original_url)
result.password = '*****' if result.password.present?
result
end
end
......@@ -91,4 +91,21 @@ module SearchHelper
def search_result_sanitize(str)
Sanitize.clean(str)
end
def search_filter_path(options={})
exist_opts = {
search: params[:search],
project_id: params[:project_id],
group_id: params[:group_id],
scope: params[:scope]
}
options = exist_opts.merge(options)
search_path(options)
end
# Sanitize html generated after parsing markdown from issue description or comment
def search_md_sanitize(html)
sanitize(html, tags: %w(a p ol ul li pre code))
end
end
......@@ -49,6 +49,10 @@ module Issuable
where("LOWER(title) like :query", query: "%#{query.downcase}%")
end
def full_search(query)
where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%")
end
def sort(method)
case method.to_s
when 'newest' then reorder("#{table_name}.created_at DESC")
......
......@@ -15,7 +15,6 @@
# milestone_id :integer
# state :string(255)
# iid :integer
# attachment :string(255)
#
require 'carrierwave/orm/activerecord'
......
......@@ -31,7 +31,9 @@ class Key < ActiveRecord::Base
after_create :add_to_shell
after_create :notify_user
after_create :post_create_hook
after_destroy :remove_from_shell
after_destroy :post_destroy_hook
def strip_white_space
self.key = key.strip unless key.blank?
......@@ -58,6 +60,10 @@ class Key < ActiveRecord::Base
NotificationService.new.new_key(self)
end
def post_create_hook
SystemHooksService.new.execute_hooks_for(self, :create)
end
def remove_from_shell
GitlabShellWorker.perform_async(
:remove_key,
......@@ -66,6 +72,10 @@ class Key < ActiveRecord::Base
)
end
def post_destroy_hook
SystemHooksService.new.execute_hooks_for(self, :destroy)
end
private
def generate_fingerpint
......
# == Schema Information
#
# Table name: labels
#
# id :integer not null, primary key
# title :string(255)
# color :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
#
class Label < ActiveRecord::Base
DEFAULT_COLOR = '#428BCA'
......
# == Schema Information
#
# Table name: label_links
#
# id :integer not null, primary key
# label_id :integer
# target_id :integer
# target_type :string(255)
# created_at :datetime
# updated_at :datetime
#
class LabelLink < ActiveRecord::Base
belongs_to :target, polymorphic: true
belongs_to :label
......
......@@ -17,6 +17,7 @@
# target_project_id :integer not null
# iid :integer
# description :text
# position :integer default(0)
#
require Rails.root.join("app/models/commit")
......
......@@ -3,7 +3,7 @@
# Table name: merge_request_diffs
#
# id :integer not null, primary key
# state :string(255) default("collected"), not null
# state :string(255)
# st_commits :text
# st_diffs :text
# merge_request_id :integer not null
......
......@@ -178,12 +178,6 @@ module Network
space = find_free_space(time_range, 2, space_base)
leaves.each do |l|
l.spaces << space
# Also add space to parent
l.parents(@map).each do |parent|
if 0 < parent.space && parent.space < space
parent.spaces << space
end
end
end
# and mark it as reserved
......
......@@ -117,6 +117,25 @@ class Note < ActiveRecord::Base
})
end
def create_new_commits_note(noteable, project, author, commits)
commits_text = ActionController::Base.helpers.pluralize(commits.size, 'new commit')
body = "Added #{commits_text}:\n\n"
commits.each do |commit|
message = "* #{commit.short_id} - #{commit.title}"
body << message
body << "\n"
end
create(
noteable: noteable,
project: project,
author: author,
note: body,
system: true
)
end
def discussions_from_notes(notes)
discussion_ids = []
discussions = []
......@@ -146,6 +165,10 @@ class Note < ActiveRecord::Base
def cross_reference_exists?(noteable, mentioner)
where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any?
end
def search(query)
where("note like :query", query: "%#{query}%")
end
end
def commit_author
......@@ -186,9 +209,10 @@ class Note < ActiveRecord::Base
noteable.diffs.each do |mr_diff|
next unless mr_diff.new_path == self.diff.new_path
Gitlab::DiffParser.new(mr_diff.diff.lines.to_a, mr_diff.new_path).
each do |full_line, type, line_code, line_new, line_old|
if full_line == diff_line
lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a)
lines.each do |line|
if line.text == diff_line
return true
end
end
......@@ -209,6 +233,14 @@ class Note < ActiveRecord::Base
diff.new_path if diff
end
def file_path
if diff.new_path.present?
diff.new_path
elsif diff.old_path.present?
diff.old_path
end
end
def diff_old_line
line_code.split('_')[1].to_i
end
......@@ -217,19 +249,49 @@ class Note < ActiveRecord::Base
line_code.split('_')[2].to_i
end
def generate_line_code(line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def diff_line
return @diff_line if @diff_line
if diff
Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
.each do |full_line, type, line_code, line_new, line_old|
@diff_line = full_line if line_code == self.line_code
diff_lines.each do |line|
if generate_line_code(line) == self.line_code
@diff_line = line.text
end
end
end
@diff_line
end
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
diff_lines.each do |line|
if generate_line_code(line) != self.line_code
if line.type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines.push(line)
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
else
prev_lines << line
return prev_lines
end
end
end
def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
end
def discussion_id
@discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
end
......@@ -333,4 +395,8 @@ class Note < ActiveRecord::Base
def set_references
notice_added_references(project, author)
end
def editable?
!system
end
end
......@@ -22,7 +22,8 @@
# visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null
# import_status :string(255)
# star_count :integer
# repository_size :float default(0.0)
# star_count :integer default(0), not null
#
class Project < ActiveRecord::Base
......@@ -73,7 +74,7 @@ class Project < ActiveRecord::Base
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
......@@ -183,11 +184,11 @@ class Project < ActiveRecord::Base
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
end
def search query
def search(query)
joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%")
end
def search_by_title query
def search_by_title(query)
where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%")
end
......@@ -414,18 +415,35 @@ class Project < ActiveRecord::Base
def update_merge_requests(oldrev, newrev, ref, user)
return true unless ref =~ /heads/
branch_name = ref.gsub("refs/heads/", "")
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
commits = self.repository.commits_between(oldrev, newrev)
c_ids = commits.map(&:id)
# Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
mrs.uniq.each do |merge_request|
MergeRequests::MergeService.new.execute(merge_request, user, nil)
end
# Update code for merge requests into project between project branches
mrs = self.merge_requests.opened.by_branch(branch_name).to_a
# Update code for merge requests between project and project fork
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
mrs.uniq.each do |merge_request|
merge_request.reload_code
merge_request.mark_as_unchecked
end
# Add comment about pushing new commits to merge requests
mrs = self.merge_requests.opened.where(source_branch: branch_name).to_a
mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
mrs.uniq.each do |merge_request|
Note.create_new_commits_note(merge_request, merge_request.project,
user, commits)
end
true
end
......
......@@ -5,21 +5,17 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
class AssemblaService < Service
include HTTParty
prop_accessor :token, :subdomain
validates :token, presence: true, if: :activated?
def title
......
......@@ -5,19 +5,15 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
class CampfireService < Service
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated?
def title
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
# Base class for CI services
# List methods you need to implement to get your CI service
# working with GitLab Merge Requests
......
......@@ -5,19 +5,15 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
class EmailsOnPushService < Service
prop_accessor :recipients
validates :recipients, presence: true, if: :activated?
def title
......
......@@ -5,21 +5,17 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
require "flowdock-git-hook"
class FlowdockService < Service
prop_accessor :token
validates :token, presence: true, if: :activated?
def title
......
......@@ -5,21 +5,17 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
require "gemnasium/gitlab_service"
class GemnasiumService < Service
prop_accessor :token, :api_key
validates :token, :api_key, presence: true, if: :activated?
def title
......
......@@ -5,19 +5,15 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# property :text
#
class GitlabCiService < CiService
prop_accessor :project_url, :token
validates :project_url, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
......
......@@ -5,21 +5,17 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
class HipchatService < Service
MAX_COMMITS = 3
prop_accessor :token, :room
validates :token, presence: true, if: :activated?
def title
......
......@@ -5,21 +5,17 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
class PivotaltrackerService < Service
include HTTParty
prop_accessor :token
validates :token, presence: true, if: :activated?
def title
......
......@@ -5,19 +5,15 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
class SlackService < Service
prop_accessor :room, :subdomain, :token
validates :room, presence: true, if: :activated?
validates :subdomain, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
......
......@@ -2,8 +2,9 @@ class ProjectWiki
include Gitlab::ShellAdapter
MARKUPS = {
"Markdown" => :markdown,
"RDoc" => :rdoc
'Markdown' => :markdown,
'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc
}
class CouldNotCreateWikiError < StandardError; end
......
......@@ -64,10 +64,10 @@ class Repository
gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
end
def add_tag(tag_name, ref)
def add_tag(tag_name, ref, message = nil)
Rails.cache.delete(cache_key(:tag_names))
gitlab_shell.add_tag(path_with_namespace, tag_name, ref)
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(branch_name)
......
......@@ -5,23 +5,21 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
#
# properties :text
# To add new service you should build a class inherited from Service
# and implement a set of methods
class Service < ActiveRecord::Base
serialize :properties, JSON
default_value_for :active, false
after_initialize :initialize_properties
belongs_to :project
has_one :service_hook
......@@ -35,6 +33,10 @@ class Service < ActiveRecord::Base
:common
end
def initialize_properties
self.properties = {} if properties.nil?
end
def title
# implement inside child
end
......@@ -63,4 +65,20 @@ class Service < ActiveRecord::Base
def can_test?
!project.empty_repo?
end
# Provide convenient accessor methods
# for each serialized property.
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}
properties['#{arg}']
end
def #{arg}=(value)
self.properties['#{arg}'] = value
end
}
end
end
end
......@@ -65,4 +65,18 @@ class Snippet < ActiveRecord::Base
def expired?
expires_at && expires_at < Time.current
end
class << self
def search(query)
where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%")
end
def search_code(query)
where('(content LIKE :query)', query: "%#{query}%")
end
def accessible_to(user)
where('private = ? OR author_id = ?', false, user)
end
end
end
......@@ -2,11 +2,11 @@
#
# Table name: users_star_projects
#
# id :integer not null, primary key
# starrer_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# id :integer not null, primary key
# project_id :integer not null
# user_id :integer not null
# created_at :datetime
# updated_at :datetime
#
class UsersStarProject < ActiveRecord::Base
......
......@@ -23,7 +23,7 @@ class WebHook < ActiveRecord::Base
default_value_for :merge_requests_events, false
# HTTParty timeout
default_timeout 10
default_timeout Gitlab.config.gitlab.webhook_timeout
validates :url, presence: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
......
......@@ -4,8 +4,8 @@ class CompareService
def execute(current_user, source_project, source_branch, target_project, target_branch)
# Try to compare branches to get commits list and diffs
#
# Note: Use satellite only when need to compare between to repos
# because satellites are slower then operations on bare repo
# Note: Use satellite only when need to compare between two repos
# because satellites are slower than operations on bare repo
if target_project == source_project
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
......
class CreateBranchService
def execute(project, branch_name, ref, current_user)
valid_branch = Gitlab::GitRefValidator.validate(branch_name)
if valid_branch == false
return error('Branch name invalid')
end
repository = project.repository
existing_branch = repository.find_branch(branch_name)
if existing_branch
return error('Branch already exists')
end
repository.add_branch(branch_name, ref)
new_branch = repository.find_branch(branch_name)
if new_branch
Event.create_ref_event(project, current_user, new_branch, 'add')
return success(new_branch)
else
return error('Invalid reference name')
end
end
def error(message)
{
message: message,
status: :error
}
end
new_branch
def success(branch)
{
branch: branch,
status: :success
}
end
end
class CreateTagService
def execute(project, tag_name, ref, current_user)
def execute(project, tag_name, ref, message, current_user)
valid_tag = Gitlab::GitRefValidator.validate(tag_name)
if valid_tag == false
return error('Tag name invalid')
end
repository = project.repository
repository.add_tag(tag_name, ref)
existing_tag = repository.find_tag(tag_name)
if existing_tag
return error('Tag already exists')
end
if message
message.gsub!(/^\s+|\s+$/, '')
end
repository.add_tag(tag_name, ref, message)
new_tag = repository.find_tag(tag_name)
if new_tag
Event.create_ref_event(project, current_user, new_tag, 'add', 'refs/tags')
return success(new_tag)
else
return error('Invalid reference name')
end
end
def error(message)
{
message: message,
status: :error
}
end
new_tag
def success(branch)
{
tag: branch,
status: :success
}
end
end
......@@ -5,21 +5,21 @@ class DeleteBranchService
# No such branch
unless branch
return error('No such branch')
return error('No such branch', 404)
end
if branch_name == repository.root_ref
return error('Cannot remove HEAD branch')
return error('Cannot remove HEAD branch', 405)
end
# Dont allow remove of protected branch
if project.protected_branch?(branch_name)
return error('Protected branch cant be removed')
return error('Protected branch cant be removed', 405)
end
# Dont allow user to remove branch if he is not allowed to push
unless current_user.can?(:push_code, project)
return error('You dont have push access to repo')
return error('You dont have push access to repo', 405)
end
if repository.rm_branch(branch_name)
......@@ -30,9 +30,10 @@ class DeleteBranchService
end
end
def error(message)
def error(message, return_code = 400)
{
message: message,
return_code: return_code,
state: :error
}
end
......
......@@ -7,7 +7,12 @@ module Projects
end
def execute
project = @from_project.dup
project_params = {
visibility_level: @from_project.visibility_level,
description: @from_project.description,
}
project = Project.new(project_params)
project.name = @from_project.name
project.path = @from_project.path
project.namespace = current_user.namespace
......
......@@ -7,30 +7,12 @@ module Search
end
def execute
query = params[:search]
query = Shellwords.shellescape(query) if query.present?
return result unless query.present?
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new.execute(current_user)
projects = projects.where(namespace_id: group.id) if group
project_ids = projects.pluck(:id)
result[:projects] = projects.search(query).limit(20)
result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20)
result[:issues] = Issue.where(project_id: project_ids).search(query).order('updated_at DESC').limit(20)
result[:total_results] = %w(projects issues merge_requests).sum { |items| result[items.to_sym].size }
result
end
def result
@result ||= {
projects: [],
merge_requests: [],
issues: [],
notes: [],
total_results: 0,
}
Gitlab::SearchResults.new(project_ids, params[:search])
end
end
end
......@@ -7,39 +7,9 @@ module Search
end
def execute
query = params[:search]
query = Shellwords.shellescape(query) if query.present?
return result unless query.present?
if params[:search_code].present?
if !@project.empty_repo?
blobs = project.repository.search_files(query,
params[:repository_ref])
else
blobs = Array.new
end
blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20)
result[:blobs] = blobs
result[:total_results] = blobs.total_count
else
result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20)
result[:issues] = project.issues.where("title like :query OR description like :query ", query: "%#{query}%").order('updated_at DESC').limit(20)
result[:notes] = Note.where(noteable_type: 'issue').where(project_id: project.id).where("note like :query", query: "%#{query}%").order('updated_at DESC').limit(20)
result[:total_results] = %w(issues merge_requests notes).sum { |items| result[items.to_sym].size }
end
result
end
def result
@result ||= {
merge_requests: [],
issues: [],
blobs: [],
notes: [],
total_results: 0,
}
Gitlab::ProjectSearchResults.new(project.id,
params[:search],
params[:repository_ref])
end
end
end
module Search
class SnippetService
attr_accessor :current_user, :params
def initialize(user, params)
@current_user, @params = user, params.dup
end
def execute
snippet_ids = Snippet.accessible_to(current_user).pluck(:id)
Gitlab::SnippetSearchResults.new(snippet_ids, params[:search])
end
end
end
......@@ -22,6 +22,16 @@ class SystemHooksService
}
case model
when Key
data.merge!(
key: model.key,
id: model.id
)
if model.user
data.merge!(
username: model.user.username
)
end
when Project
owner = model.owner
......
......@@ -94,7 +94,7 @@
%span.light.pull-right
= Milestone.count
%p
Monthly active users
Active users last 30 days
%span.light.pull-right
= User.where("current_sign_in_at > ?", 30.days.ago).count
.col-md-4
......
......@@ -70,6 +70,14 @@
%strong.cred
No
%li
%span.light Current sign-in at:
%strong
- if @user.current_sign_in_at
= @user.current_sign_in_at.stamp("Nov 12, 2031")
- else
never
%li
%span.light Last sign-in at:
%strong
......
......@@ -2,10 +2,9 @@
.panel-heading.clearfix
= search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- if current_user.can_create_group?
%span.pull-right
= link_to new_group_path, class: "btn btn-new" do
%i.icon-plus
New group
= link_to new_group_path, class: "btn btn-new pull-right" do
%i.icon-plus
New group
%ul.well-list.dash-list
- groups.each do |group|
%li.group-row
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment