Commit 3a9682a3 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge dev.gitlab.org:gitlab/gitlabhq into upstream-ce

Conflicts:
	VERSION
	app/helpers/selects_helper.rb
	app/models/project.rb
	app/views/devise/sessions/new.html.haml
	app/views/groups/_settings_nav.html.haml
	app/views/layouts/nav/_admin.html.haml
	app/views/profiles/keys/_key.html.haml
	app/views/profiles/keys/index.html.haml
	app/views/profiles/keys/show.html.haml
	app/views/projects/merge_requests/show/_mr_accept.html.haml
	config/routes.rb
	db/schema.rb
	doc/api/projects.md
	features/steps/shared/paths.rb
parents 859628ed d531de0b
*.log
*.swp
.DS_Store
.bundle .bundle
.chef
.directory
.envrc
.gitlab_shell_secret
.idea
.rbenv-version
.rbx/ .rbx/
db/*.sqlite3
db/*.sqlite3-journal
log/*.log*
tmp/
.sass-cache/
coverage/*
backups/*
*.swp
public/uploads/
.ruby-version
.ruby-gemset .ruby-gemset
.ruby-version
.rvmrc .rvmrc
.rbenv-version .sass-cache/
.directory .secret
nohup.out
Vagrantfile
.vagrant .vagrant
config/gitlab.yml Vagrantfile
backups/*
config/aws.yml
config/database.yml config/database.yml
config/gitlab.yml
config/initializers/omniauth.rb config/initializers/omniauth.rb
config/initializers/rack_attack.rb config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb config/initializers/smtp_settings.rb
config/unicorn.rb
config/resque.yml config/resque.yml
config/aws.yml config/unicorn.rb
coverage/*
db/*.sqlite3
db/*.sqlite3-journal
db/data.yml db/data.yml
.idea
.DS_Store
.chef
vendor/bundle/*
rails_best_practices_output.html
doc/code/* doc/code/*
.secret
*.log
public/uploads.*
public/assets/
.envrc
dump.rdb dump.rdb
log/*.log*
nohup.out
public/assets/
public/uploads.*
public/uploads/
rails_best_practices_output.html
tags tags
.gitlab_shell_secret tmp/
vendor/bundle/*
v 7.7.0 v 7.7.0
- -
- -
- Add Jetbrains Teamcity CI service (Jason Lippert)
- -
- -
- Mention notification level
- Markdown preview in wiki (Yuriy Glukhov)
- Raise group avatar filesize limit to 200kb
- OAuth applications feature
- Show user SSH keys in admin area
- Developer can push to protected branches option
- Set project path instead of project name in create form
- -
- -
- Updates to the messages returned by API (sponsored by O'Reilly Media)
- New UI layout with side navigation
- -
- -
- OAuth applications feature
- -
- Add alert message in case of outdated browser (IE < 10)
- -
- Set project path instead of project name in create form - Added API support for sorting projects
- Update gitlab_git to version 7.0.0.rc13
- -
- -
- New side navigation -
-
-
-
-
-
-
-
-
-
-
-
-
- Change some of application settings on fly in admin area UI
- Redesign signin/signup pages
- Close standard input in Gitlab::Popen.popen
v 7.6.0 v 7.6.0
- Fork repository to groups - Fork repository to groups
...@@ -41,8 +65,15 @@ v 7.6.0 ...@@ -41,8 +65,15 @@ v 7.6.0
- Possibility to create Milestones or Labels when Issues are disabled - Possibility to create Milestones or Labels when Issues are disabled
- Fix bug with showing gpg signature in tag - Fix bug with showing gpg signature in tag
v 7.5.3
- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
v 7.5.2 v 7.5.2
- Don't log Sidekiq arguments by default - Don't log Sidekiq arguments by default
- Fix restore of wiki repositories from backups
v 7.5.1
- Add missing timestamps to 'members' table
v 7.5.0 v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert) - API: Add support for Hipchat (Kevin Houdebert)
...@@ -62,7 +93,7 @@ v 7.5.0 ...@@ -62,7 +93,7 @@ v 7.5.0
- Performance improvements - Performance improvements
- Fix post-receive issue for projects with deleted forks - Fix post-receive issue for projects with deleted forks
- New gitlab-shell version with custom hooks support - New gitlab-shell version with custom hooks support
- Improve code - Improve code
- GitLab CI 5.2+ support (does not support older versions) - GitLab CI 5.2+ support (does not support older versions)
- Fixed bug when you can not push commits starting with 000000 to protected branches - Fixed bug when you can not push commits starting with 000000 to protected branches
- Added a password strength indicator - Added a password strength indicator
......
...@@ -32,9 +32,12 @@ gem 'omniauth-kerberos' ...@@ -32,9 +32,12 @@ gem 'omniauth-kerberos'
gem 'doorkeeper', '2.0.1' gem 'doorkeeper', '2.0.1'
gem "rack-oauth2", "~> 1.0.5" gem "rack-oauth2", "~> 1.0.5"
# Browser detection
gem "browser"
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '7.0.0.rc12' gem "gitlab_git", '7.0.0.rc13'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
...@@ -93,7 +96,7 @@ gem "github-markup" ...@@ -93,7 +96,7 @@ gem "github-markup"
gem 'redcarpet', '~> 3.1.2' gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth' gem 'RedCloth'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.9' gem 'org-ruby', '= 0.9.12'
gem 'creole', '~>0.3.6' gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1' gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4' gem 'asciidoctor', '= 0.1.4'
......
...@@ -50,6 +50,7 @@ GEM ...@@ -50,6 +50,7 @@ GEM
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootstrap-sass (3.0.3.0) bootstrap-sass (3.0.3.0)
sass (~> 3.2) sass (~> 3.2)
browser (0.7.2)
builder (3.2.2) builder (3.2.2)
capybara (2.2.1) capybara (2.2.1)
mime-types (>= 1.16) mime-types (>= 1.16)
...@@ -123,7 +124,7 @@ GEM ...@@ -123,7 +124,7 @@ GEM
equalizer (0.0.8) equalizer (0.0.8)
erubis (2.7.0) erubis (2.7.0)
escape_utils (0.2.4) escape_utils (0.2.4)
eventmachine (1.0.3) eventmachine (1.0.4)
excon (0.32.1) excon (0.32.1)
execjs (2.0.2) execjs (2.0.2)
expression_parser (0.9.0) expression_parser (0.9.0)
...@@ -182,7 +183,7 @@ GEM ...@@ -182,7 +183,7 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.0.1.1) gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1) emoji (~> 1.0.1)
gitlab_git (7.0.0.rc12) gitlab_git (7.0.0.rc13)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
...@@ -280,7 +281,7 @@ GEM ...@@ -280,7 +281,7 @@ GEM
kaminari (0.15.1) kaminari (0.15.1)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
kgio (2.8.1) kgio (2.9.2)
launchy (2.4.2) launchy (2.4.2)
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.1.2) letter_opener (1.1.2)
...@@ -343,7 +344,7 @@ GEM ...@@ -343,7 +344,7 @@ GEM
omniauth-twitter (1.0.1) omniauth-twitter (1.0.1)
multi_json (~> 1.3) multi_json (~> 1.3)
omniauth-oauth (~> 1.0) omniauth-oauth (~> 1.0)
org-ruby (0.9.9) org-ruby (0.9.12)
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pg (0.15.1) pg (0.15.1)
...@@ -409,7 +410,7 @@ GEM ...@@ -409,7 +410,7 @@ GEM
activesupport (= 4.1.1) activesupport (= 4.1.1)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
raindrops (0.12.0) raindrops (0.13.0)
rake (10.3.2) rake (10.3.2)
raphael-rails (2.1.2) raphael-rails (2.1.2)
rb-fsevent (0.9.3) rb-fsevent (0.9.3)
...@@ -619,6 +620,7 @@ DEPENDENCIES ...@@ -619,6 +620,7 @@ DEPENDENCIES
better_errors better_errors
binding_of_caller binding_of_caller
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
browser
capybara (~> 2.2.1) capybara (~> 2.2.1)
carrierwave carrierwave
coffee-rails coffee-rails
...@@ -646,7 +648,7 @@ DEPENDENCIES ...@@ -646,7 +648,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre) gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0) gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc12) gitlab_git (= 7.0.0.rc13)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.0) gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
...@@ -681,7 +683,7 @@ DEPENDENCIES ...@@ -681,7 +683,7 @@ DEPENDENCIES
omniauth-kerberos omniauth-kerberos
omniauth-shibboleth omniauth-shibboleth
omniauth-twitter omniauth-twitter
org-ruby (= 0.9.9) org-ruby (= 0.9.12)
pg pg
poltergeist (~> 1.5.1) poltergeist (~> 1.5.1)
pry pry
......
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell worker: bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default
7.6.0.pre-ee 7.7.0.pre-ee
$ ->
$(":checkbox").change ->
name = $(this).attr("name")
if name == "developers_can_push"
id = $(this).val()
checked = $(this).is(":checked")
url = $(this).data("url")
$.ajax
type: "PUT"
url: url
dataType: "json"
data:
id: id
developers_can_push: checked
success: ->
new Flash("Branch updated.", "notice")
location.reload true
error: ->
new Flash("Failed to update branch!", "alert")
...@@ -207,24 +207,16 @@ li.note { ...@@ -207,24 +207,16 @@ li.note {
} }
} }
.no-ssh-key-message { .browser-alert {
padding: 10px 0; padding: 10px;
background: #C67;
margin: 0;
color: #FFF;
margin-top: -1px;
text-align: center; text-align: center;
background: #C67;
color: #fff;
font-weight: bold;
a { a {
color: #fff; color: #fff;
text-decoration: underline; text-decoration: underline;
} }
.links-xs {
text-align: center;
font-size: 16px;
padding: 5px;
}
} }
.warning_message { .warning_message {
...@@ -282,7 +274,7 @@ img.emoji { ...@@ -282,7 +274,7 @@ img.emoji {
} }
.navless-container { .navless-container {
margin-top: 20px; margin-top: 68px;
} }
.description-block { .description-block {
...@@ -309,11 +301,17 @@ table { ...@@ -309,11 +301,17 @@ table {
.dashboard-intro-icon { .dashboard-intro-icon {
float: left; float: left;
text-align: center;
font-size: 32px; font-size: 32px;
color: #AAA; color: #AAA;
padding: 5px 0; width: 60px;
width: 50px; }
min-height: 100px;
.dashboard-intro-text {
display: inline-block;
margin-left: -60px;
padding-left: 60px;
width: 100%;
} }
.broadcast-message { .broadcast-message {
...@@ -364,3 +362,9 @@ table { ...@@ -364,3 +362,9 @@ table {
.task-status { .task-status {
margin-left: 10px; margin-left: 10px;
} }
#nprogress .spinner {
top: auto !important;
bottom: 20px !important;
left: 20px !important;
}
...@@ -31,7 +31,12 @@ fieldset legend { ...@@ -31,7 +31,12 @@ fieldset legend {
margin-bottom: 18px; margin-bottom: 18px;
background-color: whitesmoke; background-color: whitesmoke;
border-top: 1px solid #e5e5e5; border-top: 1px solid #e5e5e5;
padding-left: 17%; }
@media (min-width: $screen-sm-min) {
.form-actions {
padding-left: 17%;
}
} }
label { label {
...@@ -88,7 +93,8 @@ label { ...@@ -88,7 +93,8 @@ label {
@include box-shadow(none); @include box-shadow(none);
} }
.issuable-description { .issuable-description,
.wiki-content {
margin-top: 35px; margin-top: 35px;
} }
......
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
.edit_note, .edit_note,
.issuable-description, .issuable-description,
.milestone-description, .milestone-description,
.wiki-content,
.merge-request-form { .merge-request-form {
.nav-tabs { .nav-tabs {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -46,4 +46,4 @@ $deleted: #f77; ...@@ -46,4 +46,4 @@ $deleted: #f77;
/** /**
* NProgress customize * NProgress customize
*/ */
$nprogress-color: #3498db; $nprogress-color: #c0392b;
...@@ -145,8 +145,12 @@ ...@@ -145,8 +145,12 @@
* Last push widget * Last push widget
*/ */
.event-last-push { .event-last-push {
overflow: auto;
.event-last-push-text { .event-last-push-text {
@include str-truncated(75%); @include str-truncated(100%);
float:left;
margin-right: -150px;
padding-right: 150px;
line-height: 24px; line-height: 24px;
} }
} }
......
...@@ -4,9 +4,13 @@ ...@@ -4,9 +4,13 @@
*/ */
header { header {
&.navbar-gitlab { &.navbar-gitlab {
z-index: 100;
margin-bottom: 0; margin-bottom: 0;
min-height: 40px; min-height: 40px;
border: none; border: none;
position: fixed;
top: 0;
width: 100%;
.navbar-inner { .navbar-inner {
filter: none; filter: none;
...@@ -82,8 +86,6 @@ header { ...@@ -82,8 +86,6 @@ header {
} }
} }
z-index: 10;
.container { .container {
width: 100% !important; width: 100% !important;
padding-left: 0px; padding-left: 0px;
......
/* Login Page */ /* Login Page */
.login-page { .login-page {
h1 { .container {
font-size: 3em; max-width: 960px;
font-weight: 200;
} }
.login-box{ .navbar-gitlab .container {
padding: 0 15px; max-width: none;
}
.login-heading h3 { .brand-holder {
font-weight: 300; font-size: 18px;
line-height: 2; line-height: 1.5;
}
.login-footer { p {
margin-top: 10px; color: #888;
} }
.btn { h1:first-child {
padding: 12px !important; font-weight: normal;
@extend .btn-block; margin-bottom: 30px;
} }
}
.brand-image {
img { img {
max-width: 100%; max-width: 100%;
margin-bottom: 20px; margin-bottom: 30px;
} }
&.default-brand-image { a {
margin: 0 80px; font-weight: bold;
} }
} }
.login-logo { .login-box{
margin: 10px 0 30px 0; background: #fafafa;
display: block; border-radius: 10px;
box-shadow: 0 0px 2px #CCC;
padding: 15px;
.login-heading h3 {
font-weight: 300;
line-height: 1.5;
margin: 0;
display: none;
}
.login-footer {
margin-top: 10px;
}
a.forgot {
float: right;
padding-top: 6px
}
.nav .active a {
background: transparent;
}
} }
.form-control { .form-control {
background-color: #F5F5F5; font-size: 14px;
font-size: 16px; padding: 10px 8px;
padding: 14px 10px;
width: 100%; width: 100%;
height: auto; height: auto;
...@@ -68,11 +86,6 @@ ...@@ -68,11 +86,6 @@
} }
} }
.login-box a.forgot {
float: right;
padding-top: 6px
}
.devise-errors { .devise-errors {
h2 { h2 {
font-size: 14px; font-size: 14px;
...@@ -80,7 +93,19 @@ ...@@ -80,7 +93,19 @@
} }
} }
.brand-holder { .remember-me {
border-right: 1px solid #EEE; margin-top: -10px;
label {
font-weight: normal;
}
}
}
@media (max-width: $screen-xs-max) {
.login-page {
.col-sm-5.pull-right {
float: none !important;
}
} }
} }
...@@ -11,10 +11,27 @@ ...@@ -11,10 +11,27 @@
} }
} }
.accept-group { .accept-merge-holder {
label { margin-top: 5px;
margin: 5px;
.accept-action {
display: inline-block;
.accept_merge_request {
padding: 10px 20px;
}
}
.accept-control {
display: inline-block;
margin-left: 20px; margin-left: 20px;
padding: 10px 0;
line-height: 20px;
font-weight: bold;
.checkbox {
margin: 0;
}
} }
} }
} }
...@@ -170,7 +187,3 @@ ...@@ -170,7 +187,3 @@
.merge-request-show-labels .label { .merge-request-show-labels .label {
padding: 6px 10px; padding: 6px 10px;
} }
.mr-commits .commit {
padding: 10px 15px;
}
...@@ -62,6 +62,7 @@ ul.notes { ...@@ -62,6 +62,7 @@ ul.notes {
} }
.note-body { .note-body {
@include md-typography; @include md-typography;
overflow: auto;
} }
.note-header { .note-header {
padding-bottom: 3px; padding-bottom: 3px;
......
...@@ -308,3 +308,10 @@ ul.nav.nav-projects-tabs { ...@@ -308,3 +308,10 @@ ul.nav.nav-projects-tabs {
display: none; display: none;
} }
} }
table.table.protected-branches-list tr.no-border {
th, td {
border: 0;
}
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
} }
.sidebar-wrapper { .sidebar-wrapper {
z-index: 99;
overflow-y: auto; overflow-y: auto;
background: #F5F5F5; background: #F5F5F5;
} }
...@@ -11,6 +12,7 @@ ...@@ -11,6 +12,7 @@
width: 100%; width: 100%;
padding: 15px; padding: 15px;
background: #FFF; background: #FFF;
margin-top: 48px;
} }
.nav-sidebar { .nav-sidebar {
...@@ -85,15 +87,7 @@ ...@@ -85,15 +87,7 @@
padding-left: 0px; padding-left: 0px;
li { li {
line-height: 28px;
font-size: 12px;
list-style: none; list-style: none;
a {
padding: 5px 15px;
font-size: 12px;
padding-left: 20px;
}
} }
} }
...@@ -104,10 +98,11 @@ ...@@ -104,10 +98,11 @@
.sidebar-wrapper { .sidebar-wrapper {
width: 250px; width: 250px;
position: absolute; position: fixed;
left: 250px; left: 250px;
height: 100%; height: 100%;
margin-left: -250px; margin-left: -250px;
border-right: 1px solid #EAEAEA;
.nav-sidebar { .nav-sidebar {
margin-top: 20px; margin-top: 20px;
...@@ -119,7 +114,6 @@ ...@@ -119,7 +114,6 @@
.content-wrapper { .content-wrapper {
padding: 20px; padding: 20px;
border-left: 1px solid #EAEAEA;
} }
} }
...@@ -130,14 +124,16 @@ ...@@ -130,14 +124,16 @@
.sidebar-wrapper { .sidebar-wrapper {
width: 52px; width: 52px;
position: absolute; position: fixed;
left: 50px; top: 0;
left: 0;
height: 100%; height: 100%;
margin-left: -50px; border-right: 1px solid #EAEAEA;
overflow-x: hidden;
.nav-sidebar { .nav-sidebar {
margin-top: 20px; margin-top: 20px;
position: fixed; position: absolute;
top: 45px; top: 45px;
width: 52px; width: 52px;
......
class Admin::ApplicationSettingsController < Admin::ApplicationController
before_filter :set_application_setting
def show
end
def update
if @application_setting.update_attributes(application_setting_params)
redirect_to admin_application_settings_path,
notice: 'Application settings saved successfully'
else
render :show
end
end
private
def set_application_setting
@application_setting = ApplicationSetting.current
end
def application_setting_params
params.require(:application_setting).permit(
:default_projects_limit,
:signup_enabled,
:signin_enabled,
:gravatar_enabled,
:sign_in_text,
)
end
end
class Admin::KeysController < Admin::ApplicationController
before_filter :user, only: [:show, :destroy]
def show
@key = user.keys.find(params[:id])
respond_to do |format|
format.html
format.js { render nothing: true }
end
end
def destroy
key = user.keys.find(params[:id])
respond_to do |format|
if key.destroy
format.html { redirect_to [:admin, user], notice: 'User key was successfully removed.' }
else
format.html { redirect_to [:admin, user], alert: 'Failed to remove user key.' }
end
end
end
protected
def user
@user ||= User.find_by!(username: params[:user_id])
end
def key_params
params.require(:user_id, :id)
end
end
...@@ -11,6 +11,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -11,6 +11,7 @@ class Admin::UsersController < Admin::ApplicationController
def show def show
@personal_projects = user.personal_projects @personal_projects = user.personal_projects
@joined_projects = user.projects.joined(@user) @joined_projects = user.projects.joined(@user)
@keys = user.keys.order('id DESC')
end end
def new def new
...@@ -118,7 +119,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -118,7 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
:email, :remember_me, :bio, :name, :username, :email, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password,
:extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key,
:projects_limit, :can_create_group, :admin :projects_limit, :can_create_group, :admin, :key_id
) )
end end
end end
require 'gon' require 'gon'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings
before_filter :authenticate_user_from_token! before_filter :authenticate_user_from_token!
before_filter :authenticate_user! before_filter :authenticate_user!
before_filter :reject_blocked! before_filter :reject_blocked!
...@@ -13,7 +15,7 @@ class ApplicationController < ActionController::Base ...@@ -13,7 +15,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
helper_method :abilities, :can? helper_method :abilities, :can?, :current_application_settings
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -263,10 +265,6 @@ class ApplicationController < ActionController::Base ...@@ -263,10 +265,6 @@ class ApplicationController < ActionController::Base
# or improve current implementation to filter only issues you # or improve current implementation to filter only issues you
# created or assigned or mentioned # created or assigned or mentioned
#@filter_params[:authorized_only] = true #@filter_params[:authorized_only] = true
unless @filter_params[:assignee_id]
@filter_params[:assignee_id] = current_user.id
end
end end
@filter_params @filter_params
......
...@@ -11,7 +11,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -11,7 +11,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@milestones = case params[:f] @milestones = case params[:state]
when 'all'; @project.milestones.order("state, due_date DESC") when 'all'; @project.milestones.order("state, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC") when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC") else @project.milestones.active.order("due_date ASC")
......
...@@ -15,6 +15,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -15,6 +15,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
redirect_to project_protected_branches_path(@project) redirect_to project_protected_branches_path(@project)
end end
def update
protected_branch = @project.protected_branches.find(params[:id])
if protected_branch &&
protected_branch.update_attributes(
developers_can_push: params[:developers_can_push]
)
respond_to do |format|
format.json { render :json => protected_branch, status: :ok }
end
else
respond_to do |format|
format.json { render json: protected_branch.errors, status: :unprocessable_entity }
end
end
end
def destroy def destroy
@project.protected_branches.find(params[:id]).destroy @project.protected_branches.find(params[:id]).destroy
...@@ -27,6 +45,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -27,6 +45,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
private private
def protected_branch_params def protected_branch_params
params.require(:protected_branch).permit(:name) params.require(:protected_branch).permit(:name, :developers_can_push)
end end
end end
...@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain, :title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server :build_key, :server, :teamcity_url, :build_type
) )
end end
end end
...@@ -26,7 +26,9 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -26,7 +26,9 @@ class RegistrationsController < Devise::RegistrationsController
private private
def signup_enabled? def signup_enabled?
redirect_to new_user_session_path unless Gitlab.config.gitlab.signup_enabled unless current_application_settings.signup_enabled?
redirect_to(new_user_session_path)
end
end end
def sign_up_params def sign_up_params
......
class SessionsController < Devise::SessionsController class SessionsController < Devise::SessionsController
def new def new
redirect_path = if request.referer.present? && (params['redirect_to_referer'] == 'yes') redirect_path =
referer_uri = URI(request.referer) if request.referer.present? && (params['redirect_to_referer'] == 'yes')
if referer_uri.host == Gitlab.config.gitlab.host referer_uri = URI(request.referer)
referer_uri.path if referer_uri.host == Gitlab.config.gitlab.host
else referer_uri.path
request.fullpath else
end request.fullpath
else end
request.fullpath else
end request.fullpath
end
# Prevent a 'you are already signed in' message directly after signing: # Prevent a 'you are already signed in' message directly after signing:
# we should never redirect to '/users/sign_in' after signing in successfully. # we should never redirect to '/users/sign_in' after signing in successfully.
......
...@@ -293,4 +293,21 @@ module ApplicationHelper ...@@ -293,4 +293,21 @@ module ApplicationHelper
path << "?#{options.to_param}" path << "?#{options.to_param}"
path path
end end
def outdated_browser?
browser.ie? && browser.version.to_i < 10
end
def path_to_key(key, admin = false)
if admin
admin_user_key_path(@user, key)
else
profile_key_path(key)
end
end
def redirect_from_root?
request.env['rack.session']['user_return_to'] ==
'/'
end
end end
module ApplicationSettingsHelper
def gravatar_enabled?
current_application_settings.gravatar_enabled?
end
def signup_enabled?
current_application_settings.signup_enabled?
end
def signin_enabled?
current_application_settings.signin_enabled?
end
def extra_sign_in_text
current_application_settings.sign_in_text
end
end
module DashboardHelper module DashboardHelper
def entities_per_project(project, entity)
case entity.to_sym
when :issue then @issues.where(project_id: project.id)
when :merge_request then @merge_requests.where(target_project_id: project.id)
else
[]
end.count
end
def projects_dashboard_filter_path(options={}) def projects_dashboard_filter_path(options={})
exist_opts = { exist_opts = {
sort: params[:sort], sort: params[:sort],
scope: params[:scope], scope: params[:scope],
group: params[:group], group: params[:group],
tag: params[:tag],
visibility_level: params[:visibility_level],
} }
options = exist_opts.merge(options) options = exist_opts.merge(options)
...@@ -22,32 +15,11 @@ module DashboardHelper ...@@ -22,32 +15,11 @@ module DashboardHelper
path path
end end
def assigned_entities_count(current_user, entity, scope = nil) def assigned_issues_dashboard_path
items = current_user.send('assigned_' + entity.pluralize) issues_dashboard_path(assignee_id: current_user.id)
get_count(items, scope)
end
def authored_entities_count(current_user, entity, scope = nil)
items = current_user.send(entity.pluralize)
get_count(items, scope)
end end
def authorized_entities_count(current_user, entity, scope = nil) def assigned_mrs_dashboard_path
items = entity.classify.constantize merge_requests_dashboard_path(assignee_id: current_user.id)
get_count(items, scope, true, current_user)
end
protected
def get_count(items, scope, get_authorized = false, current_user = nil)
items = items.opened
if scope.kind_of?(Group)
items = items.of_group(scope)
elsif scope.kind_of?(Project)
items = items.of_projects(scope)
elsif get_authorized
items = items.of_projects(current_user.authorized_projects)
end
items.count
end end
end end
...@@ -33,18 +33,6 @@ module GroupsHelper ...@@ -33,18 +33,6 @@ module GroupsHelper
title title
end end
def group_filter_path(entity, options={})
exist_opts = {
status: params[:status]
}
options = exist_opts.merge(options)
path = request.path
path << "?#{options.to_param}"
path
end
def group_settings_page? def group_settings_page?
if current_controller?('groups') if current_controller?('groups')
current_action?('edit') || current_action?('projects') current_action?('edit') || current_action?('projects')
......
module MilestonesHelper
def milestones_filter_path(opts = {})
if @project
project_milestones_path(@project, opts)
elsif @group
group_milestones_path(@group, opts)
end
end
end
...@@ -14,6 +14,6 @@ module ProfileHelper ...@@ -14,6 +14,6 @@ module ProfileHelper
end end
def show_profile_remove_tab? def show_profile_remove_tab?
gitlab_config.signup_enabled signup_enabled?
end end
end end
class ApplicationSetting < ActiveRecord::Base
def self.current
ApplicationSetting.last
end
def self.create_from_defaults
create(
default_projects_limit: Settings.gitlab['default_projects_limit'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
)
end
end
...@@ -174,7 +174,7 @@ class Event < ActiveRecord::Base ...@@ -174,7 +174,7 @@ class Event < ActiveRecord::Base
def valid_push? def valid_push?
data[:ref] && ref_name.present? data[:ref] && ref_name.present?
rescue => ex rescue
false false
end end
......
...@@ -24,7 +24,7 @@ class Group < Namespace ...@@ -24,7 +24,7 @@ class Group < Namespace
has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy
validate :avatar_type, if: ->(user) { user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AttachmentUploader
......
...@@ -48,7 +48,7 @@ class WebHook < ActiveRecord::Base ...@@ -48,7 +48,7 @@ class WebHook < ActiveRecord::Base
verify: false, verify: false,
basic_auth: auth) basic_auth: auth)
end end
rescue SocketError, Errno::ECONNREFUSED => e rescue SocketError, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}") logger.error("WebHook Error => #{e}")
false false
end end
......
...@@ -192,7 +192,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -192,7 +192,9 @@ class MergeRequest < ActiveRecord::Base
end end
def automerge!(current_user, commit_message = nil) def automerge!(current_user, commit_message = nil)
MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) MergeRequests::AutoMergeService.
new(target_project, current_user).
execute(self, commit_message)
end end
def open? def open?
......
...@@ -6,12 +6,13 @@ class Notification ...@@ -6,12 +6,13 @@ class Notification
N_PARTICIPATING = 1 N_PARTICIPATING = 1
N_WATCH = 2 N_WATCH = 2
N_GLOBAL = 3 N_GLOBAL = 3
N_MENTION = 4
attr_accessor :target attr_accessor :target
class << self class << self
def notification_levels def notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH] [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION]
end end
def options_with_labels def options_with_labels
...@@ -19,12 +20,13 @@ class Notification ...@@ -19,12 +20,13 @@ class Notification
disabled: N_DISABLED, disabled: N_DISABLED,
participating: N_PARTICIPATING, participating: N_PARTICIPATING,
watch: N_WATCH, watch: N_WATCH,
mention: N_MENTION,
global: N_GLOBAL global: N_GLOBAL
} }
end end
def project_notification_levels def project_notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION]
end end
end end
...@@ -48,6 +50,10 @@ class Notification ...@@ -48,6 +50,10 @@ class Notification
target.notification_level == N_GLOBAL target.notification_level == N_GLOBAL
end end
def mention?
target.notification_level == N_MENTION
end
def level def level
target.notification_level target.notification_level
end end
......
...@@ -69,6 +69,7 @@ class Project < ActiveRecord::Base ...@@ -69,6 +69,7 @@ class Project < ActiveRecord::Base
has_one :jenkins_service, dependent: :destroy has_one :jenkins_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy has_one :buildbox_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
...@@ -321,7 +322,8 @@ class Project < ActiveRecord::Base ...@@ -321,7 +322,8 @@ class Project < ActiveRecord::Base
end end
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins pushover buildbox bamboo) %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira jenkins)
end end
def gitlab_ci? def gitlab_ci?
...@@ -485,6 +487,10 @@ class Project < ActiveRecord::Base ...@@ -485,6 +487,10 @@ class Project < ActiveRecord::Base
protected_branches_names.include?(branch_name) protected_branches_names.include?(branch_name)
end end
def developers_can_push_to_protected_branch?(branch_name)
protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push }
end
def forked? def forked?
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end end
......
...@@ -35,7 +35,7 @@ class HipchatService < Service ...@@ -35,7 +35,7 @@ class HipchatService < Service
{ type: 'text', name: 'token', placeholder: '' }, { type: 'text', name: 'token', placeholder: '' },
{ type: 'text', name: 'room', placeholder: '' }, { type: 'text', name: 'room', placeholder: '' },
{ type: 'text', name: 'server', { type: 'text', name: 'server',
placeholder: 'Leave blank for default. https://chat.hipchat.com' } placeholder: 'Leave blank for default. https://hipchat.example.com' }
] ]
end end
...@@ -47,7 +47,7 @@ class HipchatService < Service ...@@ -47,7 +47,7 @@ class HipchatService < Service
def gate def gate
options = { api_version: 'v2' } options = { api_version: 'v2' }
options[:server_url] = server unless server.nil? options[:server_url] = server unless server.blank?
@gate ||= HipChat::Client.new(token, options) @gate ||= HipChat::Client.new(token, options)
end end
......
require 'slack-notifier' require 'slack-notifier'
class SlackMessage class SlackMessage
attr_reader :after
attr_reader :before
attr_reader :commits
attr_reader :project_name
attr_reader :project_url
attr_reader :ref
attr_reader :username
def initialize(params) def initialize(params)
@after = params.fetch(:after) @after = params.fetch(:after)
@before = params.fetch(:before) @before = params.fetch(:before)
...@@ -23,14 +31,6 @@ class SlackMessage ...@@ -23,14 +31,6 @@ class SlackMessage
private private
attr_reader :after
attr_reader :before
attr_reader :commits
attr_reader :project_name
attr_reader :project_url
attr_reader :ref
attr_reader :username
def message def message
if new_branch? if new_branch?
new_branch_message new_branch_message
......
class TeamcityService < CiService
include HTTParty
prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true,
format: { with: URI::regexp }, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username, presence: true,
if: ->(service) { service.password? }, if: :activated?
validates :password, presence: true,
if: ->(service) { service.username? }, if: :activated?
attr_accessor :response
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def title
'JetBrains TeamCity CI'
end
def description
'A continuous integration and build server'
end
def help
'The build configuration in Teamcity must use the build format '\
'number %build.vcs.number% '\
'you will also want to configure monitoring of all branches so merge '\
'requests build, that setting is in the vsc root advanced settings.'
end
def to_param
'teamcity'
end
def fields
[
{ type: 'text', name: 'teamcity_url',
placeholder: 'TeamCity root URL like https://teamcity.example.com' },
{ type: 'text', name: 'build_type',
placeholder: 'Build configuration ID' },
{ type: 'text', name: 'username',
placeholder: 'A user with permissions to trigger a manual build' },
{ type: 'password', name: 'password' },
]
end
def build_info(sha)
url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
"branch:unspecified:any,number:#{sha}")
auth = {
username: username,
password: password,
}
@response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
end
def build_page(sha)
build_info(sha) if @response.nil? || !@response.code
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
"#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
"#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
"&buildTypeId=#{build_type}"
end
end
def commit_status(sha)
build_info(sha) if @response.nil? || !@response.code
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404
'Pending'
else
@response['build']['status']
end
if status.include?('SUCCESS')
'success'
elsif status.include?('FAILURE')
'failed'
elsif status.include?('Pending')
'pending'
else
:error
end
end
def execute(data)
auth = {
username: username,
password: password,
}
branch = data[:ref]
self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
)
end
end
...@@ -51,14 +51,15 @@ require 'file_size_validator' ...@@ -51,14 +51,15 @@ require 'file_size_validator'
class User < ActiveRecord::Base class User < ActiveRecord::Base
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
extend Gitlab::ConfigHelper
include TokenAuthenticatable include TokenAuthenticatable
extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings
default_value_for :admin, false default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_group, gitlab_config.default_can_create_group
default_value_for :can_create_team, false default_value_for :can_create_team, false
default_value_for :hide_no_ssh_key, false default_value_for :hide_no_ssh_key, false
default_value_for :projects_limit, gitlab_config.default_projects_limit default_value_for :projects_limit, current_application_settings.default_projects_limit
default_value_for :theme_id, gitlab_config.default_theme default_value_for :theme_id, gitlab_config.default_theme
devise :database_authenticatable, :lockable, :async, devise :database_authenticatable, :lockable, :async,
......
class BaseService class BaseService
include Gitlab::CurrentSettings
attr_accessor :project, :current_user, :params attr_accessor :project, :current_user, :params
def initialize(project, user, params = {}) def initialize(project, user, params = {})
...@@ -29,6 +31,10 @@ class BaseService ...@@ -29,6 +31,10 @@ class BaseService
SystemHooksService.new SystemHooksService.new
end end
def current_application_settings
ApplicationSetting.current
end
private private
def error(message) def error(message)
......
class GravatarService class GravatarService
include Gitlab::CurrentSettings
def execute(email, size = nil) def execute(email, size = nil)
if gravatar_config.enabled && email.present? if current_application_settings.gravatar_enabled? && email.present?
size = 40 if size.nil? || size <= 0 size = 40 if size.nil? || size <= 0
sprintf gravatar_url, sprintf gravatar_url,
......
...@@ -5,15 +5,16 @@ module MergeRequests ...@@ -5,15 +5,16 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications # mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI # Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService class AutoMergeService < BaseMergeService
def execute(merge_request, current_user, commit_message) def execute(merge_request, commit_message)
merge_request.lock_mr merge_request.lock_mr
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge merge_request.merge
notification.merge_mr(merge_request, current_user) notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
execute_project_hooks(merge_request) create_note(merge_request)
execute_hooks(merge_request)
true true
else else
......
module MergeRequests module MergeRequests
class BaseMergeService class BaseMergeService < MergeRequests::BaseService
private private
def notification
NotificationService.new
end
def create_merge_event(merge_request, current_user) def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user) EventCreateService.new.merge_mr(merge_request, current_user)
end end
def execute_project_hooks(merge_request)
if merge_request.project
hook_data = merge_request.to_hook_data(current_user)
merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end
end
end end
end end
...@@ -13,7 +13,7 @@ module MergeRequests ...@@ -13,7 +13,7 @@ module MergeRequests
merge_request.target_branch ||= merge_request.target_project.default_branch merge_request.target_branch ||= merge_request.target_project.default_branch
unless merge_request.target_branch && merge_request.source_branch unless merge_request.target_branch && merge_request.source_branch
return build_failed(merge_request, "You must select source and target branches") return build_failed(merge_request, nil)
end end
# Generate suggested MR title based on source branch name # Generate suggested MR title based on source branch name
...@@ -62,7 +62,7 @@ module MergeRequests ...@@ -62,7 +62,7 @@ module MergeRequests
end end
def build_failed(merge_request, message) def build_failed(merge_request, message)
merge_request.errors.add(:base, message) merge_request.errors.add(:base, message) unless message.nil?
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request merge_request
......
...@@ -6,12 +6,13 @@ module MergeRequests ...@@ -6,12 +6,13 @@ module MergeRequests
# Called when you do merge via command line and push code # Called when you do merge via command line and push code
# to target branch # to target branch
class MergeService < BaseMergeService class MergeService < BaseMergeService
def execute(merge_request, current_user, commit_message) def execute(merge_request, commit_message)
merge_request.merge merge_request.merge
notification.merge_mr(merge_request, current_user) notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
execute_project_hooks(merge_request) create_note(merge_request)
execute_hooks(merge_request)
true true
rescue rescue
......
...@@ -32,7 +32,9 @@ module MergeRequests ...@@ -32,7 +32,9 @@ module MergeRequests
merge_requests.uniq.select(&:source_project).each do |merge_request| merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::MergeService.new.execute(merge_request, @current_user, nil) MergeRequests::MergeService.
new(merge_request.target_project, @current_user).
execute(merge_request, nil)
end end
end end
......
module Notes
class UpdateService < BaseService
def execute
note = project.notes.find(params[:note_id])
note.note = params[:note]
if note.save
notification_service.new_note(note)
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
# Create a cross-reference note if this Note contains GFM that
# names an issue, merge request, or commit.
note.references.each do |mentioned|
Note.create_cross_reference_note(mentioned, note.noteable,
note.author, note.project)
end
end
end
note
end
end
end
...@@ -144,6 +144,10 @@ class NotificationService ...@@ -144,6 +144,10 @@ class NotificationService
# Merge project watchers # Merge project watchers
recipients = recipients.concat(project_watchers(note.project)).compact.uniq recipients = recipients.concat(project_watchers(note.project)).compact.uniq
# Reject mention users unless mentioned in comment
recipients = reject_mention_users(recipients - note.mentioned_users, note.project)
recipients = recipients + note.mentioned_users
# Reject mutes users # Reject mutes users
recipients = reject_muted_users(recipients, note.project) recipients = reject_muted_users(recipients, note.project)
...@@ -285,13 +289,39 @@ class NotificationService ...@@ -285,13 +289,39 @@ class NotificationService
end end
end end
# Remove users with notification level 'Mentioned'
def reject_mention_users(users, project = nil)
users = users.to_a.compact.uniq
users.reject do |user|
next user.notification.mention? unless project
tm = project.project_members.find_by(user_id: user.id)
if !tm && project.group
tm = project.group.group_members.find_by(user_id: user.id)
end
# reject users who globally set mention notification and has no membership
next user.notification.mention? unless tm
# reject users who set mention notification in project
next true if tm.notification.mention?
# reject users who have N_MENTION in project and disabled in global settings
tm.notification.global? && user.notification.mention?
end
end
def new_resource_email(target, project, method) def new_resource_email(target, project, method)
if target.respond_to?(:participants) if target.respond_to?(:participants)
recipients = target.participants recipients = target.participants
else else
recipients = [] recipients = []
end end
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(target.author) recipients.delete(target.author)
...@@ -302,6 +332,7 @@ class NotificationService ...@@ -302,6 +332,7 @@ class NotificationService
def close_resource_email(target, project, current_user, method) def close_resource_email(target, project, current_user, method)
recipients = reject_muted_users([target.author, target.assignee], project) recipients = reject_muted_users([target.author, target.assignee], project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user) recipients.delete(current_user)
...@@ -320,6 +351,7 @@ class NotificationService ...@@ -320,6 +351,7 @@ class NotificationService
# reject users with disabled notifications # reject users with disabled notifications
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
recipients = reject_mention_users(recipients, project)
# Reject me from recipients if I reassign an item # Reject me from recipients if I reassign an item
recipients.delete(current_user) recipients.delete(current_user)
...@@ -331,6 +363,7 @@ class NotificationService ...@@ -331,6 +363,7 @@ class NotificationService
def reopen_resource_email(target, project, current_user, method, status) def reopen_resource_email(target, project, current_user, method, status)
recipients = reject_muted_users([target.author, target.assignee], project) recipients = reject_muted_users([target.author, target.assignee], project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user) recipients.delete(current_user)
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
- if @application_setting.errors.any?
#error_explanation
.alert.alert-danger
- @application_setting.errors.full_messages.each do |msg|
%p= msg
%fieldset
%legend Features
.form-group
= f.label :signup_enabled, class: 'control-label'
.col-sm-10
= f.check_box :signup_enabled, class: 'checkbox'
.form-group
= f.label :signin_enabled, class: 'control-label'
.col-sm-10
= f.check_box :signin_enabled, class: 'checkbox'
.form-group
= f.label :gravatar_enabled, class: 'control-label'
.col-sm-10
= f.check_box :gravatar_enabled, class: 'checkbox'
%fieldset
%legend Misc
.form-group
= f.label :default_projects_limit, class: 'control-label'
.col-sm-10
= f.number_field :default_projects_limit, class: 'form-control'
.form-group
= f.label :sign_in_text, class: 'control-label'
.col-sm-10
= f.text_area :sign_in_text, class: 'form-control'
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
%h3.page-title Application settings
%hr
= render 'form'
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
%p %p
Sign up Sign up
%span.light.pull-right %span.light.pull-right
= boolean_to_icon gitlab_config.signup_enabled = boolean_to_icon signup_enabled?
%p %p
LDAP LDAP
%span.light.pull-right %span.light.pull-right
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
%p %p
Gravatar Gravatar
%span.light.pull-right %span.light.pull-right
= boolean_to_icon Gitlab.config.gravatar.enabled = boolean_to_icon gravatar_enabled?
%p %p
OmniAuth OmniAuth
%span.light.pull-right %span.light.pull-right
......
= render "profiles/keys/key_details", admin: true
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
%a{"data-toggle" => "tab", href: "#groups"} Groups %a{"data-toggle" => "tab", href: "#groups"} Groups
%li %li
%a{"data-toggle" => "tab", href: "#projects"} Projects %a{"data-toggle" => "tab", href: "#projects"} Projects
%li
%a{"data-toggle" => "tab", href: "#ssh-keys"} SSH keys
.tab-content .tab-content
#account.tab-pane.active #account.tab-pane.active
...@@ -217,3 +219,5 @@ ...@@ -217,3 +219,5 @@
- if tm.respond_to? :project - if tm.respond_to? :project
= link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
%i.fa.fa-times %i.fa.fa-times
#ssh-keys.tab-pane
= render 'profiles/keys/key_table', admin: true
%fieldset .dash-projects-filters.append-bottom-20
%ul.nav.nav-pills.nav-stacked .pull-left.append-right-20
= nav_tab :scope, nil do %ul.nav.nav-pills.nav-compact
= link_to projects_dashboard_filter_path(scope: nil) do = nav_tab :scope, nil do
All = link_to projects_dashboard_filter_path(scope: nil) do
%span.pull-right All
= current_user.authorized_projects.count = nav_tab :scope, 'personal' do
= nav_tab :scope, 'personal' do = link_to projects_dashboard_filter_path(scope: 'personal') do
= link_to projects_dashboard_filter_path(scope: 'personal') do Personal
Personal = nav_tab :scope, 'joined' do
%span.pull-right = link_to projects_dashboard_filter_path(scope: 'joined') do
= current_user.personal_projects.count Joined
= nav_tab :scope, 'joined' do = nav_tab :scope, 'owned' do
= link_to projects_dashboard_filter_path(scope: 'joined') do = link_to projects_dashboard_filter_path(scope: 'owned') do
Joined Owned
%span.pull-right
= current_user.authorized_projects.joined(current_user).count
= nav_tab :scope, 'owned' do
= link_to projects_dashboard_filter_path(scope: 'owned') do
Owned
%span.pull-right
= current_user.owned_projects.count
%fieldset .dropdown.inline.append-right-10
%legend Visibility %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter %i.fa.fa-globe
- Gitlab::VisibilityLevel.values.each do |level| %span.light Visibility:
%li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } - if params[:visibility_level].present?
= link_to projects_dashboard_filter_path(visibility_level: level) do = visibility_level_label(params[:visibility_level].to_i)
= visibility_level_icon(level) - else
= visibility_level_label(level) Any
%b.caret
%ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(visibility_level: nil) do
Any
- Gitlab::VisibilityLevel.values.each do |level|
%li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(visibility_level: level) do
= visibility_level_icon(level)
= visibility_level_label(level)
- if @groups.present? - if @groups.present?
%fieldset .dropdown.inline.append-right-10
%legend Groups %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%ul.nav.nav-pills.nav-stacked.nav-small %i.fa.fa-group
- @groups.each do |group| %span.light Group:
%li{ class: (group.name == params[:group]) ? 'active' : 'light' } - if params[:group].present?
= link_to projects_dashboard_filter_path(group: group.name) do = Group.find_by(name: params[:group]).name
%i.fa.fa-folder-o - else
= group.name Any
%small.pull-right %b.caret
= group.projects.count %ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(group: nil) do
Any
- @groups.each do |group|
%li{ class: (group.name == params[:group]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(group: group.name) do
= group.name
%small.pull-right
= group.projects.count
- if @tags.present? - if @tags.present?
%fieldset .dropdown.inline.append-right-10
%legend Tags %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%ul.nav.nav-pills.nav-stacked.nav-small %i.fa.fa-tags
- @tags.each do |tag| %span.light Tags:
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } - if params[:tag].present?
= link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do = params[:tag]
%i.fa.fa-tag - else
= tag.name Any
%b.caret
%ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(tag: nil) do
Any
- @tags.each do |tag|
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(tag: tag.name) do
%i.fa.fa-tag
= tag.name
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= @sort.humanize
- else
Name
%b.caret
%ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(sort: nil) do
Name
= link_to projects_dashboard_filter_path(sort: 'newest') do
= sort_title_recently_created
= link_to projects_dashboard_filter_path(sort: 'oldest') do
= sort_title_oldest_created
= link_to projects_dashboard_filter_path(sort: 'recently_updated') do
= sort_title_recently_updated
= link_to projects_dashboard_filter_path(sort: 'last_updated') do
= sort_title_oldest_updated
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%div %div
.dashboard-intro-icon .dashboard-intro-icon
%i.fa.fa-bookmark-o %i.fa.fa-bookmark-o
%div .dashboard-intro-text
%p.slead %p.slead
You don't have access to any projects right now. You don't have access to any projects right now.
%br %br
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
%div %div
.dashboard-intro-icon .dashboard-intro-icon
%i.fa.fa-users %i.fa.fa-users
%div .dashboard-intro-text
%p.slead %p.slead
You can create a group for several dependent projects. You can create a group for several dependent projects.
%br %br
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
%div %div
.dashboard-intro-icon .dashboard-intro-icon
%i.fa.fa-globe %i.fa.fa-globe
%div .dashboard-intro-text
%p.slead %p.slead
There are There are
%strong= @publicish_project_count %strong= @publicish_project_count
......
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues" xml.title "#{current_user.name} issues"
xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html" xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html"
......
%h3.page-title %h3.page-title
My Projects My Projects
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= @sort.humanize
- else
Name
%b.caret
%ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(sort: nil) do
Name
= link_to projects_dashboard_filter_path(sort: 'newest') do
= sort_title_recently_created
= link_to projects_dashboard_filter_path(sort: 'oldest') do
= sort_title_oldest_created
= link_to projects_dashboard_filter_path(sort: 'recently_updated') do
= sort_title_recently_updated
= link_to projects_dashboard_filter_path(sort: 'last_updated') do
= sort_title_oldest_updated
%p.light %p.light
All projects you have access to are listed here. Public projects are not included here unless you are a member All projects you have access to are listed here. Public projects are not included here unless you are a member
%hr %hr
.row .side-filters
.col-md-3.hidden-sm.hidden-xs.side-filters = render "projects_filter"
= render "projects_filter" .dash-projects
.col-md-9 %ul.bordered-list.my-projects.top-list
%ul.bordered-list.my-projects.top-list - @projects.each do |project|
- @projects.each do |project| %li.my-project-row
%li.my-project-row %h4.project-title
%h4.project-title .project-access-icon
.project-access-icon = visibility_level_icon(project.visibility_level)
= visibility_level_icon(project.visibility_level) = link_to project_path(project), class: dom_class(project) do
= link_to project_path(project), class: dom_class(project) do = project.name_with_namespace
= project.name_with_namespace
- if project.forked_from_project - if project.forked_from_project
&nbsp; &nbsp;
%small %small
%i.fa.fa-code-fork %i.fa.fa-code-fork
Forked from: Forked from:
= link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
- if current_user.can_leave_project?(project) - if current_user.can_leave_project?(project)
.pull-right
= link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do
%i.fa.fa-sign-out
Leave
.project-info
.pull-right .pull-right
- if project.archived? = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do
%span.label %i.fa.fa-sign-out
%i.fa.fa-archive Leave
Archived
- project.tags.each do |tag| .project-info
%span.label.label-info .pull-right
%i.fa.fa-tag - if project.archived?
= tag.name %span.label
- if project.description.present? %i.fa.fa-archive
%p= truncate project.description, length: 100 Archived
.last-activity - project.tags.each do |tag|
%span.light Last activity: %span.label.label-info
%span.date= project_last_activity(project) %i.fa.fa-tag
= tag.name
- if project.description.present?
%p= truncate project.description, length: 100
.last-activity
%span.light Last activity:
%span.date= project_last_activity(project)
- if @projects.blank? - if @projects.blank?
%li %li
.nothing-here-block There are no projects here. .nothing-here-block There are no projects here.
.bottom .bottom
= paginate @projects, theme: "gitlab" = paginate @projects, theme: "gitlab"
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml" xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_url, rel: "alternate", type: "text/html" xml.link href: dashboard_url, rel: "alternate", type: "text/html"
......
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
= f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus" = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus"
= f.password_field :password, class: "form-control bottom", placeholder: "Password" = f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable? - if devise_mapping.rememberable?
.clearfix.append-bottom-10 .remember-me
%label.checkbox.remember_me{for: "user_remember_me"} %label.checkbox.remember_me{for: "user_remember_me"}
= f.check_box :remember_me = f.check_box :remember_me
%span Remember me %span Remember me
......
= form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do = form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"} = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/
= button_tag "LDAP Sign in", class: "btn-save btn" = button_tag "LDAP Sign in", class: "btn-save btn"
.login-box %div
.login-heading = render 'devise/shared/signin_box'
%h3 Sign in
.login-body
- if standard_login_form_only?
%ul.nav.nav-tabs
- if ldap_enabled?
- @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i.zero?)}
= link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
- if kerberos_enabled?
%li{class: (:active unless ldap_enabled?)}
= link_to "Kerberos", "#tab-kerberos", 'data-toggle' => 'tab'
- if gitlab_config.signin_enabled
%li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content
- if ldap_enabled?
- @ldap_servers.each_with_index do |server, i|
%div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
= render 'devise/sessions/new_ldap', provider: server['provider_name']
- if kerberos_enabled?
%div#tab-kerberos.tab-pane{class: (:active unless ldap_enabled?)}
= render 'devise/sessions/new_kerberos', provider: :kerberos
- if gitlab_config.signin_enabled
%div#tab-signin.tab-pane
= render 'devise/sessions/new_base'
- elsif gitlab_config.signin_enabled - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
= render 'devise/sessions/new_base' .prepend-top-20
- else = render 'devise/shared/oauth_box'
%div
No authentication methods configured.
= render 'devise/sessions/oauth_providers' if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? - if signup_enabled?
.prepend-top-20
= render 'devise/shared/signup_box'
.login-footer .clearfix.prepend-top-20
- if gitlab_config.signup_enabled %p
%p %span.light Did not receive confirmation email?
%span.light = link_to "Send again", new_confirmation_path(resource_name)
Don't have an account?
%strong
= link_to "Sign up", new_registration_path(resource_name)
%p
%span.light Did not receive confirmation email?
= link_to "Send again", new_confirmation_path(resource_name)
- if extra_config.has_key?('sign_in_text')
%hr
= markdown(extra_config.sign_in_text)
- providers = additional_providers - providers = additional_providers
- if providers.present? - if providers.present?
.bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'} .login-box{:'data-no-turbolink' => 'data-no-turbolink'}
%span Sign in with: &nbsp; %span Sign in with &nbsp;
- providers.each do |provider| - providers.each do |provider|
%span %span
- if default_providers.include?(provider) - if default_providers.include?(provider)
......
.login-box
.login-heading
%h3 Sign in
.login-body
- if ldap_enabled?
%ul.nav.nav-tabs
- @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i.zero?)}
= link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
- if kerberos_enabled?
%li{class: (:active unless ldap_enabled?)}
= link_to "Kerberos", "#tab-kerberos", 'data-toggle' => 'tab'
- if signin_enabled?
%li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content
- @ldap_servers.each_with_index do |server, i|
%div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
= render 'devise/sessions/new_ldap', provider: server['provider_name']
- if kerberos_enabled?
%div#tab-kerberos.tab-pane{class: (:active unless ldap_enabled?)}
= render 'devise/sessions/new_kerberos', provider: :kerberos
- if signin_enabled?
%div#tab-signin.tab-pane
= render 'devise/sessions/new_base'
- elsif signin_enabled?
= render 'devise/sessions/new_base'
- else
%div
No authentication methods configured.
.login-box
.login-heading
%h3 Sign up
.login-body
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
.devise-errors
= devise_error_messages!
%div
= f.text_field :name, class: "form-control top", placeholder: "Name", required: true
%div
= f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
.form-group#password-strength
= f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
%div
= f.submit "Sign up", class: "btn-create btn"
= form_tag group_filter_path(entity), method: 'get' do
%fieldset
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if (params[:status] == 'active' || !params[:status]))}
= link_to group_filter_path(entity, status: 'active') do
Active
%li{class: ("active" if params[:status] == 'closed')}
= link_to group_filter_path(entity, status: 'closed') do
Closed
%li{class: ("active" if params[:status] == 'all')}
= link_to group_filter_path(entity, status: 'all') do
All
...@@ -2,18 +2,21 @@ ...@@ -2,18 +2,21 @@
= nav_link(path: 'groups#edit') do = nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group) do = link_to edit_group_path(@group) do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
Group %span
Group
= nav_link(path: 'groups#projects') do = nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group) do = link_to projects_group_path(@group) do
%i.fa.fa-folder %i.fa.fa-folder
Projects %span
Projects
- if ldap_enabled? - if ldap_enabled?
= nav_link(controller: :ldap_group_links) do = nav_link(controller: :ldap_group_links) do
= link_to group_ldap_group_links_path(@group) do = link_to group_ldap_group_links_path(@group) do
%i.fa.fa-exchange %i.fa.fa-exchange
LDAP Groups %span
LDAP Groups
= nav_link(controller: :audit_events) do = nav_link(controller: :audit_events) do
= link_to group_audit_events_path(@group) do = link_to group_audit_events_path(@group) do
%i.fa.fa-file-text-o %i.fa.fa-file-text-o
Audit Events %span
Audit Events
...@@ -9,42 +9,38 @@ ...@@ -9,42 +9,38 @@
%hr %hr
.row = render 'shared/milestones_filter'
.fixed.sidebar-expand-button.hidden-lg.hidden-md .milestones
%i.fa.fa-list.fa-2x .panel.panel-default
.col-md-3.responsive-side %ul.well-list
= render 'groups/filter', entity: 'milestone' - if @group_milestones.blank?
.col-md-9 %li
.panel.panel-default .nothing-here-block No milestones to show
%ul.well-list - else
- if @group_milestones.blank? - @group_milestones.each do |milestone|
%li %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
.nothing-here-block No milestones to show .pull-right
- else - if can?(current_user, :manage_group, @group)
- @group_milestones.each do |milestone| - if milestone.closed?
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen"
.pull-right - else
- if can?(current_user, :manage_group, @group) = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close"
- if milestone.closed? %h4
= link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen" = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
- else %div
= link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close"
%h4
= link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
%div %div
%div = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do = pluralize milestone.issue_count, 'Issue'
= pluralize milestone.issue_count, 'Issue' &nbsp;
&nbsp; = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do = pluralize milestone.merge_requests_count, 'Merge Request'
= pluralize milestone.merge_requests_count, 'Merge Request' &nbsp;
&nbsp; %span.light #{milestone.percent_complete}% complete
%span.light #{milestone.percent_complete}% complete .progress.progress-info
.progress.progress-info .progress-bar{style: "width: #{milestone.percent_complete}%;"}
.progress-bar{style: "width: #{milestone.percent_complete}%;"} %div
%div %br
%br - milestone.projects.each do |project|
- milestone.projects.each do |project| %span.label.label-default
%span.label.label-default = project.name
= project.name = paginate @group_milestones, theme: "gitlab"
= paginate @group_milestones, theme: "gitlab"
%h3.page-title %h4.page-title
.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" }
- if @group_milestone.closed?
Closed
- else
Open
Milestone #{@group_milestone.title} Milestone #{@group_milestone.title}
.pull-right .pull-right
- if can?(current_user, :manage_group, @group) - if can?(current_user, :manage_group, @group)
...@@ -7,46 +12,41 @@ ...@@ -7,46 +12,41 @@
- else - else
= link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen" = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen"
%hr
- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active? - if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active?
.alert.alert-success .alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now. %span All issues for this milestone are closed. You may close the milestone now.
.back-link .description
= link_to group_milestones_path(@group) do %table.table
&larr; To milestones list %thead
%tr
.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } %th Project
.state.clearfix %th Open issues
.state-label %th State
- if @group_milestone.closed? %th Due date
Closed - @group_milestone.milestones.each do |milestone|
- else %tr
Open %td
= link_to "#{milestone.project.name}", project_milestone_path(milestone.project, milestone)
%h4.title %td
= gfm escape_once(@group_milestone.title) = milestone.issues.opened.count
%td
.description
- @group_milestone.milestones.each do |milestone|
%hr
%h4
= link_to "#{milestone.project.name} - #{milestone.title}", project_milestone_path(milestone.project, milestone)
%span.pull-right= milestone.expires_at
- if milestone.closed? - if milestone.closed?
%span.label.label-danger #{milestone.state} Closed
= preserve do - else
- if milestone.description.present? Open
= milestone.description %td
= milestone.expires_at
.context
%p
Progress:
#{@group_milestone.closed_items_count} closed
&ndash;
#{@group_milestone.open_items_count} open
.progress.progress-info .context
.progress-bar{style: "width: #{@group_milestone.percent_complete}%;"} %p.lead
Progress:
#{@group_milestone.closed_items_count} closed
&ndash;
#{@group_milestone.open_items_count} open
.progress.progress-info
.progress-bar{style: "width: #{@group_milestone.percent_complete}%;"}
%ul.nav.nav-tabs %ul.nav.nav-tabs
%li.active %li.active
......
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Group feed - #{@group.name}" xml.title "Group feed - #{@group.name}"
xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml" xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml"
xml.link href: group_path(@group), rel: "alternate", type: "text/html" xml.link href: group_path(@group), rel: "alternate", type: "text/html"
......
...@@ -45,3 +45,5 @@ ...@@ -45,3 +45,5 @@
%li.hidden-xs %li.hidden-xs
= link_to current_user, class: "profile-pic", id: 'profile-pic' do = link_to current_user, class: "profile-pic", id: 'profile-pic' do
= image_tag avatar_icon(current_user.email, 26), alt: 'User activity' = image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
= render 'shared/outdated_browser'
...@@ -12,11 +12,13 @@ ...@@ -12,11 +12,13 @@
%span.sr-only Toggle navigation %span.sr-only Toggle navigation
%i.fa.fa-bars %i.fa.fa-bars
.pull-right.hidden-xs - unless current_controller?('sessions')
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new' .pull-right.hidden-xs
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new'
.navbar-collapse.collapse .navbar-collapse.collapse
%ul.nav.navbar-nav %ul.nav.navbar-nav
%li.visible-xs %li.visible-xs
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
= render 'shared/outdated_browser'
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head" = render "layouts/head"
%body.ui_basic.login-page %body.ui_mars.login-page.application
.container = render "layouts/broadcast"
.content = render "layouts/public_head_panel", title: ''
.login-title .container.navless-container
%h1= brand_title
%hr
.container
.content .content
= render "layouts/flash" - unless redirect_from_root?
.row = render "layouts/flash"
.col-md-7.brand-holder .row.prepend-top-20
.col-sm-5.pull-right
= yield
.col-sm-7.brand-holder.pull-left
%h1
= brand_title
- if brand_item - if brand_item
.brand-image = brand_image
= brand_image = brand_text
.brand_text
= brand_text
- else - else
.brand-image.default-brand-image.hidden-sm.hidden-xs %h3 Open source software to collaborate on code
= image_tag 'brand_logo.png'
.brand_text.hidden-xs
%h2 Open source software to collaborate on code
%p.lead %p
Manage git repositories with fine grained access controls that keep your code secure. Manage git repositories with fine grained access controls that keep your code secure.
Perform code reviews and enhance collaboration with merge requests. Perform code reviews and enhance collaboration with merge requests.
Each project can also have an issue tracker and a wiki. Each project can also have an issue tracker and a wiki.
- if extra_sign_in_text.present?
= markdown(extra_sign_in_text)
.col-md-5
= yield
%hr %hr
.container .container
.footer-links .footer-links
......
...@@ -44,3 +44,8 @@ ...@@ -44,3 +44,8 @@
%i.fa.fa-image %i.fa.fa-image
%span %span
Appearance Appearance
= nav_link(controller: :application_settings) do
= link_to admin_application_settings_path do
%i.fa.fa-cogs
%span
Settings
...@@ -10,13 +10,13 @@ ...@@ -10,13 +10,13 @@
%span %span
Projects Projects
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to issues_dashboard_path, class: 'shortcuts-issues' do = link_to assigned_issues_dashboard_path, class: 'shortcuts-issues' do
%i.fa.fa-exclamation-circle %i.fa.fa-exclamation-circle
%span %span
Issues Issues
%span.count= current_user.assigned_issues.opened.count %span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to merge_requests_dashboard_path, class: 'shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, class: 'shortcuts-merge_requests' do
%i.fa.fa-tasks %i.fa.fa-tasks
%span %span
Merge Requests Merge Requests
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
= nav_link(controller: :accounts) do = nav_link(controller: :accounts) do
= link_to profile_account_path do = link_to profile_account_path do
%i.fa.fa-gear %i.fa.fa-gear
Account %span
Account
= nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do
= link_to applications_profile_path do = link_to applications_profile_path do
%i.fa.fa-cloud %i.fa.fa-cloud
......
...@@ -5,8 +5,5 @@ ...@@ -5,8 +5,5 @@
= render "layouts/broadcast" = render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project) = render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete" = render "layouts/init_auto_complete"
- if can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
- @project_settings_nav = true - @project_settings_nav = true
= render 'layouts/page', sidebar: 'layouts/nav/project' = render 'layouts/page', sidebar: 'layouts/nav/project'
...@@ -5,6 +5,4 @@ ...@@ -5,6 +5,4 @@
= render "layouts/broadcast" = render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project) = render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete" = render "layouts/init_auto_complete"
- if can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render 'layouts/page', sidebar: 'layouts/nav/project' = render 'layouts/page', sidebar: 'layouts/nav/project'
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
- @commits.each do |commit| - @commits.each do |commit|
%li %li
%strong #{link_to commit.short_id, project_commit_url(@project, commit)} %strong #{link_to commit.short_id, project_commit_url(@project, commit)}
%span by #{commit.author_name} %div
%span by #{commit.author_name}
%i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
%pre #{commit.safe_message} %pre #{commit.safe_message}
%h4 Changes: %h4 Changes:
......
%li %tr
= link_to profile_key_path(key) do %td
%strong= key.title = link_to path_to_key(key, is_admin) do
%span %strong= key.title
(#{key.fingerprint}) %td
%span.cgray %span
added #{time_ago_with_tooltip(key.created_at)} (#{key.fingerprint})
%td
- unless key.is_a? LDAPKey %span.cgray
= link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right" added #{time_ago_with_tooltip(key.created_at)}
%td
- unless key.is_a? LDAPKey
= link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
- is_admin = defined?(admin) ? true : false
.row
.col-md-4
.panel.panel-default
.panel-heading
SSH Key
%ul.well-list
%li
%span.light Title:
%strong= @key.title
%li
%span.light Created on:
%strong= @key.created_at.stamp("Aug 21, 2011")
.col-md-8
%p
%span.light Fingerprint:
%strong= @key.fingerprint
%pre.well-pre
= @key.key
.pull-right
- unless @key.is_a? LDAPKey
= link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
- is_admin = defined?(admin) ? true : false
.panel.panel-default
- if @keys.any?
%table.table
%thead.panel-heading
%tr
%th Title
%th Fingerprint
%th Added at
%th
%tbody
- @keys.each do |key|
= render 'profiles/keys/key', key: key, is_admin: is_admin
- else
.nothing-here-block
- if is_admin
User has no ssh keys
- else
There are no SSH keys with access to your account.
%h3.page-title %h3.page-title
My SSH keys My SSH keys (#{@keys.count})
.pull-right .pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
%p.light %p.light
...@@ -9,14 +9,4 @@ ...@@ -9,14 +9,4 @@
= link_to "generate it", help_page_path("ssh", "ssh") = link_to "generate it", help_page_path("ssh", "ssh")
%hr %hr
= render 'key_table'
.panel.panel-default
.panel-heading
SSH Keys (#{@keys.count})
%ul.well-list#keys-table
= render partial: "key", collection: @keys
- if @keys.blank?
%li
.nothing-here-block There are no SSH keys with access to your account.
.row = render "key_details"
.col-md-4
.panel.panel-default
.panel-heading
SSH Key
%ul.well-list
%li
%span.light Title:
%strong= @key.title
%li
%span.light Created on:
%strong= @key.created_at.stamp("Aug 21, 2011")
.col-md-8
%p
%span.light Fingerprint:
%strong= @key.fingerprint
%pre.well-pre
= @key.key
.pull-right
- unless @key.is_a? LDAPKey
= link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
...@@ -15,6 +15,13 @@ ...@@ -15,6 +15,13 @@
Disabled Disabled
%p You will not get any notifications via email %p You will not get any notifications via email
.radio
= label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_MENTION, @notification.mention?, class: 'trigger-submit'
.level-title
Mention
%p You will receive notifications only for comments where you was @mentioned
.radio .radio
= label_tag nil, class: '' do = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
......
...@@ -52,10 +52,11 @@ ...@@ -52,10 +52,11 @@
- else - else
%span.light No open milestones available. %span.light No open milestones available.
&nbsp; &nbsp;
= link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank - if can? current_user, :admin_milestone, issuable.project
= link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank
.form-group .form-group
= f.label :label_ids, class: 'control-label' do = f.label :label_ids, class: 'control-label' do
%i.icon-tag %i.fa.fa-tag
Labels Labels
.col-sm-10 .col-sm-10
- if issuable.project.labels.any? - if issuable.project.labels.any?
...@@ -64,9 +65,15 @@ ...@@ -64,9 +65,15 @@
- else - else
%span.light No labels yet. %span.light No labels yet.
&nbsp; &nbsp;
= link_to 'Create new label', new_project_label_path(issuable.project), target: :blank - if can? current_user, :admin_label, issuable.project
= link_to 'Create new label', new_project_label_path(issuable.project), target: :blank
.form-actions .form-actions
- if !issuable.project.empty_repo? && contribution_guide_url(issuable.project) && !issuable.persisted?
%p
Please review the
%strong #{link_to 'guidelines for contribution', contribution_guide_url(issuable.project)}
to this repository.
- if issuable.new_record? - if issuable.new_record?
= f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create' = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else - else
......
...@@ -7,5 +7,5 @@ ...@@ -7,5 +7,5 @@
%p= pluralize(commits.count, 'commit') %p= pluralize(commits.count, 'commit')
.col-md-10 .col-md-10
%ul.bordered-list %ul.bordered-list
= render commits, project: @project = render commits, project: project
%hr.lists-separator %hr.lists-separator
...@@ -11,11 +11,9 @@ ...@@ -11,11 +11,9 @@
%ul.breadcrumb.repo-breadcrumb %ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs = commits_breadcrumbs
%li.active
commits
%div{id: dom_id(@project)} %div{id: dom_id(@project)}
#commits-list= render "commits" #commits-list= render "commits", project: @project
.clear .clear
= spinner = spinner
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }} .diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
.diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"} .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
- if diff_file.deleted_file - if diff_file.deleted_file
%span= diff_file.old_path %span="#{diff_file.old_path} deleted"
.diff-btn-group .diff-btn-group
- if @commit.parent_ids.present? - if @commit.parent_ids.present?
......
%div.issue-form-holder %div.issue-form-holder
%h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
%hr %hr
- if @repository.exists? && !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
- contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
.row
.col-sm-10.col-sm-offset-2
.alert.alert-info
= "Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe
= form_for [@project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f| = form_for [@project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
= render 'projects/issuable_form', f: f, issuable: @issue = render 'projects/issuable_form', f: f, issuable: @issue
......
...@@ -9,74 +9,103 @@ ...@@ -9,74 +9,103 @@
%span.pull-right %span.pull-right
= link_to 'Change branches', new_project_merge_request_path(@project) = link_to 'Change branches', new_project_merge_request_path(@project)
= form_for [@project, @merge_request], html: { class: "merge-request-form gfm-form" } do |f| = form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal gfm-form" } do |f|
.panel.panel-default .merge-request-form-info
.form-group
.panel-body = f.label :title, class: 'control-label' do
.form-group %strong Title *
.light .col-sm-10
= f.label :title do = f.text_field :title, maxlength: 255, autofocus: true, class: 'form-control pad js-gfm-input', required: true
Title * .form-group.issuable-description
= f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true = f.label :description, 'Description', class: 'control-label'
.form-group .col-sm-10
.light
= f.label :description, "Description"
= render layout: 'projects/md_preview' do = render layout: 'projects/md_preview' do
= render 'projects/zen', f: f, attr: :description, = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
classes: 'description form-control'
.clearfix.hint .col-sm-12-hint
.pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. .pull-left
.pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. Parsed with
#{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
.pull-right
Attach images (JPG, PNG, GIF) by dragging &amp; dropping
or #{link_to 'selecting them', '#', class: 'markdown-selector'}.
.clearfix
.error-alert .error-alert
.form-group %hr
.issue-assignee .form-group
= f.label :assignee_id do .issue-assignee
%i.fa.fa-user = f.label :assignee_id, class: 'control-label' do
Assign to %i.fa.fa-user
%div Assign to
= project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id) .col-sm-10
&nbsp; = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link' &nbsp;
.form-group = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.issue-milestone .form-group
= f.label :milestone_id do .issue-milestone
%i.fa.fa-clock-o = f.label :milestone_id, class: 'control-label' do
Milestone %i.fa.fa-clock-o
%div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'}) Milestone
.form-group .col-sm-10
= f.label :label_ids do - if milestone_options(@merge_request).present?
%i.fa.fa-tag = f.select(:milestone_id, milestone_options(@merge_request), {include_blank: 'Select milestone'}, {class: 'select2'})
Labels - else
%div %span.light No open milestones available.
= f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2' &nbsp;
- if can? current_user, :admin_milestone, @merge_request.target_project
= link_to 'Create new milestone', new_project_milestone_path(@merge_request.target_project), target: :blank
.form-group
= f.label :label_ids, class: 'control-label' do
%i.fa.fa-tag
Labels
.col-sm-10
- if @merge_request.target_project.labels.any?
= f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, {selected: @merge_request.label_ids}, multiple: true, class: 'select2'
- else
%span.light No labels yet.
&nbsp;
- if can? current_user, :admin_label, @merge_request.target_project
= link_to 'Create new label', new_project_label_path(@merge_request.target_project), target: :blank
.panel-footer .form-actions
- if contribution_guide_url(@target_project) - if contribution_guide_url(@target_project)
%p %p
Please review the Please review the
%strong #{link_to "guidelines for contribution", contribution_guide_url(@target_project)} %strong #{link_to 'guidelines for contribution', contribution_guide_url(@target_project)}
to this repository. to this repository.
= f.hidden_field :source_project_id = f.hidden_field :source_project_id
= f.hidden_field :source_branch
= f.hidden_field :target_project_id = f.hidden_field :target_project_id
= f.hidden_field :target_branch = f.hidden_field :target_branch
= f.hidden_field :source_branch = f.submit 'Submit merge request', class: 'btn btn-create'
= f.submit 'Submit merge request', class: "btn btn-create"
.mr-compare .mr-compare.merge-request
= render "projects/commits/commit_list" %ul.nav.nav-tabs.merge-request-tabs
%li.commits-tab{data: {action: 'commits'}}
%h4 Changes = link_to url_for(params) do
- if @diffs.present? %i.fa.fa-history
= render "projects/diffs/diffs", diffs: @diffs, project: @project Commits
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE %span.badge= @commits.size
.bs-callout.bs-callout-danger %li.diffs-tab{data: {action: 'diffs'}}
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. = link_to url_for(params) do
%p To preserve performance the line changes are not shown. %i.fa.fa-list-alt
- else Changes
.bs-callout.bs-callout-danger %span.badge= @diffs.size
%h4 This comparison includes huge diff.
%p To preserve performance the line changes are not shown.
.commits.tab-content
= render "projects/commits/commits", project: @project
.diffs.tab-content
- if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
.bs-callout.bs-callout-danger
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
%p To preserve performance the line changes are not shown.
- else
.bs-callout.bs-callout-danger
%h4 This comparison includes a huge diff.
%p To preserve performance the line changes are not shown.
:javascript :javascript
$('.assign-to-me-link').on('click', function(e){ $('.assign-to-me-link').on('click', function(e){
...@@ -85,3 +114,9 @@ ...@@ -85,3 +114,9 @@
}); });
window.project_image_path_upload = "#{upload_image_project_path @project}"; window.project_image_path_upload = "#{upload_image_project_path @project}";
:javascript
var merge_request
merge_request = new MergeRequest({
action: 'commits'
});
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
%span.badge= @merge_request.mr_and_commit_notes.count %span.badge= @merge_request.mr_and_commit_notes.count
%li.commits-tab{data: {action: 'commits'}} %li.commits-tab{data: {action: 'commits'}}
= link_to project_merge_request_path(@project, @merge_request), title: 'Commits' do = link_to project_merge_request_path(@project, @merge_request), title: 'Commits' do
%i.fa.fa-database %i.fa.fa-history
Commits Commits
%span.badge= @commits.size %span.badge= @commits.size
%li.diffs-tab{data: {action: 'diffs'}} %li.diffs-tab{data: {action: 'diffs'}}
......
- if @commits.present? = render "projects/commits/commits", project: @merge_request.source_project
.panel.panel-default
.panel-heading
%i.fa.fa-list
Commits (#{@commits.count})
.commits.mr-commits
- if @commits.count > 8
%ul.first-commits.well-list
- @commits.first(8).each do |commit|
= render "projects/commits/commit", commit: commit, project: @merge_request.source_project
%li.bottom
8 of #{@commits.count} commits displayed.
%strong
%a.show-all-commits Click here to show all
- if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
%ul.all-commits.hide.well-list
- @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit|
= render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
%li
other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
- else
%ul.all-commits.hide.well-list
- @commits.each do |commit|
= render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
- else
%ul.well-list
- @commits.each do |commit|
= render "projects/commits/commit", commit: commit, project: @merge_request.source_project
...@@ -13,30 +13,27 @@ ...@@ -13,30 +13,27 @@
.automerge_widget.can_be_merged.hide .automerge_widget.can_be_merged.hide
.clearfix .clearfix
= form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f| = form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f|
%h4 .accept-merge-holder.clearfix.js-toggle-container
You can accept this request automatically. .accept-action
.accept-merge-holder.clearfix = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
.accept-group - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
.pull-left .accept-control
= f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" = label_tag :should_remove_source_branch, class: "checkbox" do
- if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? = check_box_tag :should_remove_source_branch
.remove_branch_holder.pull-left Remove source-branch
= label_tag :should_remove_source_branch, class: "checkbox" do - if @merge_request.target_project.merge_requests_rebase_enabled && can_rebase?(@merge_request.target_project, @merge_request.target_branch)
= check_box_tag :should_remove_source_branch .accept-control.remove_branch_holder
Remove source-branch = label_tag :should_rebase, class: "checkbox" do
- if @merge_request.target_project.merge_requests_rebase_enabled && can_rebase?(@merge_request.target_project, @merge_request.target_branch) = check_box_tag :should_rebase
.remove_branch_holder.pull-left Rebase before merge
= label_tag :should_rebase, class: "checkbox" do .accept-control
= check_box_tag :should_rebase = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
Rebase before merge %i.fa.fa-edit
.js-toggle-container Modify commit message
%label .js-toggle-content.hide.prepend-top-20
%i.fa.fa-edit = render 'shared/commit_message_container', params: params,
= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" text: @merge_request.merge_commit_message,
.js-toggle-content.hide rows: 14, hint: true
= render 'shared/commit_message_container', params: params,
text: @merge_request.merge_commit_message,
rows: 14, hint: true
%hr %hr
.light .light
......
...@@ -7,27 +7,15 @@ ...@@ -7,27 +7,15 @@
%i.fa.fa-plus %i.fa.fa-plus
New Milestone New Milestone
.row = render 'shared/milestones_filter'
.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
%i.fa.fa-list.fa-2x
.col-md-3.responsive-side
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if (params[:f] == "active" || !params[:f]))}
= link_to project_milestones_path(@project, f: "active") do
Active
%li{class: ("active" if params[:f] == "closed")}
= link_to project_milestones_path(@project, f: "closed") do
Closed
%li{class: ("active" if params[:f] == "all")}
= link_to project_milestones_path(@project, f: "all") do
All
.col-md-9
.panel.panel-default
%ul.well-list
= render @milestones
- if @milestones.blank? .milestones
%li .panel.panel-default
.nothing-here-block No milestones to show %ul.well-list
= render @milestones
= paginate @milestones, theme: "gitlab" - if @milestones.blank?
%li
.nothing-here-block No milestones to show
= paginate @milestones, theme: "gitlab"
= render "projects/issues_nav" = render "projects/issues_nav"
%h3.page-title %h4.page-title
.issue-box{ class: issue_box_class(@milestone) } .issue-box{ class: issue_box_class(@milestone) }
- if @milestone.closed? - if @milestone.closed?
Closed Closed
...@@ -8,52 +8,44 @@ ...@@ -8,52 +8,44 @@
- else - else
Open Open
Milestone ##{@milestone.iid} Milestone ##{@milestone.iid}
.pull-right.creator %small.creator
%small= @milestone.expires_at = @milestone.expires_at
.pull-right
- if can?(current_user, :admin_milestone, @project)
= link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
- if @milestone.active?
= link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
= link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
%hr %hr
- if @milestone.issues.any? && @milestone.can_be_closed? - if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success .alert.alert-success
%span All issues for this milestone are closed. You may close milestone now. %span All issues for this milestone are closed. You may close milestone now.
.row
.col-sm-9
%h3.issue-title
= gfm escape_once(@milestone.title)
%div
- if @milestone.description.present?
.description
.wiki
= preserve do
= markdown @milestone.description
%hr
.context
%p.lead
Progress:
#{@milestone.closed_items_count} closed
&ndash;
#{@milestone.open_items_count} open
&nbsp;
%span.light #{@milestone.percent_complete}% complete
%span.pull-right= @milestone.expires_at
.progress.progress-info
.progress-bar{style: "width: #{@milestone.percent_complete}%;"}
.col-sm-3 %h3.issue-title
%div = gfm escape_once(@milestone.title)
- if can?(current_user, :admin_milestone, @project) %div
= link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-block" do - if @milestone.description.present?
%i.fa.fa-pencil-square-o .description
Edit .wiki
- if @milestone.active? = preserve do
= link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-block" = markdown @milestone.description
- else
= link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-block"
= link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-block", title: "New Issue" do
%i.fa.fa-plus
New Issue
= link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-block"
%hr
.context
%p.lead
Progress:
#{@milestone.closed_items_count} closed
&ndash;
#{@milestone.open_items_count} open
&nbsp;
%span.light #{@milestone.percent_complete}% complete
%span.pull-right= @milestone.expires_at
.progress.progress-info
.progress-bar{style: "width: #{@milestone.percent_complete}%;"}
%ul.nav.nav-tabs %ul.nav.nav-tabs
...@@ -71,6 +63,10 @@ ...@@ -71,6 +63,10 @@
%span.badge= @users.count %span.badge= @users.count
.pull-right .pull-right
= link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
New Issue
= link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped"
.tab-content .tab-content
.tab-pane.active#tab-issues .tab-pane.active#tab-issues
......
- unless @branches.empty?
%br
%h4 Already Protected:
%table.table.protected-branches-list
%thead
%tr.no-border
%th Branch
%th Developers can push
%th Last commit
%th
%tbody
- @branches.each do |branch|
- @url = project_protected_branch_path(@project, branch)
%tr
%td
= link_to project_commits_path(@project, branch.name) do
%strong= branch.name
- if @project.root_ref?(branch.name)
%span.label.label-info default
%td
= check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
%td
- if commit = branch.commit
= link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
= commit.short_id
&middot;
#{time_ago_with_tooltip(commit.committed_date)}
- else
(branch was removed from repository)
%td
.pull-right
- if can? current_user, :admin_project, @project
= link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
...@@ -22,29 +22,14 @@ ...@@ -22,29 +22,14 @@
= f.label :name, "Branch", class: 'control-label' = f.label :name, "Branch", class: 'control-label'
.col-sm-10 .col-sm-10
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"}) = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
.form-group
= f.label :developers_can_push, class: 'control-label' do
Developers can push
.col-sm-10
.checkbox
= f.check_box :developers_can_push
%span.descr Allow developers to push to this branch
.form-actions .form-actions
= f.submit 'Protect', class: "btn-create btn" = f.submit 'Protect', class: "btn-create btn"
- unless @branches.empty? = render 'branches_list'
%h5 Already Protected:
%ul.bordered-list.protected-branches-list
- @branches.each do |branch|
%li
%h4
= link_to project_commits_path(@project, branch.name) do
%strong= branch.name
- if @project.root_ref?(branch.name)
%span.label.label-info default
%span.label.label-success
%i.fa.fa-lock
.pull-right
- if can? current_user, :admin_project, @project
= link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
- if commit = branch.commit
= link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
= commit.short_id
%span.light
= gfm escape_once(truncate(commit.title, length: 40))
#{time_ago_with_tooltip(commit.committed_date)}
- else
(branch was removed from repository)
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render "home_panel" = render "home_panel"
- readme = @repository.readme - readme = @repository.readme
......
...@@ -19,13 +19,15 @@ ...@@ -19,13 +19,15 @@
%code [Link Title](page-slug) %code [Link Title](page-slug)
\. \.
.form-group .form-group.wiki-content
= f.label :content, class: 'control-label' = f.label :content, class: 'control-label'
.col-sm-10 .col-sm-10
= render 'projects/zen', f: f, attr: :content, classes: 'description form-control' = render layout: 'projects/md_preview' do
.col-sm-12.hint = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
.pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} .col-sm-12.hint
.pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
.pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
.clearfix .clearfix
.error-alert .error-alert
.form-group .form-group
......
.fixed.sidebar-expand-button.hidden-lg.hidden-md
%i.fa.fa-list.fa-2x
.responsive-side.milestones-filters.append-bottom-10
%ul.nav.nav-pills.nav-compact
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
= link_to milestones_filter_path(state: 'opened') do
%i.fa.fa-exclamation-circle
Open
%li{class: ("active" if params[:state] == 'closed')}
= link_to milestones_filter_path(state: 'closed') do
%i.fa.fa-check-circle
Closed
%li{class: ("active" if params[:state] == 'all')}
= link_to milestones_filter_path(state: 'all') do
%i.fa.fa-compass
All
- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key - if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key
.no-ssh-key-message .no-ssh-key-message.alert.alert-warning.hidden-xs
.container You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
.pull-right.hidden-xs .pull-right
= link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put
| |
= link_to 'Remind later', '#', class: 'hide-no-ssh-message' = link_to 'Remind later', '#', class: 'hide-no-ssh-message'
.links-xs.visible-xs
= link_to "Add key", new_profile_key_path
|
= link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
|
= link_to 'Later', '#', class: 'hide-no-ssh-message'
- if outdated_browser?
- link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
.browser-alert
GitLab may not work properly because you are using an outdated web browser.
%br
Please install a
= link_to 'supported web browser', link
for a better experience.
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Activity feed for #{@user.name}" xml.title "Activity feed for #{@user.name}"
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html" xml.link href: user_url(@user), rel: "alternate", type: "text/html"
......
...@@ -4,6 +4,7 @@ class ProjectServiceWorker ...@@ -4,6 +4,7 @@ class ProjectServiceWorker
sidekiq_options queue: :project_web_hook sidekiq_options queue: :project_web_hook
def perform(hook_id, data) def perform(hook_id, data)
data = data.with_indifferent_access
Service.find(hook_id).execute(data) Service.find(hook_id).execute(data)
end end
end end
...@@ -4,6 +4,7 @@ class ProjectWebHookWorker ...@@ -4,6 +4,7 @@ class ProjectWebHookWorker
sidekiq_options queue: :project_web_hook sidekiq_options queue: :project_web_hook
def perform(hook_id, data) def perform(hook_id, data)
WebHook.find(hook_id).execute data data = data.with_indifferent_access
WebHook.find(hook_id).execute(data)
end end
end end
...@@ -81,6 +81,7 @@ Gitlab::Application.routes.draw do ...@@ -81,6 +81,7 @@ Gitlab::Application.routes.draw do
# #
namespace :admin do namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
resources :keys, only: [:show, :destroy]
member do member do
put :team_update put :team_update
put :block put :block
...@@ -118,6 +119,7 @@ Gitlab::Application.routes.draw do ...@@ -118,6 +119,7 @@ Gitlab::Application.routes.draw do
end end
end end
resource :application_settings, only: [:show, :update]
root to: "dashboard#index" root to: "dashboard#index"
end end
...@@ -241,7 +243,8 @@ Gitlab::Application.routes.draw do ...@@ -241,7 +243,8 @@ Gitlab::Application.routes.draw do
end end
end end
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/} get '/compare/:from...:to' => 'compare#show', :as => 'compare',
:constraints => {from: /.+/, to: /.+/}
resources :snippets, constraints: {id: /\d+/} do resources :snippets, constraints: {id: /\d+/} do
member do member do
...@@ -285,7 +288,7 @@ Gitlab::Application.routes.draw do ...@@ -285,7 +288,7 @@ Gitlab::Application.routes.draw do
resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :refs, only: [] do resources :refs, only: [] do
collection do collection do
......
class AddDevelopersCanPushToProtectedBranches < ActiveRecord::Migration
def change
add_column :protected_branches, :developers_can_push, :boolean, default: false, null: false
end
end
class CreateApplicationSettings < ActiveRecord::Migration
def change
create_table :application_settings do |t|
t.integer :default_projects_limit
t.boolean :signup_enabled
t.boolean :signin_enabled
t.boolean :gravatar_enabled
t.text :sign_in_text
t.timestamps
end
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20141230100055) do ActiveRecord::Schema.define(version: 20150108073740) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -41,6 +41,16 @@ ActiveRecord::Schema.define(version: 20141230100055) do ...@@ -41,6 +41,16 @@ ActiveRecord::Schema.define(version: 20141230100055) do
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
create_table "application_settings", force: true do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
t.boolean "signin_enabled"
t.boolean "gravatar_enabled"
t.text "sign_in_text"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "broadcast_messages", force: true do |t| create_table "broadcast_messages", force: true do |t|
t.text "message", null: false t.text "message", null: false
t.datetime "starts_at" t.datetime "starts_at"
...@@ -380,10 +390,11 @@ ActiveRecord::Schema.define(version: 20141230100055) do ...@@ -380,10 +390,11 @@ ActiveRecord::Schema.define(version: 20141230100055) do
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
create_table "protected_branches", force: true do |t| create_table "protected_branches", force: true do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.string "name", null: false t.string "name", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.boolean "developers_can_push", default: false, null: false
end end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
......
...@@ -78,6 +78,21 @@ Parameters: ...@@ -78,6 +78,21 @@ Parameters:
- `issue_id` (required) - The ID of an issue - `issue_id` (required) - The ID of an issue
- `body` (required) - The content of a note - `body` (required) - The content of a note
### Modify existing issue note
Modify existing note of an issue.
```
PUT /projects/:id/issues/:issue_id/notes/:note_id
```
Parameters:
- `id` (required) - The ID of a project
- `issue_id` (required) - The ID of an issue
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
## Snippets ## Snippets
### List all snippet notes ### List all snippet notes
...@@ -137,7 +152,22 @@ POST /projects/:id/snippets/:snippet_id/notes ...@@ -137,7 +152,22 @@ POST /projects/:id/snippets/:snippet_id/notes
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `snippet_id` (required) - The ID of an snippet - `snippet_id` (required) - The ID of a snippet
- `body` (required) - The content of a note
### Modify existing snippet note
Modify existing note of a snippet.
```
PUT /projects/:id/snippets/:snippet_id/notes/:note_id
```
Parameters:
- `id` (required) - The ID of a project
- `snippet_id` (required) - The ID of a snippet
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note - `body` (required) - The content of a note
## Merge Requests ## Merge Requests
...@@ -199,3 +229,18 @@ Parameters: ...@@ -199,3 +229,18 @@ Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of a merge request - `merge_request_id` (required) - The ID of a merge request
- `body` (required) - The content of a note - `body` (required) - The content of a note
### Modify existing merge request note
Modify existing note of a merge request.
```
PUT /projects/:id/merge_requests/:merge_request_id/notes/:note_id
```
Parameters:
- `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of a merge request
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
...@@ -11,6 +11,8 @@ GET /projects ...@@ -11,6 +11,8 @@ GET /projects
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields
- `sort` (optional) - Return requests sorted in `asc` or `desc` order
```json ```json
[ [
...@@ -628,9 +630,11 @@ GET /projects/search/:query ...@@ -628,9 +630,11 @@ GET /projects/search/:query
Parameters: Parameters:
- query (required) - A string contained in the project name - `query` (required) - A string contained in the project name
- per_page (optional) - number of projects to return per page - `per_page` (optional) - number of projects to return per page
- page (optional) - the page to retrieve - `page` (optional) - the page to retrieve
- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields
- `sort` (optional) - Return requests sorted in `asc` or `desc` order
## Git Hooks (EE only) ## Git Hooks (EE only)
......
# GitLab operations # GitLab operations
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md) - [Sidekiq MemoryKiller](sidekiq_memory_killer.md)
- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md)
# Cleaning up stale Redis sessions
Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis.
Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If
you have been running a large GitLab server (thousands of users) since before
GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis
database after you upgrade to GitLab 7.3. You can also perform a cleanup while
still running GitLab 7.2 or older, but in that case new stale sessions will
start building up again after you clean up.
In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte
hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with
GitLab 7.3.0, the keys are
prefixed with 'session:gitlab:', so they would look like
'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to
remove the keys in the old format.
First we define a shell function with the proper Redis connection details.
```
rcli() {
# This example works for Omnibus installations of GitLab 7.3 or newer. For an
# installation from source you will have to change the socket path and the
# path to redis-cli.
sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@"
}
# test the new shell function; the response should be PONG
rcli ping
```
Now we do a search to see if there are any session keys in the old format for
us to clean up.
```
# returns the number of old-format session keys in Redis
rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l
```
If the number is larger than zero, you can proceed to expire the keys from
Redis. If the number is zero there is nothing to clean up.
```
# Tell Redis to expire each matched key after 600 seconds.
rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli
# This will print '(integer) 1' for each key that gets expired.
```
Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis
background save interval) your Redis database will be compacted. If you are
still using GitLab 7.2, users who are not clicking around in GitLab during the
10 minute expiry window will be signed out of GitLab.
...@@ -8,7 +8,6 @@ If a user is a GitLab administrator they receive all permissions. ...@@ -8,7 +8,6 @@ If a user is a GitLab administrator they receive all permissions.
## Project ## Project
| Action | Guest | Reporter | Developer | Master | Owner | | Action | Guest | Reporter | Developer | Master | Owner |
|---------------------------------------|---------|------------|-------------|----------|--------| |---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ | | Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
...@@ -29,6 +28,7 @@ If a user is a GitLab administrator they receive all permissions. ...@@ -29,6 +28,7 @@ If a user is a GitLab administrator they receive all permissions.
| Add new team members | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ |
| Enable/disable branch protection | | | | ✓ | ✓ | | Enable/disable branch protection | | | | ✓ | ✓ |
| Turn on/off prot. branch push for devs| | | | ✓ | ✓ |
| Rewrite/remove git tags | | | | ✓ | ✓ | | Rewrite/remove git tags | | | | ✓ | ✓ |
| Edit project | | | | ✓ | ✓ | | Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ | | Add deploy keys to project | | | | ✓ | ✓ |
...@@ -37,7 +37,7 @@ If a user is a GitLab administrator they receive all permissions. ...@@ -37,7 +37,7 @@ If a user is a GitLab administrator they receive all permissions.
| Transfer project to another namespace | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ | | Remove project | | | | | ✓ |
| Force push to protected branches | | | | | | | Force push to protected branches | | | | | |
| Remove protected branches | | | | | | | Remove protected branches | | | | | |
## Group ## Group
...@@ -49,4 +49,4 @@ If a user is a GitLab administrator they receive all permissions. ...@@ -49,4 +49,4 @@ If a user is a GitLab administrator they receive all permissions.
| Manage group members | | | | | ✓ | | Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ | | Remove group | | | | | ✓ |
Any user can remove himself from a group, unless he is the last Owner of the group. Any user can remove themselves from a group, unless they are the last Owner of the group.
...@@ -16,3 +16,4 @@ __Project integrations with external services for continuous integration and mor ...@@ -16,3 +16,4 @@ __Project integrations with external services for continuous integration and mor
- PivotalTracker - PivotalTracker
- Pushover - Pushover
- Slack - Slack
- TeamCity
\ No newline at end of file
# Workflow
- [Workflow](workflow.md) - [Workflow](workflow.md)
- [Project Features](project_features.md) - [Project Features](project_features.md)
- [Authorization for merge requests](authorization_for_merge_requests.md) - [Authorization for merge requests](authorization_for_merge_requests.md)
...@@ -7,3 +9,4 @@ ...@@ -7,3 +9,4 @@
- [GitLab Flow](gitlab_flow.md) - [GitLab Flow](gitlab_flow.md)
- [Notifications](notifications.md) - [Notifications](notifications.md)
- [Migrating from SVN to GitLab](migrating_from_svn.md) - [Migrating from SVN to GitLab](migrating_from_svn.md)
- [Protected branches](protected_branches.md)
![GitLab Flow](gitlab_flow.png) ![GitLab Flow](gitlab_flow.png)
# Introduction ## Introduction
Version management with git makes branching and merging much easier than older versioning systems such as SVN. Version management with git makes branching and merging much easier than older versioning systems such as SVN.
This allows a wide variety of branching strategies and workflows. This allows a wide variety of branching strategies and workflows.
...@@ -29,9 +29,9 @@ People have a hard time figuring out which branch they should develop on or depl ...@@ -29,9 +29,9 @@ People have a hard time figuring out which branch they should develop on or depl
Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html) Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html)
We think there is still room for improvement and will detail a set of practices we call GitLab flow. We think there is still room for improvement and will detail a set of practices we call GitLab flow.
# Git flow and its problems ## Git flow and its problems
[![Git Flow timeline by Vincent Driessen, used with persmission](gitdashflow.png) [![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
Git flow was one of the first proposals to use git branches and it has gotten a lot of attention. Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes. It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
...@@ -50,7 +50,7 @@ Frequently developers make a mistake and for example changes are only merged int ...@@ -50,7 +50,7 @@ Frequently developers make a mistake and for example changes are only merged int
The root cause of these errors is that git flow is too complex for most of the use cases. The root cause of these errors is that git flow is too complex for most of the use cases.
And doing releases doesn't automatically mean also doing hotfixes. And doing releases doesn't automatically mean also doing hotfixes.
# GitHub flow as a simpler alternative ## GitHub flow as a simpler alternative
![Master branch with feature branches merged in](github_flow.png) ![Master branch with feature branches merged in](github_flow.png)
...@@ -62,13 +62,13 @@ Merging everything into the master branch and deploying often means you minimize ...@@ -62,13 +62,13 @@ Merging everything into the master branch and deploying often means you minimize
But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues. But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues.
With GitLab flow we offer additional guidance for these questions. With GitLab flow we offer additional guidance for these questions.
# Production branch with GitLab flow ## Production branch with GitLab flow
![Master branch and production branch with arrow that indicate deployments](production_branch.png) ![Master branch and production branch with arrow that indicate deployments](production_branch.png)
GitHub flow does assume you are able to deploy to production every time you merge a feature branch. GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
This is possible for SaaS applications but are many cases where this is not possible. This is possible for SaaS applications but are many cases where this is not possible.
One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass AppStore validation. One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times. Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
In these cases you can make a production branch that reflects the deployed code. In these cases you can make a production branch that reflects the deployed code.
You can deploy a new version by merging in master to the production branch. You can deploy a new version by merging in master to the production branch.
...@@ -78,7 +78,7 @@ This time is pretty accurate if you automatically deploy your production branch. ...@@ -78,7 +78,7 @@ This time is pretty accurate if you automatically deploy your production branch.
If you need a more exact time you can have your deployment script create a tag on each deployment. If you need a more exact time you can have your deployment script create a tag on each deployment.
This flow prevents the overhead of releasing, tagging and merging that is common to git flow. This flow prevents the overhead of releasing, tagging and merging that is common to git flow.
# Environment branches with GitLab flow ## Environment branches with GitLab flow
![Multiple branches with the code cascading from one to another](environment_branches.png) ![Multiple branches with the code cascading from one to another](environment_branches.png)
...@@ -93,7 +93,7 @@ If master is good to go (it should be if you a practicing [continuous delivery]( ...@@ -93,7 +93,7 @@ If master is good to go (it should be if you a practicing [continuous delivery](
If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches. If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/). An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/).
# Release branches with GitLab flow ## Release branches with GitLab flow
![Master and multiple release branches that vary in length with cherrypicks from master](release_branches.png) ![Master and multiple release branches that vary in length with cherrypicks from master](release_branches.png)
...@@ -109,7 +109,7 @@ Every time a bug-fix is included in a release branch the patch version is raised ...@@ -109,7 +109,7 @@ Every time a bug-fix is included in a release branch the patch version is raised
Some projects also have a stable branch that points to the same commit as the latest released branch. Some projects also have a stable branch that points to the same commit as the latest released branch.
In this flow it is not common to have a production branch (or git flow master branch). In this flow it is not common to have a production branch (or git flow master branch).
# Merge/pull requests with GitLab flow ## Merge/pull requests with GitLab flow
![Merge request with line comments](mr_inline_comments.png) ![Merge request with line comments](mr_inline_comments.png)
...@@ -134,7 +134,7 @@ If the assigned person does not feel comfortable they can close the merge reques ...@@ -134,7 +134,7 @@ If the assigned person does not feel comfortable they can close the merge reques
In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md). In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md).
So if you want to merge it into a protected branch you assign it to someone with master authorizations. So if you want to merge it into a protected branch you assign it to someone with master authorizations.
# Issues with GitLab flow ## Issues with GitLab flow
![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png) ![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png)
...@@ -168,7 +168,7 @@ In this case it is no problem to reuse the same branch name since it was deleted ...@@ -168,7 +168,7 @@ In this case it is no problem to reuse the same branch name since it was deleted
At any time there is at most one branch for every issue. At any time there is at most one branch for every issue.
It is possible that one feature branch solves more than one issue. It is possible that one feature branch solves more than one issue.
# Linking and closing issues from merge requests ## Linking and closing issues from merge requests
![Merge request showing the linked issues that will be closed](close_issue_mr.png) ![Merge request showing the linked issues that will be closed](close_issue_mr.png)
...@@ -181,7 +181,7 @@ If you only want to make the reference without closing the issue you can also ju ...@@ -181,7 +181,7 @@ If you only want to make the reference without closing the issue you can also ju
If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue. If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue.
# Squashing commits with rebase ## Squashing commits with rebase
![Vim screen showing the rebase view](rebase.png) ![Vim screen showing the rebase view](rebase.png)
...@@ -189,7 +189,7 @@ With git you can use an interactive rebase (rebase -i) to squash multiple commit ...@@ -189,7 +189,7 @@ With git you can use an interactive rebase (rebase -i) to squash multiple commit
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical. This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server. However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them. Somebody can have referred to the commits or cherry-picked them.
When you rebase you change the identifier (SHA1) of the commit and this is confusing. When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
If you do that the same change will be known under multiple identifiers and this can cause much confusion. If you do that the same change will be known under multiple identifiers and this can cause much confusion.
If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit. If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
...@@ -207,7 +207,7 @@ If you revert a merge and you change your mind, revert the revert instead of mer ...@@ -207,7 +207,7 @@ If you revert a merge and you change your mind, revert the revert instead of mer
Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option. Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option.
Git management software will always create a merge commit when you accept a merge request. Git management software will always create a merge commit when you accept a merge request.
# Do not order commits with rebase ## Do not order commits with rebase
![List of sequential merge commits](merge_commits.png) ![List of sequential merge commits](merge_commits.png)
...@@ -231,8 +231,8 @@ The last reason for creating merge commits is having long lived branches that yo ...@@ -231,8 +231,8 @@ The last reason for creating merge commits is having long lived branches that yo
Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
That's continuous building, and a Good Thing, but there's no integration, so it's not CI.". That's continuous building, and a Good Thing, but there's no integration, so it's not CI.".
The solution to prevent many merge commits is to keep your feature branches shortlived, the vast majority should take less than one day of work. The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work.
If your feature branches commenly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html). If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
As for the long running branches that take more than one day there are two strategies. As for the long running branches that take more than one day there are two strategies.
In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time. In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time.
In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release. In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release.
...@@ -254,7 +254,7 @@ Developing software happen in small messy steps and it is OK to have your histor ...@@ -254,7 +254,7 @@ Developing software happen in small messy steps and it is OK to have your histor
You can use tools to view the network graphs of commits and understand the messy history that created your code. You can use tools to view the network graphs of commits and understand the messy history that created your code.
If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers. If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
# Voting on merge requests ## Voting on merge requests
![Voting slider in GitLab](voting_slider.png) ![Voting slider in GitLab](voting_slider.png)
...@@ -262,7 +262,7 @@ It is common to voice approval or disapproval by using +1 or -1 emoticons. ...@@ -262,7 +262,7 @@ It is common to voice approval or disapproval by using +1 or -1 emoticons.
In GitLab the +1 and -1 are aggregated and shown at the top of the merge request. In GitLab the +1 and -1 are aggregated and shown at the top of the merge request.
As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet. As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet.
# Pushing and removing branches ## Pushing and removing branches
![Remove checkbox for branch in merge requests](remove_checkbox.png) ![Remove checkbox for branch in merge requests](remove_checkbox.png)
...@@ -276,7 +276,7 @@ This ensures that the branch overview in the repository management software show ...@@ -276,7 +276,7 @@ This ensures that the branch overview in the repository management software show
This also ensures that when someone reopens the issue a new branch with the same name can be used without problem. This also ensures that when someone reopens the issue a new branch with the same name can be used without problem.
When you reopen an issue you need to create a new merge request. When you reopen an issue you need to create a new merge request.
# Committing often and with the right message ## Committing often and with the right message
![Good and bad commit message](good_commit.png) ![Good and bad commit message](good_commit.png)
...@@ -292,7 +292,7 @@ Some words that are bad commit messages because they don't contain munch informa ...@@ -292,7 +292,7 @@ Some words that are bad commit messages because they don't contain munch informa
The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number. The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number.
To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
# Testing before merging ## Testing before merging
![Merge requests showing the test states, red, yellow and green](ci_mr.png) ![Merge requests showing the test states, red, yellow and green](ci_mr.png)
...@@ -309,7 +309,7 @@ If there are no merge conflicts and the feature branches are short lived the ris ...@@ -309,7 +309,7 @@ If there are no merge conflicts and the feature branches are short lived the ris
If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests. If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
If you have long lived feature branches that last for more than a few days you should make your issues smaller. If you have long lived feature branches that last for more than a few days you should make your issues smaller.
# Merging in other code ## Merging in other code
![Shell output showing git pull output](git_pull.png) ![Shell output showing git pull output](git_pull.png)
......
# Protected branches
Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
To prevent people from messing with history or pushing code without review, we've created protected branches.
A protected branch does three simple things:
* it prevents pushes from everybody except users with Master permission
* it prevents anyone from force pushing to the branch
* it prevents anyone from deleting the branch
You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
To protect a branch, user needs to have at least a Master permission level, see [permissions document](permissions/permissions.md).
![protected branches page](protected_branches/protected_branches1.png)
Navigate to project settings page and select `protected branches`. From the `Branch` dropdown menu select the branch you want to protect.
Some workflows, like [GitLab workflow](gitlab_flow.md), require all users with write access to submit a Merge request in order to get the code into a protected branch.
Since Masters and Owners can already push to protected branches, that means Developers cannot push to protected branch and need to submit a Merge request.
However, there are workflows where that is not needed and only protecting from force pushes and branch removal is useful.
For those workflows, you can allow everyone with write access to push to a protected branch by selecting `Developers can push` check box.
On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box.
![Developers can push](protected_branches/protected_branches2.png)
@admin
Feature: Admin Settings
Background:
Given I sign in as an admin
And I visit admin settings page
Scenario: Change application settings
When I disable gravatars and save form
Then I should be see gravatar disabled
...@@ -35,3 +35,13 @@ Feature: Admin Users ...@@ -35,3 +35,13 @@ Feature: Admin Users
And I see the secondary email And I see the secondary email
When I click remove secondary email When I click remove secondary email
Then I should not see secondary email anymore Then I should not see secondary email anymore
Scenario: Show user keys
Given user "Pete" with ssh keys
And I visit admin users page
And click on user "Pete"
Then I should see key list
And I click on the key title
Then I should see key details
And I click on remove key
Then I should see the key removed
...@@ -72,3 +72,10 @@ Feature: Project Services ...@@ -72,3 +72,10 @@ Feature: Project Services
And I click Atlassian Bamboo CI service link And I click Atlassian Bamboo CI service link
And I fill Atlassian Bamboo CI settings And I fill Atlassian Bamboo CI settings
Then I should see Atlassian Bamboo CI service settings saved Then I should see Atlassian Bamboo CI service settings saved
Scenario: Activate jetBrains TeamCity CI service
When I visit project "Shop" services page
And I click jetBrains TeamCity CI service link
And I fill jetBrains TeamCity CI settings
Then I should see jetBrains TeamCity CI service settings saved
...@@ -50,6 +50,16 @@ Feature: Project Source Browse Files ...@@ -50,6 +50,16 @@ Feature: Project Source Browse Files
And I click button "Edit" And I click button "Edit"
Then I can edit code Then I can edit code
Scenario: If the file is binary the edit link is hidden
Given I visit a binary file in the repo
Then I cannot see the edit button
Scenario: If I don't have edit permission the edit link is disabled
Given public project "Community"
And I visit project "Community" source page
And I click on ".gitignore" file in repo
Then The edit button is disabled
@javascript @javascript
Scenario: I can edit and commit file Scenario: I can edit and commit file
Given I click on ".gitignore" file in repo Given I click on ".gitignore" file in repo
......
class Spinach::Features::AdminSettings < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
include Gitlab::CurrentSettings
step 'I disable gravatars and save form' do
uncheck 'Gravatar enabled'
click_button 'Save'
end
step 'I should be see gravatar disabled' do
current_application_settings.gravatar_enabled.should be_false
page.should have_content 'Application settings saved successfully'
end
end
...@@ -82,4 +82,36 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps ...@@ -82,4 +82,36 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
page.should have_content 'Account' page.should have_content 'Account'
page.should have_content 'Personal projects limit' page.should have_content 'Personal projects limit'
end end
step 'user "Pete" with ssh keys' do
user = create(:user, name: 'Pete')
create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
end
step 'click on user "Pete"' do
click_link 'Pete'
end
step 'I should see key list' do
page.should have_content 'ssh-rsa Key2'
page.should have_content 'ssh-rsa Key1'
end
step 'I click on the key title' do
click_link 'ssh-rsa Key2'
end
step 'I should see key details' do
page.should have_content 'ssh-rsa Key2'
page.should have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
end
step 'I click on remove key' do
click_link 'Remove'
end
step 'I should see the key removed' do
page.should_not have_content 'ssh-rsa Key2'
end
end end
...@@ -226,7 +226,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -226,7 +226,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end end
step 'I should see group milestone with descriptions and expiry date' do step 'I should see group milestone with descriptions and expiry date' do
page.should have_content('Lorem Ipsum is simply dummy text of the printing and typesetting industry')
page.should have_content('expires at Aug 20, 2114') page.should have_content('expires at Aug 20, 2114')
end end
......
...@@ -37,9 +37,7 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps ...@@ -37,9 +37,7 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
end end
step 'I should not see "Work" ssh key' do step 'I should not see "Work" ssh key' do
within "#keys-table" do page.should_not have_content "Work"
page.should_not have_content "Work"
end
end end
step 'I have ssh key "ssh-rsa Work"' do step 'I have ssh key "ssh-rsa Work"' do
......
...@@ -113,7 +113,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -113,7 +113,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_link 'Commits' click_link 'Commits'
end end
within '.mr-commits' do within '.commits' do
click_link Commit.truncate_sha(sample_commit.id) click_link Commit.truncate_sha(sample_commit.id)
end end
end end
...@@ -156,7 +156,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -156,7 +156,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'merge request is mergeable' do step 'merge request is mergeable' do
page.should have_content 'You can accept this request automatically' page.should have_button 'Accept Merge Request'
end end
step 'I modify merge commit message' do step 'I modify merge commit message' do
...@@ -275,7 +275,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -275,7 +275,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I click Side-by-side Diff tab' do step 'I click Side-by-side Diff tab' do
click_link 'Side-by-side' find('a', text: 'Side-by-side').trigger('click')
end end
step 'I should see comments on the side-by-side diff page' do step 'I should see comments on the side-by-side diff page' do
......
...@@ -15,6 +15,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -15,6 +15,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
page.should have_content 'Assembla' page.should have_content 'Assembla'
page.should have_content 'Pushover' page.should have_content 'Pushover'
page.should have_content 'Atlassian Bamboo' page.should have_content 'Atlassian Bamboo'
page.should have_content 'JetBrains TeamCity'
end end
step 'I click gitlab-ci service link' do step 'I click gitlab-ci service link' do
...@@ -187,4 +188,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -187,4 +188,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Build key').value.should == 'KEY' find_field('Build key').value.should == 'KEY'
find_field('Username').value.should == 'user' find_field('Username').value.should == 'user'
end end
step 'I click JetBrains TeamCity CI service link' do
click_link 'JetBrains TeamCity CI'
end
step 'I fill JetBrains TeamCity CI settings' do
check 'Active'
fill_in 'Teamcity url', with: 'http://teamcity.example.com'
fill_in 'Build type', with: 'GitlabTest_Build'
fill_in 'Username', with: 'user'
fill_in 'Password', with: 'verySecret'
click_button 'Save'
end
step 'I should see JetBrains TeamCity CI service settings saved' do
find_field('Teamcity url').value.should == 'http://teamcity.example.com'
find_field('Build type').value.should == 'GitlabTest_Build'
find_field('Username').value.should == 'user'
end
end end
...@@ -48,6 +48,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -48,6 +48,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_link 'Edit' click_link 'Edit'
end end
step 'I cannot see the edit button' do
page.should_not have_link 'edit'
end
step 'The edit button is disabled' do
page.should have_css '.disabled', text: 'Edit'
end
step 'I can edit code' do step 'I can edit code' do
set_new_content set_new_content
evaluate_script('editor.getValue()').should == new_gitignore_content evaluate_script('editor.getValue()').should == new_gitignore_content
......
module SharedPaths module SharedPaths
include Spinach::DSL include Spinach::DSL
include RepoHelpers include RepoHelpers
include DashboardHelper
step 'I visit new project page' do step 'I visit new project page' do
visit new_project_path visit new_project_path
...@@ -79,11 +80,11 @@ module SharedPaths ...@@ -79,11 +80,11 @@ module SharedPaths
end end
step 'I visit dashboard issues page' do step 'I visit dashboard issues page' do
visit issues_dashboard_path visit assigned_issues_dashboard_path
end end
step 'I visit dashboard merge requests page' do step 'I visit dashboard merge requests page' do
visit merge_requests_dashboard_path visit assigned_mrs_dashboard_path
end end
step 'I visit dashboard search page' do step 'I visit dashboard search page' do
...@@ -181,6 +182,11 @@ module SharedPaths ...@@ -181,6 +182,11 @@ module SharedPaths
step 'I visit admin email page' do step 'I visit admin email page' do
visit admin_email_path visit admin_email_path
end end
step 'I visit admin settings page' do
visit admin_application_settings_path
end
# ---------------------------------------- # ----------------------------------------
# Generic Project # Generic Project
# ---------------------------------------- # ----------------------------------------
...@@ -197,6 +203,11 @@ module SharedPaths ...@@ -197,6 +203,11 @@ module SharedPaths
visit project_tree_path(@project, root_ref) visit project_tree_path(@project, root_ref)
end end
step 'I visit a binary file in the repo' do
visit project_blob_path(@project, File.join(
root_ref, 'files/images/logo-black.png'))
end
step "I visit my project's commits page" do step "I visit my project's commits page" do
visit project_commits_path(@project, root_ref, {limit: 5}) visit project_commits_path(@project, root_ref, {limit: 5})
end end
...@@ -403,6 +414,11 @@ module SharedPaths ...@@ -403,6 +414,11 @@ module SharedPaths
visit project_path(project) visit project_path(project)
end end
step 'I visit project "Community" source page' do
project = Project.find_by(name: 'Community')
visit project_tree_path(project, root_ref)
end
step 'I visit project "Internal" page' do step 'I visit project "Internal" page' do
project = Project.find_by(name: "Internal") project = Project.find_by(name: "Internal")
visit project_path(project) visit project_path(project)
......
...@@ -14,7 +14,8 @@ module API ...@@ -14,7 +14,8 @@ module API
# Example Request: # Example Request:
# GET /projects/:id/repository/branches # GET /projects/:id/repository/branches
get ":id/repository/branches" do get ":id/repository/branches" do
present user_project.repository.branches.sort_by(&:name), with: Entities::RepoObject, project: user_project branches = user_project.repository.branches.sort_by(&:name)
present branches, with: Entities::RepoObject, project: user_project
end end
# Get a single branch # Get a single branch
...@@ -26,7 +27,7 @@ module API ...@@ -26,7 +27,7 @@ module API
# GET /projects/:id/repository/branches/:branch # GET /projects/:id/repository/branches/:branch
get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do
@branch = user_project.repository.branches.find { |item| item.name == params[:branch] } @branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
not_found!("Branch does not exist") if @branch.nil? not_found!("Branch") unless @branch
present @branch, with: Entities::RepoObject, project: user_project present @branch, with: Entities::RepoObject, project: user_project
end end
...@@ -43,7 +44,7 @@ module API ...@@ -43,7 +44,7 @@ module API
authorize_admin_project authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch]) @branch = user_project.repository.find_branch(params[:branch])
not_found! unless @branch not_found!("Branch") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch = user_project.protected_branches.find_by(name: @branch.name)
user_project.protected_branches.create(name: @branch.name) unless protected_branch user_project.protected_branches.create(name: @branch.name) unless protected_branch
...@@ -63,7 +64,7 @@ module API ...@@ -63,7 +64,7 @@ module API
authorize_admin_project authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch]) @branch = user_project.repository.find_branch(params[:branch])
not_found! unless @branch not_found!("Branch does not exist") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch protected_branch.destroy if protected_branch
......
...@@ -108,7 +108,7 @@ module API ...@@ -108,7 +108,7 @@ module API
if note.save if note.save
present note, with: Entities::CommitNote present note, with: Entities::CommitNote
else else
not_found! render_api_error!("Failed to save note #{note.errors.messages}", 400)
end end
end end
end end
......
...@@ -35,7 +35,7 @@ module API ...@@ -35,7 +35,7 @@ module API
file_path = attrs.delete(:file_path) file_path = attrs.delete(:file_path)
commit = user_project.repository.commit(ref) commit = user_project.repository.commit(ref)
not_found! "Commit" unless commit not_found! 'Commit' unless commit
blob = user_project.repository.blob_at(commit.sha, file_path) blob = user_project.repository.blob_at(commit.sha, file_path)
...@@ -53,7 +53,7 @@ module API ...@@ -53,7 +53,7 @@ module API
commit_id: commit.id, commit_id: commit.id,
} }
else else
render_api_error!('File not found', 404) not_found! 'File'
end end
end end
......
...@@ -62,7 +62,7 @@ module API ...@@ -62,7 +62,7 @@ module API
end end
present @group, with: Entities::Group present @group, with: Entities::Group
else else
not_found! render_api_error!("Failed to save group #{@group.errors.messages}", 400)
end end
end end
...@@ -105,7 +105,7 @@ module API ...@@ -105,7 +105,7 @@ module API
if result if result
present group present group
else else
not_found! render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
end end
end end
end end
......
...@@ -42,7 +42,7 @@ module API ...@@ -42,7 +42,7 @@ module API
def user_project def user_project
@project ||= find_project(params[:id]) @project ||= find_project(params[:id])
@project || not_found! @project || not_found!("Project")
end end
def find_project(id) def find_project(id)
......
...@@ -233,7 +233,7 @@ module API ...@@ -233,7 +233,7 @@ module API
if note.save if note.save
present note, with: Entities::MRNote present note, with: Entities::MRNote
else else
render_validation_error!(note) render_api_error!("Failed to save note #{note.errors.messages}", 400)
end end
end end
end end
......
...@@ -48,7 +48,7 @@ module API ...@@ -48,7 +48,7 @@ module API
if milestone.valid? if milestone.valid?
present milestone, with: Entities::Milestone present milestone, with: Entities::Milestone
else else
not_found! render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
end end
end end
...@@ -72,7 +72,7 @@ module API ...@@ -72,7 +72,7 @@ module API
if milestone.valid? if milestone.valid?
present milestone, with: Entities::Milestone present milestone, with: Entities::Milestone
else else
not_found! render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
end end
end end
end end
......
...@@ -61,9 +61,42 @@ module API ...@@ -61,9 +61,42 @@ module API
if @note.valid? if @note.valid?
present @note, with: Entities::Note present @note, with: Entities::Note
else else
not_found! not_found!("Note #{@note.errors.messages}")
end end
end end
# Modify existing +noteable+ note
#
# Parameters:
# id (required) - The ID of a project
# noteable_id (required) - The ID of an issue or snippet
# node_id (required) - The ID of a note
# body (required) - New content of a note
# Example Request:
# PUT /projects/:id/issues/:noteable_id/notes/:note_id
# PUT /projects/:id/snippets/:noteable_id/notes/:node_id
put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
required_attributes! [:body]
authorize! :admin_note, user_project.notes.find(params[:note_id])
opts = {
note: params[:body],
note_id: params[:note_id],
noteable_type: noteables_str.classify,
noteable_id: params[noteable_id_str]
}
@note = ::Notes::UpdateService.new(user_project, current_user,
opts).execute
if @note.valid?
present @note, with: Entities::Note
else
render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
end end
end end
end end
......
...@@ -53,7 +53,7 @@ module API ...@@ -53,7 +53,7 @@ module API
if @hook.errors[:url].present? if @hook.errors[:url].present?
error!("Invalid url given", 422) error!("Invalid url given", 422)
end end
not_found! not_found!("Project hook #{@hook.errors.messages}")
end end
end end
...@@ -82,7 +82,7 @@ module API ...@@ -82,7 +82,7 @@ module API
if @hook.errors[:url].present? if @hook.errors[:url].present?
error!("Invalid url given", 422) error!("Invalid url given", 422)
end end
not_found! not_found!("Project hook #{@hook.errors.messages}")
end end
end end
......
...@@ -9,7 +9,7 @@ module API ...@@ -9,7 +9,7 @@ module API
if errors[:access_level].any? if errors[:access_level].any?
error!(errors[:access_level], 422) error!(errors[:access_level], 422)
end end
not_found! not_found!(errors)
end end
end end
......
...@@ -22,6 +22,15 @@ module API ...@@ -22,6 +22,15 @@ module API
# GET /projects # GET /projects
get do get do
@projects = current_user.authorized_projects @projects = current_user.authorized_projects
sort = params[:sort] == 'desc' ? 'desc' : 'asc'
@projects = case params["order_by"]
when 'id' then @projects.reorder("id #{sort}")
when 'name' then @projects.reorder("name #{sort}")
when 'created_at' then @projects.reorder("created_at #{sort}")
when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}")
else @projects
end
# If the archived parameter is passed, limit results accordingly # If the archived parameter is passed, limit results accordingly
if params[:archived].present? if params[:archived].present?
...@@ -37,7 +46,17 @@ module API ...@@ -37,7 +46,17 @@ module API
# Example Request: # Example Request:
# GET /projects/owned # GET /projects/owned
get '/owned' do get '/owned' do
@projects = paginate current_user.owned_projects sort = params[:sort] == 'desc' ? 'desc' : 'asc'
@projects = current_user.owned_projects
@projects = case params["order_by"]
when 'id' then @projects.reorder("id #{sort}")
when 'name' then @projects.reorder("name #{sort}")
when 'created_at' then @projects.reorder("created_at #{sort}")
when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}")
else @projects
end
@projects = paginate @projects
present @projects, with: Entities::Project present @projects, with: Entities::Project
end end
...@@ -47,7 +66,17 @@ module API ...@@ -47,7 +66,17 @@ module API
# GET /projects/all # GET /projects/all
get '/all' do get '/all' do
authenticated_as_admin! authenticated_as_admin!
@projects = paginate Project sort = params[:sort] == 'desc' ? 'desc' : 'asc'
@projects = case params["order_by"]
when 'id' then Project.order("id #{sort}")
when 'name' then Project.order("name #{sort}")
when 'created_at' then Project.order("created_at #{sort}")
when 'last_activity_at' then Project.order("last_activity_at #{sort}")
else Project
end
@projects = paginate @projects
present @projects, with: Entities::Project present @projects, with: Entities::Project
end end
...@@ -198,7 +227,7 @@ module API ...@@ -198,7 +227,7 @@ module API
render_api_error!("Project already forked", 409) render_api_error!("Project already forked", 409)
end end
else else
not_found! not_found!("Source Project")
end end
end end
...@@ -251,6 +280,16 @@ module API ...@@ -251,6 +280,16 @@ module API
ids = current_user.authorized_projects.map(&:id) ids = current_user.authorized_projects.map(&:id)
visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
sort = params[:sort] == 'desc' ? 'desc' : 'asc'
projects = case params["order_by"]
when 'id' then projects.order("id #{sort}")
when 'name' then projects.order("name #{sort}")
when 'created_at' then projects.order("created_at #{sort}")
when 'last_activity_at' then projects.order("last_activity_at #{sort}")
else projects
end
present paginate(projects), with: Entities::Project present paginate(projects), with: Entities::Project
end end
......
...@@ -133,7 +133,7 @@ module API ...@@ -133,7 +133,7 @@ module API
env['api.format'] = :binary env['api.format'] = :binary
present data present data
else else
not_found! not_found!('File')
end end
end end
......
module Gitlab
module CurrentSettings
def current_application_settings
if ActiveRecord::Base.connection.table_exists?('application_settings')
ApplicationSetting.current ||
ApplicationSetting.create_from_defaults
else
fake_application_settings
end
end
def fake_application_settings
OpenStruct.new(
default_projects_limit: Settings.gitlab['default_projects_limit'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
)
end
end
end
...@@ -79,16 +79,8 @@ module Gitlab ...@@ -79,16 +79,8 @@ module Gitlab
oldrev, newrev, ref = change.split(' ') oldrev, newrev, ref = change.split(' ')
action = if project.protected_branch?(branch_name(ref)) action = if project.protected_branch?(branch_name(ref))
# we dont allow force push to protected branch protected_branch_action(project, oldrev, newrev, branch_name(ref))
if forced_push?(project, oldrev, newrev) elsif protected_tag?(project, tag_name(ref))
:force_push_code_to_protected_branches
# and we dont allow remove of protected branch
elsif newrev == Gitlab::Git::BLANK_SHA
:remove_protected_branches
else
:push_code_to_protected_branches
end
elsif project.repository.tag_names.include?(tag_name(ref))
# Prevent any changes to existing git tag unless user has permissions # Prevent any changes to existing git tag unless user has permissions
:admin_project :admin_project
else else
...@@ -171,6 +163,24 @@ module Gitlab ...@@ -171,6 +163,24 @@ module Gitlab
private private
def protected_branch_action(project, oldrev, newrev, branch_name)
# we dont allow force push to protected branch
if forced_push?(project, oldrev, newrev)
:force_push_code_to_protected_branches
# and we dont allow remove of protected branch
elsif newrev == Gitlab::Git::BLANK_SHA
:remove_protected_branches
elsif project.developers_can_push_to_protected_branch?(branch_name)
:push_code
else
:push_code_to_protected_branches
end
end
def protected_tag?(project, tag_name)
project.repository.tag_names.include?(tag_name)
end
def user_allowed?(user) def user_allowed?(user)
Gitlab::UserAccess.allowed?(user) Gitlab::UserAccess.allowed?(user)
end end
......
...@@ -21,6 +21,9 @@ module Gitlab ...@@ -21,6 +21,9 @@ module Gitlab
@cmd_output = "" @cmd_output = ""
@cmd_status = 0 @cmd_status = 0
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
# We are not using stdin so we should close it, in case the command we
# are running waits for input.
stdin.close
@cmd_output << stdout.read @cmd_output << stdout.read
@cmd_output << stderr.read @cmd_output << stderr.read
@cmd_status = wait_thr.value.exitstatus @cmd_status = wait_thr.value.exitstatus
......
...@@ -24,11 +24,12 @@ describe "User Feed", feature: true do ...@@ -24,11 +24,12 @@ describe "User Feed", feature: true do
end end
it "should have issue opened event" do it "should have issue opened event" do
body.should have_content("#{user.name} opened issue ##{issue.iid}") expect(body).to have_content("#{safe_name} opened issue ##{issue.iid}")
end end
it "should have issue comment event" do it "should have issue comment event" do
body.should have_content("#{user.name} commented on issue ##{issue.iid}") expect(body).
to have_content("#{safe_name} commented on issue ##{issue.iid}")
end end
end end
end end
...@@ -40,4 +41,8 @@ describe "User Feed", feature: true do ...@@ -40,4 +41,8 @@ describe "User Feed", feature: true do
def note_event(note, user) def note_event(note, user)
EventCreateService.new.leave_note(note, user) EventCreateService.new.leave_note(note, user)
end end
def safe_name
html_escape(user.name)
end
end end
...@@ -9,7 +9,7 @@ describe "Profile account page", feature: true do ...@@ -9,7 +9,7 @@ describe "Profile account page", feature: true do
describe "when signup is enabled" do describe "when signup is enabled" do
before do before do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) ApplicationSetting.any_instance.stub(signup_enabled?: true)
visit profile_account_path visit profile_account_path
end end
...@@ -23,7 +23,7 @@ describe "Profile account page", feature: true do ...@@ -23,7 +23,7 @@ describe "Profile account page", feature: true do
describe "when signup is disabled" do describe "when signup is disabled" do
before do before do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) ApplicationSetting.any_instance.stub(signup_enabled?: false)
visit profile_account_path visit profile_account_path
end end
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Users', feature: true do describe 'Users', feature: true do
describe "GET /users/sign_up" do describe "GET /users/sign_up" do
before do before do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) ApplicationSetting.any_instance.stub(signup_enabled?: true)
end end
it "should create a new user account" do it "should create a new user account" do
......
...@@ -87,7 +87,7 @@ describe ApplicationHelper do ...@@ -87,7 +87,7 @@ describe ApplicationHelper do
let(:user_email) { 'user@email.com' } let(:user_email) { 'user@email.com' }
it "should return a generic avatar path when Gravatar is disabled" do it "should return a generic avatar path when Gravatar is disabled" do
Gitlab.config.gravatar.stub(:enabled).and_return(false) ApplicationSetting.any_instance.stub(gravatar_enabled?: false)
gravatar_icon(user_email).should match('no_avatar.png') gravatar_icon(user_email).should match('no_avatar.png')
end end
......
...@@ -129,6 +129,13 @@ describe Gitlab::GitAccess do ...@@ -129,6 +129,13 @@ describe Gitlab::GitAccess do
} }
end end
def self.updated_permissions_matrix
updated_permissions_matrix = permissions_matrix.dup
updated_permissions_matrix[:developer][:push_protected_branch] = true
updated_permissions_matrix[:developer][:push_all] = true
updated_permissions_matrix
end
permissions_matrix.keys.each do |role| permissions_matrix.keys.each do |role|
describe "#{role} access" do describe "#{role} access" do
before { protect_feature_branch } before { protect_feature_branch }
...@@ -143,6 +150,23 @@ describe Gitlab::GitAccess do ...@@ -143,6 +150,23 @@ describe Gitlab::GitAccess do
end end
end end
end end
context "with enabled developers push to protected branches " do
updated_permissions_matrix.keys.each do |role|
describe "#{role} access" do
before { create(:protected_branch, name: 'feature', developers_can_push: true, project: project) }
before { project.team << [user, role] }
updated_permissions_matrix[role].each do |action, allowed|
context action do
subject { access.push_access_check(user, project, changes[action]) }
it { subject.allowed?.should allowed ? be_true : be_false }
end
end
end
end
end
end end
describe "pass_git_hooks?" do describe "pass_git_hooks?" do
......
require 'spec_helper'
describe ApplicationSetting, models: true do
it { ApplicationSetting.create_from_defaults.should be_valid }
end
...@@ -44,7 +44,7 @@ describe API::API, api: true do ...@@ -44,7 +44,7 @@ describe API::API, api: true do
it 'should fail on missing project access for the project to fork' do it 'should fail on missing project access for the project to fork' do
post api("/projects/fork/#{project.id}", user3) post api("/projects/fork/#{project.id}", user3)
response.status.should == 404 response.status.should == 404
json_response['message'].should == '404 Not Found' json_response['message'].should == '404 Project Not Found'
end end
it 'should fail if forked project exists in the user namespace' do it 'should fail if forked project exists in the user namespace' do
...@@ -58,7 +58,7 @@ describe API::API, api: true do ...@@ -58,7 +58,7 @@ describe API::API, api: true do
it 'should fail if project to fork from does not exist' do it 'should fail if project to fork from does not exist' do
post api('/projects/fork/424242', user) post api('/projects/fork/424242', user)
response.status.should == 404 response.status.should == 404
json_response['message'].should == '404 Not Found' json_response['message'].should == '404 Project Not Found'
end end
end end
......
...@@ -98,7 +98,8 @@ describe API::API, api: true do ...@@ -98,7 +98,8 @@ describe API::API, api: true do
it "should not create group, duplicate" do it "should not create group, duplicate" do
post api("/groups", admin), {name: "Duplicate Test", path: group2.path} post api("/groups", admin), {name: "Duplicate Test", path: group2.path}
response.status.should == 404 response.status.should == 400
response.message.should == "Bad Request"
end end
it "should return 400 bad request error if name not given" do it "should return 400 bad request error if name not given" do
......
...@@ -131,4 +131,58 @@ describe API::API, api: true do ...@@ -131,4 +131,58 @@ describe API::API, api: true do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
end end
end end
describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do
context 'when noteable is an Issue' do
it 'should return modified note' do
put api("/projects/#{project.id}/issues/#{issue.id}/"\
"notes/#{issue_note.id}", user), body: 'Hello!'
response.status.should == 200
json_response['body'].should == 'Hello!'
end
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
body: 'Hello!'
response.status.should == 404
end
it 'should return a 400 bad request error if body not given' do
put api("/projects/#{project.id}/issues/#{issue.id}/"\
"notes/#{issue_note.id}", user)
response.status.should == 400
end
end
context 'when noteable is a Snippet' do
it 'should return modified note' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
"notes/#{snippet_note.id}", user), body: 'Hello!'
response.status.should == 200
json_response['body'].should == 'Hello!'
end
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
"notes/123", user), body: "Hello!"
response.status.should == 404
end
end
context 'when noteable is a Merge Request' do
it 'should return modified note' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
"notes/#{merge_request_note.id}", user), body: 'Hello!'
response.status.should == 200
json_response['body'].should == 'Hello!'
end
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
"notes/123", user), body: "Hello!"
response.status.should == 404
end
end
end
end end
...@@ -289,7 +289,7 @@ describe API::API, api: true do ...@@ -289,7 +289,7 @@ describe API::API, api: true do
it "should return a 404 error if not found" do it "should return a 404 error if not found" do
get api("/projects/42", user) get api("/projects/42", user)
response.status.should == 404 response.status.should == 404
json_response['message'].should == '404 Not Found' json_response['message'].should == '404 Project Not Found'
end end
it "should return a 404 error if user is not a member" do it "should return a 404 error if user is not a member" do
...@@ -340,7 +340,7 @@ describe API::API, api: true do ...@@ -340,7 +340,7 @@ describe API::API, api: true do
it "should return a 404 error if not found" do it "should return a 404 error if not found" do
get api("/projects/42/events", user) get api("/projects/42/events", user)
response.status.should == 404 response.status.should == 404
json_response['message'].should == '404 Not Found' json_response['message'].should == '404 Project Not Found'
end end
it "should return a 404 error if user is not a member" do it "should return a 404 error if user is not a member" do
......
...@@ -186,7 +186,7 @@ describe API::API, api: true do ...@@ -186,7 +186,7 @@ describe API::API, api: true do
describe "GET /users/sign_up" do describe "GET /users/sign_up" do
context 'enabled' do context 'enabled' do
before do before do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) ApplicationSetting.any_instance.stub(signup_enabled?: true)
end end
it "should return sign up page if signup is enabled" do it "should return sign up page if signup is enabled" do
...@@ -197,7 +197,7 @@ describe API::API, api: true do ...@@ -197,7 +197,7 @@ describe API::API, api: true do
context 'disabled' do context 'disabled' do
before do before do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) ApplicationSetting.any_instance.stub(signup_enabled?: false)
end end
it "should redirect to sign in page if signup is disabled" do it "should redirect to sign in page if signup is disabled" do
......
...@@ -47,10 +47,10 @@ describe MergeRequests::RefreshService do ...@@ -47,10 +47,10 @@ describe MergeRequests::RefreshService do
reload_mrs reload_mrs
end end
it { @merge_request.notes.should be_empty } it { @merge_request.notes.last.note.should include('changed to merged') }
it { @merge_request.should be_merged } it { @merge_request.should be_merged }
it { @fork_merge_request.should be_merged } it { @fork_merge_request.should be_merged }
it { @fork_merge_request.notes.should be_empty } it { @fork_merge_request.notes.last.note.should include('changed to merged') }
end end
context 'push to fork repo source branch' do context 'push to fork repo source branch' do
...@@ -61,7 +61,7 @@ describe MergeRequests::RefreshService do ...@@ -61,7 +61,7 @@ describe MergeRequests::RefreshService do
it { @merge_request.notes.should be_empty } it { @merge_request.notes.should be_empty }
it { @merge_request.should be_open } it { @merge_request.should be_open }
it { @fork_merge_request.notes.should_not be_empty } it { @fork_merge_request.notes.last.note.should include('new commit') }
it { @fork_merge_request.should be_open } it { @fork_merge_request.should be_open }
end end
...@@ -84,7 +84,7 @@ describe MergeRequests::RefreshService do ...@@ -84,7 +84,7 @@ describe MergeRequests::RefreshService do
reload_mrs reload_mrs
end end
it { @merge_request.notes.should be_empty } it { @merge_request.notes.last.note.should include('changed to merged') }
it { @merge_request.should be_merged } it { @merge_request.should be_merged }
it { @fork_merge_request.should be_open } it { @fork_merge_request.should be_open }
it { @fork_merge_request.notes.should be_empty } it { @fork_merge_request.notes.should be_empty }
......
...@@ -116,6 +116,7 @@ describe NotificationService do ...@@ -116,6 +116,7 @@ describe NotificationService do
should_email(note.noteable.assignee_id) should_email(note.noteable.assignee_id)
should_not_email(note.author_id) should_not_email(note.author_id)
should_not_email(@u_mentioned.id)
should_not_email(@u_disabled.id) should_not_email(@u_disabled.id)
should_not_email(@u_not_mentioned.id) should_not_email(@u_not_mentioned.id)
notification.new_note(note) notification.new_note(note)
...@@ -168,6 +169,12 @@ describe NotificationService do ...@@ -168,6 +169,12 @@ describe NotificationService do
notification.new_note(note) notification.new_note(note)
end end
it do
@u_committer.update_attributes(notification_level: Notification::N_MENTION)
should_not_email(@u_committer.id, note)
notification.new_note(note)
end
def should_email(user_id, n) def should_email(user_id, n)
Notify.should_receive(:note_commit_email).with(user_id, n.id) Notify.should_receive(:note_commit_email).with(user_id, n.id)
end end
...@@ -190,11 +197,18 @@ describe NotificationService do ...@@ -190,11 +197,18 @@ describe NotificationService do
it do it do
should_email(issue.assignee_id) should_email(issue.assignee_id)
should_email(@u_watcher.id) should_email(@u_watcher.id)
should_not_email(@u_mentioned.id)
should_not_email(@u_participating.id) should_not_email(@u_participating.id)
should_not_email(@u_disabled.id) should_not_email(@u_disabled.id)
notification.new_issue(issue, @u_disabled) notification.new_issue(issue, @u_disabled)
end end
it do
issue.assignee.update_attributes(notification_level: Notification::N_MENTION)
should_not_email(issue.assignee_id)
notification.new_issue(issue, @u_disabled)
end
def should_email(user_id) def should_email(user_id)
Notify.should_receive(:new_issue_email).with(user_id, issue.id) Notify.should_receive(:new_issue_email).with(user_id, issue.id)
end end
...@@ -391,7 +405,7 @@ describe NotificationService do ...@@ -391,7 +405,7 @@ describe NotificationService do
@u_watcher = create(:user, notification_level: Notification::N_WATCH) @u_watcher = create(:user, notification_level: Notification::N_WATCH)
@u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
@u_disabled = create(:user, notification_level: Notification::N_DISABLED) @u_disabled = create(:user, notification_level: Notification::N_DISABLED)
@u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING) @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION)
@u_committer = create(:user, username: 'committer') @u_committer = create(:user, username: 'committer')
@u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING) @u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING)
......
File mode changed from 100755 to 100644
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