Commit 76f2e065ea51f6364b1ca3dc0b1d29d6f52a4b04

Authored by Sytse Sijbrandij
2 parents 8ccad7c3 48d37272

Merge branch 'master' into install-guide-improvements

Conflicts:
	doc/install/installation.md
Showing 64 changed files with 661 additions and 384 deletions   Show diff stats
CHANGELOG
... ... @@ -24,6 +24,7 @@ v 5.3.0
24 24 - init.d: Ensure socket is removed before starting service
25 25 - Admin area: Style teams:index, group:show pages
26 26 - Own page for failed forking
  27 + - Scrum view for milestone
27 28  
28 29 v 5.2.0
29 30 - Turbolinks
... ...
Gemfile
... ... @@ -29,7 +29,7 @@ gem 'gitlab_git', '~> 1.3.0'
29 29 gem 'gitlab-grack', '~> 1.0.1', require: 'grack'
30 30  
31 31 # LDAP Auth
32   -gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
  32 +gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap"
33 33  
34 34 # Syntax highlighter
35 35 gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb'
... ... @@ -72,6 +72,9 @@ gem "seed-fu"
72 72 gem "redcarpet", "~> 2.2.2"
73 73 gem "github-markup", "~> 0.7.4", require: 'github/markup'
74 74  
  75 +# Asciidoc to HTML
  76 +gem "asciidoctor"
  77 +
75 78 # Servers
76 79 gem "puma", '~> 2.0.1'
77 80  
... ...
Gemfile.lock
... ... @@ -46,6 +46,7 @@ GEM
46 46 rails (~> 3.0)
47 47 addressable (2.3.4)
48 48 arel (3.0.2)
  49 + asciidoctor (0.1.3)
49 50 awesome_print (1.1.0)
50 51 backports (2.6.7)
51 52 bcrypt-ruby (3.0.1)
... ... @@ -168,8 +169,8 @@ GEM
168 169 github-linguist (~> 2.3.4)
169 170 gitlab-grit (~> 2.5.1)
170 171 gitlab_meta (5.0)
171   - gitlab_omniauth-ldap (1.0.2)
172   - net-ldap (~> 0.2.2)
  172 + gitlab_omniauth-ldap (1.0.3)
  173 + net-ldap (~> 0.3.1)
173 174 omniauth (~> 1.0)
174 175 pyu-ruby-sasl (~> 0.0.3.1)
175 176 rubyntlm (~> 0.1.1)
... ... @@ -265,7 +266,7 @@ GEM
265 266 multi_xml (0.5.3)
266 267 multipart-post (1.2.0)
267 268 mysql2 (0.3.11)
268   - net-ldap (0.2.2)
  269 + net-ldap (0.3.1)
269 270 nokogiri (1.5.9)
270 271 oauth (0.4.7)
271 272 oauth2 (0.8.1)
... ... @@ -517,6 +518,7 @@ PLATFORMS
517 518 DEPENDENCIES
518 519 acts-as-taggable-on
519 520 annotate!
  521 + asciidoctor
520 522 awesome_print
521 523 better_errors
522 524 binding_of_caller
... ... @@ -544,7 +546,7 @@ DEPENDENCIES
544 546 gitlab-pygments.rb (~> 0.3.2)
545 547 gitlab_git (~> 1.3.0)
546 548 gitlab_meta (= 5.0)
547   - gitlab_omniauth-ldap (= 1.0.2)
  549 + gitlab_omniauth-ldap (= 1.0.3)
548 550 gon
549 551 grape (~> 0.4.1)
550 552 grape-entity (~> 0.3.0)
... ...
VERSION
1   -5.3.0.beta1
  1 +5.3.0
... ...
app/assets/javascripts/milestones.js.coffee
... ... @@ -1,14 +0,0 @@
1   -$ ->
2   - $('.milestone-issue-filter li[data-closed]').addClass('hide')
3   -
4   - $('.milestone-issue-filter ul.nav li a').click ->
5   - $('.milestone-issue-filter li').toggleClass('active')
6   - $('.milestone-issue-filter li[data-closed]').toggleClass('hide')
7   - false
8   -
9   - $('.milestone-merge-requests-filter li[data-closed]').addClass('hide')
10   -
11   - $('.milestone-merge-requests-filter ul.nav li a').click ->
12   - $('.milestone-merge-requests-filter li').toggleClass('active')
13   - $('.milestone-merge-requests-filter li[data-closed]').toggleClass('hide')
14   - false
app/assets/stylesheets/gitlab_bootstrap/nav.scss
... ... @@ -57,6 +57,7 @@
57 57 border-color: #CCC;
58 58 border-bottom: 1px solid #fff;
59 59 color: #333;
  60 + font-weight: bold;
60 61 }
61 62 }
62 63 }
... ...
app/assets/stylesheets/sections/events.scss
... ... @@ -58,6 +58,7 @@
58 58 background: #f9f9f9;
59 59 border-radius: 0;
60 60 color: #555;
  61 + margin: 0 20px;
61 62 }
62 63  
63 64 .note-file-attach {
... ...
app/assets/stylesheets/sections/notes.scss
... ... @@ -92,6 +92,10 @@ ul.notes {
92 92 .note-body {
93 93 @include md-typography;
94 94 margin-left: 45px;
  95 +
  96 + .highlight {
  97 + @include border-radius(4px);
  98 + }
95 99 }
96 100 .note-header {
97 101 padding-bottom: 5px;
... ...
app/contexts/issues/list_context.rb
... ... @@ -8,7 +8,7 @@ module Issues
8 8 @issues = case params[:status]
9 9 when issues_filter[:all] then @project.issues
10 10 when issues_filter[:closed] then @project.issues.closed
11   - when issues_filter[:to_me] then @project.issues.assigned(current_user)
  11 + when issues_filter[:to_me] then @project.issues.assigned_to(current_user)
12 12 when issues_filter[:by_me] then @project.issues.authored(current_user)
13 13 else @project.issues.opened
14 14 end
... ...
app/contexts/projects/create_context.rb
... ... @@ -51,6 +51,7 @@ module Projects
51 51 if shell.import_repository(@project.path_with_namespace, @project.import_url)
52 52 # We should create satellite for imported repo
53 53 @project.satellite.create unless @project.satellite.exists?
  54 + @project.imported = true
54 55 true
55 56 else
56 57 @project.errors.add(:import_url, 'cannot clone repo')
... ...
app/controllers/admin/groups_controller.rb
... ... @@ -8,10 +8,6 @@ class Admin::GroupsController < Admin::ApplicationController
8 8 end
9 9  
10 10 def show
11   - @projects = Project.scoped
12   - @projects = @projects.not_in_group(@group) if @group.projects.present?
13   - @projects = @projects.all
14   - @projects.reject!(&:empty_repo?)
15 11 end
16 12  
17 13 def new
... ...
app/controllers/admin/users_controller.rb
... ... @@ -55,8 +55,14 @@ class Admin::UsersController < Admin::ApplicationController
55 55 def create
56 56 admin = params[:user].delete("admin")
57 57  
58   - @admin_user = User.new(params[:user], as: :admin)
  58 + opts = {
  59 + force_random_password: true,
  60 + password_expires_at: Time.now
  61 + }
  62 +
  63 + @admin_user = User.new(params[:user].merge(opts), as: :admin)
59 64 @admin_user.admin = (admin && admin.to_i > 0)
  65 + @admin_user.created_by_id = current_user.id
60 66  
61 67 respond_to do |format|
62 68 if @admin_user.save
... ...
app/controllers/application_controller.rb
1 1 class ApplicationController < ActionController::Base
2 2 before_filter :authenticate_user!
3 3 before_filter :reject_blocked!
  4 + before_filter :check_password_expiration
4 5 before_filter :set_current_user_for_thread
5 6 before_filter :add_abilities
6 7 before_filter :dev_tools if Rails.env == 'development'
... ... @@ -156,4 +157,10 @@ class ApplicationController &lt; ActionController::Base
156 157 gon.gravatar_url = request.ssl? || Gitlab.config.gitlab.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
157 158 gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
158 159 end
  160 +
  161 + def check_password_expiration
  162 + if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now
  163 + redirect_to new_profile_password_path and return
  164 + end
  165 + end
159 166 end
... ...
app/controllers/passwords_controller.rb 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +class PasswordsController < ApplicationController
  2 + layout 'navless'
  3 +
  4 + skip_before_filter :check_password_expiration
  5 +
  6 + before_filter :set_user
  7 + before_filter :set_title
  8 +
  9 + def new
  10 + end
  11 +
  12 + def create
  13 + new_password = params[:user][:password]
  14 + new_password_confirmation = params[:user][:password_confirmation]
  15 +
  16 + result = @user.update_attributes(
  17 + password: new_password,
  18 + password_confirmation: new_password_confirmation
  19 + )
  20 +
  21 + if result
  22 + @user.update_attributes(password_expires_at: nil)
  23 + redirect_to root_path, notice: 'Password successfully changed'
  24 + else
  25 + render :new
  26 + end
  27 + end
  28 +
  29 + private
  30 +
  31 + def set_user
  32 + @user = current_user
  33 + end
  34 +
  35 + def set_title
  36 + @title = "New password"
  37 + end
  38 +end
... ...
app/controllers/snippets_controller.rb
... ... @@ -7,8 +7,12 @@ class SnippetsController &lt; ApplicationController
7 7 # Allow destroy snippet
8 8 before_filter :authorize_admin_snippet!, only: [:destroy]
9 9  
  10 + before_filter :set_title
  11 +
10 12 respond_to :html
11 13  
  14 + layout 'navless'
  15 +
12 16 def index
13 17 @snippets = Snippet.public.fresh.non_expired.page(params[:page]).per(20)
14 18 end
... ... @@ -98,4 +102,8 @@ class SnippetsController &lt; ApplicationController
98 102 def authorize_admin_snippet!
99 103 return render_404 unless can?(current_user, :admin_personal_snippet, @snippet)
100 104 end
  105 +
  106 + def set_title
  107 + @title = 'Snippets'
  108 + end
101 109 end
... ...
app/helpers/application_helper.rb
... ... @@ -142,7 +142,7 @@ module ApplicationHelper
142 142 end
143 143  
144 144 def user_color_scheme_class
145   - COLOR_SCHEMES[current_user.try(:color_scheme_id)]
  145 + COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
146 146 end
147 147  
148 148 # Define whenever show last push event
... ...
app/helpers/projects_helper.rb
... ... @@ -48,4 +48,36 @@ module ProjectsHelper
48 48 def remove_project_message(project)
49 49 "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
50 50 end
  51 +
  52 + def project_nav_tabs
  53 + @nav_tabs ||= get_project_nav_tabs(@project, current_user)
  54 + end
  55 +
  56 + def project_nav_tab?(name)
  57 + project_nav_tabs.include? name
  58 + end
  59 +
  60 + private
  61 +
  62 + def get_project_nav_tabs(project, current_user)
  63 + nav_tabs = [:home]
  64 +
  65 + if project.repo_exists? && can?(current_user, :download_code, project)
  66 + nav_tabs << [:files, :commits, :network, :graphs]
  67 + end
  68 +
  69 + if project.repo_exists? && project.merge_requests_enabled
  70 + nav_tabs << :merge_requests
  71 + end
  72 +
  73 + if can?(current_user, :admin_project, project)
  74 + nav_tabs << :settings
  75 + end
  76 +
  77 + [:issues, :wiki, :wall, :snippets].each do |feature|
  78 + nav_tabs << feature if project.send :"#{feature}_enabled"
  79 + end
  80 +
  81 + nav_tabs.flatten
  82 + end
51 83 end
... ...
app/models/concerns/issuable.rb
... ... @@ -22,8 +22,10 @@ module Issuable
22 22 scope :closed, -> { with_state(:closed) }
23 23 scope :of_group, ->(group) { where(project_id: group.project_ids) }
24 24 scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
25   - scope :assigned, ->(u) { where(assignee_id: u.id)}
  25 + scope :assigned_to, ->(u) { where(assignee_id: u.id)}
26 26 scope :recent, -> { order("created_at DESC") }
  27 + scope :assigned, -> { where("assignee_id IS NOT NULL") }
  28 + scope :unassigned, -> { where("assignee_id IS NULL") }
27 29  
28 30 delegate :name,
29 31 :email,
... ...
app/models/issue.rb
... ... @@ -27,7 +27,7 @@ class Issue &lt; ActiveRecord::Base
27 27  
28 28 scope :cared, ->(user) { where(assignee_id: user) }
29 29 scope :authored, ->(user) { where(author_id: user) }
30   - scope :open_for, ->(user) { opened.assigned(user) }
  30 + scope :open_for, ->(user) { opened.assigned_to(user) }
31 31  
32 32 state_machine :state, initial: :opened do
33 33 event :close do
... ...
app/models/merge_request.rb
... ... @@ -91,6 +91,15 @@ class MergeRequest &lt; ActiveRecord::Base
91 91 if target_branch == source_branch
92 92 errors.add :branch_conflict, "You can not use same branch for source and target branches"
93 93 end
  94 +
  95 + if opened? || reopened?
  96 + similar_mrs = self.project.merge_requests.where(source_branch: source_branch, target_branch: target_branch).opened
  97 + similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
  98 +
  99 + if similar_mrs.any?
  100 + errors.add :base, "There is already an open merge request for this branches"
  101 + end
  102 + end
94 103 end
95 104  
96 105 def reload_code
... ...
app/models/namespace.rb
... ... @@ -27,6 +27,7 @@ class Namespace &lt; ActiveRecord::Base
27 27 message: "only letters, digits, spaces & '_' '-' '.' allowed." }
28 28 validates :description, length: { within: 0..255 }
29 29 validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
  30 + exclusion: { in: Gitlab::Blacklist.path },
30 31 format: { with: Gitlab::Regex.path_regex,
31 32 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
32 33  
... ...
app/models/project.rb
... ... @@ -79,6 +79,7 @@ class Project &lt; ActiveRecord::Base
79 79 format: { with: Gitlab::Regex.project_name_regex,
80 80 message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
81 81 validates :path, presence: true, length: { within: 0..255 },
  82 + exclusion: { in: Gitlab::Blacklist.path },
82 83 format: { with: Gitlab::Regex.path_regex,
83 84 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
84 85 validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
... ... @@ -92,7 +93,7 @@ class Project &lt; ActiveRecord::Base
92 93 format: { with: URI::regexp(%w(http https)), message: "should be a valid url" },
93 94 if: :import?
94 95  
95   - validate :check_limit, :repo_name
  96 + validate :check_limit
96 97  
97 98 # Scopes
98 99 scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
... ... @@ -166,14 +167,6 @@ class Project &lt; ActiveRecord::Base
166 167 errors[:base] << ("Can't check your ability to create project")
167 168 end
168 169  
169   - def repo_name
170   - denied_paths = %w(admin dashboard groups help profile projects search)
171   -
172   - if denied_paths.include?(path)
173   - errors.add(:path, "like #{path} is not allowed")
174   - end
175   - end
176   -
177 170 def to_param
178 171 if namespace
179 172 namespace.path + "/" + path
... ... @@ -420,6 +413,10 @@ class Project &lt; ActiveRecord::Base
420 413 !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
421 414 end
422 415  
  416 + def imported?
  417 + imported
  418 + end
  419 +
423 420 def rename_repo
424 421 old_path_with_namespace = File.join(namespace_dir, path_was)
425 422 new_path_with_namespace = File.join(namespace_dir, path)
... ...
app/models/user.rb
... ... @@ -42,8 +42,11 @@ class User &lt; ActiveRecord::Base
42 42  
43 43 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
44 44 :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
45   - :extern_uid, :provider, as: [:default, :admin]
46   - attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin
  45 + :extern_uid, :provider, :password_expires_at,
  46 + as: [:default, :admin]
  47 +
  48 + attr_accessible :projects_limit, :can_create_team, :can_create_group,
  49 + as: :admin
47 50  
48 51 attr_accessor :force_random_password
49 52  
... ... @@ -104,6 +107,7 @@ class User &lt; ActiveRecord::Base
104 107 validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
105 108 validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
106 109 validates :username, presence: true, uniqueness: true,
  110 + exclusion: { in: Gitlab::Blacklist.path },
107 111 format: { with: Gitlab::Regex.username_regex,
108 112 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
109 113  
... ... @@ -363,4 +367,8 @@ class User &lt; ActiveRecord::Base
363 367 def accessible_deploy_keys
364 368 DeployKey.in_projects(self.master_projects).uniq
365 369 end
  370 +
  371 + def created_by
  372 + User.find_by_id(created_by_id) if created_by_id
  373 + end
366 374 end
... ...
app/observers/project_observer.rb
1 1 class ProjectObserver < BaseObserver
2 2 def after_create(project)
3   - unless project.forked?
4   - GitlabShellWorker.perform_async(
5   - :add_repository,
6   - project.path_with_namespace
7   - )
8   -
9   - log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
10   - end
  3 + return true if project.forked? || project.imported?
  4 +
  5 + GitlabShellWorker.perform_async(
  6 + :add_repository,
  7 + project.path_with_namespace
  8 + )
  9 +
  10 + log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
11 11 end
12 12  
13 13 def after_update(project)
... ...
app/views/admin/teams/members/new.html.haml
1 1 %h3.page_title
2   - Team: #{@team.name}
3   -
4   -%fieldset
5   - %legend Members (#{@team.members.count})
6   - = form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
7   - %table#members_list
8   - %thead
9   - %tr
10   - %th User name
11   - %th Default project access
12   - %th Team access
13   - %th
14   - - @team.members.each do |member|
15   - %tr.member
16   - %td
17   - = link_to [:admin, member] do
18   - = member.name
19   - %small= "(#{member.email})"
20   - %td= @team.human_default_projects_access(member)
21   - %td= @team.admin?(member) ? "Admin" : "Member"
22   - %td
23   - %tr
24   - %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_username), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
25   - %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
26   - %td
27   - %span= check_box_tag :group_admin
28   - %span Admin?
29   - %td= submit_tag 'Add', class: "btn btn-primary", id: :add_members_to_team
  2 + New members for
  3 + = link_to @team.name, admin_team_path(@team)
  4 + team
  5 +%hr
  6 += form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
  7 + - if @team.errors.any?
  8 + .alert.alert-error
  9 + %span= @team.errors.full_messages.first
  10 + .clearfix
  11 + = label_tag :user_ids do
  12 + Users to add
  13 + .input
  14 + = select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_username), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
  15 + .clearfix.group-description-holder
  16 + = label_tag :default_project_access do
  17 + Default permission in projects
  18 + .input
  19 + = select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
  20 + .clearfix
  21 + = label_tag :group_admin do
  22 + Is team admin
  23 + .input
  24 + = check_box_tag :group_admin
  25 + .clearfix.form-actions
  26 + = submit_tag 'Add users into team', class: "btn btn-primary", id: :add_members_to_team
  27 + = link_to 'Cancel', :back, class: "btn"
... ...
app/views/admin/users/_form.html.haml
... ... @@ -24,19 +24,25 @@
24 24 = f.text_field :email, required: true, autocomplete: "off"
25 25 %span.help-inline * required
26 26  
27   - %fieldset
28   - %legend Password
29   - .clearfix
30   - = f.label :password
31   - .input= f.password_field :password, disabled: f.object.force_random_password
32   - .clearfix
33   - = f.label :password_confirmation
34   - .input= f.password_field :password_confirmation, disabled: f.object.force_random_password
35   - -if f.object.new_record?
  27 + - if @admin_user.new_record?
  28 + %fieldset
  29 + %legend Password
  30 + .clearfix
  31 + = f.label :password
  32 + .input
  33 + %strong
  34 + A temporary password will be generated and sent to user.
  35 + %br
  36 + User will be forced to change it after first sign in
  37 + - else
  38 + %fieldset
  39 + %legend Password
  40 + .clearfix
  41 + = f.label :password
  42 + .input= f.password_field :password, disabled: f.object.force_random_password
36 43 .clearfix
37   - = f.label :force_random_password do
38   - %span Generate random password
39   - .input= f.check_box :force_random_password, {}, true, nil
  44 + = f.label :password_confirmation
  45 + .input= f.password_field :password_confirmation, disabled: f.object.force_random_password
40 46  
41 47 %fieldset
42 48 %legend Access
... ...
app/views/admin/users/show.html.haml
  1 +%h3.page_title
  2 + User:
  3 + = @admin_user.name
  4 + - if @admin_user.blocked?
  5 + %span.cred (Blocked)
  6 + - if @admin_user.admin
  7 + %span.cred (Admin)
  8 +
  9 + .pull-right
  10 + = link_to edit_admin_user_path(@admin_user), class: "btn grouped btn-small" do
  11 + %i.icon-edit
  12 + Edit
  13 + - unless @admin_user == current_user
  14 + - if @admin_user.blocked?
  15 + = link_to 'Unblock', unblock_admin_user_path(@admin_user), method: :put, class: "btn grouped btn-small success"
  16 + - else
  17 + = link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn grouped btn-small btn-remove"
  18 + = link_to 'Destroy', [:admin, @admin_user], confirm: "USER #{@admin_user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn grouped btn-small btn-remove"
  19 +%hr
  20 +
1 21 .row
2 22 .span6
3   - %h3.page_title
4   - = image_tag gravatar_icon(@admin_user.email, 90), class: "avatar s90"
5   - = @admin_user.name
6   - - if @admin_user.blocked?
7   - %span.cred (Blocked)
8   - - if @admin_user.admin
9   - %span.cred (Admin)
10   - .pull-right
11   - = link_to edit_admin_user_path(@admin_user), class: "btn pull-right" do
12   - %i.icon-edit
13   - Edit
14   - %br
15   - %small @#{@admin_user.username}
16   - %br
17   - %small member since #{@admin_user.created_at.stamp("Nov 12, 2031")}
18   - .clearfix
19   - %hr
20   - %p
21   - %span.btn.btn-small
22   - %i.icon-envelope
23   - = mail_to @admin_user.email
24   - - unless @admin_user == current_user
25   - - if @admin_user.blocked?
26   - = link_to 'Unblock', unblock_admin_user_path(@admin_user), method: :put, class: "btn btn-small success"
27   - - else
28   - = link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
29   - = link_to 'Destroy', [:admin, @admin_user], confirm: "USER #{@admin_user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-small btn-remove"
  23 + .ui-box
  24 + %h5.title
  25 + Account:
  26 + .pull-right
  27 + = image_tag gravatar_icon(@admin_user.email, 32), class: "avatar s32"
  28 + %ul.well-list
  29 + %li
  30 + %span.light Name:
  31 + %strong= @admin_user.name
  32 + %li
  33 + %span.light Username:
  34 + %strong
  35 + = @admin_user.username
  36 + %li
  37 + %span.light Email:
  38 + %strong
  39 + = mail_to @admin_user.email
  40 +
  41 + %li
  42 + %span.light Member since:
  43 + %strong
  44 + = @admin_user.created_at.stamp("Nov 12, 2031")
  45 +
  46 + %li
  47 + %span.light Last sign-in at:
  48 + %strong
  49 + - if @admin_user.last_sign_in_at
  50 + = @admin_user.last_sign_in_at.stamp("Nov 12, 2031")
  51 + - else
  52 + never
  53 +
  54 + - if @admin_user.ldap_user?
  55 + %li
  56 + %span.light LDAP uid:
  57 + %strong
  58 + = @admin_user.extern_uid
  59 +
  60 + - if @admin_user.created_by
  61 + %li
  62 + %span.light Created by:
  63 + %strong
  64 + = link_to @admin_user.created_by.name, [:admin, @admin_user.created_by]
  65 +
30 66 %hr
31 67 %h5
32 68 Add User to Projects
... ... @@ -67,11 +103,11 @@
67 103  
68 104  
69 105 .span6
70   - = render 'users/profile', user: @admin_user
71 106 .ui-box
72 107 %h5.title Projects (#{@projects.count})
73 108 %ul.well-list
74 109 - @projects.sort_by(&:name_with_namespace).each do |project|
  110 + - tm = project.team.get_tm(@admin_user.id)
75 111 %li
76 112 = link_to admin_project_path(project), class: dom_class(project) do
77 113 - if project.namespace
... ... @@ -79,16 +115,17 @@
79 115 \/
80 116 %strong.well-title
81 117 = truncate(project.name, length: 45)
82   - %span.pull-right.light
83   - - if project.owner == @admin_user
84   - %i.icon-wrench
85   - - tm = project.team.get_tm(@admin_user.id)
86   - - if tm
87   - = tm.project_access_human
88   - = link_to edit_admin_project_member_path(project, tm.user), class: "btn btn-small" do
  118 +
  119 + - if project.owner == @admin_user
  120 + %span.label.label-info owner
  121 +
  122 + - if tm
  123 + .pull-right
  124 + = link_to edit_admin_project_member_path(project, tm.user), class: "btn grouped btn-small" do
89 125 %i.icon-edit
90   - = link_to admin_project_member_path(project, tm.user), confirm: remove_from_project_team_message(project, @admin_user), method: :delete, class: "btn btn-small btn-remove" do
  126 + = link_to admin_project_member_path(project, tm.user), confirm: remove_from_project_team_message(project, @admin_user), method: :delete, class: "btn grouped btn-small btn-remove" do
91 127 %i.icon-remove
92   - %p.light
93   - %i.icon-wrench
94   - &ndash; user is a project owner
  128 +
  129 + .pull-right.light
  130 + = tm.project_access_human
  131 + &nbsp;
... ...
app/views/devise/sessions/_oauth_providers.html.haml
1   -- if enabled_oauth_providers.present?
  1 +- providers = (enabled_oauth_providers - [:ldap])
  2 +- if providers.present?
2 3 %hr
3 4 %div{:'data-no-turbolink' => 'data-no-turbolink'}
4 5 %span Sign in with: &nbsp;
5   - - (enabled_oauth_providers - [:ldap]).each do |provider|
  6 + - providers.each do |provider|
6 7 %span
7 8 - if default_providers.include?(provider)
8 9 = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
... ...
app/views/edit_tree/show.html.haml
... ... @@ -23,7 +23,7 @@
23 23 = hidden_field_tag 'last_commit', @last_commit
24 24 = hidden_field_tag 'content', '', id: :file_content
25 25 .commit-button-annotation
26   - = button_tag "Commit", class: 'btn commit-btn js-commit-button'
  26 + = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary'
27 27 .message
28 28 to branch
29 29 %strong= @ref
... ...
app/views/layouts/nav/_project.html.haml
... ... @@ -3,43 +3,48 @@
3 3 = link_to project_path(@project), title: "Project" do
4 4 %i.icon-home
5 5  
6   - - unless @project.empty_repo?
7   - - if can? current_user, :download_code, @project
8   - = nav_link(controller: %w(tree blob blame)) do
9   - = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
10   - = nav_link(controller: %w(commit commits compare repositories protected_branches)) do
11   - = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref)
12   - = nav_link(controller: %w(network)) do
13   - = link_to "Network", project_network_path(@project, @ref || @repository.root_ref)
14   - = nav_link(controller: %w(graphs)) do
15   - = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref)
16   -
17   - - if @project.issues_enabled
  6 + - if project_nav_tab? :files
  7 + = nav_link(controller: %w(tree blob blame)) do
  8 + = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
  9 +
  10 + - if project_nav_tab? :commits
  11 + = nav_link(controller: %w(commit commits compare repositories protected_branches)) do
  12 + = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref)
  13 +
  14 + - if project_nav_tab? :network
  15 + = nav_link(controller: %w(network)) do
  16 + = link_to "Network", project_network_path(@project, @ref || @repository.root_ref)
  17 +
  18 + - if project_nav_tab? :graphs
  19 + = nav_link(controller: %w(graphs)) do
  20 + = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref)
  21 +
  22 + - if project_nav_tab? :issues
18 23 = nav_link(controller: %w(issues milestones labels)) do
19 24 = link_to url_for_project_issues do
20 25 Issues
21 26 - if @project.used_default_issues_tracker?
22 27 %span.count.issue_counter= @project.issues.opened.count
23 28  
24   - - if @project.repo_exists? && @project.merge_requests_enabled
  29 + - if project_nav_tab? :merge_requests
25 30 = nav_link(controller: :merge_requests) do
26 31 = link_to project_merge_requests_path(@project) do
27 32 Merge Requests
28 33 %span.count.merge_counter= @project.merge_requests.opened.count
29 34  
30   - - if @project.wiki_enabled
  35 + - if project_nav_tab? :wiki
31 36 = nav_link(controller: :wikis) do
32 37 = link_to 'Wiki', project_wiki_path(@project, :home)
33 38  
34   - - if @project.wall_enabled
  39 + - if project_nav_tab? :wall
35 40 = nav_link(controller: :walls) do
36 41 = link_to 'Wall', project_wall_path(@project)
37 42  
38   - - if @project.snippets_enabled
  43 + - if project_nav_tab? :snippets
39 44 = nav_link(controller: :snippets) do
40 45 = link_to 'Snippets', project_snippets_path(@project)
41 46  
42   - - if can? current_user, :admin_project, @project
  47 + - if project_nav_tab? :settings
43 48 = nav_link(html_options: {class: "#{project_tab_class}"}) do
44 49 = link_to edit_project_path(@project), class: "stat-tab tab " do
45 50 Settings
... ...
app/views/layouts/nav/_team.html.haml
... ... @@ -18,7 +18,7 @@
18 18 Members
19 19 %span.count= @team.members.count
20 20  
21   - - if can? current_user, :admin_user_team, @team
  21 + - if can? current_user, :manage_user_team, @team
22 22 = nav_link(path: 'teams#edit') do
23 23 = link_to edit_team_path(@team), class: "stat-tab tab " do
24 24 Settings
... ...
app/views/layouts/snippets.html.haml
... ... @@ -1,20 +0,0 @@
1   -!!! 5
2   -%html{ lang: "en"}
3   - = render "layouts/head", title: "Snipepts"
4   - %body{class: "#{app_theme} application", :'data-page' => body_data_page}
5   - = render "layouts/head_panel", title: "Snippets"
6   - = render "layouts/flash"
7   - %nav.main-nav
8   - .container
9   - %ul
10   - = nav_link(path: 'snippets#user_index', html_options: {class: 'home'}) do
11   - = link_to user_snippets_path(current_user), title: "My Snippets" do
12   - %i.icon-home
13   - = nav_link(path: 'snippets#new') do
14   - = link_to new_snippet_path do
15   - New snippet
16   - = nav_link(path: 'snippets#index') do
17   - = link_to snippets_path do
18   - Discover snippets
19   - .container
20   - .content= yield
app/views/milestones/_issues.html.haml 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +.ui-box
  2 + %h5.title= title
  3 + %ul.well-list
  4 + - issues.each do |issue|
  5 + %li
  6 + = link_to [@project, issue] do
  7 + %span.badge{class: issue.closed? ? 'badge-important' : 'badge-info'} ##{issue.id}
  8 + = link_to_gfm truncate(issue.title, length: 60), [@project, issue]
  9 + - if issue.assignee
  10 + .pull-right
  11 + = image_tag gravatar_icon(issue.assignee.email, 16), class: "avatar s16"
... ...
app/views/milestones/_merge_request.html.haml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +%li
  2 + = link_to [@project, merge_request] do
  3 + %span.badge.badge-info ##{merge_request.id}
  4 + &ndash;
  5 + = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request]
... ...
app/views/milestones/show.html.haml
... ... @@ -55,39 +55,52 @@
55 55 = markdown @milestone.description
56 56  
57 57  
58   -.row
59   - .span6
60   - .ui-box.milestone-issue-filter
61   - .title
62   - %ul.nav.nav-pills
63   - %li.active= link_to('Open Issues', '#')
64   - %li=link_to('All Issues', '#')
65   - %ul.well-list
66   - - @issues.each do |issue|
67   - %li{data: {closed: issue.closed?}}
68   - = link_to [@project, issue] do
69   - %span.badge.badge-info ##{issue.id}
70   - &ndash;
71   - = link_to_gfm truncate(issue.title, length: 60), [@project, issue]
  58 +%ul.nav.nav-tabs
  59 + %li.active
  60 + = link_to '#tab-issues', 'data-toggle' => 'tab' do
  61 + Issues
  62 + %span.badge= @issues.count
  63 + %li
  64 + = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
  65 + Merge Requests
  66 + %span.badge= @merge_requests.count
  67 + %li
  68 + = link_to '#tab-participants', 'data-toggle' => 'tab' do
  69 + Participants
  70 + %span.badge= @users.count
72 71  
73   - .span6
74   - .ui-box.milestone-merge-requests-filter
75   - .title
76   - %ul.nav.nav-pills
77   - %li.active= link_to('Open Merge Requests', '#')
78   - %li=link_to('All Merge Requests', '#')
79   - %ul.well-list
80   - - @merge_requests.each do |merge_request|
81   - %li{data: {closed: merge_request.closed? || merge_request.merged?}}
82   - = link_to [@project, merge_request] do
83   - %span.badge.badge-info ##{merge_request.id}
84   - &ndash;
85   - = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request]
86 72  
87   -%hr
88   -%h6 Participants:
89   -%div
90   - - @users.each do |user|
91   - = link_to_member(@project, user)
  73 +.tab-content
  74 + .tab-pane.active#tab-issues
  75 + .row
  76 + .span4
  77 + = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned)
  78 + .span4
  79 + = render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned)
  80 + .span4
  81 + = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed)
  82 +
  83 + .tab-pane#tab-merge-requests
  84 + .row
  85 + .span6
  86 + .ui-box
  87 + %h5.title Open
  88 + %ul.well-list
  89 + - @merge_requests.opened.each do |merge_request|
  90 + = render 'merge_request', merge_request: merge_request
  91 + .span6
  92 + .ui-box
  93 + %h5.title Closed
  94 + %ul.well-list
  95 + - @merge_requests.closed.each do |merge_request|
  96 + = render 'merge_request', merge_request: merge_request
92 97  
93   -.clearfix
  98 + .tab-pane#tab-participants
  99 + %ul.bordered-list
  100 + - @users.each do |user|
  101 + %li
  102 + = link_to user, title: user.name, class: "dark" do
  103 + = image_tag gravatar_icon(user.email, 32), class: "avatar s32"
  104 + %strong= truncate(user.name, lenght: 40)
  105 + %br
  106 + %small.cgray= user.username
... ...
app/views/notify/new_user_email.html.haml
... ... @@ -8,13 +8,14 @@
8 8 %p
9 9 login..........................................
10 10 %code= @user['email']
11   -%p
12   - - unless Gitlab.config.gitlab.signup_enabled
  11 +
  12 +- if @user.created_by_id
  13 + %p
13 14 password..................................
14 15 %code= @password
15 16  
16   -%p
17   - Please change your password immediately after login.
  17 + %p
  18 + You will be forced to change this password immediately after login.
18 19  
19 20 %p
20 21 = link_to "Click here to login", root_url
... ...
app/views/notify/new_user_email.text.erb
... ... @@ -3,10 +3,11 @@ Hi &lt;%= @user.name %&gt;!
3 3 The Administrator created an account for you. Now you are a member of company GitLab application.
4 4  
5 5 login.................. <%= @user.email %>
6   -<% unless Gitlab.config.gitlab.signup_enabled %>
  6 +<% if @user.created_by_id %>
7 7 password............... <%= @password %>
  8 +
  9 + You will be forced to change this password immediately after login.
8 10 <% end %>
9 11  
10   -Please change your password immediately after login.
11 12  
12 13 Click here to login: <%= url_for(root_url) %>
... ...
app/views/passwords/new.html.haml 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 += form_for @user, url: profile_password_path, method: :post do |f|
  2 + .light-well.padded
  3 + %p.slead
  4 + Please set new password before proceed.
  5 + %br
  6 + After successful password update you will be redirected to login screen
  7 + -if @user.errors.any?
  8 + .alert.alert-error
  9 + %ul
  10 + - @user.errors.full_messages.each do |msg|
  11 + %li= msg
  12 +
  13 + .clearfix
  14 + = f.label :password
  15 + .input= f.password_field :password, required: true
  16 + .clearfix
  17 + = f.label :password_confirmation
  18 + .input
  19 + = f.password_field :password_confirmation, required: true
  20 + .clearfix
  21 + .input
  22 + = f.submit 'Set new password', class: "btn btn-create"
... ...
app/views/projects/_settings_nav.html.haml
... ... @@ -5,8 +5,8 @@
5 5 Edit
6 6 = nav_link(controller: [:team_members, :teams]) do
7 7 = link_to project_team_index_path(@project), class: "team-tab tab" do
8   - %i.icon-user
9   - Team
  8 + %i.icon-group
  9 + Project Members
10 10 = nav_link(controller: :deploy_keys) do
11 11 = link_to project_deploy_keys_path(@project) do
12 12 %span
... ... @@ -14,7 +14,7 @@
14 14 = nav_link(controller: :hooks) do
15 15 = link_to project_hooks_path(@project) do
16 16 %span
17   - Hooks
  17 + Web Hooks
18 18 = nav_link(controller: :services) do
19 19 = link_to project_services_path(@project) do
20 20 %span
... ...
app/views/snippets/current_user_index.html.haml
1 1 %h3.page_title
2 2 My Snippets
3 3 %small share code pastes with others out of git repository
4   - = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
5   - Add new snippet
  4 + .pull-right
  5 + = link_to new_snippet_path, class: "btn btn-small add_new grouped btn-primary", title: "New Snippet" do
  6 + Add new snippet
  7 + = link_to snippets_path, class: "btn btn-small grouped" do
  8 + Discover snippets
6 9  
7 10 %hr
8 11  
... ...
app/views/snippets/index.html.haml
1 1 %h3.page_title
2 2 Public snippets
3 3 %small share code pastes with others out of git repository
4   - = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
5   - Add new snippet
  4 +
  5 + .pull-right
  6 + = link_to new_snippet_path, class: "btn btn-small add_new grouped btn-primary", title: "New Snippet" do
  7 + Add new snippet
  8 + = link_to user_snippets_path(current_user), class: "btn btn-small grouped" do
  9 + My snippets
6 10  
7 11 %hr
8 12 .row
... ...
app/views/team_members/index.html.haml
1 1 = render "projects/settings_nav"
2 2 %h3.page_title
3   - Team Members
  3 + Project Members
4 4 (#{@project.users.count})
5 5 %small
6 6 Read more about project permissions
... ...
app/views/teams/edit.html.haml
... ... @@ -5,8 +5,9 @@
5 5 = link_to 'Projects', '#tab-projects', 'data-toggle' => 'tab'
6 6 %li
7 7 = link_to 'Edit Team', '#tab-edit', 'data-toggle' => 'tab'
8   - %li
9   - = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab'
  8 + - if can? current_user, :admin_user_team, @team
  9 + %li
  10 + = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab'
10 11  
11 12 .span9
12 13 .tab-content
... ...
config/environments/production.rb
... ... @@ -52,7 +52,7 @@ Gitlab::Application.configure do
52 52 # config.action_mailer.raise_delivery_errors = false
53 53  
54 54 # Enable threaded mode
55   - config.threadsafe!
  55 + config.threadsafe! unless $rails_rake_task
56 56  
57 57 # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
58 58 # the I18n.default_locale when a translation can not be found)
... ...
config/routes.rb
... ... @@ -123,6 +123,7 @@ Gitlab::Application.routes.draw do
123 123 end
124 124  
125 125 resource :notifications
  126 + resource :password
126 127 end
127 128  
128 129 resources :keys
... ...
db/fixtures/production/001_admin.rb
... ... @@ -3,7 +3,8 @@ admin = User.create(
3 3 name: "Administrator",
4 4 username: 'root',
5 5 password: "5iveL!fe",
6   - password_confirmation: "5iveL!fe"
  6 + password_confirmation: "5iveL!fe",
  7 + password_expires_at: Time.now
7 8 )
8 9  
9 10 admin.projects_limit = 10000
... ...
db/migrate/20130613165816_add_password_expires_at_to_users.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddPasswordExpiresAtToUsers < ActiveRecord::Migration
  2 + def change
  3 + add_column :users, :password_expires_at, :datetime
  4 + end
  5 +end
... ...
db/migrate/20130613173246_add_created_by_id_to_user.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddCreatedByIdToUser < ActiveRecord::Migration
  2 + def change
  3 + add_column :users, :created_by_id, :integer
  4 + end
  5 +end
... ...
db/migrate/20130614132337_add_improted_to_project.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddImprotedToProject < ActiveRecord::Migration
  2 + def change
  3 + add_column :projects, :imported, :boolean, default: false, null: false
  4 + end
  5 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20130522141856) do
  14 +ActiveRecord::Schema.define(:version => 20130614132337) do
15 15  
16 16 create_table "deploy_keys_projects", :force => true do |t|
17 17 t.integer "deploy_key_id", :null => false
... ... @@ -172,6 +172,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130522141856) do
172 172 t.string "issues_tracker_id"
173 173 t.boolean "snippets_enabled", :default => true, :null => false
174 174 t.datetime "last_activity_at"
  175 + t.boolean "imported", :default => false, :null => false
175 176 end
176 177  
177 178 add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
... ... @@ -292,6 +293,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130522141856) do
292 293 t.string "state"
293 294 t.integer "color_scheme_id", :default => 1, :null => false
294 295 t.integer "notification_level", :default => 1, :null => false
  296 + t.datetime "password_expires_at"
  297 + t.integer "created_by_id"
295 298 end
296 299  
297 300 add_index "users", ["admin"], :name => "index_users_on_admin"
... ...
doc/api/README.md
... ... @@ -69,15 +69,15 @@ When listing resources you can pass the following parameters:
69 69  
70 70 ## Contents
71 71  
72   -+ [Users](doc/api/users.md)
73   -+ [Session](doc/api/session.md)
74   -+ [Projects](doc/api/projects.md)
75   -+ [Project Snippets](doc/api/project_snippets.md)
76   -+ [Repositories](doc/api/repositories.md)
77   -+ [Issues](doc/api/issues.md)
78   -+ [Milestones](doc/api/milestones.md)
79   -+ [Notes](doc/api/notes.md)
80   -+ [Deploy Keys](doc/api/deploy_keys.md)
81   -+ [System Hooks](doc/api/system_hooks.md)
82   -+ [Groups](doc/api/groups.md)
83   -+ [User Teams](doc/api/user_teams.md)
  72 ++ [Users](users.md)
  73 ++ [Session](session.md)
  74 ++ [Projects](projects.md)
  75 ++ [Project Snippets](project_snippets.md)
  76 ++ [Repositories](repositories.md)
  77 ++ [Issues](issues.md)
  78 ++ [Milestones](milestones.md)
  79 ++ [Notes](notes.md)
  80 ++ [Deploy Keys](deploy_keys.md)
  81 ++ [System Hooks](system_hooks.md)
  82 ++ [Groups](groups.md)
  83 ++ [User Teams](user_teams.md)
... ...
doc/install/installation.md
... ... @@ -148,10 +148,10 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install
148 148 cd /home/git/gitlab
149 149  
150 150 # Checkout to stable release
151   - sudo -u git -H git checkout 5-2-stable
  151 + sudo -u git -H git checkout 5-3-stable
152 152  
153 153 **Note:**
154   -You can change `5-2-stable` to `master` if you want the *bleeding edge* version, but do so with caution!
  154 +You can change `5-3-stable` to `master` if you want the *bleeding edge* version, but do so with caution!
155 155  
156 156 ## Configure it
157 157  
... ... @@ -352,10 +352,10 @@ GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already
352 352  
353 353 These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation.
354 354  
355   -* Add `gem "omniauth-your-auth-provider"` to the [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/Gemfile#L18)
  355 +* Add `gem "omniauth-your-auth-provider"` to the [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/Gemfile#L18)
356 356 * Run `sudo -u git -H bundle install` to install the new gem(s)
357   -* Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/gitlab.yml.example#L53) as a reference)
358   -* Add icons for the new provider into the [vendor/assets/images/authbuttons](https://github.com/gitlabhq/gitlabhq/tree/5-2-stable/vendor/assets/images/authbuttons) directory (you can find some more popular ones over at https://github.com/intridea/authbuttons)
  357 +* Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/gitlab.yml.example#L53) as a reference)
  358 +* Add icons for the new provider into the [vendor/assets/images/authbuttons](https://github.com/gitlabhq/gitlabhq/tree/5-3-stable/vendor/assets/images/authbuttons) directory (you can find some more popular ones over at https://github.com/intridea/authbuttons)
359 359 * Restart GitLab
360 360  
361 361 ### Examples
... ...
doc/update/5.2-to-5.3.md 0 → 100644
... ... @@ -0,0 +1,80 @@
  1 +# From 5.2 to 5.3
  2 +
  3 +### 0. Backup
  4 +
  5 +It's useful to make a backup just in case things go south:
  6 +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
  7 +
  8 +```bash
  9 +cd /home/git/gitlab
  10 +sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create
  11 +```
  12 +
  13 +### 1. Stop server
  14 +
  15 + sudo service gitlab stop
  16 +
  17 +### 2. Get latest code
  18 +
  19 +```bash
  20 +cd /home/git/gitlab
  21 +sudo -u git -H git fetch
  22 +sudo -u git -H git checkout 5-3-stable
  23 +```
  24 +
  25 +### 3. Install libs, migrations, etc.
  26 +
  27 +```bash
  28 +cd /home/git/gitlab
  29 +
  30 +# MySQL
  31 +sudo -u git -H bundle install --without development test postgres --deployment
  32 +
  33 +#PostgreSQL
  34 +sudo -u git -H bundle install --without development test mysql --deployment
  35 +
  36 +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
  37 +```
  38 +
  39 +### 4. Update config files
  40 +
  41 +* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/gitlab.yml.example but with your settings.
  42 +* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/puma.rb.example but with your settings.
  43 +
  44 +### 5. Update Init script
  45 +
  46 +```bash
  47 +sudo rm /etc/init.d/gitlab
  48 +sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-3-stable/lib/support/init.d/gitlab
  49 +sudo chmod +x /etc/init.d/gitlab
  50 +```
  51 +
  52 +### 6. Start application
  53 +
  54 + sudo service gitlab start
  55 + sudo service nginx restart
  56 +
  57 +### 7. Check application status
  58 +
  59 +Check if GitLab and its environment are configured correctly:
  60 +
  61 + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
  62 +
  63 +To make sure you didn't miss anything run a more thorough check with:
  64 +
  65 + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
  66 +
  67 +If all items are green, then congratulations upgrade complete!
  68 +
  69 +## Things went south? Revert to previous version (5.2)
  70 +
  71 +### 1. Revert the code to the previous version
  72 +Follow the [`upgrade guide from 5.1 to 5.2`](5.1-to-5.2.md), except for the database migration
  73 +(The backup is already migrated to the previous version)
  74 +
  75 +### 2. Restore from the backup:
  76 +
  77 +```bash
  78 +cd /home/git/gitlab
  79 +sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore
  80 +```
... ...
features/project/issues/milestones.feature
... ... @@ -22,5 +22,3 @@ Feature: Project Milestones
22 22 Given the milestone has open and closed issues
23 23 And I click link "v2.2"
24 24 Then I should see 3 issues
25   - When I click link "All Issues"
26   - Then I should see 4 issues
... ...
features/steps/project/project_active_tab.rb
... ... @@ -45,7 +45,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps
45 45 # Sub Tabs: Home
46 46  
47 47 Given 'I click the "Team" tab' do
48   - click_link('Team')
  48 + click_link('Project Members')
49 49 end
50 50  
51 51 Given 'I click the "Attachments" tab' do
... ... @@ -61,7 +61,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps
61 61 end
62 62  
63 63 Given 'I click the "Hooks" tab' do
64   - click_link('Hooks')
  64 + click_link('Web Hooks')
65 65 end
66 66  
67 67 Given 'I click the "Deploy Keys" tab' do
... ... @@ -73,7 +73,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps
73 73 end
74 74  
75 75 Then 'the active sub tab should be Team' do
76   - ensure_active_sub_tab('Team')
  76 + ensure_active_sub_tab('Project Members')
77 77 end
78 78  
79 79 Then 'the active sub tab should be Attachments' do
... ... @@ -89,7 +89,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps
89 89 end
90 90  
91 91 Then 'the active sub tab should be Hooks' do
92   - ensure_active_sub_tab('Hooks')
  92 + ensure_active_sub_tab('Web Hooks')
93 93 end
94 94  
95 95 Then 'the active sub tab should be Deploy Keys' do
... ...
features/steps/project/project_merge_requests.rb
... ... @@ -57,8 +57,8 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
57 57  
58 58 And 'I submit new merge request "Wiki Feature"' do
59 59 fill_in "merge_request_title", with: "Wiki Feature"
60   - select "master", from: "merge_request_source_branch"
61   - select "stable", from: "merge_request_target_branch"
  60 + select "bootstrap", from: "merge_request_source_branch"
  61 + select "master", from: "merge_request_target_branch"
62 62 click_button "Submit merge request"
63 63 end
64 64  
... ...
features/steps/project/project_milestones.rb
... ... @@ -50,12 +50,6 @@ class ProjectMilestones &lt; Spinach::FeatureSteps
50 50 end
51 51  
52 52 Then "I should see 3 issues" do
53   - page.should have_selector('.milestone-issue-filter .well-list li', count: 4)
54   - page.should have_selector('.milestone-issue-filter .well-list li.hide', count: 1)
55   - end
56   -
57   - Then "I should see 4 issues" do
58   - page.should have_selector('.milestone-issue-filter .well-list li', count: 4)
59   - page.should_not have_selector('.milestone-issue-filter .well-list li.hide')
  53 + page.should have_selector('#tab-issues li', count: 4)
60 54 end
61 55 end
... ...
features/steps/userteams/userteams.rb
... ... @@ -93,7 +93,7 @@ class Userteams &lt; Spinach::FeatureSteps
93 93 Then 'I should see issues from this team assigned to me' do
94 94 team = UserTeam.last
95 95 team.projects.each do |project|
96   - project.issues.assigned(current_user).each do |issue|
  96 + project.issues.assigned_to(current_user).each do |issue|
97 97 page.should have_content issue.title
98 98 end
99 99 end
... ... @@ -121,7 +121,7 @@ class Userteams &lt; Spinach::FeatureSteps
121 121 team = UserTeam.last
122 122 team.projects.each do |project|
123 123 team.members.each do |member|
124   - project.issues.assigned(member).each do |issue|
  124 + project.issues.assigned_to(member).each do |issue|
125 125 page.should have_content issue.title
126 126 end
127 127 end
... ... @@ -131,9 +131,7 @@ class Userteams &lt; Spinach::FeatureSteps
131 131 Given 'project from team has merge requests assigned to me' do
132 132 team = UserTeam.last
133 133 team.projects.each do |project|
134   - team.members.each do |member|
135   - 3.times { create(:merge_request, assignee: member, project: project) }
136   - end
  134 + create(:merge_request, assignee: current_user, project: project)
137 135 end
138 136 end
139 137  
... ... @@ -145,10 +143,8 @@ class Userteams &lt; Spinach::FeatureSteps
145 143 Then 'I should see merge requests from this team assigned to me' do
146 144 team = UserTeam.last
147 145 team.projects.each do |project|
148   - team.members.each do |member|
149   - project.issues.assigned(member).each do |merge_request|
150   - page.should have_content merge_request.title
151   - end
  146 + project.merge_requests.each do |merge_request|
  147 + page.should have_content merge_request.title
152 148 end
153 149 end
154 150 end
... ... @@ -156,20 +152,8 @@ class Userteams &lt; Spinach::FeatureSteps
156 152 Given 'project from team has merge requests assigned to team members' do
157 153 team = UserTeam.last
158 154 team.projects.each do |project|
159   - team.members.each do |member|
160   - 3.times { create(:merge_request, assignee: member, project: project) }
161   - end
162   - end
163   - end
164   -
165   - Then 'I should see merge requests from this team assigned to me' do
166   - team = UserTeam.last
167   - team.projects.each do |project|
168   - team.members.each do |member|
169   - project.issues.assigned(member).each do |merge_request|
170   - page.should have_content merge_request.title
171   - end
172   - end
  155 + member = team.members.sample
  156 + create(:merge_request, assignee: member, project: project)
173 157 end
174 158 end
175 159  
... ...
lib/gitlab/backend/grack_auth.rb
1 1 require_relative 'shell_env'
2   -require 'omniauth-ldap'
  2 +require_relative 'grack_ldap'
  3 +require_relative 'grack_helpers'
3 4  
4 5 module Grack
5 6 class Auth < Rack::Auth::Basic
6   - attr_accessor :user, :project
  7 + include LDAP
  8 + include Helpers
  9 +
  10 + attr_accessor :user, :project, :ref, :env
7 11  
8 12 def call(env)
9 13 @env = env
... ... @@ -14,42 +18,52 @@ module Grack
14 18 @env['PATH_INFO'] = @request.path
15 19 @env['SCRIPT_NAME'] = ""
16 20  
17   - return render_not_found unless project
18   - return unauthorized unless project.public || @auth.provided?
19   - return bad_request if @auth.provided? && !@auth.basic?
20   -
21   - if valid?
22   - if @auth.provided?
23   - @env['REMOTE_USER'] = @auth.username
24   - end
25   - return @app.call(env)
26   - else
27   - unauthorized
28   - end
  21 + auth!
29 22 end
30 23  
31   - def valid?
  24 + private
  25 +
  26 + def auth!
  27 + return render_not_found unless project
  28 +
32 29 if @auth.provided?
  30 + return bad_request unless @auth.basic?
  31 +
33 32 # Authentication with username and password
34 33 login, password = @auth.credentials
35 34  
36   - @user = authenticate(login, password)
37   - return false unless @user
  35 + @user = authenticate_user(login, password)
38 36  
39   - Gitlab::ShellEnv.set_env(@user)
  37 + if @user
  38 + Gitlab::ShellEnv.set_env(@user)
  39 + @env['REMOTE_USER'] = @auth.username
  40 + else
  41 + return unauthorized
  42 + end
  43 +
  44 + else
  45 + return unauthorized unless project.public
40 46 end
41 47  
  48 + if authorized_git_request?
  49 + @app.call(env)
  50 + else
  51 + unauthorized
  52 + end
  53 + end
  54 +
  55 + def authorized_git_request?
42 56 # Git upload and receive
43 57 if @request.get?
44   - validate_get_request
  58 + authorize_request(@request.params['service'])
45 59 elsif @request.post?
46   - validate_post_request
  60 + authorize_request(File.basename(@request.path))
47 61 else
48 62 false
49 63 end
50 64 end
51 65  
52   - def authenticate(login, password)
  66 + def authenticate_user(login, password)
53 67 user = User.find_by_email(login) || User.find_by_username(login)
54 68  
55 69 # If the provided login was not a known email or username
... ... @@ -65,34 +79,12 @@ module Grack
65 79 end
66 80 end
67 81  
68   - def ldap_auth(login, password)
69   - # Check user against LDAP backend if user is not authenticated
70   - # Only check with valid login and password to prevent anonymous bind results
71   - return nil unless ldap_conf.enabled && !login.blank? && !password.blank?
72   -
73   - ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
74   - ldap_user = ldap.bind_as(
75   - filter: Net::LDAP::Filter.eq(ldap.uid, login),
76   - size: 1,
77   - password: password
78   - )
79   -
80   - User.find_by_extern_uid_and_provider(ldap_user.dn, 'ldap') if ldap_user
81   - end
82   -
83   - def validate_get_request
84   - validate_request(@request.params['service'])
85   - end
86   -
87   - def validate_post_request
88   - validate_request(File.basename(@request.path))
89   - end
90   -
91   - def validate_request(service)
92   - if service == 'git-upload-pack'
  82 + def authorize_request(service)
  83 + case service
  84 + when 'git-upload-pack'
93 85 project.public || can?(user, :download_code, project)
94   - elsif service == 'git-receive-pack'
95   - action = if project.protected_branch?(current_ref)
  86 + when'git-receive-pack'
  87 + action = if project.protected_branch?(ref)
96 88 :push_code_to_protected_branches
97 89 else
98 90 :push_code
... ... @@ -104,49 +96,24 @@ module Grack
104 96 end
105 97 end
106 98  
107   - def can?(object, action, subject)
108   - abilities.allowed?(object, action, subject)
109   - end
110   -
111   - def current_ref
112   - if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
113   - input = Zlib::GzipReader.new(@request.body).read
114   - else
115   - input = @request.body.read
116   - end
117   - # Need to reset seek point
118   - @request.body.rewind
119   - /refs\/heads\/([\w\.-]+)/n.match(input.force_encoding('ascii-8bit')).to_a.last
120   - end
121   -
122 99 def project
123   - unless instance_variable_defined? :@project
124   - # Find project by PATH_INFO from env
125   - if m = /^\/([\w\.\/-]+)\.git/.match(@request.path_info).to_a
126   - @project = Project.find_with_namespace(m.last)
127   - end
128   - end
129   - return @project
  100 + @project ||= project_by_path(@request.path_info)
130 101 end
131 102  
132   - PLAIN_TYPE = {"Content-Type" => "text/plain"}
133   -
134   - def render_not_found
135   - [404, PLAIN_TYPE, ["Not Found"]]
  103 + def ref
  104 + @ref ||= parse_ref
136 105 end
137 106  
138   - protected
  107 + def parse_ref
  108 + input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
  109 + Zlib::GzipReader.new(@request.body).read
  110 + else
  111 + @request.body.read
  112 + end
139 113  
140   - def abilities
141   - @abilities ||= begin
142   - abilities = Six.new
143   - abilities << Ability
144   - abilities
145   - end
146   - end
147   -
148   - def ldap_conf
149   - @ldap_conf ||= Gitlab.config.ldap
  114 + # Need to reset seek point
  115 + @request.body.rewind
  116 + /refs\/heads\/([\w\.-]+)/n.match(input.force_encoding('ascii-8bit')).to_a.last
150 117 end
151   - end# Auth
152   -end# Grack
  118 + end
  119 +end
... ...
lib/gitlab/backend/grack_helpers.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +module Grack
  2 + module Helpers
  3 + def project_by_path(path)
  4 + if m = /^\/([\w\.\/-]+)\.git/.match(path).to_a
  5 + path_with_namespace = m.last
  6 + path_with_namespace.gsub!(/.wiki$/, '')
  7 +
  8 + Project.find_with_namespace(path_with_namespace)
  9 + end
  10 + end
  11 +
  12 + def render_not_found
  13 + [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
  14 + end
  15 +
  16 + def can?(object, action, subject)
  17 + abilities.allowed?(object, action, subject)
  18 + end
  19 +
  20 + def abilities
  21 + @abilities ||= begin
  22 + abilities = Six.new
  23 + abilities << Ability
  24 + abilities
  25 + end
  26 + end
  27 + end
  28 +end
... ...
lib/gitlab/backend/grack_ldap.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +require 'omniauth-ldap'
  2 +
  3 +module Grack
  4 + module LDAP
  5 + def ldap_auth(login, password)
  6 + # Check user against LDAP backend if user is not authenticated
  7 + # Only check with valid login and password to prevent anonymous bind results
  8 + return nil unless ldap_conf.enabled && !login.blank? && !password.blank?
  9 +
  10 + ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
  11 + ldap_user = ldap.bind_as(
  12 + filter: Net::LDAP::Filter.eq(ldap.uid, login),
  13 + size: 1,
  14 + password: password
  15 + )
  16 +
  17 + User.find_by_extern_uid_and_provider(ldap_user.dn, 'ldap') if ldap_user
  18 + end
  19 +
  20 + def ldap_conf
  21 + @ldap_conf ||= Gitlab.config.ldap
  22 + end
  23 + end
  24 +end
... ...
lib/gitlab/blacklist.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +module Gitlab
  2 + module Blacklist
  3 + extend self
  4 +
  5 + def path
  6 + %w(admin dashboard groups help profile projects search public assets u s teams merge_requests issues users snippets )
  7 + end
  8 + end
  9 +end
... ...
spec/features/admin/admin_users_spec.rb
... ... @@ -20,13 +20,10 @@ describe &quot;Admin::Users&quot; do
20 20  
21 21 describe "GET /admin/users/new" do
22 22 before do
23   - @password = "123ABC"
24 23 visit new_admin_user_path
25 24 fill_in "user_name", with: "Big Bang"
26 25 fill_in "user_username", with: "bang"
27 26 fill_in "user_email", with: "bigbang@mail.com"
28   - fill_in "user_password", with: @password
29   - fill_in "user_password_confirmation", with: @password
30 27 end
31 28  
32 29 it "should create new user" do
... ... @@ -57,26 +54,13 @@ describe &quot;Admin::Users&quot; do
57 54 end
58 55  
59 56 it "should send valid email to user with email & password" do
60   - Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
61 57 User.observers.enable :user_observer do
62 58 click_button "Create user"
63 59 user = User.last
64 60 email = ActionMailer::Base.deliveries.last
65 61 email.subject.should have_content("Account was created")
66 62 email.text_part.body.should have_content(user.email)
67   - email.text_part.body.should have_content(@password)
68   - end
69   - end
70   -
71   - it "should send valid email to user with email without password when signup is enabled" do
72   - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
73   - User.observers.enable :user_observer do
74   - click_button "Create user"
75   - user = User.last
76   - email = ActionMailer::Base.deliveries.last
77   - email.subject.should have_content("Account was created")
78   - email.text_part.body.should have_content(user.email)
79   - email.text_part.body.should_not have_content(@password)
  63 + email.text_part.body.should have_content('password')
80 64 end
81 65 end
82 66 end
... ...
spec/mailers/notify_spec.rb
... ... @@ -15,7 +15,7 @@ describe Notify do
15 15  
16 16 describe 'for new users, the email' do
17 17 let(:example_site_path) { root_path }
18   - let(:new_user) { create(:user, email: 'newguy@example.com') }
  18 + let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) }
19 19  
20 20 subject { Notify.new_user_email(new_user.id, new_user.password) }
21 21  
... ... @@ -32,8 +32,7 @@ describe Notify do
32 32 end
33 33  
34 34 it 'contains the new user\'s password' do
35   - Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
36   - should have_body_text /#{new_user.password}/
  35 + should have_body_text /password/
37 36 end
38 37  
39 38 it 'includes a link to the site' do
... ... @@ -61,8 +60,7 @@ describe Notify do
61 60 end
62 61  
63 62 it 'should not contain the new user\'s password' do
64   - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
65   - should_not have_body_text /#{new_user.password}/
  63 + should_not have_body_text /password/
66 64 end
67 65  
68 66 it 'includes a link to the site' do
... ...