Commit eefb27f5 authored by Sebastian Ziebell's avatar Sebastian Ziebell

Merge branch 'master' into fixes/api

Conflicts:
	spec/requests/api/projects_spec.rb
parents 1b97a2ee b7ac654b
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.rbx/ .rbx/
db/*.sqlite3 db/*.sqlite3
db/*.sqlite3-journal db/*.sqlite3-journal
log/*.log log/*.log*
tmp/ tmp/
.sass-cache/ .sass-cache/
coverage/* coverage/*
...@@ -20,6 +20,7 @@ config/database.yml ...@@ -20,6 +20,7 @@ config/database.yml
config/initializers/omniauth.rb config/initializers/omniauth.rb
config/unicorn.rb config/unicorn.rb
config/resque.yml config/resque.yml
config/aws.yml
db/data.yml db/data.yml
.idea .idea
.DS_Store .DS_Store
......
...@@ -70,6 +70,9 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup' ...@@ -70,6 +70,9 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup'
# Servers # Servers
gem "unicorn", "~> 4.4.0" gem "unicorn", "~> 4.4.0"
# State machine
gem "state_machine"
# Issue tags # Issue tags
gem "acts-as-taggable-on", "2.3.3" gem "acts-as-taggable-on", "2.3.3"
......
...@@ -425,6 +425,7 @@ GEM ...@@ -425,6 +425,7 @@ GEM
rack (~> 1.0) rack (~> 1.0)
tilt (~> 1.1, != 1.3.0) tilt (~> 1.1, != 1.3.0)
stamp (0.3.0) stamp (0.3.0)
state_machine (1.1.2)
temple (0.5.5) temple (0.5.5)
test_after_commit (0.0.1) test_after_commit (0.0.1)
therubyracer (0.10.2) therubyracer (0.10.2)
...@@ -536,6 +537,7 @@ DEPENDENCIES ...@@ -536,6 +537,7 @@ DEPENDENCIES
slim slim
spinach-rails spinach-rails
stamp stamp
state_machine
test_after_commit test_after_commit
therubyracer therubyracer
thin thin
......
...@@ -49,6 +49,10 @@ $ -> ...@@ -49,6 +49,10 @@ $ ->
# Bottom tooltip # Bottom tooltip
$('.has_bottom_tooltip').tooltip(placement: 'bottom') $('.has_bottom_tooltip').tooltip(placement: 'bottom')
# Form submitter
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
# Flash # Flash
if (flash = $("#flash-container")).length > 0 if (flash = $("#flash-container")).length > 0
flash.click -> $(@).slideUp("slow") flash.click -> $(@).slideUp("slow")
......
...@@ -27,7 +27,7 @@ class MergeRequest ...@@ -27,7 +27,7 @@ class MergeRequest
this.$el.find(selector) this.$el.find(selector)
initMergeWidget: -> initMergeWidget: ->
this.showState( @opts.current_state ) this.showState( @opts.current_status )
if this.$('.automerge_widget').length and @opts.check_enable if this.$('.automerge_widget').length and @opts.check_enable
$.get @opts.url_to_automerge_check, (data) => $.get @opts.url_to_automerge_check, (data) =>
......
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
color: $style_color; color: $style_color;
text-shadow: 0 1px 1px #FFF; text-shadow: 0 1px 1px #FFF;
font-family: 'Yanone', sans-serif; font-family: 'Yanone', sans-serif;
font-size: 26px; font-size: 24px;
line-height: 42px; line-height: 36px;
font-weight: normal; font-weight: normal;
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
a{ a{
color: $style_color; color: $style_color;
} }
> span { > span {
font-family: $monospace_font; font-family: $monospace_font;
font-size: 14px; font-size: 14px;
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
.wrap{ .wrap{
display: inline-block; display: inline-block;
} }
.frame { .frame {
display: inline-block; display: inline-block;
background-color: #fff; background-color: #fff;
...@@ -149,7 +149,7 @@ ...@@ -149,7 +149,7 @@
.view.swipe{ .view.swipe{
position: relative; position: relative;
.swipe-frame{ .swipe-frame{
display: block; display: block;
margin: auto; margin: auto;
...@@ -228,7 +228,7 @@ ...@@ -228,7 +228,7 @@
bottom: 0px; bottom: 0px;
left: 50%; left: 50%;
margin-left: -150px; margin-left: -150px;
.drag-track{ .drag-track{
display: block; display: block;
position: absolute; position: absolute;
...@@ -237,7 +237,7 @@ ...@@ -237,7 +237,7 @@
width: 276px; width: 276px;
background: url('onion_skin_sprites.gif') -4px -20px repeat-x; background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
} }
.dragger { .dragger {
display: block; display: block;
position: absolute; position: absolute;
...@@ -248,7 +248,7 @@ ...@@ -248,7 +248,7 @@
background: url('onion_skin_sprites.gif') 0px -34px repeat-x; background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
cursor: pointer; cursor: pointer;
} }
.transparent { .transparent {
display: block; display: block;
position: absolute; position: absolute;
...@@ -258,7 +258,7 @@ ...@@ -258,7 +258,7 @@
width: 10px; width: 10px;
background: url('onion_skin_sprites.gif') -2px 0px no-repeat; background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
} }
.opaque { .opaque {
display: block; display: block;
position: absolute; position: absolute;
...@@ -275,19 +275,19 @@ ...@@ -275,19 +275,19 @@
padding: 10px; padding: 10px;
text-align: center; text-align: center;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
ul, li{ ul, li{
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
display: inline-block; display: inline-block;
} }
li{ li{
color: grey; color: grey;
border-left: 1px solid #c1c1c1; border-left: 1px solid #c1c1c1;
...@@ -322,12 +322,12 @@ ...@@ -322,12 +322,12 @@
} }
.commit-author, .commit-committer{ .commit-author, .commit-committer{
display: block; display: block;
color: #999; color: #999;
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }
.commit-author strong, .commit-committer strong{ .commit-author strong, .commit-committer strong{
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
...@@ -337,7 +337,6 @@ ...@@ -337,7 +337,6 @@
*/ */
.commit { .commit {
.browse_code_link_holder { .browse_code_link_holder {
@extend .span2;
float: right; float: right;
} }
......
...@@ -5,15 +5,16 @@ ...@@ -5,15 +5,16 @@
header { header {
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
height: 45px; height: 40px;
padding: 5px; padding: 3px;
background: #F1F1F1; background: #F1F1F1;
filter: none;
.nav > li > a { .nav > li > a {
color: $style_color; color: $style_color;
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
font-size: 18px; font-size: 16px;
padding: 12px; padding: 10px;
} }
/** NAV block with links and profile **/ /** NAV block with links and profile **/
...@@ -25,7 +26,6 @@ header { ...@@ -25,7 +26,6 @@ header {
} }
z-index: 10; z-index: 10;
/*height: 60px;*/
/** /**
* *
...@@ -34,7 +34,7 @@ header { ...@@ -34,7 +34,7 @@ header {
*/ */
.app_logo { .app_logo {
float: left; float: left;
margin-right: 15px; margin-right: 9px;
position: relative; position: relative;
top: -5px; top: -5px;
padding-top: 5px; padding-top: 5px;
...@@ -42,10 +42,10 @@ header { ...@@ -42,10 +42,10 @@ header {
a { a {
float: left; float: left;
padding: 0px; padding: 0px;
margin: 0 10px; margin: 0 6px;
h1 { h1 {
background: url('logo_dark.png') no-repeat 0px 2px; background: url('logo_dark.png') no-repeat center 1px;
float: left; float: left;
height: 40px; height: 40px;
width: 40px; width: 40px;
...@@ -79,7 +79,6 @@ header { ...@@ -79,7 +79,6 @@ header {
.search { .search {
margin-right: 45px; margin-right: 45px;
margin-left: 10px; margin-left: 10px;
margin-top: 2px;
.search-input { .search-input {
@extend .span2; @extend .span2;
...@@ -105,7 +104,7 @@ header { ...@@ -105,7 +104,7 @@ header {
.account-box { .account-box {
position: absolute; position: absolute;
right: 0; right: 0;
top: 6px; top: 4px;
z-index: 10000; z-index: 10000;
width: 128px; width: 128px;
font-size: 11px; font-size: 11px;
...@@ -228,6 +227,7 @@ header { ...@@ -228,6 +227,7 @@ header {
.search-input { .search-input {
background-color: #D2D5DA; background-color: #D2D5DA;
background-color: rgba(255, 255, 255, 0.5); background-color: rgba(255, 255, 255, 0.5);
border: 1px solid #AAA;
&:focus { &:focus {
background-color: white; background-color: white;
...@@ -240,13 +240,16 @@ header { ...@@ -240,13 +240,16 @@ header {
.app_logo { .app_logo {
a { a {
h1 { h1 {
background: url('logo_white.png') no-repeat center center; background: url('logo_white.png') no-repeat center 1px;
color: #fff; color: #fff;
text-shadow: 0 1px 1px #111; text-shadow: 0 1px 1px #111;
} }
} }
} }
.project_name { .project_name {
a {
color: #FFF;
}
color: #fff; color: #fff;
text-shadow: 0 1px 1px #111; text-shadow: 0 1px 1px #111;
} }
...@@ -261,11 +264,11 @@ header { ...@@ -261,11 +264,11 @@ header {
.separator { .separator {
float: left; float: left;
height: 60px; height: 46px;
width: 1px; width: 1px;
background: white; background: white;
border-left: 1px solid #DDD; border-left: 1px solid #DDD;
margin-top: -10px; margin-top: -3px;
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
} }
......
...@@ -115,3 +115,7 @@ ul.nav.nav-projects-tabs { ...@@ -115,3 +115,7 @@ ul.nav.nav-projects-tabs {
} }
} }
} }
.team_member_row form {
margin: 0px;
}
...@@ -8,66 +8,27 @@ ...@@ -8,66 +8,27 @@
* *
*/ */
.ui_mars { .ui_mars {
/* /*
* Application Header * Application Header
* *
*/ */
header { header {
@extend .header-dark;
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
background: #474D57 url('bg-header.png') repeat-x bottom; background: #474D57;
border-bottom: 1px solid #444; border-bottom: 1px solid #373D47;
.app_logo {
.nav > li > a { &:hover {
color: #eee; background-color: #373D47;
text-shadow: 0 1px 0 #444; }
} }
} }
} }
.search { .separator {
float: right; background: #31363E;
margin-right: 45px; border-left: 1px solid #666;
.search-input {
border: 1px solid rgba(0, 0, 0, 0.7);
background-color: #D2D5DA;
background-color: rgba(255, 255, 255, 0.5);
&:focus {
background-color: white;
}
}
}
.search-input::-webkit-input-placeholder {
color: #666;
}
.app_logo {
a {
h1 {
background: url('logo_white.png') no-repeat center center;
color: #eee;
text-shadow: 0 1px 1px #111;
}
}
&:hover {
background-color: #41464e;
}
}
.project_name {
color: #eee;
text-shadow: 0 1px 1px #111;
} }
} }
.separator {
background: #31363E;
border-left: 1px solid #666;
}
/*
* End of Application Header
*
*/
} }
...@@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext ...@@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext
end end
merge_requests = merge_requests.page(params[:page]).per(20) merge_requests = merge_requests.page(params[:page]).per(20)
merge_requests = merge_requests.includes(:author, :project).order("closed, created_at desc") merge_requests = merge_requests.includes(:author, :project).order("state, created_at desc")
# Filter by specific assignee_id (or lack thereof)? # Filter by specific assignee_id (or lack thereof)?
if params[:assignee_id].present? if params[:assignee_id].present?
......
...@@ -38,6 +38,8 @@ module Projects ...@@ -38,6 +38,8 @@ module Projects
if @project.valid? && @project.import_url.present? if @project.valid? && @project.import_url.present?
shell = Gitlab::Shell.new shell = Gitlab::Shell.new
if shell.import_repository(@project.path_with_namespace, @project.import_url) if shell.import_repository(@project.path_with_namespace, @project.import_url)
# We should create satellite for imported repo
@project.satellite.create unless @project.satellite.exists?
true true
else else
@project.errors.add(:import_url, 'cannot clone repo') @project.errors.add(:import_url, 'cannot clone repo')
......
...@@ -5,7 +5,7 @@ class DashboardController < ApplicationController ...@@ -5,7 +5,7 @@ class DashboardController < ApplicationController
before_filter :event_filter, only: :show before_filter :event_filter, only: :show
def show def show
@groups = current_user.authorized_groups @groups = current_user.authorized_groups.sort_by(&:human_name)
@has_authorized_projects = @projects.count > 0 @has_authorized_projects = @projects.count > 0
@teams = current_user.authorized_teams @teams = current_user.authorized_teams
@projects_count = @projects.count @projects_count = @projects.count
......
class FilesController < ApplicationController
def download
note = Note.find(params[:id])
if can?(current_user, :read_project, note.project)
uploader = note.attachment
send_file uploader.file.path, disposition: 'attachment'
else
not_found!
end
end
end
...@@ -73,14 +73,14 @@ class MergeRequestsController < ProjectResourceController ...@@ -73,14 +73,14 @@ class MergeRequestsController < ProjectResourceController
if @merge_request.unchecked? if @merge_request.unchecked?
@merge_request.check_if_can_be_merged @merge_request.check_if_can_be_merged
end end
render json: {state: @merge_request.human_state} render json: {merge_status: @merge_request.human_merge_status}
rescue Gitlab::SatelliteNotExistError rescue Gitlab::SatelliteNotExistError
render json: {state: :no_satellite} render json: {merge_status: :no_satellite}
end end
def automerge def automerge
return access_denied! unless can?(current_user, :accept_mr, @project) return access_denied! unless can?(current_user, :accept_mr, @project)
if @merge_request.open? && @merge_request.can_be_merged? if @merge_request.opened? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch] @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user) @merge_request.automerge!(current_user)
@status = true @status = true
......
...@@ -12,7 +12,7 @@ class MilestonesController < ProjectResourceController ...@@ -12,7 +12,7 @@ class MilestonesController < ProjectResourceController
def index def index
@milestones = case params[:f] @milestones = case params[:f]
when 'all'; @project.milestones.order("closed, 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")
end end
......
...@@ -51,7 +51,9 @@ class ProfilesController < ApplicationController ...@@ -51,7 +51,9 @@ class ProfilesController < ApplicationController
end end
def update_username def update_username
@user.update_attributes(username: params[:user][:username]) if @user.can_change_username?
@user.update_attributes(username: params[:user][:username])
end
respond_to do |format| respond_to do |format|
format.js format.js
......
...@@ -4,7 +4,11 @@ class TeamMembersController < ProjectResourceController ...@@ -4,7 +4,11 @@ class TeamMembersController < ProjectResourceController
before_filter :authorize_admin_project!, except: [:index, :show] before_filter :authorize_admin_project!, except: [:index, :show]
def index def index
@teams = UserTeam.scoped @team = @project.users_projects.scoped
@team = @team.send(params[:type]) if %w(masters developers reporters guests).include?(params[:type])
@team = @team.sort_by(&:project_access).reverse.group_by(&:project_access)
@assigned_teams = @project.user_team_project_relationships
end end
def show def show
......
...@@ -27,7 +27,13 @@ class Teams::MembersController < Teams::ApplicationController ...@@ -27,7 +27,13 @@ class Teams::MembersController < Teams::ApplicationController
end end
def update def update
options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]} member_params = params[:team_member]
options = {
default_projects_access: member_params[:permission],
group_admin: member_params[:group_admin]
}
if user_team.update_membership(team_member, options) if user_team.update_membership(team_member, options)
redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users." redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
else else
...@@ -45,5 +51,4 @@ class Teams::MembersController < Teams::ApplicationController ...@@ -45,5 +51,4 @@ class Teams::MembersController < Teams::ApplicationController
def team_member def team_member
@member ||= user_team.members.find_by_username(params[:id]) @member ||= user_team.members.find_by_username(params[:id])
end end
end end
...@@ -9,13 +9,11 @@ class TeamsController < ApplicationController ...@@ -9,13 +9,11 @@ class TeamsController < ApplicationController
layout 'user_team', except: [:new, :create] layout 'user_team', except: [:new, :create]
def show def show
user_team
projects projects
@events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0) @events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0)
end end
def edit def edit
user_team
end end
def update def update
...@@ -41,6 +39,9 @@ class TeamsController < ApplicationController ...@@ -41,6 +39,9 @@ class TeamsController < ApplicationController
@team.path = @team.name.dup.parameterize if @team.name @team.path = @team.name.dup.parameterize if @team.name
if @team.save if @team.save
# Add current user as Master to the team
@team.add_members([current_user.id], UsersProject::MASTER, true)
redirect_to team_path(@team) redirect_to team_path(@team)
else else
render action: :new render action: :new
......
...@@ -73,8 +73,8 @@ module ApplicationHelper ...@@ -73,8 +73,8 @@ module ApplicationHelper
def search_autocomplete_source def search_autocomplete_source
projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } } projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } }
groups = current_user.authorized_groups.map { |group| { label: "group: #{group.name}", url: group_path(group) } } groups = current_user.authorized_groups.map { |group| { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) } }
teams = current_user.authorized_teams.map { |team| { label: "team: #{team.name}", url: team_path(team) } } teams = current_user.authorized_teams.map { |team| { label: "team: #{simple_sanitize(team.name)}", url: team_path(team) } }
default_nav = [ default_nav = [
{ label: "My Profile", url: profile_path }, { label: "My Profile", url: profile_path },
...@@ -159,8 +159,13 @@ module ApplicationHelper ...@@ -159,8 +159,13 @@ module ApplicationHelper
alt: "Sign in with #{provider.to_s.titleize}") alt: "Sign in with #{provider.to_s.titleize}")
end end
def simple_sanitize str
sanitize(str, tags: %w(a span))
end
def image_url(source) def image_url(source)
root_url + path_to_image(source) root_url + path_to_image(source)
end end
alias_method :url_to_image, :image_url alias_method :url_to_image, :image_url
end end
...@@ -27,6 +27,6 @@ module DashboardHelper ...@@ -27,6 +27,6 @@ module DashboardHelper
items.opened items.opened
end end
items.where(assignee_id: current_user.id).count items.cared(current_user).count
end end
end end
...@@ -6,7 +6,7 @@ module IssuesHelper ...@@ -6,7 +6,7 @@ module IssuesHelper
def issue_css_classes issue def issue_css_classes issue
classes = "issue" classes = "issue"
classes << " closed" if issue.closed classes << " closed" if issue.closed?
classes << " today" if issue.today? classes << " today" if issue.today?
classes classes
end end
......
...@@ -12,7 +12,7 @@ module MergeRequestsHelper ...@@ -12,7 +12,7 @@ module MergeRequestsHelper
def mr_css_classes mr def mr_css_classes mr
classes = "merge_request" classes = "merge_request"
classes << " closed" if mr.closed classes << " closed" if mr.closed?
classes << " merged" if mr.merged? classes << " merged" if mr.merged?
classes classes
end end
......
...@@ -10,8 +10,8 @@ module NamespacesHelper ...@@ -10,8 +10,8 @@ module NamespacesHelper
global_opts = ["Global", [['/', Namespace.global_id]] ] global_opts = ["Global", [['/', Namespace.global_id]] ]
group_opts = ["Groups", groups.map {|g| [g.human_name, g.id]} ] group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
users_opts = [ "Users", users.map {|u| [u.human_name, u.id]} ] users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
options = [] options = []
options << global_opts if current_user.admin options << global_opts if current_user.admin
......
module ProjectsHelper module ProjectsHelper
def grouper_project_members(project)
@project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
end
def grouper_project_teams(project)
@project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access)
end
def remove_from_project_team_message(project, user) def remove_from_project_team_message(project, user)
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?" "You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end end
...@@ -56,7 +48,7 @@ module ProjectsHelper ...@@ -56,7 +48,7 @@ module ProjectsHelper
def project_title project def project_title project
if project.group if project.group
content_tag :span do content_tag :span do
link_to(project.group.name, group_path(project.group)) + " / " + project.name link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
end end
else else
project.name project.name
......
...@@ -123,7 +123,7 @@ class Ability ...@@ -123,7 +123,7 @@ class Ability
def user_team_abilities user, team def user_team_abilities user, team
rules = [] rules = []
# Only group owner and administrators can manage group # Only group owner and administrators can manage team
if team.owner == user || team.admin?(user) || user.admin? if team.owner == user || team.admin?(user) || user.admin?
rules << [ :manage_user_team ] rules << [ :manage_user_team ]
end end
......
...@@ -17,10 +17,9 @@ module Issuable ...@@ -17,10 +17,9 @@ module Issuable
validates :project, presence: true validates :project, presence: true
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
validates :closed, inclusion: { in: [true, false] }
scope :opened, -> { where(closed: false) } scope :opened, -> { with_state(:opened) }
scope :closed, -> { where(closed: true) } scope :closed, -> { with_state(:closed) }
scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :assigned, ->(u) { where(assignee_id: u.id)} scope :assigned, ->(u) { where(assignee_id: u.id)}
...@@ -62,14 +61,6 @@ module Issuable ...@@ -62,14 +61,6 @@ module Issuable
assignee_id_changed? assignee_id_changed?
end end
def is_being_closed?
closed_changed? && closed
end
def is_being_reopened?
closed_changed? && !closed
end
# #
# Votes # Votes
# #
......
...@@ -130,10 +130,6 @@ class Event < ActiveRecord::Base ...@@ -130,10 +130,6 @@ class Event < ActiveRecord::Base
target if target_type == "MergeRequest" target if target_type == "MergeRequest"
end end
def author
@author ||= User.find(author_id)
end
def action_name def action_name
if closed? if closed?
"closed" "closed"
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
# project_id :integer # project_id :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# closed :boolean default(FALSE), not null # state :string default(FALSE), not null
# position :integer default(0) # position :integer default(0)
# branch_name :string(255) # branch_name :string(255)
# description :text # description :text
...@@ -19,12 +19,35 @@ ...@@ -19,12 +19,35 @@
class Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
include Issuable include Issuable
attr_accessible :title, :assignee_id, :closed, :position, :description, attr_accessible :title, :assignee_id, :position, :description,
:milestone_id, :label_list, :author_id_of_changes :milestone_id, :label_list, :author_id_of_changes,
:state_event
acts_as_taggable_on :labels acts_as_taggable_on :labels
def self.open_for(user) class << self
opened.assigned(user) def cared(user)
where('assignee_id = :user', user: user.id)
end
def open_for(user)
opened.assigned(user)
end
end
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
end
event :reopen do
transition closed: :reopened
end
state :opened
state :reopened
state :closed
end end
end end
...@@ -35,7 +35,7 @@ class Key < ActiveRecord::Base ...@@ -35,7 +35,7 @@ class Key < ActiveRecord::Base
def fingerprintable_key def fingerprintable_key
return true unless key # Don't test if there is no key. return true unless key # Don't test if there is no key.
# `ssh-keygen -lf /dev/stdin <<< "#{key}"` errors with: redirection unexpected
file = Tempfile.new('key_file') file = Tempfile.new('key_file')
begin begin
file.puts key file.puts key
...@@ -45,7 +45,7 @@ class Key < ActiveRecord::Base ...@@ -45,7 +45,7 @@ class Key < ActiveRecord::Base
file.close file.close
file.unlink # deletes the temp file file.unlink # deletes the temp file
end end
errors.add(:key, "can't be fingerprinted") if fingerprint_output.match("failed") errors.add(:key, "can't be fingerprinted") if $?.exitstatus != 0
end end
def set_identifier def set_identifier
......
...@@ -9,15 +9,14 @@ ...@@ -9,15 +9,14 @@
# author_id :integer # author_id :integer
# assignee_id :integer # assignee_id :integer
# title :string(255) # title :string(255)
# closed :boolean default(FALSE), not null # state :string(255) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# st_commits :text(2147483647) # st_commits :text(2147483647)
# st_diffs :text(2147483647) # st_diffs :text(2147483647)
# merged :boolean default(FALSE), not null # merge_status :integer default(1), not null
# state :integer default(1), not null
# milestone_id :integer
# #
# milestone_id :integer
require Rails.root.join("app/models/commit") require Rails.root.join("app/models/commit")
require Rails.root.join("lib/static_model") require Rails.root.join("lib/static_model")
...@@ -25,11 +24,33 @@ require Rails.root.join("lib/static_model") ...@@ -25,11 +24,33 @@ require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base class MergeRequest < ActiveRecord::Base
include Issuable include Issuable
attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id, attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id,
:author_id_of_changes :author_id_of_changes, :state_event
attr_accessor :should_remove_source_branch attr_accessor :should_remove_source_branch
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
end
event :merge do
transition [:reopened, :opened] => :merged
end
event :reopen do
transition closed: :reopened
end
state :opened
state :reopened
state :closed
state :merged
end
BROKEN_DIFF = "--broken-diff" BROKEN_DIFF = "--broken-diff"
UNCHECKED = 1 UNCHECKED = 1
...@@ -43,21 +64,33 @@ class MergeRequest < ActiveRecord::Base ...@@ -43,21 +64,33 @@ class MergeRequest < ActiveRecord::Base
validates :target_branch, presence: true validates :target_branch, presence: true
validate :validate_branches validate :validate_branches
def self.find_all_by_branch(branch_name) scope :merged, -> { with_state(:merged) }
where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
end
def self.find_all_by_milestone(milestone) class << self
where("milestone_id = :milestone_id", milestone_id: milestone) def find_all_by_branch(branch_name)
where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
end
def cared(user)
where('assignee_id = :user OR author_id = :user', user: user.id)
end
def find_all_by_branch(branch_name)
where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
end
def find_all_by_milestone(milestone)
where("milestone_id = :milestone_id", milestone_id: milestone)
end
end end
def human_state def human_merge_status
states = { merge_statuses = {
CAN_BE_MERGED => "can_be_merged", CAN_BE_MERGED => "can_be_merged",
CANNOT_BE_MERGED => "cannot_be_merged", CANNOT_BE_MERGED => "cannot_be_merged",
UNCHECKED => "unchecked" UNCHECKED => "unchecked"
} }
states[self.state] merge_statuses[self.merge_status]
end end
def validate_branches def validate_branches
...@@ -72,20 +105,20 @@ class MergeRequest < ActiveRecord::Base ...@@ -72,20 +105,20 @@ class MergeRequest < ActiveRecord::Base
end end
def unchecked? def unchecked?
state == UNCHECKED merge_status == UNCHECKED
end end
def mark_as_unchecked def mark_as_unchecked
self.state = UNCHECKED self.merge_status = UNCHECKED
self.save self.save
end end
def can_be_merged? def can_be_merged?
state == CAN_BE_MERGED merge_status == CAN_BE_MERGED
end end
def check_if_can_be_merged def check_if_can_be_merged
self.state = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged? self.merge_status = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
CAN_BE_MERGED CAN_BE_MERGED
else else
CANNOT_BE_MERGED CANNOT_BE_MERGED
...@@ -98,7 +131,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -98,7 +131,7 @@ class MergeRequest < ActiveRecord::Base
end end
def reloaded_diffs def reloaded_diffs
if open? && unmerged_diffs.any? if opened? && unmerged_diffs.any?
self.st_diffs = unmerged_diffs self.st_diffs = unmerged_diffs
self.save self.save
end end
...@@ -128,10 +161,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -128,10 +161,6 @@ class MergeRequest < ActiveRecord::Base
commits.first commits.first
end end
def merged?
merged && merge_event
end
def merge_event def merge_event
self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
end end
...@@ -146,26 +175,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -146,26 +175,16 @@ class MergeRequest < ActiveRecord::Base
def probably_merged? def probably_merged?
unmerged_commits.empty? && unmerged_commits.empty? &&
commits.any? && open? commits.any? && opened?
end
def open?
!closed
end
def mark_as_merged!
self.merged = true
self.closed = true
save
end end
def mark_as_unmergable def mark_as_unmergable
self.state = CANNOT_BE_MERGED self.merge_status = CANNOT_BE_MERGED
self.save self.save
end end
def reloaded_commits def reloaded_commits
if open? && unmerged_commits.any? if opened? && unmerged_commits.any?
self.st_commits = unmerged_commits self.st_commits = unmerged_commits
save save
end end
...@@ -181,7 +200,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -181,7 +200,8 @@ class MergeRequest < ActiveRecord::Base
end end
def merge!(user_id) def merge!(user_id)
self.mark_as_merged! self.merge
Event.create( Event.create(
project: self.project, project: self.project,
action: Event::MERGED, action: Event::MERGED,
......
...@@ -13,19 +13,32 @@ ...@@ -13,19 +13,32 @@
# #
class Milestone < ActiveRecord::Base class Milestone < ActiveRecord::Base
attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
attr_accessor :author_id_of_changes attr_accessor :author_id_of_changes
belongs_to :project belongs_to :project
has_many :issues has_many :issues
has_many :merge_requests has_many :merge_requests
scope :active, -> { where(closed: false) } scope :active, -> { with_state(:active) }
scope :closed, -> { where(closed: true) } scope :closed, -> { with_state(:closed) }
validates :title, presence: true validates :title, presence: true
validates :project, presence: true validates :project, presence: true
validates :closed, inclusion: { in: [true, false] }
state_machine :state, initial: :active do
event :close do
transition active: :closed
end
event :activate do
transition closed: :active
end
state :closed
state :active
end
def expired? def expired?
if due_date if due_date
...@@ -68,17 +81,13 @@ class Milestone < ActiveRecord::Base ...@@ -68,17 +81,13 @@ class Milestone < ActiveRecord::Base
end end
def can_be_closed? def can_be_closed?
open? && issues.opened.count.zero? active? && issues.opened.count.zero?
end end
def is_empty? def is_empty?
total_items_count.zero? total_items_count.zero?
end end
def open?
!closed
end
def author_id def author_id
author_id_of_changes author_id_of_changes
end end
......
...@@ -17,11 +17,15 @@ class Namespace < ActiveRecord::Base ...@@ -17,11 +17,15 @@ class Namespace < ActiveRecord::Base
has_many :projects, dependent: :destroy has_many :projects, dependent: :destroy
belongs_to :owner, class_name: "User" belongs_to :owner, class_name: "User"
validates :name, presence: true, uniqueness: true validates :owner, presence: true
validates :name, presence: true, uniqueness: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed." }
validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex, format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
validates :owner, presence: true
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
......
...@@ -43,7 +43,7 @@ class Project < ActiveRecord::Base ...@@ -43,7 +43,7 @@ class Project < ActiveRecord::Base
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy has_many :merge_requests, dependent: :destroy
has_many :issues, dependent: :destroy, order: "closed, created_at DESC" has_many :issues, dependent: :destroy, order: "state, created_at DESC"
has_many :milestones, dependent: :destroy has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy has_many :notes, dependent: :destroy
...@@ -146,7 +146,7 @@ class Project < ActiveRecord::Base ...@@ -146,7 +146,7 @@ class Project < ActiveRecord::Base
end end
def saved? def saved?
id && valid? id && persisted?
end end
def import? def import?
......
...@@ -132,16 +132,16 @@ class Repository ...@@ -132,16 +132,16 @@ class Repository
return nil unless commit return nil unless commit
# Build file path # Build file path
file_name = self.path_with_namespace + "-" + commit.id.to_s + ".tar.gz" file_name = self.path_with_namespace.gsub("/","_") + "-" + commit.id.to_s + ".tar.gz"
storage_path = Rails.root.join("tmp", "repositories") storage_path = Rails.root.join("tmp", "repositories")
file_path = File.join(storage_path, file_name) file_path = File.join(storage_path, self.path_with_namespace, file_name)
# Put files into a directory before archiving # Put files into a directory before archiving
prefix = self.path_with_namespace + "/" prefix = self.path_with_namespace + "/"
# Create file if not exists # Create file if not exists
unless File.exists?(file_path) unless File.exists?(file_path)
FileUtils.mkdir_p storage_path FileUtils.mkdir_p File.dirname(file_path)
file = self.repo.archive_to_file(ref, prefix, file_path) file = self.repo.archive_to_file(ref, prefix, file_path)
end end
......
...@@ -234,8 +234,12 @@ class User < ActiveRecord::Base ...@@ -234,8 +234,12 @@ class User < ActiveRecord::Base
keys.count == 0 keys.count == 0
end end
def can_change_username?
Gitlab.config.gitlab.username_changing_enabled
end
def can_create_project? def can_create_project?
projects_limit > personal_projects.count projects_limit > owned_projects.count
end end
def can_create_group? def can_create_group?
...@@ -263,7 +267,7 @@ class User < ActiveRecord::Base ...@@ -263,7 +267,7 @@ class User < ActiveRecord::Base
end end
def cared_merge_requests def cared_merge_requests
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id) MergeRequest.cared(self)
end end
# Remove user from all projects and # Remove user from all projects and
......
...@@ -21,8 +21,11 @@ class UserTeam < ActiveRecord::Base ...@@ -21,8 +21,11 @@ class UserTeam < ActiveRecord::Base
has_many :projects, through: :user_team_project_relationships has_many :projects, through: :user_team_project_relationships
has_many :members, through: :user_team_user_relationships, source: :user has_many :members, through: :user_team_user_relationships, source: :user
validates :name, presence: true, uniqueness: true
validates :owner, presence: true validates :owner, presence: true
validates :name, presence: true, uniqueness: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed." }
validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex, format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
......
...@@ -26,6 +26,10 @@ class UserTeamProjectRelationship < ActiveRecord::Base ...@@ -26,6 +26,10 @@ class UserTeamProjectRelationship < ActiveRecord::Base
user_team.name user_team.name
end end
def human_max_access
UserTeam.access_roles.key(greatest_access)
end
private private
def check_greatest_access def check_greatest_access
......
...@@ -20,15 +20,23 @@ class ActivityObserver < ActiveRecord::Observer ...@@ -20,15 +20,23 @@ class ActivityObserver < ActiveRecord::Observer
end end
end end
def after_save(record) def after_close(record, transition)
if record.changed.include?("closed") && record.author_id_of_changes
Event.create( Event.create(
project: record.project, project: record.project,
target_id: record.id, target_id: record.id,
target_type: record.class.name, target_type: record.class.name,
action: (record.closed ? Event::CLOSED : Event::REOPENED), action: Event::CLOSED,
author_id: record.author_id_of_changes
)
end
def after_reopen(record, transition)
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
action: Event::REOPENED,
author_id: record.author_id_of_changes author_id: record.author_id_of_changes
) )
end
end end
end end
...@@ -7,22 +7,31 @@ class IssueObserver < ActiveRecord::Observer ...@@ -7,22 +7,31 @@ class IssueObserver < ActiveRecord::Observer
end end
end end
def after_update(issue) def after_close(issue, transition)
send_reassigned_email(issue) if issue.is_being_reassigned? send_reassigned_email(issue) if issue.is_being_reassigned?
status = nil create_note(issue)
status = 'closed' if issue.is_being_closed? end
status = 'reopened' if issue.is_being_reopened?
if status def after_reopen(issue, transition)
Note.create_status_change_note(issue, current_user, status) send_reassigned_email(issue) if issue.is_being_reassigned?
[issue.author, issue.assignee].compact.each do |recipient|
Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) create_note(issue)
end end
end
def after_update(issue)
send_reassigned_email(issue) if issue.is_being_reassigned?
end end
protected protected
def create_note(issue)
Note.create_status_change_note(issue, current_user, issue.state)
[issue.author, issue.assignee].compact.each do |recipient|
Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id)
end
end
def send_reassigned_email(issue) def send_reassigned_email(issue)
recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id } recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
......
...@@ -7,15 +7,20 @@ class MergeRequestObserver < ActiveRecord::Observer ...@@ -7,15 +7,20 @@ class MergeRequestObserver < ActiveRecord::Observer
end end
end end
def after_update(merge_request) def after_close(merge_request, transition)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned? send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
status = nil Note.create_status_change_note(merge_request, current_user, merge_request.state)
status = 'closed' if merge_request.is_being_closed? end
status = 'reopened' if merge_request.is_being_reopened?
if status def after_reopen(merge_request, transition)
Note.create_status_change_note(merge_request, current_user, status) send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
end
Note.create_status_change_note(merge_request, current_user, merge_request.state)
end
def after_update(merge_request)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
end end
protected protected
......
...@@ -19,4 +19,12 @@ class AttachmentUploader < CarrierWave::Uploader::Base ...@@ -19,4 +19,12 @@ class AttachmentUploader < CarrierWave::Uploader::Base
rescue rescue
false false
end end
def secure_url
if self.class.storage == CarrierWave::Storage::File
"/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
else
url
end
end
end end
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
%td= group.path %td= group.path
%td= group.projects.count %td= group.projects.count
%td %td
= link_to group.owner_name, admin_user_path(group.owner_id) = link_to group.owner_name, admin_user_path(group.owner)
%td.bgred %td.bgred
= link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%td= team.projects.count %td= team.projects.count
%td= team.members.count %td= team.members.count
%td %td
= link_to team.owner.name, admin_user_path(team.owner_id) = link_to team.owner.name, admin_user_path(team.owner)
%td.bgred %td.bgred
= link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small" = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
= link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
......
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
= commit.author_link avatar: true, size: 24 = commit.author_link avatar: true, size: 24
&nbsp; &nbsp;
= link_to_gfm truncate(commit.title, length: 50), project_commit_path(@project, commit.id), class: "row_title" = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title"
%span.committed_ago %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
= time_ago_in_words(commit.committed_date) = time_ago_in_words(commit.committed_date)
ago ago
&nbsp; &nbsp;
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
= markdown truncate(event.target.note, length: 70) = markdown truncate(event.target.note, length: 70)
- note = event.target - note = event.target
- if note.attachment.url - if note.attachment.url
= link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
- if note.attachment.image? - if note.attachment.image?
= image_tag note.attachment.url, class: 'note-image-attach' = image_tag note.attachment.url, class: 'note-image-attach'
- else - else
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
= link_to group_filter_path(entity, project_id: project.id) do = link_to group_filter_path(entity, project_id: project.id) do
= project.name_with_namespace = project.name_with_namespace
%small.pull-right= entities_per_project(project, entity) %small.pull-right= entities_per_project(project, entity)
- if @projects.blank?
%p.nothing_here_message This group has no projects yet
%fieldset %fieldset
%hr %hr
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
= link_to people_group_path(@group, project_id: project.id) do = link_to people_group_path(@group, project_id: project.id) do
= project.name_with_namespace = project.name_with_namespace
%small.pull-right= project.users.count %small.pull-right= project.users.count
- if @projects.blank?
%p.nothing_here_message This group has no projects yet
%fieldset %fieldset
%hr %hr
......
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
= link_to 'Team', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Team', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
= link_to 'Remove', project, confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" = link_to 'Remove', project, confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
- if @group.projects.blank?
%p.nothing_here_message This group has no projects yet
.span5 .span5
.ui-box .ui-box
......
%h3.page_title %h3.page_title
GITLAB GITLAB
.pull-right .pull-right
%span= Gitlab::Version %span= Gitlab::VERSION
%small= Gitlab::Revision %small= Gitlab::REVISION
%hr %hr
%p.lead %p.lead
Self Hosted Git Management Self Hosted Git Management
......
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
%i.icon-comment %i.icon-comment
= issue.notes.count = issue.notes.count
- if can? current_user, :modify_issue, issue - if can? current_user, :modify_issue, issue
- if issue.closed - if issue.closed?
= link_to 'Reopen', project_issue_path(issue.project, issue, issue: {closed: false }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true
- else - else
= link_to 'Close', project_issue_path(issue.project, issue, issue: {closed: true }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true
= link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do
%i.icon-edit %i.icon-edit
Edit Edit
......
...@@ -7,10 +7,10 @@ ...@@ -7,10 +7,10 @@
%span.pull-right %span.pull-right
- if can?(current_user, :admin_project, @project) || @issue.author == current_user - if can?(current_user, :admin_project, @project) || @issue.author == current_user
- if @issue.closed - if @issue.closed?
= link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped reopen_issue" = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue"
- else - else
= link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue" = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
- if can?(current_user, :admin_project, @project) || @issue.author == current_user - if can?(current_user, :admin_project, @project) || @issue.author == current_user
= link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
%i.icon-edit %i.icon-edit
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.ui-box.ui-box-show .ui-box.ui-box-show
.ui-box-head .ui-box-head
%h4.box-title %h4.box-title
- if @issue.closed - if @issue.closed?
.error.status_info Closed .error.status_info Closed
= gfm escape_once(@issue.title) = gfm escape_once(@issue.title)
......
...@@ -29,10 +29,10 @@ ...@@ -29,10 +29,10 @@
$(function(){ $(function(){
merge_request = new MergeRequest({ merge_request = new MergeRequest({
url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}", url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
check_enable: #{@merge_request.state == MergeRequest::UNCHECKED ? "true" : "false"}, check_enable: #{@merge_request.merge_status == MergeRequest::UNCHECKED ? "true" : "false"},
url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}", url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
ci_enable: #{@project.gitlab_ci? ? "true" : "false"}, ci_enable: #{@project.gitlab_ci? ? "true" : "false"},
current_state: "#{@merge_request.human_state}", current_status: "#{@merge_request.human_merge_status}",
action: "#{controller.action_name}" action: "#{controller.action_name}"
}); });
}); });
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%strong Only masters can accept MR %strong Only masters can accept MR
- if @merge_request.open? && @commits.any? && can?(current_user, :accept_mr, @project) - if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project)
.automerge_widget.can_be_merged{style: "display:none"} .automerge_widget.can_be_merged{style: "display:none"}
.alert.alert-success .alert.alert-success
%span %span
......
.ui-box.ui-box-show .ui-box.ui-box-show
.ui-box-head .ui-box-head
%h4.box-title %h4.box-title
- if @merge_request.merged - if @merge_request.merged?
.error.status_info .error.status_info
%i.icon-ok %i.icon-ok
Merged Merged
- elsif @merge_request.closed - elsif @merge_request.closed?
.error.status_info Closed .error.status_info Closed
= gfm escape_once(@merge_request.title) = gfm escape_once(@merge_request.title)
...@@ -21,14 +21,14 @@ ...@@ -21,14 +21,14 @@
%strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone) %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
- if @merge_request.closed - if @merge_request.closed?
.ui-box-bottom .ui-box-bottom
- if @merge_request.merged? %span
%span Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
Merged by #{link_to_member(@project, @merge_request.merge_event.author)} %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
%small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago. - if @merge_request.merged?
- elsif @merge_request.closed_event .ui-box-bottom
%span %span
Closed by #{link_to_member(@project, @merge_request.closed_event.author)} Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
%small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
- if @merge_request.open? && @commits.any? - if @merge_request.opened? && @commits.any?
.ci_widget.ci-success{style: "display:none"} .ci_widget.ci-success{style: "display:none"}
.alert.alert-success .alert.alert-success
%i.icon-ok %i.icon-ok
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%span.pull-right %span.pull-right
- if can?(current_user, :modify_merge_request, @merge_request) - if can?(current_user, :modify_merge_request, @merge_request)
- if @merge_request.open? - if @merge_request.opened?
.left.btn-group .left.btn-group
%a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.icon-download-alt %i.icon-download-alt
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
%li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
%li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
= link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {closed: true }, status_only: true), method: :put, class: "btn grouped btn-close", title: "Close merge request" = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request"
= link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do
%i.icon-edit %i.icon-edit
......
%li{class: "milestone milestone-#{milestone.closed ? 'closed' : 'open'}", id: dom_id(milestone) } %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right .pull-right
- if can?(current_user, :admin_milestone, milestone.project) and milestone.open? - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do
%i.icon-edit %i.icon-edit
Edit Edit
%h4 %h4
= link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone) = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone)
- if milestone.expired? and not milestone.closed - if milestone.expired? and not milestone.closed?
%span.cred (Expired) %span.cred (Expired)
%small %small
= milestone.expires_at = milestone.expires_at
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
&larr; To milestones list &larr; To milestones list
.span6 .span6
.pull-right .pull-right
- unless @milestone.closed - unless @milestone.closed?
= link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do
%i.icon-plus %i.icon-plus
New Issue New Issue
...@@ -25,12 +25,12 @@ ...@@ -25,12 +25,12 @@
%hr %hr
%p %p
%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.
= link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {closed: true }), method: :put, class: "btn btn-small btn-remove" = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-remove"
.ui-box.ui-box-show .ui-box.ui-box-show
.ui-box-head .ui-box-head
%h4.box-title %h4.box-title
- if @milestone.closed - if @milestone.closed?
.error.status_info Closed .error.status_info Closed
- elsif @milestone.expired? - elsif @milestone.expired?
.error.status_info Expired .error.status_info Expired
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
%li=link_to('All Issues', '#') %li=link_to('All Issues', '#')
%ul.well-list %ul.well-list
- @issues.each do |issue| - @issues.each do |issue|
%li{data: {closed: issue.closed}} %li{data: {closed: issue.closed?}}
= link_to [@project, issue] do = link_to [@project, issue] do
%span.badge.badge-info ##{issue.id} %span.badge.badge-info ##{issue.id}
&ndash; &ndash;
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
%li=link_to('All Merge Requests', '#') %li=link_to('All Merge Requests', '#')
%ul.well-list %ul.well-list
- @merge_requests.each do |merge_request| - @merge_requests.each do |merge_request|
%li{data: {closed: merge_request.closed}} %li{data: {closed: merge_request.closed?}}
= link_to [@project, merge_request] do = link_to [@project, merge_request] do
%span.badge.badge-info ##{merge_request.id} %span.badge.badge-info ##{merge_request.id}
&ndash; &ndash;
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
- if note.attachment.image? - if note.attachment.image?
= image_tag note.attachment.url, class: 'note-image-attach' = image_tag note.attachment.url, class: 'note-image-attach'
.attachment.pull-right .attachment.pull-right
= link_to note.attachment.url, target: "_blank" do = link_to note.attachment.secure_url, target: "_blank" do
%i.icon-paper-clip %i.icon-paper-clip
= note.attachment_identifier = note.attachment_identifier
.clear .clear
...@@ -53,29 +53,30 @@ ...@@ -53,29 +53,30 @@
%fieldset.update-username - if current_user.can_change_username?
%legend %fieldset.update-username
Username %legend
%small.cred.pull-right Username
Changing your username can have unintended side effects! %small.cred.pull-right
= form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| Changing your username can have unintended side effects!
.padded = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
= f.label :username .padded
.input = f.label :username
= f.text_field :username, required: true .input
&nbsp; = f.text_field :username, required: true
%span.loading-gif.hide= image_tag "ajax_loader.gif" &nbsp;
%span.update-success.cgreen.hide %span.loading-gif.hide= image_tag "ajax_loader.gif"
%i.icon-ok %span.update-success.cgreen.hide
Saved %i.icon-ok
%span.update-failed.cred.hide Saved
%i.icon-remove %span.update-failed.cred.hide
Failed %i.icon-remove
%ul.cred Failed
%li It will change web url for personal projects. %ul.cred
%li It will change the git path to repositories for personal projects. %li It will change web url for personal projects.
.input %li It will change the git path to repositories for personal projects.
= f.submit 'Save username', class: "btn btn-save" .input
= f.submit 'Save username', class: "btn btn-save"
- if Gitlab.config.gitlab.signup_enabled - if Gitlab.config.gitlab.signup_enabled
%fieldset.remove-account %fieldset.remove-account
...@@ -83,4 +84,4 @@ ...@@ -83,4 +84,4 @@
Remove account Remove account
%small.cred.pull-right %small.cred.pull-right
Before removing the account you must remove all projects! Before removing the account you must remove all projects!
= link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove delete-key btn-small pull-right" = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
\ No newline at end of file
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
%legend %legend
Personal projects: Personal projects:
%small.pull-right %small.pull-right
%span= current_user.personal_projects.count %span= current_user.owned_projects.count
of of
%span= current_user.projects_limit %span= current_user.projects_limit
.padded .padded
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.input .input
= f.text_field :import_url, class: 'xlarge', placeholder: 'https://github.com/randx/six.git' = f.text_field :import_url, class: 'xlarge', placeholder: 'https://github.com/randx/six.git'
.light .light
URL should be clonable URL must be clonable
%p.padded %p.padded
New projects are private by default. You choose who can see the project and commit to repository. New projects are private by default. You choose who can see the project and commit to repository.
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- @notes.each do |note| - @notes.each do |note|
%tr %tr
%td %td
%a{href: note.attachment.url} = link_to note.attachment.secure_url, target: "_blank" do
= image_tag gravatar_icon(note.author_email), class: "avatar s24" = image_tag gravatar_icon(note.author_email), class: "avatar s24"
= note.attachment_identifier = note.attachment_identifier
%td %td
......
%li{id: dom_id(team), class: "user_team_row team_#{team.id}"}
.pull-right
- if can?(current_user, :admin_team_member, @project)
= link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn btn-remove btn-tiny" do
%i.icon-minus.icon-white
%strong= link_to team.name, team_path(team), title: team.name, class: "dark"
%br
%small.cgray Members: #{team.members.count}
%small.cgray Max access: #{team_relation.human_max_access}
.ui-box
%ul.well-list
- assigned_teams.sort_by(&:team_name).each do |team_relation|
= render "team_members/assigned_team", team_relation: team_relation, team: team_relation.user_team
- team = team_rel.user_team
- allow_admin = can? current_user, :admin_team_member, @project
%li{id: dom_id(team), class: "user_team_row team_#{team.id}"}
.row
.span6
%strong= link_to team.name, team_path(team), title: team.name, class: "dark"
%br
%small.cgray Members: #{team.members.count}
.span5.pull-right
.pull-right
- if allow_admin
.left
= link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn btn-remove small" do
%i.icon-minus.icon-white
- grouper_project_members(@project).each do |access, members| - team.each do |access, members|
.ui-box .ui-box
%h5.title %h5.title
= Project.access_options.key(access).pluralize = Project.access_options.key(access).pluralize
%small= members.size %small= members.size
%ul.well-list %ul.well-list
- members.sort_by(&:user_name).each do |up| - members.sort_by(&:user_name).each do |team_member|
= render(partial: 'team_members/show', locals: {member: up}) = render 'team_members/team_member', member: team_member
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- allow_admin = can? current_user, :admin_project, @project - allow_admin = can? current_user, :admin_project, @project
%li{id: dom_id(user), class: "team_member_row user_#{user.id}"} %li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
.row .row
.span6 .span4
= link_to project_team_member_path(@project, user), title: user.name, class: "dark" do = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar s32" = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to project_team_member_path(@project, user), title: user.name, class: "dark" do = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
...@@ -10,18 +10,18 @@ ...@@ -10,18 +10,18 @@
%br %br
%small.cgray= user.email %small.cgray= user.email
.span5.pull-right .span4.pull-right
- if allow_admin - if allow_admin
.left .left
= form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f| = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"
.pull-right .pull-right
- if current_user == user - if current_user == user
%span.btn.disabled This is you! %span.label This is you!
- if @project.namespace_owner == user - if @project.namespace_owner == user
%span.btn.disabled Owner %span.label Owner
- elsif user.blocked - elsif user.blocked
%span.btn.disabled.blocked Blocked %span.label Blocked
- elsif allow_admin - elsif allow_admin
= link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do
%i.icon-minus.icon-white %i.icon-minus.icon-white
......
- grouper_project_teams(@project).each do |access, teams|
.ui-box
%h5.title
= UserTeam.access_roles.key(access).pluralize
%small= teams.size
%ul.well-list
- teams.sort_by(&:team_name).each do |tofr|
= render(partial: 'team_members/show_team', locals: {team_rel: tofr})
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})
...@@ -18,16 +18,39 @@ ...@@ -18,16 +18,39 @@
%hr %hr
.clearfix .clearfix
%div.team-table .row
= render partial: "team_members/team", locals: {project: @project} .span3
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if !params[:type])}
= link_to project_team_members_path(type: nil) do
All
%li{class: ("active" if params[:type] == 'masters')}
= link_to project_team_members_path(type: 'masters') do
Masters
%span.pull-right= @project.users_projects.masters.count
%li{class: ("active" if params[:type] == 'developers')}
= link_to project_team_members_path(type: 'developers') do
Developers
%span.pull-right= @project.users_projects.developers.count
%li{class: ("active" if params[:type] == 'reporters')}
= link_to project_team_members_path(type: 'reporters') do
Reporters
%span.pull-right= @project.users_projects.reporters.count
%li{class: ("active" if params[:type] == 'guests')}
= link_to project_team_members_path(type: 'guests') do
Guests
%span.pull-right= @project.users_projects.guests.count
- if @assigned_teams.present?
%h5
Assigned teams
(#{@project.user_teams.count})
%div
= render "team_members/assigned_teams", assigned_teams: @assigned_teams
.span9
%div.team-table
= render "team_members/team", team: @team
%h3.page_title
Assigned teams
(#{@project.user_teams.count})
%hr
.clearfix
%div.team-table
= render partial: "team_members/teams", locals: {project: @project}
%h3.page_title= "Edit Team #{@team.name}" %h3.page_title= "Edit Team #{@team.name}"
%hr %hr
= form_for @team, url: team_path(@team) do |f| .row
- if @team.errors.any? .span7
.alert.alert-error = form_for @team, url: team_path(@team) do |f|
%span= @team.errors.full_messages.first - if @team.errors.any?
.clearfix .alert.alert-error
= f.label :name do %span= @team.errors.full_messages.first
Team name is .clearfix
.input = f.label :name do
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" Team name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xlarge left"
.clearfix
= f.label :path do
Team path is
.input
= f.text_field :path, placeholder: "opensource", class: "xlarge left"
.form-actions
= f.submit 'Save team changes', class: "btn btn-save"
.span5
.ui-box
%h5.title Remove team
.padded.bgred
%p
Removed team can not be restored!
= link_to 'Remove team', team_path(@team), method: :delete, confirm: "You are sure?", class: "btn btn-remove btn-small"
.clearfix
= f.label :path do
Team path is
.input
= f.text_field :path, placeholder: "opensource", class: "xxlarge left"
.form-actions
= f.submit 'Save team changes', class: "btn btn-primary"
= link_to 'Delete team', team_path(@team), method: :delete, confirm: "You are shure?", class: "btn btn-remove pull-right"
...@@ -10,22 +10,21 @@ ...@@ -10,22 +10,21 @@
%br %br
%small.cgray= user.email %small.cgray= user.email
.span6.pull-right .span4
- if allow_admin - if allow_admin
.left.span2 = form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f|
= form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f| = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium trigger-submit"
= f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium project-access-select span2" %br
.left.span2 = label_tag do
%span = f.check_box :group_admin, class: 'trigger-submit'
= check_box_tag :group_admin, true, @team.admin?(user) %span Admin access
Admin access .pull-right
.pull-right - if current_user == user
- if current_user == user %span.btn.disabled This is you!
%span.btn.disabled This is you! - if @team.owner == user
- if @team.owner == user %span.btn.disabled Owner
%span.btn.disabled.btn-success Owner - elsif user.blocked
- elsif user.blocked %span.btn.disabled.blocked Blocked
%span.btn.disabled.blocked Blocked - elsif allow_admin
- elsif allow_admin = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove" do
= link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove" do %i.icon-minus.icon-white
%i.icon-minus.icon-white
...@@ -17,3 +17,17 @@ ...@@ -17,3 +17,17 @@
%li All created teams are public (users can view who enter into team and which project are assigned for this team) %li All created teams are public (users can view who enter into team and which project are assigned for this team)
%li People within a team see only projects they have access to %li People within a team see only projects they have access to
%li You will be able to assign existing projects for team %li You will be able to assign existing projects for team
%hr
- if current_user.can_create_group?
.clearfix
.input.light
Need a group for several dependent projects?
= link_to new_group_path, class: "btn btn-tiny" do
Create a group
- if current_user.can_create_project?
.clearfix
.input.light
Want to create a project?
= link_to new_project_path, class: "btn btn-tiny" do
Create a project
...@@ -21,14 +21,18 @@ class PostReceive ...@@ -21,14 +21,18 @@ class PostReceive
return false return false
end end
# Ignore push from non-gitlab users user = if identifier.blank?
user = if identifier.nil? # Local push from gitlab
raise identifier.inspect
email = project.repository.commit(newrev).author.email rescue nil email = project.repository.commit(newrev).author.email rescue nil
User.find_by_email(email) if email User.find_by_email(email) if email
elsif /^[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}$/.match(identifier)
User.find_by_email(identifier) elsif identifier =~ /\Auser-\d+\Z/
elsif identifier =~ /key/ # git push over http
user_id = identifier.gsub("user-", "")
User.find_by_id(user_id)
elsif identifier =~ /\Akey-\d+\Z/
# git push over ssh
key_id = identifier.gsub("key-", "") key_id = identifier.gsub("key-", "")
Key.find_by_id(key_id).try(:user) Key.find_by_id(key_id).try(:user)
end end
......
...@@ -7,121 +7,132 @@ ...@@ -7,121 +7,132 @@
# 2. Replace gitlab -> host with your domain # 2. Replace gitlab -> host with your domain
# 3. Replace gitlab -> email_from # 3. Replace gitlab -> email_from
# production: &base
# 1. GitLab app settings #
# ========================== # 1. GitLab app settings
# ==========================
## GitLab settings
gitlab: ## GitLab settings
## Web server settings gitlab:
host: localhost ## Web server settings
port: 80 host: localhost
https: false port: 80
# Uncomment and customize to run in non-root path https: false
# Note that ENV['RAILS_RELATIVE_URL_ROOT'] in config/unicorn.rb may need to be changed # Uncomment and customize to run in non-root path
# relative_url_root: /gitlab # Note that ENV['RAILS_RELATIVE_URL_ROOT'] in config/unicorn.rb may need to be changed
# relative_url_root: /gitlab
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git # Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git
## Email settings
# Email address used in the "From" field in mails sent by GitLab ## Email settings
email_from: gitlab@localhost # Email address used in the "From" field in mails sent by GitLab
email_from: gitlab@localhost
# Email address of your support contact (default: same as email_from)
support_email: support@localhost # Email address of your support contact (default: same as email_from)
support_email: support@localhost
## Project settings
default_projects_limit: 10 ## Project settings
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled. default_projects_limit: 10
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
## Gravatar # username_changing_enabled: false # default: true - User can change her username/namespace
gravatar:
enabled: true # Use user avatar images from Gravatar.com (default: true) ## Gravatar
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm gravatar:
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm enabled: true # Use user avatar images from Gravatar.com (default: true)
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
#
# 2. Auth settings
# ========================== #
# 2. Auth settings
## LDAP settings # ==========================
ldap:
enabled: false ## LDAP settings
host: '_your_ldap_server' ldap:
base: '_the_base_where_you_search_for_users' enabled: false
port: 636 host: '_your_ldap_server'
uid: 'sAMAccountName' base: '_the_base_where_you_search_for_users'
method: 'ssl' # "ssl" or "plain" port: 636
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' uid: 'sAMAccountName'
password: '_the_password_of_the_bind_user' method: 'ssl' # "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
## Omniauth settings password: '_the_password_of_the_bind_user'
omniauth:
# Enable ability for users ## Omniauth settings
# Allow logging in via Twitter, Google, etc. using Omniauth providers omniauth:
enabled: false # Enable ability for users
# Allow logging in via Twitter, Google, etc. using Omniauth providers
enabled: false
# CAUTION!
# This allows users to login without having a user account first (default: false)
# User accounts will be created automatically when authentication was successful.
allow_single_sign_on: false
# Locks down those users until they have been cleared by the admin (default: true)
block_auto_created_users: true
## Auth providers
# Uncomment the lines and fill in the data of the auth provider you want to use
# If your favorite auth provider is not listed you can user others:
# see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers
# The 'app_id' and 'app_secret' parameters are always passed as the first two
# arguments, followed by optional 'args' which can be either a hash or an array.
providers:
# - { name: 'google_oauth2', app_id: 'YOUR APP ID',
# app_secret: 'YOUR APP SECRET',
# args: { access_type: 'offline', approval_prompt: '' } }
# - { name: 'twitter', app_id: 'YOUR APP ID',
# app_secret: 'YOUR APP SECRET'}
# - { name: 'github', app_id: 'YOUR APP ID',
# app_secret: 'YOUR APP SECRET' }
#
# 3. Advanced settings
# ==========================
# GitLab Satellites
satellites:
# Relative paths are relative to Rails.root (default: tmp/repo_satellites/)
path: /home/git/gitlab-satellites/
## Backup settings
backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
# keep_time: 604800 # default: 0 (forever) (in seconds)
## GitLab Shell settings
gitlab_shell:
# REPOS_PATH MUST NOT BE A SYMLINK!!!
repos_path: /home/git/repositories/
hooks_path: /home/git/gitlab-shell/hooks/
# Git over HTTP
upload_pack: true
receive_pack: true
# If you use non-standart ssh port you need to specify it
# ssh_port: 22
## Git settings
# CAUTION! # CAUTION!
# This allows users to login without having a user account first (default: false) # Use the default values unless you really know what you are doing
# User accounts will be created automatically when authentication was successful. git:
allow_single_sign_on: false bin_path: /usr/bin/git
# Locks down those users until they have been cleared by the admin (default: true) # Max size of git object like commit, in bytes
block_auto_created_users: true # This value can be increased if you have a very large commits
max_size: 5242880 # 5.megabytes
## Auth providers # Git timeout to read commit, in seconds
# Uncomment the lines and fill in the data of the auth provider you want to use timeout: 10
# If your favorite auth provider is not listed you can user others:
# see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers development:
# The 'app_id' and 'app_secret' parameters are always passed as the first two <<: *base
# arguments, followed by optional 'args' which can be either a hash or an array.
providers: test:
# - { name: 'google_oauth2', app_id: 'YOUR APP ID', <<: *base
# app_secret: 'YOUR APP SECRET',
# args: { access_type: 'offline', approval_prompt: '' } } staging:
# - { name: 'twitter', app_id: 'YOUR APP ID', <<: *base
# app_secret: 'YOUR APP SECRET'}
# - { name: 'github', app_id: 'YOUR APP ID',
# app_secret: 'YOUR APP SECRET' }
#
# 3. Advanced settings
# ==========================
# GitLab Satellites
satellites:
# Relative paths are relative to Rails.root (default: tmp/repo_satellites/)
path: /home/git/gitlab-satellites/
## Backup settings
backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
# keep_time: 604800 # default: 0 (forever) (in seconds)
## GitLab Shell settings
gitlab_shell:
# REPOS_PATH MUST NOT BE A SYMLINK!!!
repos_path: /home/git/repositories/
hooks_path: /home/git/gitlab-shell/hooks/
# Git over HTTP
upload_pack: true
receive_pack: true
# If you use non-standart ssh port you need to specify it
# ssh_port: 22
## Git settings
# CAUTION!
# Use the default values unless you really know what you are doing
git:
bin_path: /usr/bin/git
# Max size of git object like commit, in bytes
# This value can be increased if you have a very large commits
max_size: 5242880 # 5.megabytes
# Git timeout to read commit, in seconds
timeout: 10
class Settings < Settingslogic class Settings < Settingslogic
source "#{Rails.root}/config/gitlab.yml" source "#{Rails.root}/config/gitlab.yml"
namespace Rails.env
class << self class << self
def gitlab_on_non_standard_port? def gitlab_on_non_standard_port?
...@@ -56,6 +57,7 @@ Settings.gitlab['support_email'] ||= Settings.gitlab.email_from ...@@ -56,6 +57,7 @@ Settings.gitlab['support_email'] ||= Settings.gitlab.email_from
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git' Settings.gitlab['user'] ||= 'git'
Settings.gitlab['signup_enabled'] ||= false Settings.gitlab['signup_enabled'] ||= false
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
# #
# Gravatar # Gravatar
......
module Gitlab module Gitlab
Version = File.read(Rails.root.join("VERSION")) VERSION = File.read(Rails.root.join("VERSION")).strip
Revision = `git log --pretty=format:'%h' -n 1` REVISION = `git log --pretty=format:'%h' -n 1`
def self.config def self.config
Settings Settings
......
...@@ -46,6 +46,11 @@ Gitlab::Application.routes.draw do ...@@ -46,6 +46,11 @@ Gitlab::Application.routes.draw do
root to: "projects#index" root to: "projects#index"
end end
#
# Attachments serving
#
get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /[a-zA-Z.0-9_\-\+]+/ }
# #
# Admin Area # Admin Area
# #
......
root = Gitlab.config.gitolite.repos_path root = Gitlab.config.gitlab_shell.repos_path
projects = [ projects = [
{ path: 'underscore.git', git: 'https://github.com/documentcloud/underscore.git' }, { path: 'underscore.git', git: 'https://github.com/documentcloud/underscore.git' },
......
...@@ -16,7 +16,7 @@ Gitlab::Seeder.quiet do ...@@ -16,7 +16,7 @@ Gitlab::Seeder.quiet do
project_id: project.id, project_id: project.id,
author_id: user_id, author_id: user_id,
assignee_id: user_id, assignee_id: user_id,
closed: [true, false].sample, state: ['opened', 'closed'].sample,
milestone: project.milestones.sample, milestone: project.milestones.sample,
title: Faker::Lorem.sentence(6) title: Faker::Lorem.sentence(6)
}]) }])
......
...@@ -17,7 +17,7 @@ Gitlab::Seeder.quiet do ...@@ -17,7 +17,7 @@ Gitlab::Seeder.quiet do
project_id: project.id, project_id: project.id,
author_id: user_id, author_id: user_id,
assignee_id: user_id, assignee_id: user_id,
closed: [true, false].sample, state: ['opened', 'closed'].sample,
milestone: project.milestones.sample, milestone: project.milestones.sample,
title: Faker::Lorem.sentence(6) title: Faker::Lorem.sentence(6)
}]) }])
......
class RenameStateToMergeStatusInMilestone < ActiveRecord::Migration
def change
rename_column :merge_requests, :state, :merge_status
end
end
class AddStateToIssue < ActiveRecord::Migration
def change
add_column :issues, :state, :string
end
end
class AddStateToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :state, :string
end
end
class AddStateToMilestone < ActiveRecord::Migration
def change
add_column :milestones, :state, :string
end
end
class ConvertClosedToStateInIssue < ActiveRecord::Migration
def up
Issue.transaction do
Issue.where(closed: true).update_all("state = 'closed'")
Issue.where(closed: false).update_all("state = 'opened'")
end
end
def down
Issue.transaction do
Issue.where(state: :closed).update_all("closed = 1")
end
end
end
class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
def up
MergeRequest.transaction do
MergeRequest.where("closed = 1 AND merged = 1").update_all("state = 'merged'")
MergeRequest.where("closed = 1 AND merged = 0").update_all("state = 'closed'")
MergeRequest.where("closed = 0").update_all("state = 'opened'")
end
end
def down
MergeRequest.transaction do
MergeRequest.where(state: :closed).update_all("closed = 1")
MergeRequest.where(state: :merged).update_all("closed = 1, merged = 1")
end
end
end
class ConvertClosedToStateInMilestone < ActiveRecord::Migration
def up
Milestone.transaction do
Milestone.where(closed: false).update_all("state = 'opened'")
Milestone.where(closed: false).update_all("state = 'active'")
end
end
def down
Milestone.transaction do
Milestone.where(state: :closed).update_all("closed = 1")
end
end
end
class RemoveMergedFromMergeRequest < ActiveRecord::Migration
def up
remove_column :merge_requests, :merged
end
def down
add_column :merge_requests, :merged, :boolean, default: true, null: false
end
end
class RemoveClosedFromIssue < ActiveRecord::Migration
def up
remove_column :issues, :closed
end
def down
add_column :issues, :closed, :boolean
end
end
class RemoveClosedFromMergeRequest < ActiveRecord::Migration
def up
remove_column :merge_requests, :closed
end
def down
add_column :merge_requests, :closed, :boolean
end
end
class RemoveClosedFromMilestone < ActiveRecord::Migration
def up
remove_column :milestones, :closed
end
def down
add_column :milestones, :closed, :boolean
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130131070232) do ActiveRecord::Schema.define(:version => 20130218141554) do
create_table "events", :force => true do |t| create_table "events", :force => true do |t|
t.string "target_type" t.string "target_type"
...@@ -37,18 +37,17 @@ ActiveRecord::Schema.define(:version => 20130131070232) do ...@@ -37,18 +37,17 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.integer "assignee_id" t.integer "assignee_id"
t.integer "author_id" t.integer "author_id"
t.integer "project_id" t.integer "project_id"
t.datetime "created_at", :null => false t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false t.datetime "updated_at", :null => false
t.boolean "closed", :default => false, :null => false
t.integer "position", :default => 0 t.integer "position", :default => 0
t.string "branch_name" t.string "branch_name"
t.text "description" t.text "description"
t.integer "milestone_id" t.integer "milestone_id"
t.string "state"
end end
add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id" add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id"
add_index "issues", ["author_id"], :name => "index_issues_on_author_id" add_index "issues", ["author_id"], :name => "index_issues_on_author_id"
add_index "issues", ["closed"], :name => "index_issues_on_closed"
add_index "issues", ["created_at"], :name => "index_issues_on_created_at" add_index "issues", ["created_at"], :name => "index_issues_on_created_at"
add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id" add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id"
add_index "issues", ["project_id"], :name => "index_issues_on_project_id" add_index "issues", ["project_id"], :name => "index_issues_on_project_id"
...@@ -69,25 +68,23 @@ ActiveRecord::Schema.define(:version => 20130131070232) do ...@@ -69,25 +68,23 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
add_index "keys", ["user_id"], :name => "index_keys_on_user_id" add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
create_table "merge_requests", :force => true do |t| create_table "merge_requests", :force => true do |t|
t.string "target_branch", :null => false t.string "target_branch", :null => false
t.string "source_branch", :null => false t.string "source_branch", :null => false
t.integer "project_id", :null => false t.integer "project_id", :null => false
t.integer "author_id" t.integer "author_id"
t.integer "assignee_id" t.integer "assignee_id"
t.string "title" t.string "title"
t.boolean "closed", :default => false, :null => false t.datetime "created_at", :null => false
t.datetime "created_at", :null => false t.datetime "updated_at", :null => false
t.datetime "updated_at", :null => false
t.text "st_commits", :limit => 2147483647 t.text "st_commits", :limit => 2147483647
t.text "st_diffs", :limit => 2147483647 t.text "st_diffs", :limit => 2147483647
t.boolean "merged", :default => false, :null => false t.integer "merge_status", :default => 1, :null => false
t.integer "state", :default => 1, :null => false
t.integer "milestone_id" t.integer "milestone_id"
t.string "state"
end end
add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id" add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id" add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
add_index "merge_requests", ["closed"], :name => "index_merge_requests_on_closed"
add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at" add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id" add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id"
add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id" add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"
...@@ -96,13 +93,13 @@ ActiveRecord::Schema.define(:version => 20130131070232) do ...@@ -96,13 +93,13 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title" add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title"
create_table "milestones", :force => true do |t| create_table "milestones", :force => true do |t|
t.string "title", :null => false t.string "title", :null => false
t.integer "project_id", :null => false t.integer "project_id", :null => false
t.text "description" t.text "description"
t.date "due_date" t.date "due_date"
t.boolean "closed", :default => false, :null => false t.datetime "created_at", :null => false
t.datetime "created_at", :null => false t.datetime "updated_at", :null => false
t.datetime "updated_at", :null => false t.string "state"
end end
add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date" add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date"
......
...@@ -34,7 +34,6 @@ POST /projects/:id/milestones ...@@ -34,7 +34,6 @@ POST /projects/:id/milestones
Parameters: Parameters:
+ `id` (required) - The ID of a project + `id` (required) - The ID of a project
+ `milestone_id` (required) - The ID of a project milestone
+ `title` (required) - The title of an milestone + `title` (required) - The title of an milestone
+ `description` (optional) - The description of the milestone + `description` (optional) - The description of the milestone
+ `due_date` (optional) - The due date of the milestone + `due_date` (optional) - The due date of the milestone
......
...@@ -23,7 +23,7 @@ GET /projects ...@@ -23,7 +23,7 @@ GET /projects
"blocked": false, "blocked": false,
"created_at": "2012-05-23T08:00:58Z" "created_at": "2012-05-23T08:00:58Z"
}, },
"private": true, "public": true,
"path": "rails", "path": "rails",
"path_with_namespace": "rails/rails", "path_with_namespace": "rails/rails",
"issues_enabled": false, "issues_enabled": false,
...@@ -45,7 +45,7 @@ GET /projects ...@@ -45,7 +45,7 @@ GET /projects
"blocked": false, "blocked": false,
"created_at": "2012-05-23T08:00:58Z" "created_at": "2012-05-23T08:00:58Z"
}, },
"private": true, "public": true,
"path": "gitlab", "path": "gitlab",
"path_with_namespace": "randx/gitlab", "path_with_namespace": "randx/gitlab",
"issues_enabled": true, "issues_enabled": true,
...@@ -89,7 +89,7 @@ Parameters: ...@@ -89,7 +89,7 @@ Parameters:
"blocked": false, "blocked": false,
"created_at": "2012-05-23T08:00:58Z" "created_at": "2012-05-23T08:00:58Z"
}, },
"private": true, "public": true,
"path": "gitlab", "path": "gitlab",
"path_with_namespace": "randx/gitlab", "path_with_namespace": "randx/gitlab",
"issues_enabled": true, "issues_enabled": true,
......
...@@ -27,7 +27,7 @@ GitLab supports the following databases: ...@@ -27,7 +27,7 @@ GitLab supports the following databases:
mysql> \q mysql> \q
# Try connecting to the new database with the new user # Try connecting to the new database with the new user
sudo -u gitlab -H mysql -u gitlab -p -D gitlabhq_production sudo -u git -H mysql -u gitlab -p -D gitlabhq_production
## PostgreSQL ## PostgreSQL
...@@ -47,5 +47,5 @@ GitLab supports the following databases: ...@@ -47,5 +47,5 @@ GitLab supports the following databases:
template1=# \q template1=# \q
# Try connecting to the new database with the new user # Try connecting to the new database with the new user
sudo -u gitlab -H psql -d gitlabhq_production sudo -u git -H psql -d gitlabhq_production
This installation guide was created for Debian/Ubuntu and tested on it. This installation guide was created for Debian/Ubuntu and tested on it.
Please read `doc/install/requirements.md` for hardware and platform requirements. Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements.
**Important Note:** **Important Note:**
...@@ -8,12 +8,13 @@ The following steps have been known to work. ...@@ -8,12 +8,13 @@ The following steps have been known to work.
If you deviate from this guide, do it with caution and make sure you don't If you deviate from this guide, do it with caution and make sure you don't
violate any assumptions GitLab makes about its environment. violate any assumptions GitLab makes about its environment.
For things like AWS installation scripts, init scripts or config files for For things like AWS installation scripts, init scripts or config files for
alternative web server have a look at the "Advanced Setup Tips" section. alternative web server have a look at the [`Advanced Setup
Tips`](./installation.md#advanced-setup-tips) section.
**Important Note:** **Important Note:**
If you find a bug/error in this guide please submit an issue or pull request If you find a bug/error in this guide please submit an issue or pull request
following the contribution guide (see `CONTRIBUTING.md`). following the [`contribution guide`](../../CONTRIBUTING.md).
- - - - - -
...@@ -24,7 +25,7 @@ The GitLab installation consists of setting up the following components: ...@@ -24,7 +25,7 @@ The GitLab installation consists of setting up the following components:
1. Packages / Dependencies 1. Packages / Dependencies
2. Ruby 2. Ruby
3. System Users 3. System Users
4. Gitolite 4. GitLab shell
5. Database 5. Database
6. GitLab 6. GitLab
7. Nginx 7. Nginx
...@@ -32,16 +33,13 @@ The GitLab installation consists of setting up the following components: ...@@ -32,16 +33,13 @@ The GitLab installation consists of setting up the following components:
# 1. Packages / Dependencies # 1. Packages / Dependencies
`sudo` is not installed on Debian by default. If you don't have it you'll need `sudo` is not installed on Debian by default. Make sure your system is
to install it first. up-to-date and install it.
# run as root # run as root
apt-get update && apt-get upgrade && apt-get install sudo apt-get update
apt-get upgrade
Make sure your system is up-to-date: apt-get install sudo
sudo apt-get update
sudo apt-get upgrade
**Note:** **Note:**
Vim is an editor that is used here whenever there are files that need to be Vim is an editor that is used here whenever there are files that need to be
...@@ -96,25 +94,24 @@ Create a `git` user for Gitlab: ...@@ -96,25 +94,24 @@ Create a `git` user for Gitlab:
# 4. GitLab shell # 4. GitLab shell
# login as git # Login as git
sudo su git sudo su git
# go to home directory # Go to home directory
cd /home/git cd /home/git
# clone gitlab shell # Clone gitlab shell
git clone https://github.com/gitlabhq/gitlab-shell.git git clone https://github.com/gitlabhq/gitlab-shell.git
# setup # Setup
cd gitlab-shell cd gitlab-shell
cp config.yml.example config.yml cp config.yml.example config.yml
./bin/install ./bin/install
# 5. Database # 5. Database
To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md) . To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md).
# 6. GitLab # 6. GitLab
...@@ -154,9 +151,13 @@ do so with caution! ...@@ -154,9 +151,13 @@ do so with caution!
sudo chmod -R u+rwX log/ sudo chmod -R u+rwX log/
sudo chmod -R u+rwX tmp/ sudo chmod -R u+rwX tmp/
# Make directory for satellites # Create directory for satellites
sudo -u git -H mkdir /home/git/gitlab-satellites sudo -u git -H mkdir /home/git/gitlab-satellites
# Create directory for pids and make sure GitLab can write to it
sudo -u git -H mkdir tmp/pids/
sudo chmod -R u+rwX tmp/pids/
# Copy the example Unicorn config # Copy the example Unicorn config
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
...@@ -187,7 +188,9 @@ Make sure to update username/password in config/database.yml. ...@@ -187,7 +188,9 @@ Make sure to update username/password in config/database.yml.
## Initialise Database and Activate Advanced Features ## Initialise Database and Activate Advanced Features
sudo -u git -H bundle exec rake db:setup RAILS_ENV=production
sudo -u git -H bundle exec rake db:seed_fu RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
...@@ -205,7 +208,7 @@ Make GitLab start on boot: ...@@ -205,7 +208,7 @@ Make GitLab start on boot:
## Check Application Status ## Check Application Status
Check if GitLab and its environment is configured correctly: Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
...@@ -227,7 +230,7 @@ However there are still a few steps left. ...@@ -227,7 +230,7 @@ However there are still a few steps left.
**Note:** **Note:**
If you can't or don't want to use Nginx as your web server, have a look at the If you can't or don't want to use Nginx as your web server, have a look at the
"Advanced Setup Tips" section. [`Advanced Setup Tips`](./installation.md#advanced-setup-tips) section.
## Installation ## Installation
sudo apt-get install nginx sudo apt-get install nginx
...@@ -244,11 +247,11 @@ Make sure to edit the config file to match your setup: ...@@ -244,11 +247,11 @@ Make sure to edit the config file to match your setup:
# Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN**
# to the IP address and fully-qualified domain name # to the IP address and fully-qualified domain name
# of your host serving GitLab # of your host serving GitLab
sudo vim /etc/nginx/sites-enabled/gitlab sudo vim /etc/nginx/sites-available/gitlab
## Restart ## Restart
sudo /etc/init.d/nginx restart sudo service nginx restart
# Done! # Done!
...@@ -282,7 +285,7 @@ a different host, you can configure its connection string via the ...@@ -282,7 +285,7 @@ a different host, you can configure its connection string via the
## Custom SSH Connection ## Custom SSH Connection
If you are running SSH on a non-standard port, you must change the gitlab user'S SSH config. If you are running SSH on a non-standard port, you must change the gitlab user's SSH config.
# Add to /home/git/.ssh/config # Add to /home/git/.ssh/config
host localhost # Give your setup a name (here: override localhost) host localhost # Give your setup a name (here: override localhost)
......
...@@ -43,6 +43,6 @@ class ProfileSshKeys < Spinach::FeatureSteps ...@@ -43,6 +43,6 @@ class ProfileSshKeys < Spinach::FeatureSteps
end end
And 'I have ssh key "ssh-rsa Work"' do And 'I have ssh key "ssh-rsa Work"' do
create(:key, :user => @user, :title => "ssh-rsa Work", :key => "jfKLJDFKSFJSHFJssh-rsa Work") create(:key, :user => @user, :title => "ssh-rsa Work", :key => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work")
end end
end end
...@@ -122,10 +122,9 @@ class ProjectIssues < Spinach::FeatureSteps ...@@ -122,10 +122,9 @@ class ProjectIssues < Spinach::FeatureSteps
And 'project "Shop" have "Release 0.3" closed issue' do And 'project "Shop" have "Release 0.3" closed issue' do
project = Project.find_by_name("Shop") project = Project.find_by_name("Shop")
create(:issue, create(:closed_issue,
:title => "Release 0.3", :title => "Release 0.3",
:project => project, :project => project,
:author => project.users.first, :author => project.users.first)
:closed => true)
end end
end end
...@@ -26,7 +26,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps ...@@ -26,7 +26,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps
Then 'I should see closed merge request "Bug NS-04"' do Then 'I should see closed merge request "Bug NS-04"' do
mr = MergeRequest.find_by_title("Bug NS-04") mr = MergeRequest.find_by_title("Bug NS-04")
mr.closed.should be_true mr.closed?.should be_true
page.should have_content "Closed by" page.should have_content "Closed by"
end end
...@@ -80,11 +80,10 @@ class ProjectMergeRequests < Spinach::FeatureSteps ...@@ -80,11 +80,10 @@ class ProjectMergeRequests < Spinach::FeatureSteps
And 'project "Shop" have "Feature NS-03" closed merge request' do And 'project "Shop" have "Feature NS-03" closed merge request' do
project = Project.find_by_name("Shop") project = Project.find_by_name("Shop")
create(:merge_request, create(:closed_merge_request,
title: "Feature NS-03", title: "Feature NS-03",
project: project, project: project,
author: project.users.first, author: project.users.first)
closed: true)
end end
And 'I switch to the diff tab' do And 'I switch to the diff tab' do
......
...@@ -20,7 +20,7 @@ module Gitlab ...@@ -20,7 +20,7 @@ module Gitlab
class Project < Grape::Entity class Project < Grape::Entity
expose :id, :name, :description, :default_branch expose :id, :name, :description, :default_branch
expose :owner, using: Entities::UserBasic expose :owner, using: Entities::UserBasic
expose :private_flag, as: :private expose :public
expose :path, :path_with_namespace expose :path, :path_with_namespace
expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
expose :namespace expose :namespace
...@@ -35,12 +35,11 @@ module Gitlab ...@@ -35,12 +35,11 @@ module Gitlab
class Group < Grape::Entity class Group < Grape::Entity
expose :id, :name, :path, :owner_id expose :id, :name, :path, :owner_id
end end
class GroupDetail < Group class GroupDetail < Group
expose :projects, using: Entities::Project expose :projects, using: Entities::Project
end end
class RepoObject < Grape::Entity class RepoObject < Grape::Entity
expose :name, :commit expose :name, :commit
expose :protected do |repo, options| expose :protected do |repo, options|
...@@ -63,7 +62,7 @@ module Gitlab ...@@ -63,7 +62,7 @@ module Gitlab
class Milestone < Grape::Entity class Milestone < Grape::Entity
expose :id expose :id
expose (:project_id) {|milestone| milestone.project.id} expose (:project_id) {|milestone| milestone.project.id}
expose :title, :description, :due_date, :closed, :updated_at, :created_at expose :title, :description, :due_date, :state, :updated_at, :created_at
end end
class Issue < Grape::Entity class Issue < Grape::Entity
...@@ -73,7 +72,7 @@ module Gitlab ...@@ -73,7 +72,7 @@ module Gitlab
expose :label_list, as: :labels expose :label_list, as: :labels
expose :milestone, using: Entities::Milestone expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic expose :assignee, :author, using: Entities::UserBasic
expose :closed, :updated_at, :created_at expose :state, :updated_at, :created_at
end end
class SSHKey < Grape::Entity class SSHKey < Grape::Entity
...@@ -81,7 +80,7 @@ module Gitlab ...@@ -81,7 +80,7 @@ module Gitlab
end end
class MergeRequest < Grape::Entity class MergeRequest < Grape::Entity
expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged expose :id, :target_branch, :source_branch, :project_id, :title, :state
expose :author, :assignee, using: Entities::UserBasic expose :author, :assignee, using: Entities::UserBasic
end end
......
...@@ -40,7 +40,9 @@ module Gitlab ...@@ -40,7 +40,9 @@ module Gitlab
get "/check" do get "/check" do
{ {
api_version: '3' api_version: Gitlab::API.version,
gitlab_version: Gitlab::VERSION,
gitlab_rev: Gitlab::REVISION,
} }
end end
end end
......
...@@ -69,14 +69,14 @@ module Gitlab ...@@ -69,14 +69,14 @@ module Gitlab
# assignee_id (optional) - The ID of a user to assign issue # assignee_id (optional) - The ID of a user to assign issue
# milestone_id (optional) - The ID of a milestone to assign issue # milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue # labels (optional) - The labels of an issue
# closed (optional) - The state of an issue (0 = false, 1 = true) # state (optional) - The state of an issue (close|reopen)
# Example Request: # Example Request:
# PUT /projects/:id/issues/:issue_id # PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do put ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id]) @issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, @issue authorize! :modify_issue, @issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present? attrs[:label_list] = params[:labels] if params[:labels].present?
IssueObserver.current_user = current_user IssueObserver.current_user = current_user
if @issue.update_attributes attrs if @issue.update_attributes attrs
......
...@@ -91,12 +91,12 @@ module Gitlab ...@@ -91,12 +91,12 @@ module Gitlab
# target_branch - The target branch # target_branch - The target branch
# assignee_id - Assignee user ID # assignee_id - Assignee user ID
# title - Title of MR # title - Title of MR
# closed - Status of MR. true - closed # state_event - Status of MR. (close|reopen|merge)
# Example: # Example:
# PUT /projects/:id/merge_request/:merge_request_id # PUT /projects/:id/merge_request/:merge_request_id
# #
put ":id/merge_request/:merge_request_id" do put ":id/merge_request/:merge_request_id" do
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed] attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
merge_request = user_project.merge_requests.find(params[:merge_request_id]) merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request authorize! :modify_merge_request, merge_request
......
...@@ -74,14 +74,14 @@ module Gitlab ...@@ -74,14 +74,14 @@ module Gitlab
# title (optional) - The title of a milestone # title (optional) - The title of a milestone
# description (optional) - The description of a milestone # description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone # due_date (optional) - The due date of a milestone
# closed (optional) - The status of the milestone # state (optional) - The status of the milestone (close|activate)
# Example Request: # Example Request:
# PUT /projects/:id/milestones/:milestone_id # PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do put ":id/milestones/:milestone_id" do
authorize! :admin_milestone, user_project authorize! :admin_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id]) @milestone = user_project.milestones.find(params[:milestone_id])
attrs = attributes_for_keys [:title, :description, :due_date, :closed] attrs = attributes_for_keys [:title, :description, :due_date, :state_event]
if @milestone.update_attributes attrs if @milestone.update_attributes attrs
present @milestone, with: Entities::Milestone present @milestone, with: Entities::Milestone
else else
......
...@@ -184,6 +184,7 @@ module Gitlab ...@@ -184,6 +184,7 @@ module Gitlab
# Example Request: # Example Request:
# GET /projects/:id/hooks/:hook_id # GET /projects/:id/hooks/:hook_id
get ":id/hooks/:hook_id" do get ":id/hooks/:hook_id" do
authorize! :admin_project, user_project
@hook = user_project.hooks.find(params[:hook_id]) @hook = user_project.hooks.find(params[:hook_id])
present @hook, with: Entities::Hook present @hook, with: Entities::Hook
end end
......
require_relative 'shell_env'
module Grack module Grack
class Auth < Rack::Auth::Basic class Auth < Rack::Auth::Basic
attr_accessor :user, :project attr_accessor :user, :project
...@@ -7,9 +9,6 @@ module Grack ...@@ -7,9 +9,6 @@ module Grack
@request = Rack::Request.new(env) @request = Rack::Request.new(env)
@auth = Request.new(env) @auth = Request.new(env)
# Pass Gitolite update hook
ENV['GL_BYPASS_UPDATE_HOOK'] = "true"
# Need this patch due to the rails mount # Need this patch due to the rails mount
@env['PATH_INFO'] = @request.path @env['PATH_INFO'] = @request.path
@env['SCRIPT_NAME'] = "" @env['SCRIPT_NAME'] = ""
...@@ -35,8 +34,7 @@ module Grack ...@@ -35,8 +34,7 @@ module Grack
self.user = User.find_by_email(login) || User.find_by_username(login) self.user = User.find_by_email(login) || User.find_by_username(login)
return false unless user.try(:valid_password?, password) return false unless user.try(:valid_password?, password)
# Set GL_USER env variable Gitlab::ShellEnv.set_env(user)
ENV['GL_USER'] = user.email
end end
# Git upload and receive # Git upload and receive
......
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
# add_repository("gitlab/gitlab-ci") # add_repository("gitlab/gitlab-ci")
# #
def add_repository(name) def add_repository(name)
system("/home/git/gitlab-shell/bin/gitlab-projects add-project #{name}.git") system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects add-project #{name}.git")
end end
# Import repository # Import repository
...@@ -21,7 +21,7 @@ module Gitlab ...@@ -21,7 +21,7 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
# #
def import_repository(name, url) def import_repository(name, url)
system("/home/git/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}") system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}")
end end
# Remove repository from file system # Remove repository from file system
...@@ -32,7 +32,7 @@ module Gitlab ...@@ -32,7 +32,7 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci") # remove_repository("gitlab/gitlab-ci")
# #
def remove_repository(name) def remove_repository(name)
system("/home/git/gitlab-shell/bin/gitlab-projects rm-project #{name}.git") system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects rm-project #{name}.git")
end end
# Add new key to gitlab-shell # Add new key to gitlab-shell
...@@ -41,7 +41,7 @@ module Gitlab ...@@ -41,7 +41,7 @@ module Gitlab
# add_key("key-42", "sha-rsa ...") # add_key("key-42", "sha-rsa ...")
# #
def add_key(key_id, key_content) def add_key(key_id, key_content)
system("/home/git/gitlab-shell/bin/gitlab-keys add-key #{key_id} \"#{key_content}\"") system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys add-key #{key_id} \"#{key_content}\"")
end end
# Remove ssh key from gitlab shell # Remove ssh key from gitlab shell
...@@ -50,12 +50,16 @@ module Gitlab ...@@ -50,12 +50,16 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...") # remove_key("key-342", "sha-rsa ...")
# #
def remove_key(key_id, key_content) def remove_key(key_id, key_content)
system("/home/git/gitlab-shell/bin/gitlab-keys rm-key #{key_id} \"#{key_content}\"") system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys rm-key #{key_id} \"#{key_content}\"")
end end
def url_to_repo path def url_to_repo path
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end end
def gitlab_shell_user_home
File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
end
end end
end end
module Gitlab
# This module provide 2 methods
# to set specific ENV variabled for GitLab Shell
module ShellEnv
extend self
def set_env(user)
# Set GL_ID env variable
ENV['GL_ID'] = "user-#{user.id}"
end
def reset_env
# Reset GL_ID env variable
ENV['GL_ID'] = nil
end
end
end
...@@ -10,6 +10,10 @@ module Gitlab ...@@ -10,6 +10,10 @@ module Gitlab
/\A[a-zA-Z][a-zA-Z0-9_\-\. ]*\z/ /\A[a-zA-Z][a-zA-Z0-9_\-\. ]*\z/
end end
def name_regex
/\A[a-zA-Z0-9_\-\. ]*\z/
end
def path_regex def path_regex
default_regex default_regex
end end
......
...@@ -17,6 +17,8 @@ module Gitlab ...@@ -17,6 +17,8 @@ module Gitlab
# * Locks the satellite repo # * Locks the satellite repo
# * Yields the prepared satellite repo # * Yields the prepared satellite repo
def in_locked_and_timed_satellite def in_locked_and_timed_satellite
Gitlab::ShellEnv.set_env(user)
Grit::Git.with_timeout(options[:git_timeout]) do Grit::Git.with_timeout(options[:git_timeout]) do
project.satellite.lock do project.satellite.lock do
return yield project.satellite.repo return yield project.satellite.repo
...@@ -28,6 +30,8 @@ module Gitlab ...@@ -28,6 +30,8 @@ module Gitlab
rescue Grit::Git::GitTimeout => ex rescue Grit::Git::GitTimeout => ex
Gitlab::GitLogger.error(ex.message) Gitlab::GitLogger.error(ex.message)
return false return false
ensure
Gitlab::ShellEnv.reset_env
end end
# * Clears the satellite # * Clears the satellite
......
...@@ -40,8 +40,8 @@ namespace :gitlab do ...@@ -40,8 +40,8 @@ namespace :gitlab do
puts "" puts ""
puts "GitLab information".yellow puts "GitLab information".yellow
puts "Version:\t#{Gitlab::Version}" puts "Version:\t#{Gitlab::VERSION}"
puts "Revision:\t#{Gitlab::Revision}" puts "Revision:\t#{Gitlab::REVISION}"
puts "Directory:\t#{Rails.root}" puts "Directory:\t#{Rails.root}"
puts "DB Adapter:\t#{database_adapter}" puts "DB Adapter:\t#{database_adapter}"
puts "URL:\t\t#{Gitlab.config.gitlab.url}" puts "URL:\t\t#{Gitlab.config.gitlab.url}"
......
namespace :gitlab do namespace :gitlab do
desc "GITLAB | Setup production application" desc "GITLAB | Setup production application"
task :setup => :environment do task :setup => :environment do
setup setup_db
end end
def setup def setup_db
warn_user_is_not_gitlab warn_user_is_not_gitlab
puts "This will create the necessary database tables and seed the database." puts "This will create the necessary database tables and seed the database."
......
...@@ -25,12 +25,13 @@ namespace :gitlab do ...@@ -25,12 +25,13 @@ namespace :gitlab do
def setup def setup
warn_user_is_not_gitlab warn_user_is_not_gitlab
gitlab_shell_authorized_keys = File.join(File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}"),'.ssh/authorized_keys')
puts "This will rebuild an authorized_keys file." puts "This will rebuild an authorized_keys file."
puts "You will lose any data stored in /home/git/.ssh/authorized_keys." puts "You will lose any data stored in #{gitlab_shell_authorized_keys}."
ask_to_continue ask_to_continue
puts "" puts ""
system("echo '# Managed by gitlab-shell' > /home/git/.ssh/authorized_keys") system("echo '# Managed by gitlab-shell' > #{gitlab_shell_authorized_keys}")
Key.find_each(batch_size: 1000) do |key| Key.find_each(batch_size: 1000) do |key|
if Gitlab::Shell.new.add_key(key.shell_id, key.key) if Gitlab::Shell.new.add_key(key.shell_id, key.key)
......
...@@ -77,8 +77,7 @@ namespace :gitlab do ...@@ -77,8 +77,7 @@ namespace :gitlab do
end end
def gid_for(group_name) def gid_for(group_name)
group_line = File.read("/etc/group").lines.select{|l| l.start_with?("#{group_name}:")}.first Etc.getgrnam(group_name).gid
group_line.split(":")[2].to_i
end end
def warn_user_is_not_gitlab def warn_user_is_not_gitlab
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<link href="/static.css" media="screen" rel="stylesheet" type="text/css" /> <link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<h1>Deploy in progress</h1> <h1><center><img src="/gitlab_logo.png"/></center>Deploy in progress</h1>
<h3>Please try again in few minutes or contact your administrator.</h3> <h3>Please try again in few minutes or contact your administrator.</h3>
</body> </body>
</html> </html>
...@@ -54,10 +54,15 @@ FactoryGirl.define do ...@@ -54,10 +54,15 @@ FactoryGirl.define do
project project
trait :closed do trait :closed do
closed true state :closed
end
trait :reopened do
state :reopened
end end
factory :closed_issue, traits: [:closed] factory :closed_issue, traits: [:closed]
factory :reopened_issue, traits: [:reopened]
end end
factory :merge_request do factory :merge_request do
...@@ -67,10 +72,6 @@ FactoryGirl.define do ...@@ -67,10 +72,6 @@ FactoryGirl.define do
source_branch "master" source_branch "master"
target_branch "stable" target_branch "stable"
trait :closed do
closed true
end
# pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d) # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
trait :with_diffs do trait :with_diffs do
target_branch "master" # pretend bcf03b5d~3 target_branch "master" # pretend bcf03b5d~3
...@@ -85,7 +86,16 @@ FactoryGirl.define do ...@@ -85,7 +86,16 @@ FactoryGirl.define do
end end
end end
trait :closed do
state :closed
end
trait :reopened do
state :reopened
end
factory :closed_merge_request, traits: [:closed] factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs] factory :merge_request_with_diffs, traits: [:with_diffs]
end end
...@@ -148,11 +158,23 @@ FactoryGirl.define do ...@@ -148,11 +158,23 @@ FactoryGirl.define do
"ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa ++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa ++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
end end
end end
factory :invalid_key do
key do
"ssh-rsa this_is_invalid_key=="
end
end
end end
factory :milestone do factory :milestone do
title title
project project
trait :closed do
state :closed
end
factory :closed_milestone, traits: [:closed]
end end
factory :system_hook do factory :system_hook do
......
require 'spec_helper' require 'spec_helper'
INVALID_FACTORIES = [:key_with_a_space_in_the_middle] INVALID_FACTORIES = [
:key_with_a_space_in_the_middle,
:invalid_key,
]
FactoryGirl.factories.map(&:name).each do |factory_name| FactoryGirl.factories.map(&:name).each do |factory_name|
next if INVALID_FACTORIES.include?(factory_name) next if INVALID_FACTORIES.include?(factory_name)
......
...@@ -15,7 +15,6 @@ describe Issue, "Issuable" do ...@@ -15,7 +15,6 @@ describe Issue, "Issuable" do
it { should validate_presence_of(:author) } it { should validate_presence_of(:author) }
it { should validate_presence_of(:title) } it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
it { should ensure_inclusion_of(:closed).in_array([true, false]) }
end end
describe "Scope" do describe "Scope" do
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
# project_id :integer # project_id :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# closed :boolean default(FALSE), not null # state :string default(FALSE), not null
# position :integer default(0) # position :integer default(0)
# branch_name :string(255) # branch_name :string(255)
# description :text # description :text
...@@ -44,34 +44,15 @@ describe Issue do ...@@ -44,34 +44,15 @@ describe Issue do
end end
end end
describe '#is_being_closed?' do describe '#is_being_reassigned?' do
it 'returns true if the closed attribute has changed and is now true' do it 'returnes issues assigned to user' do
subject.closed = true user = create :user
subject.is_being_closed?.should be_true
end
it 'returns false if the closed attribute has changed and is now false' do
issue = create(:closed_issue)
issue.closed = false
issue.is_being_closed?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_closed?.should be_false
end
end
2.times do
issue = create :issue, assignee: user
end
describe '#is_being_reopened?' do Issue.open_for(user).count.should eq 2
it 'returns true if the closed attribute has changed and is now false' do
issue = create(:closed_issue)
issue.closed = false
issue.is_being_reopened?.should be_true
end
it 'returns false if the closed attribute has changed and is now true' do
subject.closed = true
subject.is_being_reopened?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_reopened?.should be_false
end end
end end
end end
...@@ -73,8 +73,12 @@ describe Key do ...@@ -73,8 +73,12 @@ describe Key do
build(:key, user: user).should be_valid build(:key, user: user).should be_valid
end end
it "rejects the unfingerprintable key" do it "rejects the unfingerprintable key (contains space in middle)" do
build(:key_with_a_space_in_the_middle).should_not be_valid build(:key_with_a_space_in_the_middle).should_not be_valid
end end
it "rejects the unfingerprintable key (not a key)" do
build(:invalid_key).should_not be_valid
end
end end
end end
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# st_commits :text(2147483647) # st_commits :text(2147483647)
# st_diffs :text(2147483647) # st_diffs :text(2147483647)
# merged :boolean default(FALSE), not null # merged :boolean default(FALSE), not null
# state :integer default(1), not null # merge_status :integer default(1), not null
# milestone_id :integer # milestone_id :integer
# #
...@@ -36,6 +36,10 @@ describe MergeRequest do ...@@ -36,6 +36,10 @@ describe MergeRequest do
it { should include_module(Issuable) } it { should include_module(Issuable) }
end end
describe "#mr_and_commit_notes" do
end
describe "#mr_and_commit_notes" do describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) } let!(:merge_request) { create(:merge_request) }
...@@ -62,35 +66,4 @@ describe MergeRequest do ...@@ -62,35 +66,4 @@ describe MergeRequest do
subject.is_being_reassigned?.should be_false subject.is_being_reassigned?.should be_false
end end
end end
describe '#is_being_closed?' do
it 'returns true if the closed attribute has changed and is now true' do
subject.closed = true
subject.is_being_closed?.should be_true
end
it 'returns false if the closed attribute has changed and is now false' do
merge_request = create(:closed_merge_request)
merge_request.closed = false
merge_request.is_being_closed?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_closed?.should be_false
end
end
describe '#is_being_reopened?' do
it 'returns true if the closed attribute has changed and is now false' do
merge_request = create(:closed_merge_request)
merge_request.closed = false
merge_request.is_being_reopened?.should be_true
end
it 'returns false if the closed attribute has changed and is now true' do
subject.closed = true
subject.is_being_reopened?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_reopened?.should be_false
end
end
end end
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
# project_id :integer not null # project_id :integer not null
# description :text # description :text
# due_date :date # due_date :date
# closed :boolean default(FALSE), not null # state :string default(FALSE), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# #
...@@ -27,7 +27,6 @@ describe Milestone do ...@@ -27,7 +27,6 @@ describe Milestone do
describe "Validation" do describe "Validation" do
it { should validate_presence_of(:title) } it { should validate_presence_of(:title) }
it { should validate_presence_of(:project) } it { should validate_presence_of(:project) }
it { should ensure_inclusion_of(:closed).in_array([true, false]) }
end end
let(:milestone) { create(:milestone) } let(:milestone) { create(:milestone) }
...@@ -41,7 +40,7 @@ describe Milestone do ...@@ -41,7 +40,7 @@ describe Milestone do
it "should count closed issues" do it "should count closed issues" do
IssueObserver.current_user = issue.author IssueObserver.current_user = issue.author
issue.update_attributes(closed: true) issue.close
milestone.issues << issue milestone.issues << issue
milestone.percent_complete.should == 100 milestone.percent_complete.should == 100
end end
...@@ -96,7 +95,7 @@ describe Milestone do ...@@ -96,7 +95,7 @@ describe Milestone do
describe :items_count do describe :items_count do
before do before do
milestone.issues << create(:issue) milestone.issues << create(:issue)
milestone.issues << create(:issue, closed: true) milestone.issues << create(:closed_issue)
milestone.merge_requests << create(:merge_request) milestone.merge_requests << create(:merge_request)
end end
...@@ -110,7 +109,35 @@ describe Milestone do ...@@ -110,7 +109,35 @@ describe Milestone do
it { milestone.can_be_closed?.should be_true } it { milestone.can_be_closed?.should be_true }
end end
describe :open? do describe :is_empty? do
it { milestone.open?.should be_true } before do
issue = create :closed_issue, milestone: milestone
merge_request = create :merge_request, milestone: milestone
end
it 'Should return total count of issues and merge requests assigned to milestone' do
milestone.total_items_count.should eq 2
end
end
describe :can_be_closed? do
before do
milestone = create :milestone
create :closed_issue, milestone: milestone
issue = create :issue
end
it 'should be true if milestone active and all nestied issues closed' do
milestone.can_be_closed?.should be_true
end
it 'should be false if milestone active and not all nestied issues closed' do
issue.milestone = milestone
issue.save
milestone.can_be_closed?.should be_false
end
end end
end end
...@@ -121,10 +121,7 @@ describe Project do ...@@ -121,10 +121,7 @@ describe Project do
let(:project) { create(:project) } let(:project) { create(:project) }
before do before do
@merge_request = create(:merge_request, @merge_request = create(:merge_request, project: project)
project: project,
merged: false,
closed: false)
@key = create(:key, user_id: project.owner.id) @key = create(:key, user_id: project.owner.id)
end end
...@@ -133,8 +130,7 @@ describe Project do ...@@ -133,8 +130,7 @@ describe Project do
@merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user) project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user)
@merge_request.reload @merge_request.reload
@merge_request.merged.should be_true @merge_request.merged?.should be_true
@merge_request.closed.should be_true
end end
it "should update merge request commits with new one if pushed to source branch" do it "should update merge request commits with new one if pushed to source branch" do
......
require 'spec_helper' require 'spec_helper'
describe IssueObserver do describe IssueObserver do
let(:some_user) { double(:user, id: 1) } let(:some_user) { create :user }
let(:assignee) { double(:user, id: 2) } let(:assignee) { create :user }
let(:author) { double(:user, id: 3) } let(:author) { create :user }
let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) } let(:mock_issue) { double(:issue, id: 42, assignee: assignee, author: author) }
let(:assigned_issue) { create(:issue, assignee: assignee, author: author) }
let(:unassigned_issue) { create(:issue, author: author) }
let(:closed_assigned_issue) { create(:closed_issue, assignee: assignee, author: author) }
let(:closed_unassigned_issue) { create(:closed_issue, author: author) }
before(:each) { subject.stub(:current_user).and_return(some_user) } before(:each) { subject.stub(:current_user).and_return(some_user) }
...@@ -21,137 +26,91 @@ describe IssueObserver do ...@@ -21,137 +26,91 @@ describe IssueObserver do
end end
it 'sends an email to the assignee' do it 'sends an email to the assignee' do
Notify.should_receive(:new_issue_email).with(issue.id) Notify.should_receive(:new_issue_email).with(mock_issue.id)
subject.after_create(issue) subject.after_create(mock_issue)
end end
it 'does not send an email to the assignee if assignee created the issue' do it 'does not send an email to the assignee if assignee created the issue' do
subject.stub(:current_user).and_return(assignee) subject.stub(:current_user).and_return(assignee)
Notify.should_not_receive(:new_issue_email) Notify.should_not_receive(:new_issue_email)
subject.after_create(issue) subject.after_create(mock_issue)
end end
end end
context '#after_update' do context '#after_close' do
before(:each) do
issue.stub(:is_being_reassigned?).and_return(false)
issue.stub(:is_being_closed?).and_return(false)
issue.stub(:is_being_reopened?).and_return(false)
end
it 'is called when an issue is changed' do
changed = create(:issue, project: create(:project))
subject.should_receive(:after_update)
Issue.observers.enable :issue_observer do
changed.description = 'I changed'
changed.save
end
end
context 'a reassigned email' do
it 'is sent if the issue is being reassigned' do
issue.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:send_reassigned_email).with(issue)
subject.after_update(issue)
end
it 'is not sent if the issue is not being reassigned' do
issue.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:send_reassigned_email)
subject.after_update(issue)
end
end
context 'a status "closed"' do context 'a status "closed"' do
it 'note is created if the issue is being closed' do it 'note is created if the issue is being closed' do
issue.should_receive(:is_being_closed?).and_return(true) Note.should_receive(:create_status_change_note).with(assigned_issue, some_user, 'closed')
Notify.should_receive(:issue_status_changed_email).twice
Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue)
end
it 'note is not created if the issue is not being closed' do
issue.should_receive(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue) assigned_issue.close
end end
it 'notification is delivered if the issue being closed' do it 'notification is delivered if the issue being closed' do
issue.stub(:is_being_closed?).and_return(true)
Notify.should_receive(:issue_status_changed_email).twice Notify.should_receive(:issue_status_changed_email).twice
Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue) assigned_issue.close
end
it 'notification is not delivered if the issue not being closed' do
issue.stub(:is_being_closed?).and_return(false)
Notify.should_not_receive(:issue_status_changed_email)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue)
end end
it 'notification is delivered only to author if the issue being closed' do it 'notification is delivered only to author if the issue being closed' do
issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
issue_without_assignee.stub(:is_being_closed?).and_return(true)
issue_without_assignee.stub(:is_being_reopened?).and_return(false)
Notify.should_receive(:issue_status_changed_email).once Notify.should_receive(:issue_status_changed_email).once
Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed') Note.should_receive(:create_status_change_note).with(unassigned_issue, some_user, 'closed')
subject.after_update(issue_without_assignee) unassigned_issue.close
end end
end end
context 'a status "reopened"' do context 'a status "reopened"' do
it 'note is created if the issue is being reopened' do it 'note is created if the issue is being reopened' do
Note.should_receive(:create_status_change_note).with(closed_assigned_issue, some_user, 'reopened')
closed_assigned_issue.reopen
end
it 'notification is delivered if the issue being reopened' do
Notify.should_receive(:issue_status_changed_email).twice Notify.should_receive(:issue_status_changed_email).twice
issue.should_receive(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
subject.after_update(issue) closed_assigned_issue.reopen
end end
it 'note is not created if the issue is not being reopened' do it 'notification is delivered only to author if the issue being reopened' do
issue.should_receive(:is_being_reopened?).and_return(false) Notify.should_receive(:issue_status_changed_email).once
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') Note.should_receive(:create_status_change_note).with(closed_unassigned_issue, some_user, 'reopened')
subject.after_update(issue) closed_unassigned_issue.reopen
end end
end
end
it 'notification is delivered if the issue being reopened' do context '#after_update' do
issue.stub(:is_being_reopened?).and_return(true) before(:each) do
Notify.should_receive(:issue_status_changed_email).twice mock_issue.stub(:is_being_reassigned?).and_return(false)
Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') end
it 'is called when an issue is changed' do
changed = create(:issue, project: create(:project))
subject.should_receive(:after_update)
subject.after_update(issue) Issue.observers.enable :issue_observer do
changed.description = 'I changed'
changed.save
end end
end
it 'notification is not delivered if the issue not being reopened' do context 'a reassigned email' do
issue.stub(:is_being_reopened?).and_return(false) it 'is sent if the issue is being reassigned' do
Notify.should_not_receive(:issue_status_changed_email) mock_issue.should_receive(:is_being_reassigned?).and_return(true)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') subject.should_receive(:send_reassigned_email).with(mock_issue)
subject.after_update(issue) subject.after_update(mock_issue)
end end
it 'notification is delivered only to author if the issue being reopened' do it 'is not sent if the issue is not being reassigned' do
issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil) mock_issue.should_receive(:is_being_reassigned?).and_return(false)
issue_without_assignee.stub(:is_being_reassigned?).and_return(false) subject.should_not_receive(:send_reassigned_email)
issue_without_assignee.stub(:is_being_closed?).and_return(false)
issue_without_assignee.stub(:is_being_reopened?).and_return(true)
Notify.should_receive(:issue_status_changed_email).once
Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened')
subject.after_update(issue_without_assignee) subject.after_update(mock_issue)
end end
end end
end end
...@@ -160,23 +119,23 @@ describe IssueObserver do ...@@ -160,23 +119,23 @@ describe IssueObserver do
let(:previous_assignee) { double(:user, id: 3) } let(:previous_assignee) { double(:user, id: 3) }
before(:each) do before(:each) do
issue.stub(:assignee_id).and_return(assignee.id) mock_issue.stub(:assignee_id).and_return(assignee.id)
issue.stub(:assignee_id_was).and_return(previous_assignee.id) mock_issue.stub(:assignee_id_was).and_return(previous_assignee.id)
end end
def it_sends_a_reassigned_email_to(recipient) def it_sends_a_reassigned_email_to(recipient)
Notify.should_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id) Notify.should_receive(:reassigned_issue_email).with(recipient, mock_issue.id, previous_assignee.id)
end end
def it_does_not_send_a_reassigned_email_to(recipient) def it_does_not_send_a_reassigned_email_to(recipient)
Notify.should_not_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id) Notify.should_not_receive(:reassigned_issue_email).with(recipient, mock_issue.id, previous_assignee.id)
end end
it 'sends a reassigned email to the previous and current assignees' do it 'sends a reassigned email to the previous and current assignees' do
it_sends_a_reassigned_email_to assignee.id it_sends_a_reassigned_email_to assignee.id
it_sends_a_reassigned_email_to previous_assignee.id it_sends_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, issue) subject.send(:send_reassigned_email, mock_issue)
end end
context 'does not send an email to the user who made the reassignment' do context 'does not send an email to the user who made the reassignment' do
...@@ -185,14 +144,14 @@ describe IssueObserver do ...@@ -185,14 +144,14 @@ describe IssueObserver do
it_sends_a_reassigned_email_to previous_assignee.id it_sends_a_reassigned_email_to previous_assignee.id
it_does_not_send_a_reassigned_email_to assignee.id it_does_not_send_a_reassigned_email_to assignee.id
subject.send(:send_reassigned_email, issue) subject.send(:send_reassigned_email, mock_issue)
end end
it 'if the user is the previous assignee' do it 'if the user is the previous assignee' do
subject.stub(:current_user).and_return(previous_assignee) subject.stub(:current_user).and_return(previous_assignee)
it_sends_a_reassigned_email_to assignee.id it_sends_a_reassigned_email_to assignee.id
it_does_not_send_a_reassigned_email_to previous_assignee.id it_does_not_send_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, issue) subject.send(:send_reassigned_email, mock_issue)
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe MergeRequestObserver do describe MergeRequestObserver do
let(:some_user) { double(:user, id: 1) } let(:some_user) { create :user }
let(:assignee) { double(:user, id: 2) } let(:assignee) { create :user }
let(:author) { double(:user, id: 3) } let(:author) { create :user }
let(:mr) { double(:merge_request, id: 42, assignee: assignee, author: author) } let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
let(:unassigned_mr) { create(:merge_request, author: author) }
let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
before(:each) { subject.stub(:current_user).and_return(some_user) } before(:each) { subject.stub(:current_user).and_return(some_user) }
...@@ -21,23 +25,21 @@ describe MergeRequestObserver do ...@@ -21,23 +25,21 @@ describe MergeRequestObserver do
end end
it 'sends an email to the assignee' do it 'sends an email to the assignee' do
Notify.should_receive(:new_merge_request_email).with(mr.id) Notify.should_receive(:new_merge_request_email).with(mr_mock.id)
subject.after_create(mr) subject.after_create(mr_mock)
end end
it 'does not send an email to the assignee if assignee created the merge request' do it 'does not send an email to the assignee if assignee created the merge request' do
subject.stub(:current_user).and_return(assignee) subject.stub(:current_user).and_return(assignee)
Notify.should_not_receive(:new_merge_request_email) Notify.should_not_receive(:new_merge_request_email)
subject.after_create(mr) subject.after_create(mr_mock)
end end
end end
context '#after_update' do context '#after_update' do
before(:each) do before(:each) do
mr.stub(:is_being_reassigned?).and_return(false) mr_mock.stub(:is_being_reassigned?).and_return(false)
mr.stub(:is_being_closed?).and_return(false)
mr.stub(:is_being_reopened?).and_return(false)
end end
it 'is called when a merge request is changed' do it 'is called when a merge request is changed' do
...@@ -52,97 +54,50 @@ describe MergeRequestObserver do ...@@ -52,97 +54,50 @@ describe MergeRequestObserver do
context 'a reassigned email' do context 'a reassigned email' do
it 'is sent if the merge request is being reassigned' do it 'is sent if the merge request is being reassigned' do
mr.should_receive(:is_being_reassigned?).and_return(true) mr_mock.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:send_reassigned_email).with(mr) subject.should_receive(:send_reassigned_email).with(mr_mock)
subject.after_update(mr) subject.after_update(mr_mock)
end end
it 'is not sent if the merge request is not being reassigned' do it 'is not sent if the merge request is not being reassigned' do
mr.should_receive(:is_being_reassigned?).and_return(false) mr_mock.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:send_reassigned_email) subject.should_not_receive(:send_reassigned_email)
subject.after_update(mr) subject.after_update(mr_mock)
end end
end end
end
context '#after_close' do
context 'a status "closed"' do context 'a status "closed"' do
it 'note is created if the merge request is being closed' do it 'note is created if the merge request is being closed' do
mr.should_receive(:is_being_closed?).and_return(true) Note.should_receive(:create_status_change_note).with(assigned_mr, some_user, 'closed')
Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'note is not created if the merge request is not being closed' do
mr.should_receive(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'notification is delivered if the merge request being closed' do
mr.stub(:is_being_closed?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'notification is not delivered if the merge request not being closed' do
mr.stub(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr) assigned_mr.close
end end
it 'notification is delivered only to author if the merge request is being closed' do it 'notification is delivered only to author if the merge request is being closed' do
mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil) Note.should_receive(:create_status_change_note).with(unassigned_mr, some_user, 'closed')
mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
mr_without_assignee.stub(:is_being_closed?).and_return(true)
mr_without_assignee.stub(:is_being_reopened?).and_return(false)
Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'closed')
subject.after_update(mr_without_assignee) unassigned_mr.close
end end
end end
end
context '#after_reopen' do
context 'a status "reopened"' do context 'a status "reopened"' do
it 'note is created if the merge request is being reopened' do it 'note is created if the merge request is being reopened' do
mr.should_receive(:is_being_reopened?).and_return(true) Note.should_receive(:create_status_change_note).with(closed_assigned_mr, some_user, 'reopened')
Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'note is not created if the merge request is not being reopened' do
mr.should_receive(:is_being_reopened?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'notification is delivered if the merge request being reopened' do
mr.stub(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'notification is not delivered if the merge request is not being reopened' do
mr.stub(:is_being_reopened?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr) closed_assigned_mr.reopen
end end
it 'notification is delivered only to author if the merge request is being reopened' do it 'notification is delivered only to author if the merge request is being reopened' do
mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil) Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, some_user, 'reopened')
mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
mr_without_assignee.stub(:is_being_closed?).and_return(false)
mr_without_assignee.stub(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'reopened')
subject.after_update(mr_without_assignee) closed_unassigned_mr.reopen
end end
end end
end end
...@@ -151,23 +106,23 @@ describe MergeRequestObserver do ...@@ -151,23 +106,23 @@ describe MergeRequestObserver do
let(:previous_assignee) { double(:user, id: 3) } let(:previous_assignee) { double(:user, id: 3) }
before(:each) do before(:each) do
mr.stub(:assignee_id).and_return(assignee.id) mr_mock.stub(:assignee_id).and_return(assignee.id)
mr.stub(:assignee_id_was).and_return(previous_assignee.id) mr_mock.stub(:assignee_id_was).and_return(previous_assignee.id)
end end
def it_sends_a_reassigned_email_to(recipient) def it_sends_a_reassigned_email_to(recipient)
Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id) Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
end end
def it_does_not_send_a_reassigned_email_to(recipient) def it_does_not_send_a_reassigned_email_to(recipient)
Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id) Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
end end
it 'sends a reassigned email to the previous and current assignees' do it 'sends a reassigned email to the previous and current assignees' do
it_sends_a_reassigned_email_to assignee.id it_sends_a_reassigned_email_to assignee.id
it_sends_a_reassigned_email_to previous_assignee.id it_sends_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, mr) subject.send(:send_reassigned_email, mr_mock)
end end
context 'does not send an email to the user who made the reassignment' do context 'does not send an email to the user who made the reassignment' do
...@@ -176,14 +131,14 @@ describe MergeRequestObserver do ...@@ -176,14 +131,14 @@ describe MergeRequestObserver do
it_sends_a_reassigned_email_to previous_assignee.id it_sends_a_reassigned_email_to previous_assignee.id
it_does_not_send_a_reassigned_email_to assignee.id it_does_not_send_a_reassigned_email_to assignee.id
subject.send(:send_reassigned_email, mr) subject.send(:send_reassigned_email, mr_mock)
end end
it 'if the user is the previous assignee' do it 'if the user is the previous assignee' do
subject.stub(:current_user).and_return(previous_assignee) subject.stub(:current_user).and_return(previous_assignee)
it_sends_a_reassigned_email_to assignee.id it_sends_a_reassigned_email_to assignee.id
it_does_not_send_a_reassigned_email_to previous_assignee.id it_does_not_send_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, mr) subject.send(:send_reassigned_email, mr_mock)
end end
end end
end end
......
...@@ -54,14 +54,24 @@ describe Gitlab::API do ...@@ -54,14 +54,24 @@ describe Gitlab::API do
end end
end end
describe "PUT /projects/:id/issues/:issue_id" do describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title', labels: 'label2', closed: 1 title: 'updated title'
response.status.should == 200 response.status.should == 200
json_response['title'].should == 'updated title' json_response['title'].should == 'updated title'
end
end
describe "PUT /projects/:id/issues/:issue_id to update state and label" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'label2', state_event: "close"
response.status.should == 200
json_response['labels'].should == ['label2'] json_response['labels'].should == ['label2']
json_response['closed'].should be_true json_response['state'].should eq "closed"
end end
end end
......
...@@ -66,6 +66,23 @@ describe Gitlab::API do ...@@ -66,6 +66,23 @@ describe Gitlab::API do
end end
end end
describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
response.status.should == 200
json_response['state'].should == 'closed'
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id to merge MR" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "merge"
response.status.should == 200
json_response['state'].should == 'merged'
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id" do describe "PUT /projects/:id/merge_request/:merge_request_id" do
it "should return merge_request" do it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title" put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
......
...@@ -68,4 +68,14 @@ describe Gitlab::API do ...@@ -68,4 +68,14 @@ describe Gitlab::API do
response.status.should == 404 response.status.should == 404
end end
end end
describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
it "should update a project milestone" do
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
state_event: 'close'
response.status.should == 200
json_response['state'].should == 'closed'
end
end
end end
...@@ -33,6 +33,20 @@ describe Gitlab::API do ...@@ -33,6 +33,20 @@ describe Gitlab::API do
end end
describe "POST /projects" do describe "POST /projects" do
context "maximum number of projects reached" do
before do
(1..user2.projects_limit).each do |project|
post api("/projects", user2), name: "foo#{project}"
end
end
it "should not create new project" do
expect {
post api("/projects", user2), name: 'foo'
}.to change {Project.count}.by(0)
end
end
it "should create new project without path" do it "should create new project without path" do
expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1)
end end
...@@ -46,6 +60,12 @@ describe Gitlab::API do ...@@ -46,6 +60,12 @@ describe Gitlab::API do
response.status.should == 400 response.status.should == 400
end end
it "should create last project before reaching project limit" do
(1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" }
post api("/projects", user2), name: "foo"
response.status.should == 201
end
it "should respond with 201 on success" do it "should respond with 201 on success" do
post api("/projects", user), name: 'foo' post api("/projects", user), name: 'foo'
response.status.should == 201 response.status.should == 201
...@@ -314,22 +334,44 @@ describe Gitlab::API do ...@@ -314,22 +334,44 @@ describe Gitlab::API do
end end
describe "GET /projects/:id/hooks" do describe "GET /projects/:id/hooks" do
it "should return project hooks" do context "authorized user" do
get api("/projects/#{project.id}/hooks", user) it "should return project hooks" do
get api("/projects/#{project.id}/hooks", user)
response.status.should == 200
response.status.should == 200 json_response.should be_an Array
json_response.count.should == 1
json_response.first['url'].should == "http://example.com"
end
end
json_response.should be_an Array context "unauthorized user" do
json_response.count.should == 1 it "should not access project hooks" do
json_response.first['url'].should == "http://example.com" get api("/projects/#{project.id}/hooks", user3)
response.status.should == 403
end
end end
end end
describe "GET /projects/:id/hooks/:hook_id" do describe "GET /projects/:id/hooks/:hook_id" do
it "should return a project hook" do context "authorized user" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user) it "should return a project hook" do
response.status.should == 200 get api("/projects/#{project.id}/hooks/#{hook.id}", user)
json_response['url'].should == hook.url response.status.should == 200
json_response['url'].should == hook.url
end
it "should return a 404 error if hook id is not available" do
get api("/projects/#{project.id}/hooks/1234", user)
response.status.should == 404
end
end
context "unauthorized user" do
it "should not access an existing hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
response.status.should == 403
end
end end
it "should return a 404 error if hook id is not available" do it "should return a 404 error if hook id is not available" do
......
...@@ -58,8 +58,7 @@ describe "Issues" do ...@@ -58,8 +58,7 @@ describe "Issues" do
it "should be able to search on different statuses" do it "should be able to search on different statuses" do
issue = Issue.first # with title 'foobar' issue = Issue.first # with title 'foobar'
issue.closed = true issue.close
issue.save
visit project_issues_path(project) visit project_issues_path(project)
click_link 'Closed' click_link 'Closed'
......
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