Commit 1a6353b3 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-stable-to-ee-stable' into '8-2-stable-ee'

Merge 8-2-stable into 8-2-stable-ee



See merge request !59
parents 834e440f 5918ba9b
...@@ -6,6 +6,10 @@ v 8.1.0 ...@@ -6,6 +6,10 @@ v 8.1.0
v 8.0.1 v 8.0.1
v 8.1.0 (unreleased) v 8.1.0 (unreleased)
v 8.2.0 (unreleased) v 8.2.0 (unreleased)
v 8.3.0 (unreleased)
v 8.2.0
- Fix grouping of contributors by email in graph.
- Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu)
- Fix Drone CI service template not saving properly (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu)
- Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu)
...@@ -37,6 +41,7 @@ v 8.2.0 (unreleased) ...@@ -37,6 +41,7 @@ v 8.2.0 (unreleased)
- Add "added", "modified" and "removed" properties to commit object in webhook - Add "added", "modified" and "removed" properties to commit object in webhook
- Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Rename "Back to" links to "Go to" because its not always a case it point to place user come from
- Allow groups to appear in the search results if the group owner allows it - Allow groups to appear in the search results if the group owner allows it
- Add email notification to former assignee upon unassignment (Adam Lieskovský)
- New design for project graphs page - New design for project graphs page
- Remove deprecated dumped yaml file generated from previous job definitions - Remove deprecated dumped yaml file generated from previous job definitions
- Fix incoming email config defaults - Fix incoming email config defaults
...@@ -48,6 +53,9 @@ v 8.2.0 (unreleased) ...@@ -48,6 +53,9 @@ v 8.2.0 (unreleased)
- Ability to add release notes (markdown text and attachments) to git tags (aka Releases) - Ability to add release notes (markdown text and attachments) to git tags (aka Releases)
- Relative links from a repositories README.md now link to the default branch - Relative links from a repositories README.md now link to the default branch
- Fix trailing whitespace issue in merge request/issue title - Fix trailing whitespace issue in merge request/issue title
- Fix bug when milestone/label filter was empty for dashboard issues page
- Add ability to create milestone in group projects from single form
- Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez)
v 8.1.4 v 8.1.4
- Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu)
......
...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic
## Security vulnerability disclosure ## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Closing policy for issues and merge requests ## Closing policy for issues and merge requests
...@@ -35,7 +35,7 @@ The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab ...@@ -35,7 +35,7 @@ The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab
Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple. Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](https://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
### Issue tracker guidelines ### Issue tracker guidelines
...@@ -72,7 +72,7 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -72,7 +72,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) 1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message 1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. If you have multiple commits please combine them into one commit by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork 1. Push the commit to your fork
1. Submit a merge request (MR) to the master branch 1. Submit a merge request (MR) to the master branch
1. The MR title should describe the change you want to make 1. The MR title should describe the change you want to make
...@@ -181,4 +181,4 @@ This code of conduct applies both within project spaces and in public spaces whe ...@@ -181,4 +181,4 @@ This code of conduct applies both within project spaces and in public spaces whe
Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/)
8.2.0.rc1-ee 8.2.0.rc1-ee
\ No newline at end of file
...@@ -28,6 +28,8 @@ class Dispatcher ...@@ -28,6 +28,8 @@ class Dispatcher
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.milestone-form')) new DropzoneInput($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
when 'projects:compare:show' when 'projects:compare:show'
new Diff() new Diff()
when 'projects:issues:new','projects:issues:edit' when 'projects:issues:new','projects:issues:edit'
......
...@@ -29,6 +29,7 @@ class @Notes ...@@ -29,6 +29,7 @@ class @Notes
$(document).on "ajax:success", "form.edit_note", @updateNote $(document).on "ajax:success", "form.edit_note", @updateNote
# Edit note link # Edit note link
$(document).on "click", ".js-note-edit", @showEditForm
$(document).on "click", ".note-edit-cancel", @cancelEdit $(document).on "click", ".note-edit-cancel", @cancelEdit
# Reopen and close actions for Issue/MR combined with note form submit # Reopen and close actions for Issue/MR combined with note form submit
...@@ -66,6 +67,7 @@ class @Notes ...@@ -66,6 +67,7 @@ class @Notes
$(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", ".js-discussion-note-form"
$(document).off "ajax:success", "form.edit_note" $(document).off "ajax:success", "form.edit_note"
$(document).off "click", ".js-note-edit"
$(document).off "click", ".note-edit-cancel" $(document).off "click", ".note-edit-cancel"
$(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-delete"
$(document).off "click", ".js-note-attachment-delete" $(document).off "click", ".js-note-attachment-delete"
...@@ -285,14 +287,13 @@ class @Notes ...@@ -285,14 +287,13 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels if the user cancels
### ###
showEditForm: (note, formHTML) -> showEditForm: (e) ->
nodeText = note.find(".note-text"); e.preventDefault()
nodeText.hide() note = $(this).closest(".note")
note.find('.note-edit-form').remove()
nodeText.after(formHTML)
note.find(".note-body > .note-text").hide() note.find(".note-body > .note-text").hide()
note.find(".note-header").hide() note.find(".note-header").hide()
form = note.find(".note-edit-form") base_form = note.find(".note-edit-form")
form = base_form.clone().insertAfter(base_form)
form.addClass('current-note-edit-form gfm-form') form.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove() form.find('.div-dropzone').remove()
......
...@@ -6,7 +6,7 @@ window.ContributorsStatGraphUtil = ...@@ -6,7 +6,7 @@ window.ContributorsStatGraphUtil =
for entry in log for entry in log
@add_date(entry.date, total) unless total[entry.date]? @add_date(entry.date, total) unless total[entry.date]?
data = by_author[entry.author_name] #|| by_email[entry.author_email] data = by_author[entry.author_name] || by_email[entry.author_email]
data ?= @add_author(entry, by_author, by_email) data ?= @add_author(entry, by_author, by_email)
@add_date(entry.date, data) unless data[entry.date] @add_date(entry.date, data) unless data[entry.date]
...@@ -95,5 +95,4 @@ window.ContributorsStatGraphUtil = ...@@ -95,5 +95,4 @@ window.ContributorsStatGraphUtil =
if date_range is null || date_range[0] <= new Date(date) <= date_range[1] if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
true true
else else
false false
\ No newline at end of file
module GlobalMilestones
extend ActiveSupport::Concern
def milestones
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end
def milestone
milestones = Milestone.of_projects(@projects).where(title: params[:title])
if milestones.present?
@milestone = GlobalMilestone.new(params[:title], milestones)
else
render_404
end
end
end
class Dashboard::MilestonesController < Dashboard::ApplicationController class Dashboard::MilestonesController < Dashboard::ApplicationController
before_action :load_projects include GlobalMilestones
before_action :projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show]
def index def index
project_milestones = case params[:state]
when 'all'; state
when 'closed'; state('closed')
else state('active')
end
@dashboard_milestones = Milestones::GroupService.new(project_milestones).execute
@dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(PER_PAGE)
end end
def show def show
project_milestones = Milestone.where(project_id: @projects).order("due_date ASC")
@dashboard_milestone = Milestones::GroupService.new(project_milestones).milestone(title)
end end
private private
def load_projects def projects
@projects = current_user.authorized_projects.sorted_by_activity.non_archived @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
def title
params[:title]
end
def state(state = nil)
conditions = { project_id: @projects }
conditions.reverse_merge!(state: state) if state
Milestone.where(conditions).order("title ASC")
end end
end end
class DashboardController < Dashboard::ApplicationController class DashboardController < Dashboard::ApplicationController
before_action :event_filter, only: :activity before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests]
respond_to :html respond_to :html
...@@ -47,4 +48,8 @@ class DashboardController < Dashboard::ApplicationController ...@@ -47,4 +48,8 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations @events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
end end
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end end
class Groups::ApplicationController < ApplicationController class Groups::ApplicationController < ApplicationController
layout 'group' layout 'group'
before_action :group
private private
......
class Groups::AvatarsController < ApplicationController class Groups::AvatarsController < Groups::ApplicationController
def destroy def destroy
@group = Group.find_by(path: params[:group_id])
@group.remove_avatar! @group.remove_avatar!
@group.save @group.save
redirect_to edit_group_path(@group) redirect_to edit_group_path(@group)
......
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
skip_before_action :authenticate_user!, only: [:index] skip_before_action :authenticate_user!, only: [:index]
before_action :group
# Authorize # Authorize
before_action :authorize_read_group! before_action :authorize_read_group!
before_action :authorize_admin_group!, except: [:index, :leave] before_action :authorize_admin_group_member!, except: [:index, :leave]
before_action :authorize_admin_group_member!, only: [:create, :resend_invite]
def index def index
@project = @group.projects.find(params[:project_id]) if params[:project_id] @project = @group.projects.find(params[:project_id]) if params[:project_id]
...@@ -18,7 +16,8 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -18,7 +16,8 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
@members = @members.order('access_level DESC').page(params[:page]).per(50) @members = @members.order('access_level DESC').page(params[:page]).per(50)
@group_member = GroupMember.new
@group_member = @group.group_members.new
end end
def create def create
...@@ -36,30 +35,28 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -36,30 +35,28 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def update def update
@member = @group.group_members.find(params[:id]) @group_member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :update_group_member, @member) return render_403 unless can?(current_user, :update_group_member, @group_member)
old_access_level = @member.human_access old_access_level = @group_member.human_access
if @member.update_attributes(member_params) if @group_member.update_attributes(member_params)
log_audit_event(@member, action: :update, old_access_level: old_access_level) log_audit_event(@group_member, action: :update, old_access_level: old_access_level)
end end
end end
def destroy def destroy
@group_member = @group.group_members.find(params[:id]) @group_member = @group.group_members.find(params[:id])
if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner. return render_403 unless can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
log_audit_event(@group_member, action: :destroy)
respond_to do |format| @group_member.destroy
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } log_audit_event(@group_member, action: :destroy)
format.js { render nothing: true }
end respond_to do |format|
else format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
return render_403 format.js { render nothing: true }
end end
end end
...@@ -78,7 +75,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -78,7 +75,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def leave def leave
@group_member = @group.group_members.where(user_id: current_user.id).first @group_member = @group.group_members.find_by(user_id: current_user)
if can?(current_user, :destroy_group_member, @group_member) if can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy @group_member.destroy
......
class Groups::MilestonesController < Groups::ApplicationController class Groups::MilestonesController < Groups::ApplicationController
before_action :authorize_group_milestone!, only: :update include GlobalMilestones
before_action :projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show, :update]
before_action :authorize_group_milestone!, only: [:create, :update]
def index def index
project_milestones = case params[:state]
when 'all'; state
when 'closed'; state('closed')
else state('active')
end
@group_milestones = Milestones::GroupService.new(project_milestones).execute
@group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE)
end end
def show def new
project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") @milestone = Milestone.new
@group_milestone = Milestones::GroupService.new(project_milestones).milestone(title)
end end
def update def create
project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") project_ids = params[:milestone][:project_ids]
@group_milestones = Milestones::GroupService.new(project_milestones).milestone(title) title = milestone_params[:title]
@group_milestones.milestones.each do |milestone| @group.projects.where(id: project_ids).each do |project|
Milestones::UpdateService.new(milestone.project, current_user, params[:milestone]).execute(milestone) Milestones::CreateService.new(project, current_user, milestone_params).execute
end end
respond_to do |format| redirect_to milestone_path(title)
format.js end
format.html do
redirect_to group_milestones_path(group) def show
end end
def update
@milestone.milestones.each do |milestone|
Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone)
end end
redirect_back_or_default(default: milestone_path(@milestone.title))
end end
private private
def group def authorize_group_milestone!
@group ||= Group.find_by(path: params[:group_id]) return render_404 unless can?(current_user, :admin_milestones, group)
end end
def title def milestone_params
params[:title] params.require(:milestone).permit(:title, :description, :due_date, :state_event)
end end
def state(state = nil) def milestone_path(title)
conditions = { project_id: group.projects } group_milestone_path(@group, title.parameterize, title: title)
conditions.reverse_merge!(state: state) if state
Milestone.where(conditions).order("title ASC")
end end
def authorize_group_milestone! def projects
return render_404 unless can?(current_user, :admin_group, group) @projects ||= @group.projects
end end
end end
...@@ -20,8 +20,8 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -20,8 +20,8 @@ class Projects::CompareController < Projects::ApplicationController
if compare_result if compare_result
@commits = Commit.decorate(compare_result.commits, @project) @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs @diffs = compare_result.diffs
@commit = @commits.last @commit = @project.commit(head_ref)
@first_commit = @commits.first @first_commit = @project.commit(base_ref)
@line_notes = [] @line_notes = []
end end
end end
......
...@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_read_note! before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy] before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :find_current_user_notes, except: [:destroy, :edit, :delete_attachment] before_action :find_current_user_notes, except: [:destroy, :delete_attachment]
def index def index
current_fetched_at = Time.now.to_i current_fetched_at = Time.now.to_i
...@@ -29,11 +29,6 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -29,11 +29,6 @@ class Projects::NotesController < Projects::ApplicationController
end end
end end
def edit
@note = note
render layout: false
end
def update def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
......
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project!, except: :leave before_action :authorize_admin_project_member!, except: :leave
def index def index
@project_members = @project.project_members @project_members = @project.project_members
...@@ -30,10 +30,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -30,10 +30,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project_group_links = @project.project_group_links @project_group_links = @project.project_group_links
end end
def new
@project_member = @project.project_members.new
end
def create def create
@project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user) @project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user)
members = @project.project_members.where(user_id: params[:user_ids].split(',')) members = @project.project_members.where(user_id: params[:user_ids].split(','))
...@@ -47,6 +43,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -47,6 +43,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def update def update
@project_member = @project.project_members.find(params[:id]) @project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :update_project_member, @project_member)
old_access_level = @project_member.human_access old_access_level = @project_member.human_access
if @project_member.update_attributes(member_params) if @project_member.update_attributes(member_params)
...@@ -56,7 +55,11 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -56,7 +55,11 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def destroy def destroy
@project_member = @project.project_members.find(params[:id]) @project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_project_member, @project_member)
@project_member.destroy @project_member.destroy
log_audit_event(@project_member, action: :destroy) log_audit_event(@project_member, action: :destroy)
respond_to do |format| respond_to do |format|
...@@ -82,18 +85,24 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -82,18 +85,24 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end end
def leave def leave
if @project.namespace == current_user.namespace
message = 'You can not leave your own project. Transfer or delete the project.'
return redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
end
@project_member = @project.project_members.find_by(user_id: current_user) @project_member = @project.project_members.find_by(user_id: current_user)
@project_member.destroy
log_audit_event(@project_member, action: :destroy)
respond_to do |format| if can?(current_user, :destroy_project_member, @project_member)
format.html { redirect_to dashboard_projects_path } @project_member.destroy
format.js { render nothing: true }
log_audit_event(@project_member, action: :destroy)
respond_to do |format|
format.html { redirect_to dashboard_projects_path, notice: "You left the project." }
format.js { render nothing: true }
end
else
if current_user == @project.owner
message = 'You can not leave your own project. Transfer or delete the project.'
redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
else
render_403
end
end end
end end
......
class MilestonesFinder
def execute(projects, params)
milestones = Milestone.of_projects(projects)
milestones = milestones.order("due_date ASC")
case params[:state]
when 'closed' then milestones.closed
when 'all' then milestones
else milestones.active
end
end
end
module DiffHelper module DiffHelper
def diff_view
params[:view] == 'parallel' ? 'parallel' : 'inline'
end
def allowed_diff_size def allowed_diff_size
if diff_hard_limit_enabled? if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_FILES Commit::DIFF_HARD_LIMIT_FILES
...@@ -137,7 +141,7 @@ module DiffHelper ...@@ -137,7 +141,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button # Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format) params_copy.delete(:format)
link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do link_to url_for(params_copy), id: "inline-diff-btn", class: (diff_view == 'inline' ? 'btn active' : 'btn') do
'Inline' 'Inline'
end end
end end
...@@ -148,7 +152,7 @@ module DiffHelper ...@@ -148,7 +152,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button # Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format) params_copy.delete(:format)
link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do link_to url_for(params_copy), id: "parallel-diff-btn", class: (diff_view == 'parallel' ? 'btn active' : 'btn') do
'Side-by-side' 'Side-by-side'
end end
end end
...@@ -171,7 +175,7 @@ module DiffHelper ...@@ -171,7 +175,7 @@ module DiffHelper
def commit_for_diff(diff) def commit_for_diff(diff)
if diff.deleted_file if diff.deleted_file
first_commit = @first_commit || @commit first_commit = @first_commit || @commit
first_commit.parent first_commit.parent || @first_commit
else else
@commit @commit
end end
......
...@@ -12,7 +12,7 @@ module GroupsHelper ...@@ -12,7 +12,7 @@ module GroupsHelper
end end
def should_user_see_group_roles?(user, group) def should_user_see_group_roles?(user, group)
if user if user && group
user.is_admin? || group.members.exists?(user_id: user.id) user.is_admin? || group.members.exists?(user_id: user.id)
else else
false false
......
...@@ -100,7 +100,7 @@ module LabelsHelper ...@@ -100,7 +100,7 @@ module LabelsHelper
Label.where(project_id: @projects) Label.where(project_id: @projects)
end end
grouped_labels = Labels::GroupService.new(labels).execute grouped_labels = GlobalLabel.build_collection(labels)
grouped_labels.unshift(Label::None) grouped_labels.unshift(Label::None)
grouped_labels.unshift(Label::Any) grouped_labels.unshift(Label::Any)
......
...@@ -28,7 +28,7 @@ module MilestonesHelper ...@@ -28,7 +28,7 @@ module MilestonesHelper
Milestone.where(project_id: @projects) Milestone.where(project_id: @projects)
end.active end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones.unshift(Milestone::None) grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any) grouped_milestones.unshift(Milestone::Any)
......
...@@ -5,17 +5,18 @@ class Ability ...@@ -5,17 +5,18 @@ class Ability
return [] unless user.kind_of?(User) return [] unless user.kind_of?(User)
return [] if user.blocked? return [] if user.blocked?
abilities = abilities =
case subject.class.name case subject.class.name
when "Project" then project_abilities(user, subject) when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(user, subject) when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(user, subject) when "Note" then note_abilities(user, subject)
when "ProjectSnippet" then project_snippet_abilities(user, subject) when "ProjectSnippet" then project_snippet_abilities(user, subject)
when "PersonalSnippet" then personal_snippet_abilities(user, subject) when "PersonalSnippet" then personal_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject) when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject) when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject)
when "GroupMember" then group_member_abilities(user, subject) when "GroupMember" then group_member_abilities(user, subject)
when "ProjectMember" then project_member_abilities(user, subject)
else [] else []
end end
...@@ -247,21 +248,22 @@ class Ability ...@@ -247,21 +248,22 @@ class Ability
# Only group masters and group owners can create new projects in group # Only group masters and group owners can create new projects in group
if group.has_master?(user) || group.has_owner?(user) || user.admin? if group.has_master?(user) || group.has_owner?(user) || user.admin?
rules.push(*[ rules += [
:create_projects, :create_projects,
]) :admin_milestones
]
end end
# Only group owner and administrators can admin group # Only group owner and administrators can admin group
if group.has_owner?(user) || user.admin? if group.has_owner?(user) || user.admin?
rules.push(*[ rules += [
:admin_group, :admin_group,
:admin_namespace, :admin_namespace,
:admin_group_member :admin_group_member
]) ]
unless group.ldap_synced? if group.ldap_synced?
rules << :admin_group_member rules.delete(:admin_group_member)
end end
end end
...@@ -273,16 +275,15 @@ class Ability ...@@ -273,16 +275,15 @@ class Ability
# Only namespace owner and administrators can admin it # Only namespace owner and administrators can admin it
if namespace.owner == user || user.admin? if namespace.owner == user || user.admin?
rules.push(*[ rules += [
:create_projects, :create_projects,
:admin_namespace :admin_namespace
]) ]
end end
rules.flatten rules.flatten
end end
[:issue, :merge_request].each do |name| [:issue, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject| define_method "#{name}_abilities" do |user, subject|
rules = [] rules = []
...@@ -323,15 +324,39 @@ class Ability ...@@ -323,15 +324,39 @@ class Ability
rules = [] rules = []
target_user = subject.user target_user = subject.user
group = subject.group group = subject.group
can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && (user != target_user) unless group.last_owner?(target_user)
rules << :update_group_member can_manage = group_abilities(user, group).include?(:admin_group_member)
rules << :destroy_group_member
if can_manage && user != target_user
rules << :update_group_member
rules << :destroy_group_member
end
if user == target_user
rules << :destroy_group_member
end
end end
if !group.last_owner?(user) && (can_manage || (user == target_user)) rules
rules << :destroy_group_member end
def project_member_abilities(user, subject)
rules = []
target_user = subject.user
project = subject.project
unless target_user == project.owner
can_manage = project_abilities(user, project).include?(:admin_project_member)
if can_manage && user != target_user
rules << :update_project_member
rules << :destroy_project_member
end
if user == target_user
rules << :destroy_project_member
end
end end
rules rules
...@@ -339,10 +364,10 @@ class Ability ...@@ -339,10 +364,10 @@ class Ability
def abilities def abilities
@abilities ||= begin @abilities ||= begin
abilities = Six.new abilities = Six.new
abilities << self abilities << self
abilities abilities
end end
end end
private private
......
class GroupLabel class GlobalLabel
attr_accessor :title, :labels attr_accessor :title, :labels
alias_attribute :name, :title alias_attribute :name, :title
def self.build_collection(labels)
labels = labels.group_by(&:title)
labels.map do |title, label|
new(title, label)
end
end
def initialize(title, labels) def initialize(title, labels)
@title = title @title = title
@labels = labels @labels = labels
......
class GroupMilestone class GlobalMilestone
attr_accessor :title, :milestones attr_accessor :title, :milestones
alias_attribute :name, :title alias_attribute :name, :title
def self.build_collection(milestones)
milestones = milestones.group_by(&:title)
milestones.map do |title, milestones|
new(title, milestones)
end
end
def initialize(title, milestones) def initialize(title, milestones)
@title = title @title = title
@milestones = milestones @milestones = milestones
...@@ -10,7 +18,7 @@ class GroupMilestone ...@@ -10,7 +18,7 @@ class GroupMilestone
def safe_title def safe_title
@title.parameterize @title.parameterize
end end
def projects def projects
milestones.map { |milestone| milestone.project } milestones.map { |milestone| milestone.project }
end end
...@@ -60,15 +68,15 @@ class GroupMilestone ...@@ -60,15 +68,15 @@ class GroupMilestone
end end
def issues def issues
@group_issues ||= milestones.map(&:issues).flatten.group_by(&:state) @issues ||= milestones.map(&:issues).flatten.group_by(&:state)
end end
def merge_requests def merge_requests
@group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
end end
def participants def participants
@group_participants ||= milestones.map(&:participants).flatten.compact.uniq @participants ||= milestones.map(&:participants).flatten.compact.uniq
end end
def opened_issues def opened_issues
...@@ -86,4 +94,8 @@ class GroupMilestone ...@@ -86,4 +94,8 @@ class GroupMilestone
def closed_merge_requests def closed_merge_requests
merge_requests.values_at("closed", "merged", "locked").compact.flatten merge_requests.values_at("closed", "merged", "locked").compact.flatten
end end
def complete?
total_items_count == closed_items_count
end
end end
...@@ -20,8 +20,9 @@ require 'file_size_validator' ...@@ -20,8 +20,9 @@ require 'file_size_validator'
class Group < Namespace class Group < Namespace
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Referable include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
alias_method :members, :group_members
has_many :users, through: :group_members has_many :users, through: :group_members
has_many :project_group_links, dependent: :destroy has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project has_many :shared_projects, through: :project_group_links, source: :project
...@@ -114,10 +115,6 @@ class Group < Namespace ...@@ -114,10 +115,6 @@ class Group < Namespace
has_owner?(user) && owners.size == 1 has_owner?(user) && owners.size == 1
end end
def members
group_members
end
def avatar_type def avatar_type
unless self.avatar.image? unless self.avatar.image?
self.errors.add :avatar, "only images allowed" self.errors.add :avatar, "only images allowed"
......
...@@ -31,13 +31,22 @@ class Member < ActiveRecord::Base ...@@ -31,13 +31,22 @@ class Member < ActiveRecord::Base
validates :user, presence: true, unless: :invite? validates :user, presence: true, unless: :invite?
validates :source, presence: true validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source", message: "already exists in source",
allow_nil: true } allow_nil: true }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :invite_email, presence: { if: :invite? }, validates :invite_email,
email: { strict_mode: true, allow_nil: true }, presence: {
uniqueness: { scope: [:source_type, :source_id], allow_nil: true } if: :invite?
},
email: {
strict_mode: true,
allow_nil: true
},
uniqueness: {
scope: [:source_type, :source_id],
allow_nil: true
}
scope :invite, -> { where(user_id: nil) } scope :invite, -> { where(user_id: nil) }
scope :non_invite, -> { where("user_id IS NOT NULL") } scope :non_invite, -> { where("user_id IS NOT NULL") }
...@@ -74,7 +83,7 @@ class Member < ActiveRecord::Base ...@@ -74,7 +83,7 @@ class Member < ActiveRecord::Base
def add_user(members, user_id, access_level, current_user = nil, skip_notification: false) def add_user(members, user_id, access_level, current_user = nil, skip_notification: false)
user = user_for_id(user_id) user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited # `user` can be either a User object or an email to be invited
if user.is_a?(User) if user.is_a?(User)
member = members.find_or_initialize_by(user_id: user.id) member = members.find_or_initialize_by(user_id: user.id)
...@@ -83,12 +92,23 @@ class Member < ActiveRecord::Base ...@@ -83,12 +92,23 @@ class Member < ActiveRecord::Base
member.invite_email = user member.invite_email = user
end end
member.created_by ||= current_user if can_update_member?(current_user, member)
member.access_level = access_level member.created_by ||= current_user
member.access_level = access_level
member.skip_notification = skip_notification
member.save
end
end
member.skip_notification = skip_notification private
member.save def can_update_member?(current_user, member)
# There is no current user for bulk actions, in which case anything is allowed
!current_user ||
current_user.can?(:update_group_member, member) ||
current_user.can?(:update_project_member, member)
end end
end end
...@@ -98,7 +118,7 @@ class Member < ActiveRecord::Base ...@@ -98,7 +118,7 @@ class Member < ActiveRecord::Base
def accept_invite!(new_user) def accept_invite!(new_user)
return false unless invite? return false unless invite?
self.invite_token = nil self.invite_token = nil
self.invite_accepted_at = Time.now.utc self.invite_accepted_at = Time.now.utc
......
...@@ -86,6 +86,8 @@ class ProjectWiki ...@@ -86,6 +86,8 @@ class ProjectWiki
commit = commit_details(:created, message, title) commit = commit_details(:created, message, title)
wiki.write_page(title, format, content, commit) wiki.write_page(title, format, content, commit)
update_project_activity
rescue Gollum::DuplicatePageError => e rescue Gollum::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}" @error_message = "Duplicate page: #{e.message}"
return false return false
...@@ -95,10 +97,14 @@ class ProjectWiki ...@@ -95,10 +97,14 @@ class ProjectWiki
commit = commit_details(:updated, message, page.title) commit = commit_details(:updated, message, page.title)
wiki.update_page(page, page.name, format, content, commit) wiki.update_page(page, page.name, format, content, commit)
update_project_activity
end end
def delete_page(page, message = nil) def delete_page(page, message = nil)
wiki.delete_page(page, commit_details(:deleted, message, page.title)) wiki.delete_page(page, commit_details(:deleted, message, page.title))
update_project_activity
end end
def page_title_and_dir(title) def page_title_and_dir(title)
...@@ -146,4 +152,8 @@ class ProjectWiki ...@@ -146,4 +152,8 @@ class ProjectWiki
def path_to_repo def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
end end
def update_project_activity
@project.touch(:last_activity_at)
end
end end
module Labels
class GroupService < ::BaseService
def initialize(project_labels)
@project_labels = project_labels.group_by(&:title)
end
def execute
build(@project_labels)
end
def label(title)
if title
group_label = @project_labels[title].group_by(&:title)
build(group_label).first
else
nil
end
end
private
def build(label)
label.map { |title, labels| GroupLabel.new(title, labels) }
end
end
end
module Milestones
class GroupService < Milestones::BaseService
def initialize(project_milestones)
@project_milestones = project_milestones.group_by(&:title)
end
def execute
build(@project_milestones)
end
def milestone(title)
if title
group_milestone = @project_milestones[title].group_by(&:title)
build(group_milestone).first
else
nil
end
end
private
def build(milestone)
milestone.map{ |title, milestones| GroupMilestone.new(title, milestones) }
end
end
end
...@@ -361,11 +361,13 @@ class NotificationService ...@@ -361,11 +361,13 @@ class NotificationService
end end
def reassign_resource_email(target, project, current_user, method) def reassign_resource_email(target, project, current_user, method)
assignee_id_was = previous_record(target, "assignee_id") previous_assignee_id = previous_record(target, "assignee_id")
recipients = build_recipients(target, project, current_user) previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
recipients = build_recipients(target, project, current_user, [previous_assignee])
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, assignee_id_was, current_user.id) mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id)
end end
end end
...@@ -377,9 +379,11 @@ class NotificationService ...@@ -377,9 +379,11 @@ class NotificationService
end end
end end
def build_recipients(target, project, current_user) def build_recipients(target, project, current_user, extra_recipients = nil)
recipients = target.participants(current_user) recipients = target.participants(current_user)
recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients
recipients = add_project_watchers(recipients, project) recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project) recipients = reject_mention_users(recipients, project)
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
......
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
- unless user.linkedin.blank? - unless user.linkedin.blank?
%li %li
%span.light LinkedIn: %span.light LinkedIn:
%strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}" %strong= link_to user.linkedin, "https://www.linkedin.com/in/#{user.linkedin}"
- unless user.twitter.blank? - unless user.twitter.blank?
%li %li
%span.light Twitter: %span.light Twitter:
%strong= link_to user.twitter, "http://www.twitter.com/#{user.twitter}" %strong= link_to user.twitter, "https://twitter.com/#{user.twitter}"
- unless user.website_url.blank? - unless user.website_url.blank?
%li %li
%span.light Website: %span.light Website:
......
...@@ -10,10 +10,10 @@ ...@@ -10,10 +10,10 @@
.milestones .milestones
%ul.content-list %ul.content-list
- if @dashboard_milestones.blank? - if @milestones.blank?
%li %li
.nothing-here-block No milestones to show .nothing-here-block No milestones to show
- else - else
- @dashboard_milestones.each do |milestone| - @milestones.each do |milestone|
= render 'milestone', milestone: milestone = render 'milestone', milestone: milestone
= paginate @dashboard_milestones, theme: "gitlab" = paginate @milestones, theme: "gitlab"
- page_title @dashboard_milestone.title, "Milestones" - page_title @milestone.title, "Milestones"
%h4.page-title %h4.page-title
.issue-box{ class: "issue-box-#{@dashboard_milestone.closed? ? 'closed' : 'open'}" } .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @dashboard_milestone.closed? - if @milestone.closed?
Closed Closed
- else - else
Open Open
Milestone #{@dashboard_milestone.title} Milestone #{@milestone.title}
%hr %hr
- if (@dashboard_milestone.total_items_count == @dashboard_milestone.closed_items_count) && @dashboard_milestone.active? - if @milestone.complete? && @milestone.active?
.alert.alert-success .alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now. %span All issues for this milestone are closed. You may close the milestone now.
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
%th Open issues %th Open issues
%th State %th State
%th Due date %th Due date
- @dashboard_milestone.milestones.each do |milestone| - @milestone.milestones.each do |milestone|
%tr %tr
%td %td
= link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
...@@ -39,46 +39,46 @@ ...@@ -39,46 +39,46 @@
.context .context
%p.lead %p.lead
Progress: Progress:
#{@dashboard_milestone.closed_items_count} closed #{@milestone.closed_items_count} closed
&ndash; &ndash;
#{@dashboard_milestone.open_items_count} open #{@milestone.open_items_count} open
= milestone_progress_bar(@dashboard_milestone) = milestone_progress_bar(@milestone)
%ul.nav.nav-tabs %ul.nav.nav-tabs
%li.active %li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do = link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues Issues
%span.badge= @dashboard_milestone.issue_count %span.badge= @milestone.issue_count
%li %li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests Merge Requests
%span.badge= @dashboard_milestone.merge_requests_count %span.badge= @milestone.merge_requests_count
%li %li
= link_to '#tab-participants', 'data-toggle' => 'tab' do = link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants Participants
%span.badge= @dashboard_milestone.participants.count %span.badge= @milestone.participants.count
.pull-right .pull-right
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @dashboard_milestone.title), class: "btn edit-milestone-link btn-grouped" = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
.tab-content .tab-content
.tab-pane.active#tab-issues .tab-pane.active#tab-issues
.row .row
.col-md-6 .col-md-6
= render 'issues', title: "Open", issues: @dashboard_milestone.opened_issues = render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6 .col-md-6
= render 'issues', title: "Closed", issues: @dashboard_milestone.closed_issues = render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests .tab-pane#tab-merge-requests
.row .row
.col-md-6 .col-md-6
= render 'merge_requests', title: "Open", merge_requests: @dashboard_milestone.opened_merge_requests = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6 .col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @dashboard_milestone.closed_merge_requests = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants .tab-pane#tab-participants
%ul.bordered-list %ul.bordered-list
- @dashboard_milestone.participants.each do |user| - @milestone.participants.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32" = image_tag avatar_icon(user, 32), class: "avatar s32"
......
- user = member.user - user = member.user
- return unless user || member.invite? - return unless user || member.invite?
- show_roles = true if show_roles.nil?
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)} %span{class: ("list-item-name" if show_controls)}
...@@ -25,11 +24,11 @@ ...@@ -25,11 +24,11 @@
= link_to member.created_by.name, user_path(member.created_by) = link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at) = time_ago_with_tooltip(member.created_at)
- if show_controls && can?(current_user, :admin_group_member, member) - if show_controls && can?(current_user, :admin_group_member, @group)
= link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite Resend invite
- if show_roles - if should_user_see_group_roles?(current_user, @group)
%span.pull-right %span.pull-right
%strong= member.human_access %strong= member.human_access
- if show_controls - if show_controls
...@@ -37,6 +36,7 @@ ...@@ -37,6 +36,7 @@
= button_tag class: "btn-xs btn js-toggle-button", = button_tag class: "btn-xs btn js-toggle-button",
title: 'Edit access level', type: 'button' do title: 'Edit access level', type: 'button' do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
- if can?(current_user, :destroy_group_member, member) - if can?(current_user, :destroy_group_member, member)
&nbsp; &nbsp;
- if current_user == user - if current_user == user
......
- page_title "Members" - page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group)) - header_title group_title(@group, "Members", group_group_members_path(@group))
- show_roles = should_user_see_group_roles?(current_user, @group) - if should_user_see_group_roles?(current_user, @group)
- if show_roles
%p.light %p.light
Members of group have access to all group projects. Members of group have access to all group projects.
Read more about permissions Read more about permissions
...@@ -49,7 +47,7 @@ ...@@ -49,7 +47,7 @@
(#{@members.total_count}) (#{@members.total_count})
%ul.well-list %ul.well-list
- @members.each do |member| - @members.each do |member|
= render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true = render 'groups/group_members/group_member', member: member, show_controls: true
= paginate @members, theme: 'gitlab' = paginate @members, theme: 'gitlab'
......
:plain :plain
$("##{dom_id(@member)}").replaceWith('#{escape_javascript(render(@member, member: @member, show_controls: true))}'); $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member, show_controls: true))}');
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
%span.label.label-gray %span.label.label-gray
= milestone.project.name = milestone.project.name
.col-sm-6 .col-sm-6
- if can?(current_user, :admin_group, @group) - if can?(current_user, :admin_milestones, @group)
- if milestone.closed? - if milestone.closed?
= link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen" = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
- else - else
......
...@@ -3,15 +3,22 @@ ...@@ -3,15 +3,22 @@
= render 'shared/milestones_filter' = render 'shared/milestones_filter'
.gray-content-block .gray-content-block
Only milestones from - if can?(current_user, :admin_milestones, @group)
%strong #{@group.name} .pull-right
group are listed here. %span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
New Milestone
.oneline
Only milestones from
%strong #{@group.name}
group are listed here.
.milestones .milestones
%ul.content-list %ul.content-list
- if @group_milestones.blank? - if @milestones.blank?
%li %li
.nothing-here-block No milestones to show .nothing-here-block No milestones to show
- else - else
- @group_milestones.each do |milestone| - @milestones.each do |milestone|
= render 'milestone', milestone: milestone = render 'milestone', milestone: milestone
= paginate @group_milestones, theme: "gitlab" = paginate @milestones, theme: "gitlab"
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
%h3.page-title
New Milestone
%p.light
This will create milestone in every selected project
%hr
= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f|
.row
.col-md-6
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
%p.hint Required
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
.clearfix
.error-alert
.form-group
= f.label :projects, "Projects", class: "control-label"
.col-sm-10
= f.collection_select :project_ids, @group.projects, :id, :name,
{ selected: @group.projects.map(&:id) }, multiple: true, class: 'select2'
.col-md-6
.form-group
= f.label :due_date, "Due Date", class: "control-label"
.col-sm-10= f.hidden_field :due_date
.col-sm-10
.datepicker
.form-actions
= f.submit 'Create Milestone', class: "btn-create btn"
= link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
:javascript
$(".datepicker").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
- page_title @group_milestone.title, "Milestones" - page_title @milestone.title, "Milestones"
= render "header_title" = render "header_title"
%h4.page-title %h4.page-title
.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @group_milestone.closed? - if @milestone.closed?
Closed Closed
- else - else
Open Open
Milestone #{@group_milestone.title} Milestone #{@milestone.title}
.pull-right .pull-right
- if can?(current_user, :admin_group, @group) - if can?(current_user, :admin_milestones, @group)
- if @group_milestone.active? - if @milestone.active?
= link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
- else - else
= link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
%hr %hr
- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active? - if @milestone.complete? && @milestone.active?
.alert.alert-success .alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now. %span All issues for this milestone are closed. You may close the milestone now.
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%th Open issues %th Open issues
%th State %th State
%th Due date %th Due date
- @group_milestone.milestones.each do |milestone| - @milestone.milestones.each do |milestone|
%tr %tr
%td %td
= link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
...@@ -47,46 +47,46 @@ ...@@ -47,46 +47,46 @@
.context .context
%p.lead %p.lead
Progress: Progress:
#{@group_milestone.closed_items_count} closed #{@milestone.closed_items_count} closed
&ndash; &ndash;
#{@group_milestone.open_items_count} open #{@milestone.open_items_count} open
= milestone_progress_bar(@group_milestone) = milestone_progress_bar(@milestone)
%ul.nav.nav-tabs %ul.nav.nav-tabs
%li.active %li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do = link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues Issues
%span.badge= @group_milestone.issue_count %span.badge= @milestone.issue_count
%li %li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests Merge Requests
%span.badge= @group_milestone.merge_requests_count %span.badge= @milestone.merge_requests_count
%li %li
= link_to '#tab-participants', 'data-toggle' => 'tab' do = link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants Participants
%span.badge= @group_milestone.participants.count %span.badge= @milestone.participants.count
.pull-right .pull-right
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @group_milestone.title), class: "btn edit-milestone-link btn-grouped" = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
.tab-content .tab-content
.tab-pane.active#tab-issues .tab-pane.active#tab-issues
.row .row
.col-md-6 .col-md-6
= render 'issues', title: "Open", issues: @group_milestone.opened_issues = render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6 .col-md-6
= render 'issues', title: "Closed", issues: @group_milestone.closed_issues = render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests .tab-pane#tab-merge-requests
.row .row
.col-md-6 .col-md-6
= render 'merge_requests', title: "Open", merge_requests: @group_milestone.opened_merge_requests = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6 .col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @group_milestone.closed_merge_requests = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants .tab-pane#tab-participants
%ul.bordered-list %ul.bordered-list
- @group_milestone.participants.each do |user| - @milestone.participants.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32" = image_tag avatar_icon(user, 32), class: "avatar s32"
......
Project #{@old_path_with_namespace} was moved to another location Project <%= @old_path_with_namespace %> was moved to another location
The project is now located under The project is now located under
<%= namespace_project_url(@project.namespace, @project) %> <%= namespace_project_url(@project.namespace, @project) %>
......
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
= render "commit_box" = render "commit_box"
= render "ci_menu" if @ci_commit = render "ci_menu" if @ci_commit
= render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form", view: params[:view] = render "projects/notes/notes_with_form"
- if params[:view] == 'parallel' - if diff_view == 'parallel'
- fluid_layout true - fluid_layout true
- diff_files = safe_diff_files(diffs) - diff_files = safe_diff_files(diffs)
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
-# Skipp all non non-supported blobs -# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?') - return unless blob.respond_to?('text?')
- if blob.text? - if blob.text?
- if params[:view] == 'parallel' - if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else - else
= render "projects/diffs/text_file", diff_file: diff_file, index: i = render "projects/diffs/text_file", diff_file: diff_file, index: i
...@@ -42,4 +42,3 @@ ...@@ -42,4 +42,3 @@
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
- else - else
.nothing-here-block No preview for this file type .nothing-here-block No preview for this file type
...@@ -23,9 +23,7 @@ ...@@ -23,9 +23,7 @@
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
.hint = render 'projects/notes/hints'
.pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
.pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
.clearfix .clearfix
.error-alert .error-alert
.col-md-6 .col-md-6
...@@ -45,7 +43,7 @@ ...@@ -45,7 +43,7 @@
:javascript :javascript
$( ".datepicker" ).datepicker({ $(".datepicker").datepicker({
dateFormat: "yy-mm-dd", dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f| = form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
= hidden_field_tag :view, params[:view] = hidden_field_tag :view, diff_view
= hidden_field_tag :line_type = hidden_field_tag :line_type
= note_target_fields(@note) = note_target_fields(@note)
= f.hidden_field :commit_id = f.hidden_field :commit_id
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.note-header .note-header
- if note_editable?(note) - if note_editable?(note)
.note-actions .note-actions
= link_to edit_namespace_project_note_path(note.project.namespace, note.project, note), title: 'Edit comment', remote: true, class: 'js-note-edit' do = link_to '#', title: 'Edit comment', class: 'js-note-edit' do
= icon('pencil-square-o') = icon('pencil-square-o')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
.note-text .note-text
= preserve do = preserve do
= markdown(note.note, {no_header_anchors: true}) = markdown(note.note, {no_header_anchors: true})
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
- if note.attachment.url - if note.attachment.url
.note-attachment .note-attachment
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.js-main-target-form .js-main-target-form
- if can? current_user, :create_note, @project - if can? current_user, :create_note, @project
= render "projects/notes/form", view: params[:view] = render "projects/notes/form", view: diff_view
:javascript :javascript
window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
$note = $('.note-row-<%= @note.id %>:visible');
_notes.showEditForm($note, '<%= escape_javascript(render('edit_form', note: @note)) %>');
...@@ -24,18 +24,19 @@ ...@@ -24,18 +24,19 @@
= link_to member.created_by.name, user_path(member.created_by) = link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at) = time_ago_with_tooltip(member.created_at)
- if current_user_can_admin_project - if can?(current_user, :admin_project_member, @project)
= link_to resend_invite_namespace_project_project_member_path(@project.namespace, @project, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do = link_to resend_invite_namespace_project_project_member_path(@project.namespace, @project, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite Resend invite
- if current_user_can_admin_project - if can?(current_user, :admin_project_member, @project)
- unless @project.personal? && user == current_user .pull-right
.pull-right %strong= member.human_access
%strong= member.human_access - if can?(current_user, :update_project_member, member)
= button_tag class: "btn-xs btn js-toggle-button", = button_tag class: "btn-xs btn js-toggle-button",
title: 'Edit access level', type: 'button' do title: 'Edit access level', type: 'button' do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
- if can?(current_user, :destroy_project_member, member)
&nbsp; &nbsp;
- if current_user == user - if current_user == user
= link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
......
- can_admin_project = can?(current_user, :admin_project, @project)
.panel.panel-default.prepend-top-20 .panel.panel-default.prepend-top-20
.panel-heading .panel-heading
%strong #{@project.name} %strong #{@project.name}
...@@ -8,4 +6,4 @@ ...@@ -8,4 +6,4 @@
(#{members.count}) (#{members.count})
%ul.well-list %ul.well-list
- members.each do |project_member| - members.each do |project_member|
= render 'project_member', member: project_member, current_user_can_admin_project: can_admin_project = render 'project_member', member: project_member
- can_admin_project = can?(current_user, :admin_project, @project)
:plain :plain
$("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member, current_user_can_admin_project: can_admin_project))}'); $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member))}');
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
The import will time out after 4 minutes. For big repositories, use a clone/push combination. The import will time out after 4 minutes. For big repositories, use a clone/push combination.
%li %li
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
......
...@@ -32,11 +32,11 @@ ...@@ -32,11 +32,11 @@
= icon('skype') = icon('skype')
- unless @user.linkedin.blank? - unless @user.linkedin.blank?
.profile-link-holder .profile-link-holder
= link_to "http://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do
= icon('linkedin-square') = icon('linkedin-square')
- unless @user.twitter.blank? - unless @user.twitter.blank?
.profile-link-holder .profile-link-holder
= link_to "http://www.twitter.com/#{@user.twitter}", title: "Twitter" do = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do
= icon('twitter-square') = icon('twitter-square')
- unless @user.website_url.blank? - unless @user.website_url.blank?
.profile-link-holder .profile-link-holder
......
...@@ -398,7 +398,7 @@ Gitlab::Application.routes.draw do ...@@ -398,7 +398,7 @@ Gitlab::Application.routes.draw do
end end
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
resources :milestones, only: [:index, :show, :update] resources :milestones, only: [:index, :show, :update, :new, :create]
end end
get "/audit_events" => "audit_events#group_log" get "/audit_events" => "audit_events#group_log"
...@@ -712,7 +712,7 @@ Gitlab::Application.routes.draw do ...@@ -712,7 +712,7 @@ Gitlab::Application.routes.draw do
resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ }
resources :notes, constraints: { id: /\d+/ } do resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
member do member do
delete :delete_attachment delete :delete_attachment
end end
......
class AddUniqueForLfsOidIndex < ActiveRecord::Migration
def change
remove_index :lfs_objects, :oid
remove_index :lfs_objects, [:oid, :size]
add_index :lfs_objects, :oid, unique: true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151114113410) do ActiveRecord::Schema.define(version: 20151116144118) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -48,9 +48,9 @@ ActiveRecord::Schema.define(version: 20151114113410) do ...@@ -48,9 +48,9 @@ ActiveRecord::Schema.define(version: 20151114113410) do
t.boolean "twitter_sharing_enabled", default: true t.boolean "twitter_sharing_enabled", default: true
t.text "help_text" t.text "help_text"
t.text "restricted_visibility_levels" t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility" t.integer "default_project_visibility"
t.boolean "version_check_enabled", default: true
t.integer "default_snippet_visibility" t.integer "default_snippet_visibility"
t.text "restricted_signup_domains" t.text "restricted_signup_domains"
t.boolean "user_oauth_applications", default: true t.boolean "user_oauth_applications", default: true
...@@ -491,8 +491,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do ...@@ -491,8 +491,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do
t.string "file" t.string "file"
end end
add_index "lfs_objects", ["oid", "size"], name: "index_lfs_objects_on_oid_and_size", using: :btree add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", using: :btree
create_table "lfs_objects_projects", force: true do |t| create_table "lfs_objects_projects", force: true do |t|
t.integer "lfs_object_id", null: false t.integer "lfs_object_id", null: false
...@@ -715,21 +714,21 @@ ActiveRecord::Schema.define(version: 20151114113410) do ...@@ -715,21 +714,21 @@ ActiveRecord::Schema.define(version: 20151114113410) do
t.string "avatar" t.string "avatar"
t.string "import_status" t.string "import_status"
t.float "repository_size", default: 0.0 t.float "repository_size", default: 0.0
t.text "merge_requests_template"
t.integer "star_count", default: 0, null: false t.integer "star_count", default: 0, null: false
t.boolean "merge_requests_rebase_enabled", default: false
t.string "import_type" t.string "import_type"
t.string "import_source" t.string "import_source"
t.text "merge_requests_template"
t.boolean "merge_requests_rebase_enabled", default: false
t.integer "commit_count", default: 0
t.integer "approvals_before_merge", default: 0, null: false t.integer "approvals_before_merge", default: 0, null: false
t.boolean "reset_approvals_on_push", default: true t.boolean "reset_approvals_on_push", default: true
t.integer "commit_count", default: 0
t.boolean "merge_requests_ff_only_enabled", default: false t.boolean "merge_requests_ff_only_enabled", default: false
t.text "issues_template" t.text "issues_template"
t.text "import_error"
t.boolean "mirror", default: false, null: false t.boolean "mirror", default: false, null: false
t.datetime "mirror_last_update_at" t.datetime "mirror_last_update_at"
t.datetime "mirror_last_successful_update_at" t.datetime "mirror_last_successful_update_at"
t.integer "mirror_user_id" t.integer "mirror_user_id"
t.text "import_error"
end end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
......
...@@ -188,7 +188,7 @@ Parameters: ...@@ -188,7 +188,7 @@ Parameters:
"target_url": "http://jenkins/project/url", "target_url": "http://jenkins/project/url",
"description": "Jenkins success", "description": "Jenkins success",
"created_at": "2015-10-12T09:47:16.250Z", "created_at": "2015-10-12T09:47:16.250Z",
"started_at": "2015-10-12T09:47:16.250Z"", "started_at": "2015-10-12T09:47:16.250Z",
"finished_at": "2015-10-12T09:47:16.262Z", "finished_at": "2015-10-12T09:47:16.262Z",
"author": { "author": {
"id": 1, "id": 1,
...@@ -228,7 +228,7 @@ POST /projects/:id/statuses/:sha ...@@ -228,7 +228,7 @@ POST /projects/:id/statuses/:sha
"target_url": "http://jenkins/project/url", "target_url": "http://jenkins/project/url",
"description": "Jenkins success", "description": "Jenkins success",
"created_at": "2015-10-12T09:47:16.250Z", "created_at": "2015-10-12T09:47:16.250Z",
"started_at": "2015-10-12T09:47:16.250Z"", "started_at": "2015-10-12T09:47:16.250Z",
"finished_at": "2015-10-12T09:47:16.262Z", "finished_at": "2015-10-12T09:47:16.262Z",
"author": { "author": {
"id": 1, "id": 1,
......
...@@ -35,7 +35,7 @@ GitLab Runner then executes build scripts as `gitlab-runner` user. ...@@ -35,7 +35,7 @@ GitLab Runner then executes build scripts as `gitlab-runner` user.
```bash ```bash
$ sudo gitlab-runner register -n \ $ sudo gitlab-runner register -n \
--url http://gitlab.com/ci \ --url https://gitlab.com/ci \
--token RUNNER_TOKEN \ --token RUNNER_TOKEN \
--executor shell --executor shell
--description "My Runner" --description "My Runner"
...@@ -84,7 +84,7 @@ In order to do that follow the steps: ...@@ -84,7 +84,7 @@ In order to do that follow the steps:
```bash ```bash
$ sudo gitlab-runner register -n \ $ sudo gitlab-runner register -n \
--url http://gitlab.com/ci \ --url https://gitlab.com/ci \
--token RUNNER_TOKEN \ --token RUNNER_TOKEN \
--executor docker \ --executor docker \
--description "My Docker Runner" \ --description "My Docker Runner" \
......
...@@ -6,7 +6,7 @@ To start building projects with GitLab CI a few steps needs to be done. ...@@ -6,7 +6,7 @@ To start building projects with GitLab CI a few steps needs to be done.
First you need to have a working GitLab and GitLab CI instance. First you need to have a working GitLab and GitLab CI instance.
You can omit this step if you use [GitLab.com](http://GitLab.com/). You can omit this step if you use [GitLab.com](https://GitLab.com/).
## 2. Create repository on GitLab ## 2. Create repository on GitLab
...@@ -16,7 +16,7 @@ Push your application to that repository. ...@@ -16,7 +16,7 @@ Push your application to that repository.
## 3. Add project to CI ## 3. Add project to CI
The next part is to login to GitLab CI. The next part is to login to GitLab CI.
Point your browser to the URL you have set GitLab or use [gitlab.com/ci](http://gitlab.com/ci/). Point your browser to the URL you have set GitLab or use [gitlab.com/ci](https://gitlab.com/ci/).
On the first screen you will see a list of GitLab's projects that you have access to: On the first screen you will see a list of GitLab's projects that you have access to:
...@@ -97,7 +97,7 @@ If you do it correctly your runner should be shown under **Runners activated for ...@@ -97,7 +97,7 @@ If you do it correctly your runner should be shown under **Runners activated for
### Shared runners ### Shared runners
If you use [gitlab.com/ci](http://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc. If you use [gitlab.com/ci](https://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc.
These are special virtual machines that are run on GitLab's infrastructure that can build any project. These are special virtual machines that are run on GitLab's infrastructure that can build any project.
To enable **Shared runners** you have to go to **Runners** and click **Enable shared runners** for this project. To enable **Shared runners** you have to go to **Runners** and click **Enable shared runners** for this project.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
GitLab by default supports [Gravatar](https://gravatar.com) avatar service. GitLab by default supports [Gravatar](https://gravatar.com) avatar service.
Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
[heavily based on gravatar](http://wiki.libravatar.org/api/). [heavily based on gravatar](https://wiki.libravatar.org/api/).
This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server. This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server.
...@@ -31,7 +31,7 @@ the configuration options as follows: ...@@ -31,7 +31,7 @@ the configuration options as follows:
## Self-hosted ## Self-hosted
If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration If you are [running your own libravatar service](https://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration
but the important part is to provide the same placeholders so GitLab can parse the URL correctly. but the important part is to provide the same placeholders so GitLab can parse the URL correctly.
For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is
...@@ -63,7 +63,7 @@ Run `sudo gitlab-ctl reconfigure` for changes to take effect. ...@@ -63,7 +63,7 @@ Run `sudo gitlab-ctl reconfigure` for changes to take effect.
## Default URL for missing images ## Default URL for missing images
[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service. [Libravatar supports different sets](https://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set. In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set.
For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"` For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
...@@ -146,7 +146,7 @@ nginx ...@@ -146,7 +146,7 @@ nginx
Apache httpd Apache httpd
- [Explanation of Apache logs](http://httpd.apache.org/docs/2.2/logs.html). - [Explanation of Apache logs](https://httpd.apache.org/docs/2.2/logs.html).
- `/var/log/apache2/` contains error and output logs (on Ubuntu). - `/var/log/apache2/` contains error and output logs (on Ubuntu).
- `/var/log/httpd/` contains error and output logs (on RHEL). - `/var/log/httpd/` contains error and output logs (on RHEL).
......
...@@ -7,7 +7,7 @@ Please explore webhooks as an option if you do not have filesystem access. For a ...@@ -7,7 +7,7 @@ Please explore webhooks as an option if you do not have filesystem access. For a
Git natively supports hooks that are executed on different actions. Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update. Examples of server-side git hooks include pre-receive, post-receive, and update.
See See
[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) [Git SCM Server-Side Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks)
for more information about each hook type. for more information about each hook type.
As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## Note ## Note
We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](http://bugs.mysql.com/bug.php?id=65830) that [suggested](http://bugs.mysql.com/bug.php?id=50909) [fixes](http://bugs.mysql.com/bug.php?id=65830) [have](http://bugs.mysql.com/bug.php?id=63164). We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
## MySQL ## MySQL
......
...@@ -106,7 +106,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ...@@ -106,7 +106,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby ## 2. Ruby
The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. The use of Ruby version managers such as [RVM](https://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
Remove the old Ruby 1.8 if present Remove the old Ruby 1.8 if present
...@@ -128,11 +128,10 @@ Install the Bundler Gem: ...@@ -128,11 +128,10 @@ Install the Bundler Gem:
## 3. Go ## 3. Go
Since GitLab 8.0, Git HTTP requests are handled by gitlab-git-http-server. Since GitLab 8.0, Git HTTP requests are handled by gitlab-workhorse (formerly
This is a small daemon written in Go. gitlab-git-http-server). This is a small daemon written in Go. To install
To install gitlab-git-http-server we need a Go compiler. gitlab-workhorse we need a Go compiler. The instructions below assume you
The instructions below assume you use 64-bit Linux. You can find use 64-bit Linux. You can find downloads for other platforms at the [Go download
downloads for other platforms at the [Go download
page](https://golang.org/dl). page](https://golang.org/dl).
curl -O --progress https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz curl -O --progress https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
...@@ -211,9 +210,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -211,9 +210,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable-ee gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ee.git -b 8-2-stable-ee gitlab
**Note:** You can change `8-1-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `8-2-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
...@@ -298,7 +297,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -298,7 +297,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Install Gems ### Install Gems
**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](http://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2. **Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](https://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
# For PostgreSQL (note, the option says "without ... mysql") # For PostgreSQL (note, the option says "without ... mysql")
sudo -u git -H bundle install --deployment --without development test mysql aws kerberos sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
...@@ -313,7 +312,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -313,7 +312,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab. GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.6] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.7] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config. # By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows: # You can review (and modify) the gitlab-shell config as follows:
...@@ -323,16 +322,16 @@ GitLab Shell is an SSH access and repository management software developed speci ...@@ -323,16 +322,16 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". **Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)".
### Install gitlab-git-http-server ### Install gitlab-workhorse
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-git-http-server cd gitlab-workhorse
sudo -u git -H git checkout 0.3.0 sudo -u git -H git checkout 0.4.1
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
# Go to Gitlab installation folder # Go to Gitlab installation folder
cd /home/git/gitlab cd /home/git/gitlab
......
...@@ -75,7 +75,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server ...@@ -75,7 +75,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
# Filter LDAP users # Filter LDAP users
# #
# Format: RFC 4515 http://tools.ietf.org/search/rfc4515 # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
# Ex. (employeeType=developer) # Ex. (employeeType=developer)
# #
# Note: GitLab does not support omniauth-ldap's custom filter syntax. # Note: GitLab does not support omniauth-ldap's custom filter syntax.
...@@ -249,7 +249,7 @@ For installations from source, add the following setting in the 'ldap' section o ...@@ -249,7 +249,7 @@ For installations from source, add the following setting in the 'ldap' section o
## Using an LDAP filter to limit access to your GitLab server ## Using an LDAP filter to limit access to your GitLab server
If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter. If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter.
The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). The filter must comply with [RFC 4515](https://tools.ietf.org/search/rfc4515).
```ruby ```ruby
# For omnibus packages; new LDAP server syntax # For omnibus packages; new LDAP server syntax
......
...@@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and ...@@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and
8. It is your responsibility to notify GitLab B.V. when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab B.V.. 8. It is your responsibility to notify GitLab B.V. when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab B.V..
This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
...@@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and ...@@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and
8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. 8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
...@@ -43,7 +43,7 @@ You can also use other rich text files in GitLab. You might have to install a de ...@@ -43,7 +43,7 @@ You can also use other rich text files in GitLab. You might have to install a de
## Newlines ## Newlines
GFM honors the markdown specification in how [paragraphs and line breaks are handled](http://daringfireball.net/projects/markdown/syntax#p). GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p).
A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.
Line-breaks, or softreturns, are rendered if you end a line with two or more spaces Line-breaks, or softreturns, are rendered if you end a line with two or more spaces
...@@ -72,14 +72,14 @@ do_this_and_do_that_and_another_thing ...@@ -72,14 +72,14 @@ do_this_and_do_that_and_another_thing
GFM will autolink almost any URL you copy and paste into your text. GFM will autolink almost any URL you copy and paste into your text.
* http://www.google.com * https://www.google.com
* https://google.com/ * https://google.com/
* ftp://ftp.us.debian.org/debian/ * ftp://ftp.us.debian.org/debian/
* smb://foo/bar/baz * smb://foo/bar/baz
* irc://irc.freenode.net/gitlab * irc://irc.freenode.net/gitlab
* http://localhost:3000 * http://localhost:3000
* http://www.google.com * https://www.google.com
* https://google.com/ * https://google.com/
* ftp://ftp.us.debian.org/debian/ * ftp://ftp.us.debian.org/debian/
* smb://foo/bar/baz * smb://foo/bar/baz
...@@ -390,7 +390,7 @@ There are two ways to create links, inline-style and reference-style. ...@@ -390,7 +390,7 @@ There are two ways to create links, inline-style and reference-style.
[arbitrary case-insensitive reference text]: https://www.mozilla.org [arbitrary case-insensitive reference text]: https://www.mozilla.org
[1]: http://slashdot.org [1]: http://slashdot.org
[link text itself]: http://www.reddit.com [link text itself]: https://www.reddit.com
[I'm an inline-style link](https://www.google.com) [I'm an inline-style link](https://www.google.com)
...@@ -406,7 +406,7 @@ Some text to show that the reference links can follow later. ...@@ -406,7 +406,7 @@ Some text to show that the reference links can follow later.
[arbitrary case-insensitive reference text]: https://www.mozilla.org [arbitrary case-insensitive reference text]: https://www.mozilla.org
[1]: http://slashdot.org [1]: http://slashdot.org
[link text itself]: http://www.reddit.com [link text itself]: https://www.reddit.com
**Note** **Note**
...@@ -583,5 +583,5 @@ By including colons in the header row, you can align the text within that column ...@@ -583,5 +583,5 @@ By including colons in the header row, you can align the text within that column
## References ## References
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
- The [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. - The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. - [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
...@@ -52,7 +52,7 @@ leak memory, probably because it does not handle user requests.) ...@@ -52,7 +52,7 @@ leak memory, probably because it does not handle user requests.)
To make these memory leaks manageable, GitLab comes with the To make these memory leaks manageable, GitLab comes with the
[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This [unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This
gem [monkey-patches](http://en.wikipedia.org/wiki/Monkey_patch) the Unicorn gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn
workers to do a memory self-check after every 16 requests. If the memory of the workers to do a memory self-check after every 16 requests. If the memory of the
Unicorn worker exceeds a pre-set limit then the worker process exits. The Unicorn worker exceeds a pre-set limit then the worker process exits. The
Unicorn master then automatically replaces the worker process. Unicorn master then automatically replaces the worker process.
...@@ -83,4 +83,4 @@ is a normal value for our current GitLab.com setup and traffic. ...@@ -83,4 +83,4 @@ is a normal value for our current GitLab.com setup and traffic.
The high frequency of Unicorn memory restarts on some GitLab sites can be a The high frequency of Unicorn memory restarts on some GitLab sites can be a
source of confusion for administrators. Usually they are a [red source of confusion for administrators. Usually they are a [red
herring](http://en.wikipedia.org/wiki/Red_herring). herring](https://en.wikipedia.org/wiki/Red_herring).
...@@ -37,9 +37,9 @@ template are explained below: ...@@ -37,9 +37,9 @@ template are explained below:
### Xth: (6 working days before the 22nd) ### Xth: (6 working days before the 22nd)
- [ ] Merge CE `master` into EE `master` via merge request (#LINK)
- [ ] Determine QA person and notify this person - [ ] Determine QA person and notify this person
- [ ] Check the tasks in [how to rc1 guide](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/release/howto_rc1.md) and delegate tasks if necessary - [ ] Check the tasks in [how to rc1 guide](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/release/howto_rc1.md) and delegate tasks if necessary
- [ ] Merge CE `master` into EE `master` via merge request (#LINK)
- [ ] Create CE and EE RC1 versions (#LINK) - [ ] Create CE and EE RC1 versions (#LINK)
- [ ] Build RC1 packages - [ ] Build RC1 packages
...@@ -54,21 +54,25 @@ template are explained below: ...@@ -54,21 +54,25 @@ template are explained below:
- [ ] Update GitLab.com with RC1 - [ ] Update GitLab.com with RC1
- [ ] Create the regression issue in the CE issue tracker: - [ ] Create the regression issue in the CE issue tracker:
> This is a meta issue to index possible regressions in this monthly release ```
> and any patch versions. This is a meta issue to index possible regressions in this monthly release
> and any patch versions.
> Please do not raise or discuss issues directly in this issue but link to
> issues that might warrant a patch release. If there is a Merge Request Please do not raise or discuss issues directly in this issue but link to
> that fixes the issue, please link to that as well. issues that might warrant a patch release. If there is a Merge Request
> that fixes the issue, please link to that as well.
> Please only post one regression issue and/or merge request per comment.
> Comments will be updated by the release manager as they are addressed. Please only post one regression issue and/or merge request per comment.
Comments will be updated by the release manager as they are addressed.
```
- [ ] Tweet about RC1 release: - [ ] Tweet about RC1 release:
> GitLab x.y.0.rc1 is available: https://packages.gitlab.com/gitlab/unstable ```
> Use at your own risk. Please link regressions issues from GitLab x.y.0.rc1 is available: https://packages.gitlab.com/gitlab/unstable
> LINK_TO_REGRESSION_ISSUE Use at your own risk. Please link regressions issues from
LINK_TO_REGRESSION_ISSUE
```
### Xth: (3 working days before the 22nd) ### Xth: (3 working days before the 22nd)
......
...@@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses ...@@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses
## Security vulnerability disclosure ## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Release Procedure ## Release Procedure
...@@ -25,7 +25,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c ...@@ -25,7 +25,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Send tweets about the release from `@gitlabhq` 1. Send tweets about the release from `@gitlabhq`
1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq) 1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number. CVE is only needed for bugs that allow someone to own the server (Remote Code Execution) or access to code of projects they are not a member of. 1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number. CVE is only needed for bugs that allow someone to own the server (Remote Code Execution) or access to code of projects they are not a member of.
1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/) 1. Add the security researcher to the [Security Researcher Acknowledgments list](https://about.gitlab.com/vulnerability-acknowledgements/)
1. Thank the security researcher in an email for their cooperation 1. Thank the security researcher in an email for their cooperation
1. Update the blog post and the CHANGELOG when we receive the CVE number 1. Update the blog post and the CHANGELOG when we receive the CVE number
......
...@@ -77,7 +77,7 @@ Deploy keys can be shared between projects, you just need to add them to each pr ...@@ -77,7 +77,7 @@ Deploy keys can be shared between projects, you just need to add them to each pr
### Eclipse ### Eclipse
How to add your ssh key to Eclipse: http://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration How to add your ssh key to Eclipse: https://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration
## Tip: Non-default OpenSSH key file names or locations ## Tip: Non-default OpenSSH key file names or locations
......
...@@ -47,7 +47,7 @@ Download and compile Ruby: ...@@ -47,7 +47,7 @@ Download and compile Ruby:
```bash ```bash
mkdir /tmp/ruby && cd /tmp/ruby mkdir /tmp/ruby && cd /tmp/ruby
curl --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz curl --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz
cd ruby-2.1.6 cd ruby-2.1.6
./configure --disable-install-rdoc ./configure --disable-install-rdoc
make make
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
**NOTE:** GitLab 8.0 introduced several significant changes related to **NOTE:** GitLab 8.0 introduced several significant changes related to
installation and configuration which *are not duplicated here*. Be sure you're installation and configuration which *are not duplicated here*. Be sure you're
already running a working version of 8.0 before proceeding with this guide. already running a working version of at least 8.0 before proceeding with this
guide.
### 0. Double-check your Git version ### 0. Double-check your Git version
...@@ -67,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee ...@@ -67,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch sudo -u git -H git fetch
sudo -u git -H git checkout v2.6.5 sudo -u git -H git checkout v2.6.7
``` ```
### 5. Replace gitlab-git-http-server with gitlab-workhorse ### 5. Replace gitlab-git-http-server with gitlab-workhorse
...@@ -80,7 +81,7 @@ from GitLab 8.1. ...@@ -80,7 +81,7 @@ from GitLab 8.1.
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse cd gitlab-workhorse
sudo -u git -H git checkout 0.3.1 sudo -u git -H git checkout 0.4.1
sudo -u git -H make sudo -u git -H make
``` ```
...@@ -165,12 +166,12 @@ To make sure you didn't miss anything run a more thorough check: ...@@ -165,12 +166,12 @@ To make sure you didn't miss anything run a more thorough check:
If all items are green, then congratulations, the upgrade is complete! If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.0) ## Things went south? Revert to previous version (8.1)
### 1. Revert the code to the previous version ### 1. Revert the code to the previous version
Follow the [upgrade guide from 7.14 to 8.0](7.14-to-8.0.md), except for the database migration Follow the [upgrade guide from 8.0 to 8.1](8.0-to-8.1.md), except for the
(The backup is already migrated to the previous version) database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup ### 2. Restore from the backup
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
- [Two-factor Authentication (2FA)](two_factor_authentication.md) - [Two-factor Authentication (2FA)](two_factor_authentication.md)
- [Web Editor](web_editor.md) - [Web Editor](web_editor.md)
- [Releases](releases.md) - [Releases](releases.md)
- [Milestones](milestones.md)
- [Merge Requests](merge_requests.md) - [Merge Requests](merge_requests.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md)
- [Repository Mirroring](repository_mirroring.md) - [Repository Mirroring](repository_mirroring.md)
# Git LFS
Managing large files such as audio, video and graphics files has always been one of the shortcomings of Git.
The general recommendation is to not have Git repositories larger than 1GB to preserve performance.
GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) (EE only), however in certain
environments it is not always convenient to use different commands to differentiate between the large files and regular ones.
Git LFS makes this simpler for the end user by removing the requirement to learn new commands.
<!-- more -->
## How it works
Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests.
Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file.
## Requirements
* Git LFS is supported in GitLab starting with version 8.2
* Git LFS [client](https://git-lfs.github.com) version 0.6.0 and up
## GitLab and Git LFS
### Configuration
Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on.
There are two configuration options to help GitLab server administrators:
* Enabling/disabling Git LFS support
* Changing the location of LFS object storage
#### Omnibus packages
In `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['lfs_enabled'] = false
gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects"
```
#### Installations from source
In `config/gitlab.yml`:
```yaml
lfs:
enabled: false
storage_path: /mnt/storage/lfs-objects
```
## Known limitations
* Git LFS v1 original API is not supported since it was deprecated early in LFS development, starting with Git LFS version 0.6.0
* When SSH is set as a remote, Git LFS objects still go through HTTPS
* Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended
* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported
* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the url to Git config manually (see #troubleshooting-tips)
## Using Git LFS
Lets take a look at the workflow when you need to check large files into your Git repository with Git LFS:
For example, if you want to upload a very large file and check it into your Git repository:
```bash
git clone git@gitlab.example.com:group/project.git
git lfs init # initialize the Git LFS project project
git lfs track "*.iso" # select the file extensions that you want to treat as large files
```
Once a certain file extension is marked for tracking as a LFS object you can use Git as usual without having to redo the command to track a file with the same extension:
```bash
cp ~/tmp/debian.iso ./ # copy a large file into the current directory
git add . # add the large file to the project
git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server
```
Downloading a single large file is also very simple:
```bash
git clone git@gitlab.example.com:group/project.git
git lfs fetch debian.iso # download the large file
```
## Troubleshooting
### error: Repository or object not found
There are a couple of reasons why this error can occur:
* Wrong version of LFS client used:
Check the version of Git LFS on the client machine with `git lfs version`. Only version 0.6.0 and newer are supported.
* Project is using deprecated LFS API
Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try using Git LFS client newer than 0.6.0.
### Invalid status for <url> : 501
When attempting to push a LFS object to a GitLab server that doesn't have Git LFS support enabled, server will return status `error 501`. Check with your GitLab administrator why Git LFS is not enabled on the server. See [Configuration section](#configuration) for instructions on how to enable LFS support.
### getsockopt: connection refused
If you push a LFS object to a project and you receive an error similar to: `Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`,
the LFS client is trying to reach GitLab through HTTPS. However, your GitLab instance is being served on HTTP.
This behaviour is caused by Git LFS using HTTPS connections by default when a `lfsurl` is not set in the Git config.
To prevent this from happening, set the lfs url in project Git config:
```bash
git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/objects/batch"
```
### Credentials are always required when pushing an object
Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required.
By default, Git has support for remembering the credentials for each repository you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials).
For example, you can tell Git to remember the password for a period of time in which you expect to push the objects:
```bash
git config --global credential.helper 'cache --timeout=3600'
```
This will remember the credentials for an hour after which Git operations will require re-authentication.
If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available.
More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage)
\ No newline at end of file
...@@ -7,7 +7,7 @@ This allows a wide variety of branching strategies and workflows. ...@@ -7,7 +7,7 @@ This allows a wide variety of branching strategies and workflows.
Almost all of these are an improvement over the methods used before git. Almost all of these are an improvement over the methods used before git.
But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems. But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems.
Therefore we propose the GitLab flow as clearly defined set of best practices. Therefore we propose the GitLab flow as clearly defined set of best practices.
It combines [feature driven development](http://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking. It combines [feature driven development](https://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow. Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow.
This article describes the GitLab flow that integrates the git workflow with an issue tracking system. This article describes the GitLab flow that integrates the git workflow with an issue tracking system.
...@@ -91,7 +91,7 @@ This workflow where commits only flow downstream ensures that everything has bee ...@@ -91,7 +91,7 @@ This workflow where commits only flow downstream ensures that everything has bee
If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch. If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch.
If master is good to go (it should be if you a practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches. If master is good to go (it should be if you a practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches.
If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches. If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/). An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](https://teatro.io/).
## Release branches with GitLab flow ## Release branches with GitLab flow
...@@ -104,7 +104,7 @@ By branching as late as possible you minimize the time you have to apply bug fix ...@@ -104,7 +104,7 @@ By branching as late as possible you minimize the time you have to apply bug fix
After a release branch is announced, only serious bug fixes are included in the release branch. After a release branch is announced, only serious bug fixes are included in the release branch.
If possible these bug fixes are first merged into master and then cherry-picked into the release branch. If possible these bug fixes are first merged into master and then cherry-picked into the release branch.
This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases. This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases.
This is called an 'upstream first' policy that is also practiced by [Google](http://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](http://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo). This is called an 'upstream first' policy that is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo).
Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag. Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag.
Some projects also have a stable branch that points to the same commit as the latest released branch. Some projects also have a stable branch that points to the same commit as the latest released branch.
In this flow it is not common to have a production branch (or git flow master branch). In this flow it is not common to have a production branch (or git flow master branch).
...@@ -200,7 +200,7 @@ And to understand a change in context one can always look at the merge commit th ...@@ -200,7 +200,7 @@ And to understand a change in context one can always look at the merge commit th
After you merge multiple commits from a feature branch into the master branch this is harder to undo. After you merge multiple commits from a feature branch into the master branch this is harder to undo.
If you would have squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed. If you would have squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed.
Fortunately [reverting a merge made some time ago](http://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git. Fortunately [reverting a merge made some time ago](https://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git.
This however, requires having specific merge commits for the commits your want to revert. This however, requires having specific merge commits for the commits your want to revert.
If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise. If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise.
...@@ -215,7 +215,7 @@ With git you can also rebase your feature branch commits to order them after the ...@@ -215,7 +215,7 @@ With git you can also rebase your feature branch commits to order them after the
This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history. This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history.
However, just like with squashing you should never rebase commits you have pushed to a remote server. However, just like with squashing you should never rebase commits you have pushed to a remote server.
This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend. This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend.
When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](http://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/). When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](https://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/).
You can reuse recorded resolutions (rerere) sometimes, but without rebasing you only have to solve the conflicts one time and you’re set. You can reuse recorded resolutions (rerere) sometimes, but without rebasing you only have to solve the conflicts one time and you’re set.
There has to be a better way to avoid many merge commits. There has to be a better way to avoid many merge commits.
...@@ -317,7 +317,7 @@ When initiating a feature branch, always start with an up to date master to bran ...@@ -317,7 +317,7 @@ When initiating a feature branch, always start with an up to date master to bran
If you know beforehand that your work absolutely depends on another branch you can also branch from there. If you know beforehand that your work absolutely depends on another branch you can also branch from there.
If you need to merge in another branch after starting explain the reason in the merge commit. If you need to merge in another branch after starting explain the reason in the merge commit.
If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch. If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch.
Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/). Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](https://lwn.net/Articles/328438/).
Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history. Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history.
## References ## References
......
...@@ -6,9 +6,9 @@ Git is a distributed version control system. ...@@ -6,9 +6,9 @@ Git is a distributed version control system.
There are some major differences between the two, for more information consult your favorite search engine. There are some major differences between the two, for more information consult your favorite search engine.
Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at
[git documentation pages](http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion). [git documentation pages](https://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion).
Apart from the [official git documentation](http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also Apart from the [official git documentation](https://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also
user created step by step guide for migrating from SVN to GitLab. user created step by step guide for migrating from SVN to GitLab.
[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca). [Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca).
......
# Milestones
Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date.
A common use is keeping track of an upcoming software version. Milestones are created per-project.
![milestone form](milestones/form.png)
## Groups and milestones
You can create a milestone for several projects in the same group simultaneously.
On the group's milestones page, you will be able to see the status of that milestone across all of the selected projects.
![group milestone form](milestones/group_form.png)
...@@ -66,6 +66,14 @@ Feature: Groups ...@@ -66,6 +66,14 @@ Feature: Groups
When I select "Mike" as "Reporter" When I select "Mike" as "Reporter"
Then I should see "Mike" in team list as "Reporter" Then I should see "Mike" in team list as "Reporter"
@javascript
Scenario: Ignore add user to group when is already Owner
Given gitlab user "Mike"
When I visit group "Owned" members page
And I click link "Add members"
When I select "Mike" as "Reporter"
Then I should see "Mike" in team list as "Owner"
@javascript @javascript
Scenario: Invite user to group Scenario: Invite user to group
When I visit group "Owned" members page When I visit group "Owned" members page
...@@ -171,7 +179,14 @@ Feature: Groups ...@@ -171,7 +179,14 @@ Feature: Groups
And I go to "Audit Events" And I go to "Audit Events"
Then I should see the audit event listed Then I should see the audit event listed
# Group projects in settings Scenario: Create multiple milestones with one form
Given I visit group "Owned" milestones page
And I click new milestone button
And I fill milestone name
When I press create mileston button
Then milestone in each project should be created
# Group projects in settings
Scenario: I should see all projects in the project list in settings Scenario: I should see all projects in the project list in settings
Given Group "Owned" has archived project Given Group "Owned" has archived project
When I visit group "Owned" projects page When I visit group "Owned" projects page
...@@ -187,4 +202,4 @@ Feature: Groups ...@@ -187,4 +202,4 @@ Feature: Groups
When I visit group "Owned" page When I visit group "Owned" page
Then I should see group "Owned" Then I should see group "Owned"
Then I should see project "Public-project" Then I should see project "Public-project"
...@@ -48,6 +48,17 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -48,6 +48,17 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
click_button "Add users to group" click_button "Add users to group"
end end
step 'I select "Mike" as "Master"' do
user = User.find_by(name: "Mike")
page.within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Master", from: "access_level"
end
click_button "Add users to group"
end
step 'I should see "Mike" in team list as "Reporter"' do step 'I should see "Mike" in team list as "Reporter"' do
page.within '.well-list' do page.within '.well-list' do
expect(page).to have_content('Mike') expect(page).to have_content('Mike')
...@@ -55,6 +66,13 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -55,6 +66,13 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end end
end end
step 'I should see "Mike" in team list as "Owner"' do
page.within '.well-list' do
expect(page).to have_content('Mike')
expect(page).to have_content('Owner')
end
end
step 'I select "sjobs@apple.com" as "Reporter"' do step 'I select "sjobs@apple.com" as "Reporter"' do
page.within ".users-group-form" do page.within ".users-group-form" do
select2("sjobs@apple.com", from: "#user_ids", multiple: true) select2("sjobs@apple.com", from: "#user_ids", multiple: true)
...@@ -135,6 +153,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -135,6 +153,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
click_on 'Save' click_on 'Save'
end end
end end
page.within "#group_member_#{member.id}" do
expect(page).to have_content "Developer"
end
end end
step 'I go to "Audit Events"' do step 'I go to "Audit Events"' do
...@@ -293,6 +315,28 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -293,6 +315,28 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
end end
step 'I fill milestone name' do
fill_in 'milestone_title', with: 'v2.9.0'
end
step 'I click new milestone button' do
click_link "New Milestone"
end
step 'I press create mileston button' do
click_button "Create Milestone"
end
step 'milestone in each project should be created' do
group = Group.find_by(name: 'Owned')
expect(page).to have_content "Milestone v2.9.0"
expect(group.projects).to be_present
group.projects.each do |project|
expect(page).to have_content project.name
end
end
protected protected
def assigned_to_me(key) def assigned_to_me(key)
......
...@@ -219,7 +219,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -219,7 +219,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end end
step 'The code block should be unchanged' do step 'The code block should be unchanged' do
expect(page).to have_content("Command [1]: /usr/local/bin/git , see [text](doc/text)") expect(page).to have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```")
end end
step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do
......
...@@ -35,6 +35,10 @@ module SharedPaths ...@@ -35,6 +35,10 @@ module SharedPaths
visit merge_requests_group_path(Group.find_by(name: "Owned")) visit merge_requests_group_path(Group.find_by(name: "Owned"))
end end
step 'I visit group "Owned" milestones page' do
visit group_milestones_path(Group.find_by(name: "Owned"))
end
step 'I visit group "Owned" members page' do step 'I visit group "Owned" members page' do
visit group_group_members_path(Group.find_by(name: "Owned")) visit group_group_members_path(Group.find_by(name: "Owned"))
end end
......
...@@ -150,17 +150,15 @@ module Backup ...@@ -150,17 +150,15 @@ module Backup
private private
def backup_contents def backup_contents
folders_to_backup + ["uploads.tar.gz", "builds.tar.gz", "artifacts.tar.gz", "backup_information.yml"] folders_to_backup + archives_to_backup + ["backup_information.yml"]
end end
def folders_to_backup def archives_to_backup
folders = %w{repositories db} %w{uploads builds artifacts}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
end
if ENV["SKIP"]
return folders.reject{ |folder| ENV["SKIP"].include?(folder) }
end
folders def folders_to_backup
%w{repositories db}.reject{ |name| skipped?(name) }
end end
def settings def settings
......
...@@ -67,7 +67,7 @@ server { ...@@ -67,7 +67,7 @@ server {
location / { location / {
## Serve static files from defined root folder. ## Serve static files from defined root folder.
## @gitlab is a named location for the upstream fallback, see below. ## @gitlab is a named location for the upstream fallback, see below.
try_files $uri $uri/index.html $uri.html @gitlab; try_files $uri /index.html $uri.html @gitlab;
} }
## We route uploads through GitLab to prevent XSS and enforce access control. ## We route uploads through GitLab to prevent XSS and enforce access control.
...@@ -114,7 +114,6 @@ server { ...@@ -114,7 +114,6 @@ server {
} }
location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse; error_page 418 = @gitlab-workhorse;
return 418; return 418;
...@@ -140,7 +139,6 @@ server { ...@@ -140,7 +139,6 @@ server {
# Build artifacts should be submitted to this location # Build artifacts should be submitted to this location
location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse; error_page 418 = @gitlab-workhorse;
return 418; return 418;
...@@ -148,13 +146,13 @@ server { ...@@ -148,13 +146,13 @@ server {
# Build artifacts should be submitted to this location # Build artifacts should be submitted to this location
location ~ /ci/api/v1/builds/[0-9]+/artifacts { location ~ /ci/api/v1/builds/[0-9]+/artifacts {
client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse; error_page 418 = @gitlab-workhorse;
return 418; return 418;
} }
location @gitlab-workhorse { location @gitlab-workhorse {
client_max_body_size 0;
## If you use HTTPS make sure you disable gzip compression ## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack. ## to be safe against BREACH attack.
# gzip off; # gzip off;
......
...@@ -112,7 +112,7 @@ server { ...@@ -112,7 +112,7 @@ server {
location / { location / {
## Serve static files from defined root folder. ## Serve static files from defined root folder.
## @gitlab is a named location for the upstream fallback, see below. ## @gitlab is a named location for the upstream fallback, see below.
try_files $uri $uri/index.html $uri.html @gitlab; try_files $uri /index.html $uri.html @gitlab;
} }
## We route uploads through GitLab to prevent XSS and enforce access control. ## We route uploads through GitLab to prevent XSS and enforce access control.
...@@ -161,7 +161,6 @@ server { ...@@ -161,7 +161,6 @@ server {
} }
location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse; error_page 418 = @gitlab-workhorse;
return 418; return 418;
...@@ -187,7 +186,6 @@ server { ...@@ -187,7 +186,6 @@ server {
# Build artifacts should be submitted to this location # Build artifacts should be submitted to this location
location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse; error_page 418 = @gitlab-workhorse;
return 418; return 418;
...@@ -195,13 +193,13 @@ server { ...@@ -195,13 +193,13 @@ server {
# Build artifacts should be submitted to this location # Build artifacts should be submitted to this location
location ~ /ci/api/v1/builds/[0-9]+/artifacts { location ~ /ci/api/v1/builds/[0-9]+/artifacts {
client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse; error_page 418 = @gitlab-workhorse;
return 418; return 418;
} }
location @gitlab-workhorse { location @gitlab-workhorse {
client_max_body_size 0;
## If you use HTTPS make sure you disable gzip compression ## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack. ## to be safe against BREACH attack.
gzip off; gzip off;
......
...@@ -65,6 +65,12 @@ describe 'Comments', feature: true do ...@@ -65,6 +65,12 @@ describe 'Comments', feature: true do
end end
describe 'when editing a note', js: true do describe 'when editing a note', js: true do
it 'should contain the hidden edit form' do
page.within("#note_#{note.id}") do
is_expected.to have_css('.note-edit-form', visible: false)
end
end
describe 'editing the note' do describe 'editing the note' do
before do before do
find('.note').hover find('.note').hover
......
...@@ -51,6 +51,7 @@ feature 'Task Lists', feature: true do ...@@ -51,6 +51,7 @@ feature 'Task Lists', feature: true do
expect(page).to have_selector(container) expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close') expect(page).to have_selector('a.btn-close')
end end
...@@ -89,6 +90,7 @@ feature 'Task Lists', feature: true do ...@@ -89,6 +90,7 @@ feature 'Task Lists', feature: true do
expect(page).to have_selector('.note .js-task-list-container') expect(page).to have_selector('.note .js-task-list-container')
expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
end end
it 'is only editable by author' do it 'is only editable by author' do
...@@ -125,6 +127,7 @@ feature 'Task Lists', feature: true do ...@@ -125,6 +127,7 @@ feature 'Task Lists', feature: true do
expect(page).to have_selector(container) expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close') expect(page).to have_selector('a.btn-close')
end end
......
require 'spec_helper' require 'spec_helper'
describe Milestones::GroupService do describe GlobalMilestone do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
...@@ -14,57 +14,52 @@ describe Milestones::GroupService do ...@@ -14,57 +14,52 @@ describe Milestones::GroupService do
let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) }
let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) }
describe 'execute' do describe :build_collection do
context 'with valid projects' do before do
before do milestones =
milestones = [
[ milestone1_project1,
milestone1_project1, milestone1_project2,
milestone1_project2, milestone1_project3,
milestone1_project3, milestone2_project1,
milestone2_project1, milestone2_project2,
milestone2_project2, milestone2_project3
milestone2_project3 ]
]
@group_milestones = Milestones::GroupService.new(milestones).execute
end
it 'should have all project milestones' do @global_milestones = GlobalMilestone.build_collection(milestones)
expect(@group_milestones.count).to eq(2) end
end
it 'should have all project milestones' do
expect(@global_milestones.count).to eq(2)
end
it 'should have all project milestones titles' do it 'should have all project milestones titles' do
expect(@group_milestones.map { |group_milestone| group_milestone.title }).to match_array(['Milestone v1.2', 'VD-123']) expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123'])
end end
it 'should have all project milestones' do it 'should have all project milestones' do
expect(@group_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6)
end
end end
end end
describe 'milestone' do describe :initialize do
context 'with valid title' do before do
before do milestones =
milestones = [
[ milestone1_project1,
milestone1_project1, milestone1_project2,
milestone1_project2, milestone1_project3,
milestone1_project3, ]
milestone2_project1,
milestone2_project2,
milestone2_project3
]
@group_milestones = Milestones::GroupService.new(milestones).milestone('Milestone v1.2')
end
it 'should have exactly one group milestone' do @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones)
expect(@group_milestones.title).to eq('Milestone v1.2') end
end
it 'should have exactly one group milestone' do
expect(@global_milestone.title).to eq('Milestone v1.2')
end
it 'should have all project milestones with the same title' do it 'should have all project milestones with the same title' do
expect(@group_milestones.milestones.count).to eq(3) expect(@global_milestone.milestones.count).to eq(3)
end
end end
end end
end end
...@@ -184,6 +184,12 @@ describe ProjectWiki do ...@@ -184,6 +184,12 @@ describe ProjectWiki do
subject.create_page("test page", "some content", :markdown, "commit message") subject.create_page("test page", "some content", :markdown, "commit message")
expect(subject.pages.first.page.version.message).to eq("commit message") expect(subject.pages.first.page.version.message).to eq("commit message")
end end
it 'updates project activity' do
expect(subject).to receive(:update_project_activity)
subject.create_page('Test Page', 'This is content')
end
end end
describe "#update_page" do describe "#update_page" do
...@@ -205,6 +211,12 @@ describe ProjectWiki do ...@@ -205,6 +211,12 @@ describe ProjectWiki do
it "sets the correct commit message" do it "sets the correct commit message" do
expect(@page.version.message).to eq("updated page") expect(@page.version.message).to eq("updated page")
end end
it 'updates project activity' do
expect(subject).to receive(:update_project_activity)
subject.update_page(@gollum_page, 'Yet more content', :markdown, 'Updated page again')
end
end end
describe "#delete_page" do describe "#delete_page" do
...@@ -217,6 +229,12 @@ describe ProjectWiki do ...@@ -217,6 +229,12 @@ describe ProjectWiki do
subject.delete_page(@page) subject.delete_page(@page)
expect(subject.pages.count).to eq(0) expect(subject.pages.count).to eq(0)
end end
it 'updates project activity' do
expect(subject).to receive(:update_project_activity)
subject.delete_page(@page)
end
end end
private private
......
...@@ -3,13 +3,15 @@ require 'spec_helper' ...@@ -3,13 +3,15 @@ require 'spec_helper'
describe Issues::UpdateService do describe Issues::UpdateService do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:issue) { create(:issue, title: 'Old title') } let(:user3) { create(:user) }
let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) }
let(:label) { create(:label) } let(:label) { create(:label) }
let(:project) { issue.project } let(:project) { issue.project }
before do before do
project.team << [user, :master] project.team << [user, :master]
project.team << [user2, :developer] project.team << [user2, :developer]
project.team << [user3, :developer]
end end
describe 'execute' do describe 'execute' do
...@@ -34,9 +36,11 @@ describe Issues::UpdateService do ...@@ -34,9 +36,11 @@ describe Issues::UpdateService do
it { expect(@issue.labels.count).to eq(1) } it { expect(@issue.labels.count).to eq(1) }
it { expect(@issue.labels.first.title).to eq('Bug') } it { expect(@issue.labels.first.title).to eq('Bug') }
it 'should send email to user2 about assign of new issue' do it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do
email = ActionMailer::Base.deliveries.last deliveries = ActionMailer::Base.deliveries
expect(email.to.first).to eq(user2.email) email = deliveries.last
recipients = deliveries.last(2).map(&:to).flatten
expect(recipients).to include(user2.email, user3.email)
expect(email.subject).to include(issue.title) expect(email.subject).to include(issue.title)
end end
......
...@@ -3,7 +3,8 @@ require 'spec_helper' ...@@ -3,7 +3,8 @@ require 'spec_helper'
describe MergeRequests::UpdateService do describe MergeRequests::UpdateService do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, :simple, title: 'Old title') } let(:user3) { create(:user) }
let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) }
let(:project) { merge_request.project } let(:project) { merge_request.project }
let(:label) { create(:label) } let(:label) { create(:label) }
...@@ -47,9 +48,11 @@ describe MergeRequests::UpdateService do ...@@ -47,9 +48,11 @@ describe MergeRequests::UpdateService do
with(@merge_request, 'update') with(@merge_request, 'update')
end end
it 'should send email to user2 about assign of new merge_request' do it 'should send email to user2 about assign of new merge request and email to user3 about merge request unassignment' do
email = ActionMailer::Base.deliveries.last deliveries = ActionMailer::Base.deliveries
expect(email.to.first).to eq(user2.email) email = deliveries.last
recipients = deliveries.last(2).map(&:to).flatten
expect(recipients).to include(user2.email, user3.email)
expect(email.subject).to include(merge_request.title) expect(email.subject).to include(merge_request.title)
end end
......
require 'spec_helper'
describe Milestones::CloseService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) }
before do
project.team << [user, :master]
end
describe :execute do
before do
Milestones::CloseService.new(project, user, {}).execute(milestone)
end
it { expect(milestone).to be_valid }
it { expect(milestone).to be_closed }
describe :event do
let(:event) { Event.first }
it { expect(event.milestone).to be_truthy }
it { expect(event.target).to eq(milestone) }
it { expect(event.action_name).to eq('closed') }
end
end
end
require 'spec_helper'
describe Milestones::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
describe :execute do
context "valid params" do
before do
project.team << [user, :master]
opts = {
title: 'v2.1.9',
description: 'Patch release to fix security issue'
}
@milestone = Milestones::CreateService.new(project, user, opts).execute
end
it { expect(@milestone).to be_valid }
it { expect(@milestone.title).to eq('v2.1.9') }
end
end
end
...@@ -149,7 +149,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -149,7 +149,7 @@ describe 'gitlab:app namespace rake task' do
# Redirect STDOUT and run the rake task # Redirect STDOUT and run the rake task
orig_stdout = $stdout orig_stdout = $stdout
$stdout = StringIO.new $stdout = StringIO.new
ENV["SKIP"] = "repositories" ENV["SKIP"] = "repositories,uploads"
run_rake_task('gitlab:backup:create') run_rake_task('gitlab:backup:create')
$stdout = orig_stdout $stdout = orig_stdout
...@@ -180,6 +180,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -180,6 +180,7 @@ describe 'gitlab:app namespace rake task' do
expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke
expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke
expect(Rake::Task["gitlab:backup:uploads:restore"]).not_to receive :invoke
expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke
expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke
expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke
......
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