Commit be942d74aff137f55b1e75e0c66b91cad1fd2001

Authored by Dmitriy Zaporozhets
2 parents 5f7dc99a 1d7fdf45

Merge pull request #2051 from gitlabhq/namespaces

User/Group namespaces for projects
Showing 83 changed files with 698 additions and 423 deletions   Show diff stats
Gemfile
... ... @@ -139,7 +139,7 @@ group :development, :test do
139 139 gem 'rb-inotify', require: linux_only('rb-inotify')
140 140  
141 141 # PhantomJS driver for Capybara
142   - gem 'poltergeist'
  142 + gem 'poltergeist', git: 'https://github.com/jonleighton/poltergeist.git', ref: '5c2e092001074a8cf09f332d3714e9ba150bc8ca'
143 143 end
144 144  
145 145 group :test do
... ...
Gemfile.lock
... ... @@ -59,6 +59,18 @@ GIT
59 59 specs:
60 60 yaml_db (0.2.2)
61 61  
  62 +GIT
  63 + remote: https://github.com/jonleighton/poltergeist.git
  64 + revision: 5c2e092001074a8cf09f332d3714e9ba150bc8ca
  65 + ref: 5c2e092001074a8cf09f332d3714e9ba150bc8ca
  66 + specs:
  67 + poltergeist (1.0.2)
  68 + capybara (~> 1.1)
  69 + childprocess (~> 0.3)
  70 + faye-websocket (~> 0.4, >= 0.4.4)
  71 + http_parser.rb (~> 0.5.3)
  72 + multi_json (~> 1.0)
  73 +
62 74 GEM
63 75 remote: http://rubygems.org/
64 76 specs:
... ... @@ -279,12 +291,6 @@ GEM
279 291 omniauth-oauth (~> 1.0)
280 292 orm_adapter (0.4.0)
281 293 pg (0.14.1)
282   - poltergeist (1.0.2)
283   - capybara (~> 1.1)
284   - childprocess (~> 0.3)
285   - faye-websocket (~> 0.4, >= 0.4.4)
286   - http_parser.rb (~> 0.5.3)
287   - multi_json (~> 1.0)
288 294 polyglot (0.3.3)
289 295 posix-spawn (0.3.6)
290 296 pry (0.9.10)
... ... @@ -490,7 +496,7 @@ DEPENDENCIES
490 496 omniauth-ldap!
491 497 omniauth-twitter
492 498 pg
493   - poltergeist
  499 + poltergeist!
494 500 pry
495 501 pygments.rb!
496 502 quiet_assets (~> 1.0.1)
... ...
app/assets/stylesheets/gitlab_bootstrap/typography.scss
... ... @@ -77,3 +77,7 @@ a {
77 77 a:focus {
78 78 outline: none;
79 79 }
  80 +
  81 +.monospace {
  82 + font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
  83 +}
... ...
app/controllers/admin/groups_controller.rb
... ... @@ -48,15 +48,17 @@ class Admin::GroupsController < AdminController
48 48  
49 49 def project_update
50 50 project_ids = params[:project_ids]
51   - Project.where(id: project_ids).update_all(group_id: @group.id)
  51 +
  52 + Project.where(id: project_ids).each do |project|
  53 + project.transfer(@group)
  54 + end
52 55  
53 56 redirect_to :back, notice: 'Group was successfully updated.'
54 57 end
55 58  
56 59 def remove_project
57 60 @project = Project.find(params[:project_id])
58   - @project.group_id = nil
59   - @project.save
  61 + @project.transfer(nil)
60 62  
61 63 redirect_to :back, notice: 'Group was successfully updated.'
62 64 end
... ... @@ -70,6 +72,6 @@ class Admin::GroupsController < AdminController
70 72 private
71 73  
72 74 def group
73   - @group = Group.find_by_code(params[:id])
  75 + @group = Group.find_by_path(params[:id])
74 76 end
75 77 end
... ...
app/controllers/admin/projects_controller.rb
1 1 class Admin::ProjectsController < AdminController
2   - before_filter :admin_project, only: [:edit, :show, :update, :destroy, :team_update]
  2 + before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
3 3  
4 4 def index
5   - @admin_projects = Project.scoped
6   - @admin_projects = @admin_projects.search(params[:name]) if params[:name].present?
7   - @admin_projects = @admin_projects.order("name ASC").page(params[:page]).per(20)
  5 + @projects = Project.scoped
  6 + @projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present?
  7 + @projects = @projects.search(params[:name]) if params[:name].present?
  8 + @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
8 9 end
9 10  
10 11 def show
11 12 @users = User.scoped
12   - @users = @users.not_in_project(@admin_project) if @admin_project.users.present?
  13 + @users = @users.not_in_project(@project) if @project.users.present?
13 14 @users = @users.all
14 15 end
15 16  
16   - def new
17   - @admin_project = Project.new
18   - end
19   -
20 17 def edit
21 18 end
22 19  
23 20 def team_update
24   - @admin_project.add_users_ids_to_team(params[:user_ids], params[:project_access])
25   -
26   - redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.'
27   - end
  21 + @project.add_users_ids_to_team(params[:user_ids], params[:project_access])
28 22  
29   - def create
30   - @admin_project = Project.new(params[:project])
31   - @admin_project.owner = current_user
32   -
33   - if @admin_project.save
34   - redirect_to [:admin, @admin_project], notice: 'Project was successfully created.'
35   - else
36   - render action: "new"
37   - end
  23 + redirect_to [:admin, @project], notice: 'Project was successfully updated.'
38 24 end
39 25  
40 26 def update
41 27 owner_id = params[:project].delete(:owner_id)
42 28  
43 29 if owner_id
44   - @admin_project.owner = User.find(owner_id)
  30 + @project.owner = User.find(owner_id)
45 31 end
46 32  
47   - if @admin_project.update_attributes(params[:project])
48   - redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.'
  33 + if @project.update_attributes(params[:project], as: :admin)
  34 + redirect_to [:admin, @project], notice: 'Project was successfully updated.'
49 35 else
50 36 render action: "edit"
51 37 end
52 38 end
53 39  
54 40 def destroy
55   - @admin_project.destroy
  41 + @project.destroy
56 42  
57   - redirect_to admin_projects_url, notice: 'Project was successfully deleted.'
  43 + redirect_to projects_url, notice: 'Project was successfully deleted.'
58 44 end
59 45  
60   - private
  46 + protected
  47 +
  48 + def project
  49 + id = params[:project_id] || params[:id]
61 50  
62   - def admin_project
63   - @admin_project = Project.find_by_code(params[:id])
  51 + @project = Project.find_with_namespace(id)
  52 + @project || render_404
64 53 end
65 54 end
... ...
app/controllers/application_controller.rb
... ... @@ -63,7 +63,9 @@ class ApplicationController &lt; ActionController::Base
63 63 end
64 64  
65 65 def project
66   - @project ||= current_user.projects.find_by_code(params[:project_id] || params[:id])
  66 + id = params[:project_id] || params[:id]
  67 +
  68 + @project ||= current_user.projects.find_with_namespace(id)
67 69 @project || render_404
68 70 end
69 71  
... ...
app/controllers/dashboard_controller.rb
... ... @@ -4,7 +4,7 @@ class DashboardController &lt; ApplicationController
4 4 before_filter :event_filter, only: :index
5 5  
6 6 def index
7   - @groups = Group.where(id: current_user.projects.pluck(:group_id))
  7 + @groups = Group.where(id: current_user.projects.pluck(:namespace_id))
8 8 @projects = current_user.projects_sorted_by_activity
9 9 @projects = @projects.page(params[:page]).per(30)
10 10  
... ...
app/controllers/groups_controller.rb
... ... @@ -4,6 +4,7 @@ class GroupsController &lt; ApplicationController
4 4  
5 5 before_filter :group
6 6 before_filter :projects
  7 + before_filter :add_project_abilities
7 8  
8 9 def show
9 10 @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0)
... ... @@ -50,11 +51,11 @@ class GroupsController &lt; ApplicationController
50 51 protected
51 52  
52 53 def group
53   - @group ||= Group.find_by_code(params[:id])
  54 + @group ||= Group.find_by_path(params[:id])
54 55 end
55 56  
56 57 def projects
57   - @projects ||= current_user.projects_sorted_by_activity.where(group_id: @group.id)
  58 + @projects ||= current_user.projects_sorted_by_activity.where(namespace_id: @group.id)
58 59 end
59 60  
60 61 def project_ids
... ...
app/controllers/projects_controller.rb
... ... @@ -34,8 +34,16 @@ class ProjectsController &lt; ProjectResourceController
34 34 end
35 35  
36 36 def update
  37 + namespace_id = params[:project].delete(:namespace_id)
  38 +
  39 + if namespace_id
  40 + namespace = Namespace.find(namespace_id)
  41 + project.transfer(namespace)
  42 + end
  43 +
37 44 respond_to do |format|
38 45 if project.update_attributes(params[:project])
  46 + flash[:notice] = 'Project was successfully updated.'
39 47 format.html { redirect_to edit_project_path(project), notice: 'Project was successfully updated.' }
40 48 format.js
41 49 else
... ...
app/helpers/application_helper.rb
... ... @@ -74,6 +74,27 @@ module ApplicationHelper
74 74 grouped_options_for_select(options, @ref || @project.default_branch)
75 75 end
76 76  
  77 + def namespaces_options(selected = :current_user, scope = :default)
  78 + groups = current_user.namespaces.select {|n| n.type == 'Group'}
  79 +
  80 + users = if scope == :all
  81 + Namespace.root
  82 + else
  83 + current_user.namespaces.reject {|n| n.type == 'Group'}
  84 + end
  85 +
  86 + options = [
  87 + ["Groups", groups.map {|g| [g.human_name, g.id]} ],
  88 + [ "Users", users.map {|u| [u.human_name, u.id]} ]
  89 + ]
  90 +
  91 + if selected == :current_user && current_user.namespace
  92 + selected = current_user.namespace.id
  93 + end
  94 +
  95 + grouped_options_for_select(options, selected)
  96 + end
  97 +
77 98 def search_autocomplete_source
78 99 projects = current_user.projects.map{ |p| { label: p.name, url: project_path(p) } }
79 100  
... ...
app/models/ability.rb
... ... @@ -7,6 +7,7 @@ class Ability
7 7 when "Note" then note_abilities(object, subject)
8 8 when "Snippet" then snippet_abilities(object, subject)
9 9 when "MergeRequest" then merge_request_abilities(object, subject)
  10 + when "Group" then group_abilities(object, subject)
10 11 else []
11 12 end
12 13 end
... ... @@ -61,6 +62,16 @@ class Ability
61 62 rules.flatten
62 63 end
63 64  
  65 + def group_abilities user, group
  66 + rules = []
  67 +
  68 + rules << [
  69 + :manage_group
  70 + ] if group.owner == user
  71 +
  72 + rules.flatten
  73 + end
  74 +
64 75 [:issue, :note, :snippet, :merge_request].each do |name|
65 76 define_method "#{name}_abilities" do |user, subject|
66 77 if subject.author == user
... ...
app/models/group.rb
1 1 # == Schema Information
2 2 #
3   -# Table name: groups
  3 +# Table name: namespaces
4 4 #
5 5 # id :integer not null, primary key
6 6 # name :string(255) not null
7   -# code :string(255) not null
  7 +# path :string(255) not null
8 8 # owner_id :integer not null
9 9 # created_at :datetime not null
10 10 # updated_at :datetime not null
  11 +# type :string(255)
11 12 #
12 13  
13   -class Group < ActiveRecord::Base
14   - attr_accessible :code, :name, :owner_id
15   -
16   - has_many :projects
17   - belongs_to :owner, class_name: "User"
18   -
19   - validates :name, presence: true, uniqueness: true
20   - validates :code, presence: true, uniqueness: true
21   - validates :owner, presence: true
22   -
23   - delegate :name, to: :owner, allow_nil: true, prefix: true
24   -
25   - def self.search query
26   - where("name LIKE :query OR code LIKE :query", query: "%#{query}%")
27   - end
28   -
29   - def to_param
30   - code
31   - end
32   -
  14 +class Group < Namespace
33 15 def users
34 16 User.joins(:users_projects).where(users_projects: {project_id: project_ids}).uniq
35 17 end
  18 +
  19 + def human_name
  20 + name
  21 + end
36 22 end
... ...
app/models/namespace.rb 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: namespaces
  4 +#
  5 +# id :integer not null, primary key
  6 +# name :string(255) not null
  7 +# path :string(255) not null
  8 +# owner_id :integer not null
  9 +# created_at :datetime not null
  10 +# updated_at :datetime not null
  11 +# type :string(255)
  12 +#
  13 +
  14 +class Namespace < ActiveRecord::Base
  15 + attr_accessible :name, :path
  16 +
  17 + has_many :projects, dependent: :destroy
  18 + belongs_to :owner, class_name: "User"
  19 +
  20 + validates :name, presence: true, uniqueness: true
  21 + validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
  22 + format: { with: /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/,
  23 + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
  24 + validates :owner, presence: true
  25 +
  26 + delegate :name, to: :owner, allow_nil: true, prefix: true
  27 +
  28 + after_create :ensure_dir_exist
  29 + after_update :move_dir
  30 +
  31 + scope :root, where('type IS NULL')
  32 +
  33 + def self.search query
  34 + where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
  35 + end
  36 +
  37 + def to_param
  38 + path
  39 + end
  40 +
  41 + def human_name
  42 + owner_name
  43 + end
  44 +
  45 + def ensure_dir_exist
  46 + namespace_dir_path = File.join(Gitlab.config.git_base_path, path)
  47 + Dir.mkdir(namespace_dir_path) unless File.exists?(namespace_dir_path)
  48 + end
  49 +
  50 + def move_dir
  51 + old_path = File.join(Gitlab.config.git_base_path, path_was)
  52 + new_path = File.join(Gitlab.config.git_base_path, path)
  53 + system("mv #{old_path} #{new_path}")
  54 + end
  55 +end
... ...
app/models/project.rb
... ... @@ -9,14 +9,13 @@
9 9 # created_at :datetime not null
10 10 # updated_at :datetime not null
11 11 # private_flag :boolean default(TRUE), not null
12   -# code :string(255)
13 12 # owner_id :integer
14 13 # default_branch :string(255)
15 14 # issues_enabled :boolean default(TRUE), not null
16 15 # wall_enabled :boolean default(TRUE), not null
17 16 # merge_requests_enabled :boolean default(TRUE), not null
18 17 # wiki_enabled :boolean default(TRUE), not null
19   -# group_id :integer
  18 +# namespace_id :integer
20 19 #
21 20  
22 21 require "grit"
... ... @@ -27,12 +26,16 @@ class Project &lt; ActiveRecord::Base
27 26 include Authority
28 27 include Team
29 28  
30   - attr_accessible :name, :path, :description, :code, :default_branch, :issues_enabled,
31   - :wall_enabled, :merge_requests_enabled, :wiki_enabled
  29 + attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
  30 + :wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
  31 +
  32 + attr_accessible :namespace_id, as: :admin
  33 +
32 34 attr_accessor :error_code
33 35  
34 36 # Relations
35   - belongs_to :group
  37 + belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
  38 + belongs_to :namespace
36 39 belongs_to :owner, class_name: "User"
37 40 has_many :users, through: :users_projects
38 41 has_many :events, dependent: :destroy
... ... @@ -54,15 +57,16 @@ class Project &lt; ActiveRecord::Base
54 57 # Validations
55 58 validates :owner, presence: true
56 59 validates :description, length: { within: 0..2000 }
57   - validates :name, uniqueness: true, presence: true, length: { within: 0..255 }
58   - validates :path, uniqueness: true, presence: true, length: { within: 0..255 },
59   - format: { with: /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/,
60   - message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
61   - validates :code, presence: true, uniqueness: true, length: { within: 1..255 },
  60 + validates :name, presence: true, length: { within: 0..255 }
  61 + validates :path, presence: true, length: { within: 0..255 },
62 62 format: { with: /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/,
63 63 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
64 64 validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
65 65 :wiki_enabled, inclusion: { in: [true, false] }
  66 +
  67 + validates_uniqueness_of :name, scope: :namespace_id
  68 + validates_uniqueness_of :path, scope: :namespace_id
  69 +
66 70 validate :check_limit, :repo_name
67 71  
68 72 # Scopes
... ... @@ -76,14 +80,46 @@ class Project &lt; ActiveRecord::Base
76 80 end
77 81  
78 82 def search query
79   - where("name LIKE :query OR code LIKE :query OR path LIKE :query", query: "%#{query}%")
  83 + where("projects.name LIKE :query OR projects.path LIKE :query", query: "%#{query}%")
  84 + end
  85 +
  86 + def find_with_namespace(id)
  87 + if id.include?("/")
  88 + id = id.split("/")
  89 + namespace_id = Namespace.find_by_path(id.first).id
  90 + where(namespace_id: namespace_id).find_by_path(id.last)
  91 + else
  92 + find_by_path(id)
  93 + end
80 94 end
81 95  
82 96 def create_by_user(params, user)
  97 + namespace_id = params.delete(:namespace_id)
  98 +
83 99 project = Project.new params
84 100  
85 101 Project.transaction do
  102 +
  103 + # Parametrize path for project
  104 + #
  105 + # Ex.
  106 + # 'GitLab HQ'.parameterize => "gitlab-hq"
  107 + #
  108 + project.path = project.name.dup.parameterize
  109 +
86 110 project.owner = user
  111 +
  112 + # Apply namespace if user has access to it
  113 + # else fallback to user namespace
  114 + project.namespace_id = user.namespace_id
  115 +
  116 + if namespace_id
  117 + group = Group.find_by_id(namespace_id)
  118 + if user.can? :manage_group, group
  119 + project.namespace_id = namespace_id
  120 + end
  121 + end
  122 +
87 123 project.save!
88 124  
89 125 # Add user as project master
... ... @@ -134,11 +170,15 @@ class Project &lt; ActiveRecord::Base
134 170 end
135 171  
136 172 def to_param
137   - code
  173 + if namespace
  174 + namespace.path + "/" + path
  175 + else
  176 + path
  177 + end
138 178 end
139 179  
140 180 def web_url
141   - [Gitlab.config.url, code].join("/")
  181 + [Gitlab.config.url, path].join("/")
142 182 end
143 183  
144 184 def common_notes
... ... @@ -192,4 +232,31 @@ class Project &lt; ActiveRecord::Base
192 232 def gitlab_ci?
193 233 gitlab_ci_service && gitlab_ci_service.active
194 234 end
  235 +
  236 + def path_with_namespace
  237 + if namespace
  238 + namespace.path + '/' + path
  239 + else
  240 + path
  241 + end
  242 + end
  243 +
  244 + # For compatibility with old code
  245 + def code
  246 + path
  247 + end
  248 +
  249 + def transfer(new_namespace)
  250 + Project.transaction do
  251 + old_namespace = namespace
  252 + self.namespace = new_namespace
  253 +
  254 + old_dir = old_namespace.try(:path) || ''
  255 + new_dir = new_namespace.try(:path) || ''
  256 +
  257 + Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
  258 +
  259 + save!
  260 + end
  261 + end
195 262 end
... ...
app/models/user.rb
... ... @@ -30,6 +30,7 @@
30 30 # locked_at :datetime
31 31 # extern_uid :string(255)
32 32 # provider :string(255)
  33 +# username :string(255)
33 34 #
34 35  
35 36 class User < ActiveRecord::Base
... ... @@ -38,13 +39,17 @@ class User &lt; ActiveRecord::Base
38 39 devise :database_authenticatable, :token_authenticatable, :lockable,
39 40 :recoverable, :rememberable, :trackable, :validatable, :omniauthable
40 41  
41   - attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name,
  42 + attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
42 43 :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password,
43 44 :extern_uid, :provider, :as => [:default, :admin]
44 45 attr_accessible :projects_limit, :as => :admin
45 46  
46 47 attr_accessor :force_random_password
47 48  
  49 + # Namespace for personal projects
  50 + has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy
  51 + has_many :groups, class_name: "Group", foreign_key: :owner_id
  52 +
48 53 has_many :keys, dependent: :destroy
49 54 has_many :projects, through: :users_projects
50 55 has_many :users_projects, dependent: :destroy
... ... @@ -60,11 +65,14 @@ class User &lt; ActiveRecord::Base
60 65 validates :bio, length: { within: 0..255 }
61 66 validates :extern_uid, :allow_blank => true, :uniqueness => {:scope => :provider}
62 67 validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
  68 + validates :username, presence: true
63 69  
64 70 before_validation :generate_password, on: :create
65 71 before_save :ensure_authentication_token
66 72 alias_attribute :private_token, :authentication_token
67 73  
  74 + delegate :path, to: :namespace, allow_nil: true, prefix: true
  75 +
68 76 # Scopes
69 77 scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) }
70 78 scope :admins, where(admin: true)
... ...
app/observers/issue_observer.rb
... ... @@ -3,7 +3,7 @@ class IssueObserver &lt; ActiveRecord::Observer
3 3  
4 4 def after_create(issue)
5 5 if issue.assignee && issue.assignee != current_user
6   - Notify.new_issue_email(issue.id).deliver
  6 + Notify.new_issue_email(issue.id).deliver
7 7 end
8 8 end
9 9  
... ... @@ -14,8 +14,8 @@ class IssueObserver &lt; ActiveRecord::Observer
14 14 status = 'closed' if issue.is_being_closed?
15 15 status = 'reopened' if issue.is_being_reopened?
16 16 if status
17   - Note.create_status_change_note(issue, current_user, status)
18   - [issue.author, issue.assignee].compact.each do |recipient|
  17 + Note.create_status_change_note(issue, current_user, status)
  18 + [issue.author, issue.assignee].compact.each do |recipient|
19 19 Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user)
20 20 end
21 21 end
... ...
app/observers/user_observer.rb
1 1 class UserObserver < ActiveRecord::Observer
2 2 def after_create(user)
  3 + user.create_namespace(path: user.username, name: user.name)
  4 +
3 5 log_info("User \"#{user.name}\" (#{user.email}) was created")
4 6  
5 7 Notify.new_user_email(user.id, user.password).deliver
... ... @@ -9,6 +11,16 @@ class UserObserver &lt; ActiveRecord::Observer
9 11 log_info("User \"#{user.name}\" (#{user.email}) was removed")
10 12 end
11 13  
  14 + def after_save user
  15 + if user.username_changed?
  16 + if user.namespace
  17 + user.namespace.update_attributes(path: user.username)
  18 + else
  19 + user.create_namespace!(path: user.username, name: user.name)
  20 + end
  21 + end
  22 + end
  23 +
12 24 protected
13 25  
14 26 def log_info message
... ...
app/roles/account.rb
... ... @@ -26,6 +26,18 @@ module Account
26 26 is_admin?
27 27 end
28 28  
  29 + def abilities
  30 + @abilities ||= begin
  31 + abilities = Six.new
  32 + abilities << Ability
  33 + abilities
  34 + end
  35 + end
  36 +
  37 + def can? action, subject
  38 + abilities.allowed?(self, action, subject)
  39 + end
  40 +
29 41 def last_activity_project
30 42 projects.first
31 43 end
... ... @@ -70,4 +82,27 @@ module Account
70 82 def projects_sorted_by_activity
71 83 projects.order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC")
72 84 end
  85 +
  86 + def namespaces
  87 + namespaces = []
  88 +
  89 + # Add user account namespace
  90 + namespaces << self.namespace if self.namespace
  91 +
  92 + # Add groups you can manage
  93 + namespaces += if admin
  94 + Group.all
  95 + else
  96 + groups.all
  97 + end
  98 + namespaces
  99 + end
  100 +
  101 + def several_namespaces?
  102 + namespaces.size > 1
  103 + end
  104 +
  105 + def namespace_id
  106 + namespace.try :id
  107 + end
73 108 end
... ...
app/roles/push_observer.rb
... ... @@ -114,7 +114,7 @@ module PushObserver
114 114 id: commit.id,
115 115 message: commit.safe_message,
116 116 timestamp: commit.date.xmlschema,
117   - url: "#{Gitlab.config.url}/#{code}/commits/#{commit.id}",
  117 + url: "#{Gitlab.config.url}/#{path}/commits/#{commit.id}",
118 118 author: {
119 119 name: commit.author_name,
120 120 email: commit.author_email
... ...
app/roles/repository.rb
... ... @@ -79,11 +79,15 @@ module Repository
79 79 end
80 80  
81 81 def url_to_repo
82   - git_host.url_to_repo(path)
  82 + git_host.url_to_repo(path_with_namespace)
83 83 end
84 84  
85 85 def path_to_repo
86   - File.join(Gitlab.config.git_base_path, "#{path}.git")
  86 + File.join(Gitlab.config.git_base_path, namespace_dir, "#{path}.git")
  87 + end
  88 +
  89 + def namespace_dir
  90 + namespace.try(:path) || ''
87 91 end
88 92  
89 93 def update_repository
... ... @@ -160,12 +164,12 @@ module Repository
160 164 return nil unless commit
161 165  
162 166 # Build file path
163   - file_name = self.code + "-" + commit.id.to_s + ".tar.gz"
164   - storage_path = Rails.root.join("tmp", "repositories", self.code)
  167 + file_name = self.path + "-" + commit.id.to_s + ".tar.gz"
  168 + storage_path = Rails.root.join("tmp", "repositories", self.path)
165 169 file_path = File.join(storage_path, file_name)
166 170  
167 171 # Put files into a directory before archiving
168   - prefix = self.code + "/"
  172 + prefix = self.path + "/"
169 173  
170 174 # Create file if not exists
171 175 unless File.exists?(file_path)
... ...
app/views/admin/dashboard/index.html.haml
... ... @@ -27,7 +27,7 @@
27 27 = link_to admin_projects_path do
28 28 %h1= Project.count
29 29 %hr
30   - = link_to 'New Project', new_admin_project_path, class: "btn small"
  30 + = link_to 'New Project', new_project_path, class: "btn small"
31 31 .span4
32 32 .ui-box
33 33 %h5 Users
... ...
app/views/admin/groups/_form.html.haml
... ... @@ -8,12 +8,12 @@
8 8 .input
9 9 = f.text_field :name, placeholder: "Example Group", class: "xxlarge"
10 10 .clearfix
11   - = f.label :code do
  11 + = f.label :path do
12 12 URL
13 13 .input
14 14 .input-prepend
15 15 %span.add-on= web_app_url + 'groups/'
16   - = f.text_field :code, placeholder: "example"
  16 + = f.text_field :path, placeholder: "example"
17 17  
18 18 .form-actions
19 19 = f.submit 'Save group', class: "btn save-btn"
... ...
app/views/admin/groups/index.html.haml
... ... @@ -14,7 +14,7 @@
14 14 %table
15 15 %thead
16 16 %th Name
17   - %th Code
  17 + %th Path
18 18 %th Projects
19 19 %th Edit
20 20 %th.cred Danger Zone!
... ... @@ -22,7 +22,7 @@
22 22 - @groups.each do |group|
23 23 %tr
24 24 %td= link_to group.name, [:admin, group]
25   - %td= group.code
  25 + %td= group.path
26 26 %td= group.projects.count
27 27 %td= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small"
28 28 %td.bgred= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger"
... ...
app/views/admin/groups/show.html.haml
... ... @@ -20,9 +20,9 @@
20 20 %tr
21 21 %td
22 22 %b
23   - Code:
  23 + Path:
24 24 %td
25   - = @group.code
  25 + = @group.path
26 26 %tr
27 27 %td
28 28 %b
... ...
app/views/admin/projects/_form.html.haml
... ... @@ -11,26 +11,20 @@
11 11 .input
12 12 = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
13 13  
14   - %hr
15   - .adv_settings
16   - %h6 Advanced settings:
  14 + %fieldset.adv_settings
  15 + %legend Advanced settings:
17 16 .clearfix
18 17 = f.label :path do
19 18 Path
20 19 .input
21   - .input-prepend
22   - %strong
23   - = text_field_tag :ppath, @admin_project.path_to_repo, class: "xlarge", disabled: true
24   - .clearfix
25   - = f.label :code do
26   - URL
27   - .input
28   - .input-prepend
29   - %span.add-on= web_app_url
30   - = f.text_field :code, placeholder: "example"
  20 + = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true
31 21  
32 22 - unless project.new_record?
33 23 .clearfix
  24 + = f.label :namespace_id
  25 + .input= f.select :namespace_id, namespaces_options, {}, {class: 'chosen'}
  26 +
  27 + .clearfix
34 28 = f.label :owner_id
35 29 .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
36 30  
... ... @@ -40,9 +34,8 @@
40 34 .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;")
41 35  
42 36 - unless project.new_record?
43   - %hr
44   - .adv_settings
45   - %h6 Features:
  37 + %fieldset.adv_settings
  38 + %legend Features:
46 39  
47 40 .clearfix
48 41 = f.label :issues_enabled, "Issues"
... ...
app/views/admin/projects/_new_form.html.haml
... ... @@ -1,29 +0,0 @@
1   -= form_for [:admin, @admin_project] do |f|
2   - - if @admin_project.errors.any?
3   - .alert-message.block-message.error
4   - %span= @admin_project.errors.full_messages.first
5   - .clearfix.project_name_holder
6   - = f.label :name do
7   - Project name is
8   - .input
9   - = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
10   - = f.submit 'Create project', class: "btn primary project-submit"
11   -
12   - %hr
13   - %div.adv_settings
14   - %h6 Advanced settings:
15   - .clearfix
16   - = f.label :path do
17   - Git Clone
18   - .input
19   - .input-prepend
20   - %span.add-on= Gitlab.config.ssh_path
21   - = f.text_field :path, placeholder: "example_project", disabled: !@admin_project.new_record?
22   - %span.add-on= ".git"
23   - .clearfix
24   - = f.label :code do
25   - URL
26   - .input
27   - .input-prepend
28   - %span.add-on= web_app_url
29   - = f.text_field :code, placeholder: "example"
app/views/admin/projects/edit.html.haml
1   -%h3.page_title #{@admin_project.name} &rarr; Edit project
  1 +%h3.page_title #{@project.name} &rarr; Edit project
2 2 %hr
3   -= render 'form', project: @admin_project
  3 += render 'form', project: @project
... ...
app/views/admin/projects/index.html.haml
1 1 = render 'admin/shared/projects_head'
2 2 %h3.page_title
3 3 Projects
4   - = link_to 'New Project', new_admin_project_path, class: "btn small right"
  4 + = link_to 'New Project', new_project_path, class: "btn small right"
5 5 %br
6 6 = form_tag admin_projects_path, method: :get, class: 'form-inline' do
  7 + = select_tag :namespace_id, namespaces_options(params[:namespace_id], :all), class: "chosen xlarge", include_blank: true
7 8 = text_field_tag :name, params[:name], class: "xlarge"
8 9 = submit_tag "Search", class: "btn submit primary"
9 10  
10 11 %table
11 12 %thead
12 13 %th Name
13   - %th Path
  14 + %th Project
14 15 %th Team Members
15 16 %th Last Commit
16 17 %th Edit
17 18 %th.cred Danger Zone!
18 19  
19   - - @admin_projects.each do |project|
  20 + - @projects.each do |project|
20 21 %tr
21   - %td= link_to project.name, [:admin, project]
22   - %td= project.path
  22 + %td
  23 + - if project.namespace
  24 + = link_to project.namespace.human_name, [:admin, project]
  25 + &rarr;
  26 + = link_to project.name, [:admin, project]
  27 + %td
  28 + %span.monospace= project.path_with_namespace + ".git"
23 29 %td= project.users_projects.count
24 30 %td= last_commit(project)
25 31 %td= link_to 'Edit', edit_admin_project_path(project), id: "edit_#{dom_id(project)}", class: "btn small"
26 32 %td.bgred= link_to 'Destroy', [:admin, project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn small danger"
27   -= paginate @admin_projects, theme: "admin"
  33 += paginate @projects, theme: "admin"
... ...
app/views/admin/projects/new.html.haml
... ... @@ -1,12 +0,0 @@
1   -.project_new_holder
2   - %h3.page_title
3   - New Project
4   - %hr
5   - = render 'new_form'
6   -%div.save-project-loader.hide
7   - %center
8   - = image_tag "ajax_loader.gif"
9   - %h3 Creating project &amp; repository. Please wait a few minutes
10   -
11   -:javascript
12   - $(function(){ new Projects(); });
app/views/admin/projects/show.html.haml
1 1 = render 'admin/shared/projects_head'
2 2 %h3.page_title
3   - Project: #{@admin_project.name}
4   - = link_to edit_admin_project_path(@admin_project), class: "btn right" do
  3 + Project: #{@project.name}
  4 + = link_to edit_admin_project_path(@project), class: "btn right" do
5 5 %i.icon-edit
6 6 Edit
7 7  
8   -- if !@admin_project.has_post_receive_file? && @admin_project.has_commits?
  8 +- if !@project.has_post_receive_file? && @project.has_commits?
9 9 %br
10 10 .alert.alert-error
11 11 %span
... ... @@ -25,36 +25,30 @@
25 25 %b
26 26 Name:
27 27 %td
28   - = @admin_project.name
29   - %tr
30   - %td
31   - %b
32   - Code:
33   - %td
34   - = @admin_project.code
  28 + = @project.name
35 29 %tr
36 30 %td
37 31 %b
38 32 Path:
39 33 %td
40   - = @admin_project.path
  34 + %code= @project.path_to_repo
41 35 %tr
42 36 %td
43 37 %b
44 38 Owner:
45 39 %td
46   - = @admin_project.owner_name || '(deleted)'
  40 + = @project.owner_name || '(deleted)'
47 41 %tr
48 42 %td
49 43 %b
50 44 Post Receive File:
51 45 %td
52   - = check_box_tag :post_receive_file, 1, @admin_project.has_post_receive_file?, disabled: true
  46 + = check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true
53 47 %br
54 48 %h3
55 49 Team
56 50 %small
57   - (#{@admin_project.users_projects.count})
  51 + (#{@project.users_projects.count})
58 52 %br
59 53 %table.zebra-striped
60 54 %thead
... ... @@ -64,7 +58,7 @@
64 58 %th Repository Access
65 59 %th
66 60  
67   - - @admin_project.users_projects.each do |tm|
  61 + - @project.users_projects.each do |tm|
68 62 %tr
69 63 %td
70 64 = link_to tm.user_name, admin_user_path(tm.user)
... ... @@ -75,7 +69,7 @@
75 69 %br
76 70 %h3 Add new team member
77 71 %br
78   -= form_tag team_update_admin_project_path(@admin_project), class: "bulk_import", method: :put do
  72 += form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do
79 73 %table.zebra-striped
80 74 %thead
81 75 %tr
... ...
app/views/admin/users/_form.html.haml
... ... @@ -16,6 +16,11 @@
16 16 = f.text_field :name
17 17 %span.help-inline * required
18 18 .clearfix
  19 + = f.label :username
  20 + .input
  21 + = f.text_field :username
  22 + %span.help-inline * required
  23 + .clearfix
19 24 = f.label :email
20 25 .input
21 26 = f.text_field :email
... ... @@ -26,11 +31,11 @@
26 31 = f.label :force_random_password do
27 32 %span Generate random password
28 33 .input= f.check_box :force_random_password, {}, true, nil
29   -
  34 +
30 35 %div.password-fields
31 36 .clearfix
32 37 = f.label :password
33   - .input= f.password_field :password, disabled: f.object.force_random_password
  38 + .input= f.password_field :password, disabled: f.object.force_random_password
34 39 .clearfix
35 40 = f.label :password_confirmation
36 41 .input= f.password_field :password_confirmation, disabled: f.object.force_random_password
... ...
app/views/dashboard/_groups.html.haml
... ... @@ -11,7 +11,7 @@
11 11 %ul.unstyled
12 12 - groups.each do |group|
13 13 %li.wll
14   - = link_to group_path(id: group.code), class: dom_class(group) do
  14 + = link_to group_path(id: group.path), class: dom_class(group) do
15 15 %strong.group_name= truncate(group.name, length: 25)
16 16 %span.arrow
17 17 &rarr;
... ...
app/views/dashboard/_projects.html.haml
... ... @@ -12,7 +12,11 @@
12 12 - projects.each do |project|
13 13 %li.wll
14 14 = link_to project_path(project), class: dom_class(project) do
15   - %strong.project_name= truncate(project.name, length: 25)
  15 + - if project.namespace
  16 + = project.namespace.human_name
  17 + \/
  18 + %strong.project_name
  19 + = truncate(project.name, length: 25)
16 20 %span.arrow
17 21 &rarr;
18 22 %span.last_activity
... ...
app/views/groups/_projects.html.haml
... ... @@ -3,6 +3,11 @@
3 3 Projects
4 4 %small
5 5 (#{projects.count})
  6 + - if can? current_user, :manage_group, @group
  7 + %span.right
  8 + = link_to new_project_path(namespace_id: @group.id), class: "btn very_small info" do
  9 + %i.icon-plus
  10 + New Project
6 11 %ul.unstyled
7 12 - projects.each do |project|
8 13 %li.wll
... ...
app/views/groups/people.html.haml
... ... @@ -9,4 +9,6 @@
9 9 = image_tag gravatar_icon(user.email, 16), class: "avatar s16"
10 10 %strong= user.name
11 11 %span.cgray= user.email
  12 + - if @group.owner == user
  13 + %span.btn.btn-small.disabled.right Owner
12 14  
... ...
app/views/layouts/_init_auto_complete.html.haml
1 1 :javascript
2 2 $(function() {
3   - GitLab.GfmAutoComplete.Members.url = "#{ "/api/v2/projects/#{@project.code}/members" if @project }";
  3 + GitLab.GfmAutoComplete.Members.url = "#{ "/api/v2/projects/#{@project.path}/members" if @project }";
4 4 GitLab.GfmAutoComplete.Members.params.private_token = "#{current_user.private_token}";
5 5  
6 6 GitLab.GfmAutoComplete.Emoji.data = #{raw emoji_autocomplete_source};
... ...
app/views/layouts/project_resource.html.haml
... ... @@ -7,7 +7,7 @@
7 7 .container
8 8 %ul.main_menu
9 9 = nav_link(html_options: {class: "home #{project_tab_class}"}) do
10   - = link_to @project.code, project_path(@project), title: "Project"
  10 + = link_to @project.path, project_path(@project), title: "Project"
11 11  
12 12 - if @project.repo_exists?
13 13 - if can? current_user, :download_code, @project
... ...
app/views/profile/account.html.haml
... ... @@ -8,6 +8,7 @@
8 8 = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider)
9 9  
10 10  
  11 +
11 12 %fieldset
12 13 %legend
13 14 Private token
... ... @@ -44,11 +45,25 @@
44 45 .input= f.password_field :password
45 46 .clearfix
46 47 = f.label :password_confirmation
47   - .input= f.password_field :password_confirmation
48   - .actions
49   - = f.submit 'Save', class: "btn save-btn"
  48 + .input
  49 + = f.password_field :password_confirmation
  50 + .clearfix
  51 + .input
  52 + = f.submit 'Save password', class: "btn save-btn"
50 53  
51 54  
52 55  
  56 +%fieldset
  57 + %legend
  58 + Username
  59 + %small.right
  60 + Changing your username can have unintended side effects!
  61 + = form_for @user, url: profile_update_path, method: :put do |f|
  62 + .padded
  63 + = f.label :username
  64 + .input
  65 + = f.text_field :username
  66 + .input
  67 + = f.submit 'Save username', class: "btn save-btn"
53 68  
54 69  
... ...
app/views/projects/_form.html.haml
... ... @@ -9,48 +9,45 @@
9 9 Project name is
10 10 .input
11 11 = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
12   -
13 12 %fieldset
14 13 %legend Advanced settings:
15   - .clearfix
  14 + .control-group
16 15 = f.label :path do
17 16 Path
18   - .input
19   - .input-prepend
20   - %strong
21   - = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true
22   - .clearfix
23   - = f.label :code do
24   - URL
25   - .input
26   - .input-prepend
27   - %span.add-on= web_app_url
28   - = f.text_field :code, placeholder: "example"
29   -
30   - - unless @project.new_record? || @project.heads.empty?
  17 + .controls
  18 + = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true
  19 +
  20 + .control-group
  21 + = f.label :namespace_id do
  22 + %span Namespace
  23 + .controls
  24 + = f.select :namespace_id, namespaces_options(@project.namespace_id), {}, {class: 'chosen'}
  25 + &nbsp;
  26 + %span.cred Be careful. Changing project namespace can have unintended side effects
  27 +
  28 + - unless @project.heads.empty?
31 29 .clearfix
32 30 = f.label :default_branch, "Default Branch"
33 31 .input= f.select(:default_branch, @project.heads.map(&:name), {}, style: "width:210px;")
34 32  
35   - - unless @project.new_record?
36   - %fieldset
37   - %legend Features:
  33 + %fieldset
  34 + %legend Features:
38 35  
39   - .clearfix
40   - = f.label :issues_enabled, "Issues"
41   - .input= f.check_box :issues_enabled
  36 + .clearfix
  37 + = f.label :issues_enabled, "Issues"
  38 + .input= f.check_box :issues_enabled
42 39  
43   - .clearfix
44   - = f.label :merge_requests_enabled, "Merge Requests"
45   - .input= f.check_box :merge_requests_enabled
  40 + .clearfix
  41 + = f.label :merge_requests_enabled, "Merge Requests"
  42 + .input= f.check_box :merge_requests_enabled
46 43  
47   - .clearfix
48   - = f.label :wall_enabled, "Wall"
49   - .input= f.check_box :wall_enabled
  44 + .clearfix
  45 + = f.label :wall_enabled, "Wall"
  46 + .input= f.check_box :wall_enabled
50 47  
51   - .clearfix
52   - = f.label :wiki_enabled, "Wiki"
53   - .input= f.check_box :wiki_enabled
  48 + .clearfix
  49 + = f.label :wiki_enabled, "Wiki"
  50 + .input= f.check_box :wiki_enabled
54 51  
55 52 %br
56 53  
... ...
app/views/projects/_new_form.html.haml
... ... @@ -9,21 +9,12 @@
9 9 = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
10 10 = f.submit 'Create project', class: "btn primary project-submit"
11 11  
12   - %hr
13   - %div.adv_settings
14   - %h6 Advanced settings:
15   - .clearfix
16   - = f.label :path do
17   - Git Clone
18   - .input
19   - .input-prepend
20   - %span.add-on= Gitlab.config.ssh_path
21   - = f.text_field :path, placeholder: "example_project", disabled: !@project.new_record?
22   - %span.add-on= ".git"
  12 + - if current_user.several_namespaces?
23 13 .clearfix
24   - = f.label :code do
25   - URL
  14 + = f.label :namespace_id do
  15 + %span.cgray Namespace
26 16 .input
27   - .input-prepend
28   - %span.add-on= web_app_url
29   - = f.text_field :code, placeholder: "example"
  17 + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'}
  18 + %hr
  19 + %p.padded
  20 + All created project are private. You choose who can see project and commit to repository.
... ...
app/views/projects/update.js.haml
1 1 - if @project.valid?
2 2 :plain
3   - location.href = "#{edit_project_path(@project, notice: 'Project was successfully updated.')}";
  3 + location.href = "#{edit_project_path(@project)}";
4 4 - else
5 5 :plain
6 6 $('.project_edit_holder').show();
... ...
app/views/snippets/show.html.haml
... ... @@ -15,8 +15,12 @@
15 15 %span.options
16 16 = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn very_small", target: "_blank"
17 17 .file_content.code
18   - %div{class: current_user.dark_scheme ? "black" : ""}
19   - = raw @snippet.colorize(options: { linenos: 'True'})
  18 + - unless @snippet.content.empty?
  19 + %div{class: current_user.dark_scheme ? "black" : "white"}
  20 + = preserve do
  21 + = raw Pygments.highlight(@snippet.content, formatter: :gitlab)
  22 + - else
  23 + %h4.nothing_here_message Empty file
20 24  
21 25  
22 26 %div
... ...
config/routes.rb
... ... @@ -18,7 +18,7 @@ Gitlab::Application.routes.draw do
18 18 project_root: Gitlab.config.git_base_path,
19 19 upload_pack: Gitlab.config.git_upload_pack,
20 20 receive_pack: Gitlab.config.git_receive_pack
21   - }), at: '/:path', constraints: { path: /[\w\.-]+\.git/ }
  21 + }), at: '/:path', constraints: { path: /[-\/\w\.-]+\.git/ }
22 22  
23 23 #
24 24 # Help
... ... @@ -49,7 +49,7 @@ Gitlab::Application.routes.draw do
49 49 delete :remove_project
50 50 end
51 51 end
52   - resources :projects, constraints: { id: /[^\/]+/ } do
  52 + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do
53 53 member do
54 54 get :team
55 55 put :team_update
... ... @@ -107,7 +107,7 @@ Gitlab::Application.routes.draw do
107 107 #
108 108 # Project Area
109 109 #
110   - resources :projects, constraints: { id: /[^\/]+/ }, except: [:new, :create, :index], path: "/" do
  110 + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
111 111 member do
112 112 get "wall"
113 113 get "graph"
... ...
db/fixtures/development/001_admin.rb
1 1 unless User.count > 0
2 2 admin = User.create(
3   - :email => "admin@local.host",
4   - :name => "Administrator",
5   - :password => "5iveL!fe",
6   - :password_confirmation => "5iveL!fe"
  3 + email: "admin@local.host",
  4 + name: "Administrator",
  5 + username: 'root',
  6 + password: "5iveL!fe",
  7 + password_confirmation: "5iveL!fe"
7 8 )
8 9  
9 10 admin.projects_limit = 10000
... ...
db/fixtures/development/002_project.rb
1 1 Project.seed(:id, [
2   - { id: 1, name: "Underscore.js", path: "underscore", code: "underscore", owner_id: 1 },
3   - { id: 2, name: "Diaspora", path: "diaspora", code: "diaspora", owner_id: 1 },
4   - { id: 3, name: "Ruby on Rails", path: "rails", code: "rails", owner_id: 1 }
  2 + { id: 1, name: "Underscore.js", path: "underscore", owner_id: 1 },
  3 + { id: 2, name: "Diaspora", path: "diaspora", owner_id: 1 },
  4 + { id: 3, name: "Ruby on Rails", path: "rails", owner_id: 1 }
5 5 ])
... ...
db/fixtures/development/003_users.rb
1 1 User.seed(:id, [
2   - { :id => 2, :name => Faker::Internet.user_name, :email => Faker::Internet.email},
3   - { :id => 3, :name => Faker::Internet.user_name, :email => Faker::Internet.email},
4   - { :id => 4, :name => Faker::Internet.user_name, :email => Faker::Internet.email},
5   - { :id => 5, :name => Faker::Internet.user_name, :email => Faker::Internet.email},
6   - { :id => 6, :name => Faker::Internet.user_name, :email => Faker::Internet.email},
7   - { :id => 7, :name => Faker::Internet.user_name, :email => Faker::Internet.email},
8   - { :id => 8, :name => Faker::Internet.user_name, :email => Faker::Internet.email},
9   - { :id => 9, :name => Faker::Internet.user_name, :email => Faker::Internet.email}
  2 + { id: 2, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email},
  3 + { id: 3, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email},
  4 + { id: 4, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email},
  5 + { id: 5, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email},
  6 + { id: 6, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email},
  7 + { id: 7, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email},
  8 + { id: 8, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email},
  9 + { id: 9, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}
10 10 ])
11 11  
... ...
db/fixtures/production/001_admin.rb
1 1 admin = User.create(
2   - :email => "admin@local.host",
3   - :name => "Administrator",
4   - :password => "5iveL!fe",
5   - :password_confirmation => "5iveL!fe"
  2 + email: "admin@local.host",
  3 + name: "Administrator",
  4 + username: 'root',
  5 + password: "5iveL!fe",
  6 + password_confirmation: "5iveL!fe"
6 7 )
7 8  
8 9 admin.projects_limit = 10000
... ...
db/migrate/20121122145155_convert_group_to_namespace.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class ConvertGroupToNamespace < ActiveRecord::Migration
  2 + def up
  3 + rename_table 'groups', 'namespaces'
  4 + add_column :namespaces, :type, :string, null: true
  5 +
  6 + # Migrate old groups
  7 + Namespace.update_all(type: 'Group')
  8 + end
  9 +
  10 + def down
  11 + raise 'Rollback is not allowed'
  12 + end
  13 +end
... ...
db/migrate/20121122150932_add_namespace_id_to_project.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddNamespaceIdToProject < ActiveRecord::Migration
  2 + def change
  3 + rename_column :projects, :group_id, :namespace_id
  4 + end
  5 +end
... ...
db/migrate/20121123104937_add_username_to_user.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddUsernameToUser < ActiveRecord::Migration
  2 + def change
  3 + add_column :users, :username, :string, null: true
  4 + end
  5 +end
... ...
db/migrate/20121123164910_rename_code_to_path.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class RenameCodeToPath < ActiveRecord::Migration
  2 + def up
  3 + remove_column :projects, :code
  4 + rename_column :namespaces, :code, :path
  5 + end
  6 +
  7 + def down
  8 + add_column :projects, :code, :string
  9 + rename_column :namespaces, :path, :code
  10 + end
  11 +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 => 20121120113838) do
  14 +ActiveRecord::Schema.define(:version => 20121123164910) do
15 15  
16 16 create_table "events", :force => true do |t|
17 17 t.string "target_type"
... ... @@ -25,14 +25,6 @@ ActiveRecord::Schema.define(:version =&gt; 20121120113838) do
25 25 t.integer "author_id"
26 26 end
27 27  
28   - create_table "groups", :force => true do |t|
29   - t.string "name", :null => false
30   - t.string "code", :null => false
31   - t.integer "owner_id", :null => false
32   - t.datetime "created_at", :null => false
33   - t.datetime "updated_at", :null => false
34   - end
35   -
36 28 create_table "issues", :force => true do |t|
37 29 t.string "title"
38 30 t.integer "assignee_id"
... ... @@ -88,6 +80,15 @@ ActiveRecord::Schema.define(:version =&gt; 20121120113838) do
88 80 t.datetime "updated_at", :null => false
89 81 end
90 82  
  83 + create_table "namespaces", :force => true do |t|
  84 + t.string "name", :null => false
  85 + t.string "path", :null => false
  86 + t.integer "owner_id", :null => false
  87 + t.datetime "created_at", :null => false
  88 + t.datetime "updated_at", :null => false
  89 + t.string "type"
  90 + end
  91 +
91 92 create_table "notes", :force => true do |t|
92 93 t.text "note"
93 94 t.string "noteable_id"
... ... @@ -110,14 +111,13 @@ ActiveRecord::Schema.define(:version =&gt; 20121120113838) do
110 111 t.datetime "created_at", :null => false
111 112 t.datetime "updated_at", :null => false
112 113 t.boolean "private_flag", :default => true, :null => false
113   - t.string "code"
114 114 t.integer "owner_id"
115 115 t.string "default_branch"
116 116 t.boolean "issues_enabled", :default => true, :null => false
117 117 t.boolean "wall_enabled", :default => true, :null => false
118 118 t.boolean "merge_requests_enabled", :default => true, :null => false
119 119 t.boolean "wiki_enabled", :default => true, :null => false
120   - t.integer "group_id"
  120 + t.integer "namespace_id"
121 121 end
122 122  
123 123 create_table "protected_branches", :force => true do |t|
... ... @@ -194,6 +194,7 @@ ActiveRecord::Schema.define(:version =&gt; 20121120113838) do
194 194 t.datetime "locked_at"
195 195 t.string "extern_uid"
196 196 t.string "provider"
  197 + t.string "username"
197 198 end
198 199  
199 200 add_index "users", ["email"], :name => "index_users_on_email", :unique => true
... ...
features/project/issues/issues.feature
... ... @@ -57,13 +57,14 @@ Feature: Project Issues
57 57 Then I should see "Release 0.3" in issues
58 58 And I should not see "Release 0.4" in issues
59 59  
60   - @javascript
61   - Scenario: I clear search
62   - Given I click link "All"
63   - And I fill in issue search with "Something"
64   - And I fill in issue search with ""
65   - Then I should see "Release 0.4" in issues
66   - And I should see "Release 0.3" in issues
  60 + # TODO: find out solution for poltergeist/phantomjs or remove
  61 + # @javascript
  62 + # Scenario: I clear search
  63 + # Given I click link "All"
  64 + # And I fill in issue search with "Something"
  65 + # And I fill in issue search with ""
  66 + # Then I should see "Release 0.4" in issues
  67 + # And I should see "Release 0.3" in issues
67 68  
68 69 @javascript
69 70 Scenario: I create Issue with pre-selected milestone
... ...
features/steps/admin/admin_groups.rb
... ... @@ -9,7 +9,7 @@ class AdminGroups &lt; Spinach::FeatureSteps
9 9  
10 10 And 'submit form with new group info' do
11 11 fill_in 'group_name', :with => 'gitlab'
12   - fill_in 'group_code', :with => 'gitlab'
  12 + fill_in 'group_path', :with => 'gitlab'
13 13 click_button "Save group"
14 14 end
15 15  
... ...
features/steps/project/create_project.rb
... ... @@ -4,8 +4,6 @@ class CreateProject &lt; Spinach::FeatureSteps
4 4  
5 5 And 'fill project form with valid data' do
6 6 fill_in 'project_name', :with => 'NewProject'
7   - fill_in 'project_code', :with => 'NPR'
8   - fill_in 'project_path', :with => 'newproject'
9 7 click_button "Create project"
10 8 end
11 9  
... ...
features/steps/project/project_issues.rb
... ... @@ -73,7 +73,6 @@ class ProjectIssues &lt; Spinach::FeatureSteps
73 73 end
74 74  
75 75 And 'I fill in issue search with ""' do
76   - page.execute_script("$('.issue_search').val('').keyup();");
77 76 fill_in 'issue_search', with: ""
78 77 end
79 78  
... ...
features/support/env.rb
... ... @@ -5,7 +5,7 @@ require &#39;rspec&#39;
5 5 require 'database_cleaner'
6 6 require 'spinach/capybara'
7 7  
8   -%w(gitolite_stub stubbed_repository valid_commit).each do |f|
  8 +%w(namespaces_stub gitolite_stub stubbed_repository valid_commit).each do |f|
9 9 require Rails.root.join('spec', 'support', f)
10 10 end
11 11  
... ...
lib/api/helpers.rb
... ... @@ -6,7 +6,7 @@ module Gitlab
6 6  
7 7 def user_project
8 8 if @project ||= current_user.projects.find_by_id(params[:id]) ||
9   - current_user.projects.find_by_code(params[:id])
  9 + current_user.projects.find_by_path(params[:id])
10 10 else
11 11 not_found!
12 12 end
... ...
lib/api/projects.rb
... ... @@ -38,11 +38,7 @@ module Gitlab
38 38 # Example Request
39 39 # POST /projects
40 40 post do
41   - params[:code] ||= params[:name]
42   - params[:path] ||= params[:name]
43   - attrs = attributes_for_keys [:code,
44   - :path,
45   - :name,
  41 + attrs = attributes_for_keys [:name,
46 42 :description,
47 43 :default_branch,
48 44 :issues_enabled,
... ...
lib/api/users.rb
... ... @@ -38,7 +38,7 @@ module Gitlab
38 38 # POST /users
39 39 post do
40 40 authenticated_as_admin!
41   - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit]
  41 + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username]
42 42 user = User.new attrs, as: :admin
43 43 if user.save
44 44 present user, with: Entities::User
... ...
lib/gitlab/auth.rb
... ... @@ -34,6 +34,7 @@ module Gitlab
34 34 extern_uid: uid,
35 35 provider: provider,
36 36 name: name,
  37 + username: email.match(/^[^@]*/)[0],
37 38 email: email,
38 39 password: password,
39 40 password_confirmation: password,
... ...
lib/gitlab/backend/gitolite_config.rb
... ... @@ -126,7 +126,7 @@ module Gitlab
126 126 end
127 127  
128 128 def update_project_config(project, conf)
129   - repo_name = project.path
  129 + repo_name = project.path_with_namespace
130 130  
131 131 repo = if conf.has_repo?(repo_name)
132 132 conf.get_repo(repo_name)
... ...
lib/gitlab/project_mover.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +# ProjectMover class
  2 +#
  3 +# Used for moving project repositories from one subdir to another
  4 +module Gitlab
  5 + class ProjectMover
  6 + class ProjectMoveError < StandardError; end
  7 +
  8 + attr_reader :project, :old_dir, :new_dir
  9 +
  10 + def initialize(project, old_dir, new_dir)
  11 + @project = project
  12 + @old_dir = old_dir
  13 + @new_dir = new_dir
  14 + end
  15 +
  16 + def execute
  17 + # Create new dir if missing
  18 + new_dir_path = File.join(Gitlab.config.git_base_path, new_dir)
  19 + Dir.mkdir(new_dir_path) unless File.exists?(new_dir_path)
  20 +
  21 + old_path = File.join(Gitlab.config.git_base_path, old_dir, "#{project.path}.git")
  22 + new_path = File.join(new_dir_path, "#{project.path}.git")
  23 +
  24 + if system("mv #{old_path} #{new_path}")
  25 + log_info "Project #{project.name} was moved from #{old_path} to #{new_path}"
  26 + true
  27 + else
  28 + message = "Project #{project.name} cannot be moved from #{old_path} to #{new_path}"
  29 + log_info "Error! #{message}"
  30 + raise ProjectMoveError.new(message)
  31 + false
  32 + end
  33 + end
  34 +
  35 + protected
  36 +
  37 + def log_info message
  38 + Gitlab::AppLogger.info message
  39 + end
  40 + end
  41 +end
... ...
lib/tasks/gitlab/activate_namespaces.rake 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +namespace :gitlab do
  2 + desc "GITLAB | Enable usernames and namespaces for user projects"
  3 + task activate_namespaces: :environment do
  4 + User.find_each(batch_size: 500) do |user|
  5 + next if user.namespace
  6 +
  7 + User.transaction do
  8 + username = user.email.match(/^[^@]*/)[0]
  9 + if user.update_attributes!(username: username)
  10 + print '.'.green
  11 + else
  12 + print 'F'.red
  13 + end
  14 + end
  15 + end
  16 + end
  17 +end
... ...
spec/controllers/commits_controller_spec.rb
... ... @@ -13,7 +13,7 @@ describe CommitsController do
13 13 describe "GET show" do
14 14 context "as atom feed" do
15 15 it "should render as atom" do
16   - get :show, project_id: project.code, id: "master.atom"
  16 + get :show, project_id: project.path, id: "master.atom"
17 17 response.should be_success
18 18 response.content_type.should == 'application/atom+xml'
19 19 end
... ...
spec/factories.rb
... ... @@ -12,6 +12,7 @@ FactoryGirl.define do
12 12 factory :user, aliases: [:author, :assignee, :owner] do
13 13 email { Faker::Internet.email }
14 14 name
  15 + username { Faker::Internet.user_name }
15 16 password "123456"
16 17 password_confirmation { password }
17 18  
... ... @@ -25,13 +26,19 @@ FactoryGirl.define do
25 26 factory :project do
26 27 sequence(:name) { |n| "project#{n}" }
27 28 path { name.downcase.gsub(/\s/, '_') }
28   - code { name.downcase.gsub(/\s/, '_') }
29 29 owner
30 30 end
31 31  
32 32 factory :group do
33 33 sequence(:name) { |n| "group#{n}" }
34   - code { name.downcase.gsub(/\s/, '_') }
  34 + path { name.downcase.gsub(/\s/, '_') }
  35 + owner
  36 + type 'Group'
  37 + end
  38 +
  39 + factory :namespace do
  40 + sequence(:name) { |n| "group#{n}" }
  41 + path { name.downcase.gsub(/\s/, '_') }
35 42 owner
36 43 end
37 44  
... ...
spec/mailers/notify_spec.rb
... ... @@ -169,9 +169,7 @@ describe Notify do
169 169 end
170 170  
171 171 describe 'project access changed' do
172   - let(:project) { create(:project,
173   - path: "Fuu",
174   - code: "Fuu") }
  172 + let(:project) { create(:project) }
175 173 let(:user) { create(:user) }
176 174 let(:users_project) { create(:users_project,
177 175 project: project,
... ...
spec/models/group_spec.rb
1 1 # == Schema Information
2 2 #
3   -# Table name: groups
  3 +# Table name: namespaces
4 4 #
5 5 # id :integer not null, primary key
6 6 # name :string(255) not null
7   -# code :string(255) not null
  7 +# path :string(255) not null
8 8 # owner_id :integer not null
9 9 # created_at :datetime not null
10 10 # updated_at :datetime not null
  11 +# type :string(255)
11 12 #
12 13  
13 14 require 'spec_helper'
... ... @@ -18,7 +19,7 @@ describe Group do
18 19 it { should have_many :projects }
19 20 it { should validate_presence_of :name }
20 21 it { should validate_uniqueness_of(:name) }
21   - it { should validate_presence_of :code }
22   - it { should validate_uniqueness_of(:code) }
  22 + it { should validate_presence_of :path }
  23 + it { should validate_uniqueness_of(:path) }
23 24 it { should validate_presence_of :owner }
24 25 end
... ...
spec/models/namespace_spec.rb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: namespaces
  4 +#
  5 +# id :integer not null, primary key
  6 +# name :string(255) not null
  7 +# path :string(255) not null
  8 +# owner_id :integer not null
  9 +# created_at :datetime not null
  10 +# updated_at :datetime not null
  11 +# type :string(255)
  12 +#
  13 +
  14 +require 'spec_helper'
  15 +
  16 +describe Namespace do
  17 + let!(:namespace) { create(:namespace) }
  18 +
  19 + it { should have_many :projects }
  20 + it { should validate_presence_of :name }
  21 + it { should validate_uniqueness_of(:name) }
  22 + it { should validate_presence_of :path }
  23 + it { should validate_uniqueness_of(:path) }
  24 + it { should validate_presence_of :owner }
  25 +
  26 + describe "Mass assignment" do
  27 + it { should allow_mass_assignment_of(:name) }
  28 + it { should allow_mass_assignment_of(:path) }
  29 + end
  30 +
  31 + describe "Respond to" do
  32 + it { should respond_to(:human_name) }
  33 + it { should respond_to(:to_param) }
  34 + end
  35 +end
... ...
spec/models/project_spec.rb
... ... @@ -9,14 +9,13 @@
9 9 # created_at :datetime not null
10 10 # updated_at :datetime not null
11 11 # private_flag :boolean default(TRUE), not null
12   -# code :string(255)
13 12 # owner_id :integer
14 13 # default_branch :string(255)
15 14 # issues_enabled :boolean default(TRUE), not null
16 15 # wall_enabled :boolean default(TRUE), not null
17 16 # merge_requests_enabled :boolean default(TRUE), not null
18 17 # wiki_enabled :boolean default(TRUE), not null
19   -# group_id :integer
  18 +# namespace_id :integer
20 19 #
21 20  
22 21 require 'spec_helper'
... ... @@ -24,6 +23,7 @@ require &#39;spec_helper&#39;
24 23 describe Project do
25 24 describe "Associations" do
26 25 it { should belong_to(:group) }
  26 + it { should belong_to(:namespace) }
27 27 it { should belong_to(:owner).class_name('User') }
28 28 it { should have_many(:users) }
29 29 it { should have_many(:events).dependent(:destroy) }
... ... @@ -40,6 +40,7 @@ describe Project do
40 40 end
41 41  
42 42 describe "Mass assignment" do
  43 + it { should_not allow_mass_assignment_of(:namespace_id) }
43 44 it { should_not allow_mass_assignment_of(:owner_id) }
44 45 it { should_not allow_mass_assignment_of(:private_flag) }
45 46 end
... ... @@ -58,9 +59,6 @@ describe Project do
58 59  
59 60 it { should ensure_length_of(:description).is_within(0..2000) }
60 61  
61   - it { should validate_presence_of(:code) }
62   - it { should validate_uniqueness_of(:code) }
63   - it { should ensure_length_of(:code).is_within(1..255) }
64 62 # TODO: Formats
65 63  
66 64 it { should validate_presence_of(:owner) }
... ... @@ -151,7 +149,7 @@ describe Project do
151 149 end
152 150  
153 151 it "returns the full web URL for this repo" do
154   - project = Project.new(code: "somewhere")
  152 + project = Project.new(path: "somewhere")
155 153 project.web_url.should == "#{Gitlab.config.url}/somewhere"
156 154 end
157 155  
... ... @@ -162,7 +160,7 @@ describe Project do
162 160 end
163 161  
164 162 it "should be invalid repo" do
165   - project = Project.new(name: "ok_name", path: "/INVALID_PATH/", code: "NEOK")
  163 + project = Project.new(name: "ok_name", path: "/INVALID_PATH/", path: "NEOK")
166 164 project.valid_repo?.should be_false
167 165 end
168 166 end
... ...
spec/models/user_spec.rb
... ... @@ -30,14 +30,17 @@
30 30 # locked_at :datetime
31 31 # extern_uid :string(255)
32 32 # provider :string(255)
  33 +# username :string(255)
33 34 #
34 35  
35 36 require 'spec_helper'
36 37  
37 38 describe User do
38 39 describe "Associations" do
  40 + it { should have_one(:namespace) }
39 41 it { should have_many(:users_projects).dependent(:destroy) }
40 42 it { should have_many(:projects) }
  43 + it { should have_many(:groups) }
41 44 it { should have_many(:my_own_projects).class_name('Project') }
42 45 it { should have_many(:keys).dependent(:destroy) }
43 46 it { should have_many(:events).class_name('Event').dependent(:destroy) }
... ...
spec/observers/user_observer_spec.rb
... ... @@ -13,7 +13,12 @@ describe UserObserver do
13 13 end
14 14  
15 15 context 'when a new user is created' do
16   - let(:user) { double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local') }
  16 + let(:user) { double(:user, id: 42,
  17 + password: 'P@ssword!',
  18 + name: 'John',
  19 + email: 'u@mail.local',
  20 + username: 'root',
  21 + create_namespace: true) }
17 22 let(:notification) { double :notification }
18 23  
19 24 it 'sends an email' do
... ...
spec/observers/users_project_observer_spec.rb
... ... @@ -2,9 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe UsersProjectObserver do
4 4 let(:user) { create(:user) }
5   - let(:project) { create(:project,
6   - code: "Fuu",
7   - path: "Fuu" ) }
  5 + let(:project) { create(:project) }
8 6 let(:users_project) { create(:users_project,
9 7 project: project,
10 8 user: user )}
... ...
spec/requests/admin/admin_hooks_spec.rb
... ... @@ -2,9 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe "Admin::Hooks" do
4 4 before do
5   - @project = create(:project,
6   - name: "LeGiT",
7   - code: "LGT")
  5 + @project = create(:project)
8 6 login_as :admin
9 7  
10 8 @system_hook = create(:system_hook)
... ...
spec/requests/admin/admin_projects_spec.rb
... ... @@ -2,9 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe "Admin::Projects" do
4 4 before do
5   - @project = create(:project,
6   - name: "LeGiT",
7   - code: "LGT")
  5 + @project = create(:project)
8 6 login_as :admin
9 7 end
10 8  
... ... @@ -29,7 +27,7 @@ describe &quot;Admin::Projects&quot; do
29 27 end
30 28  
31 29 it "should have project info" do
32   - page.should have_content(@project.code)
  30 + page.should have_content(@project.path)
33 31 page.should have_content(@project.name)
34 32 end
35 33 end
... ... @@ -41,67 +39,27 @@ describe &quot;Admin::Projects&quot; do
41 39 end
42 40  
43 41 it "should have project edit page" do
44   - page.should have_content("Project name")
45   - page.should have_content("URL")
  42 + page.should have_content("Edit project")
  43 + page.should have_button("Save Project")
46 44 end
47 45  
48 46 describe "Update project" do
49 47 before do
50 48 fill_in "project_name", with: "Big Bang"
51   - fill_in "project_code", with: "BB1"
52 49 click_button "Save Project"
53 50 @project.reload
54 51 end
55 52  
56 53 it "should show page with new data" do
57   - page.should have_content("BB1")
58 54 page.should have_content("Big Bang")
59 55 end
60 56  
61 57 it "should change project entry" do
62 58 @project.name.should == "Big Bang"
63   - @project.code.should == "BB1"
64 59 end
65 60 end
66 61 end
67 62  
68   - describe "GET /admin/projects/new" do
69   - before do
70   - visit admin_projects_path
71   - click_link "New Project"
72   - end
73   -
74   - it "should be correct path" do
75   - current_path.should == new_admin_project_path
76   - end
77   -
78   - it "should have labels for new project" do
79   - page.should have_content("Project name is")
80   - page.should have_content("Git Clone")
81   - page.should have_content("URL")
82   - end
83   - end
84   -
85   - describe "POST /admin/projects" do
86   - before do
87   - visit new_admin_project_path
88   - fill_in 'project_name', with: 'NewProject'
89   - fill_in 'project_code', with: 'NPR'
90   - fill_in 'project_path', with: 'gitlabhq_1'
91   - expect { click_button "Create project" }.to change { Project.count }.by(1)
92   - @project = Project.last
93   - end
94   -
95   - it "should be correct path" do
96   - current_path.should == admin_project_path(@project)
97   - end
98   -
99   - it "should show project" do
100   - page.should have_content(@project.name)
101   - page.should have_content(@project.path)
102   - end
103   - end
104   -
105 63 describe "Add new team member" do
106 64 before do
107 65 @new_user = create(:user)
... ...
spec/requests/admin/admin_users_spec.rb
... ... @@ -23,6 +23,7 @@ describe &quot;Admin::Users&quot; do
23 23 @password = "123ABC"
24 24 visit new_admin_user_path
25 25 fill_in "user_name", with: "Big Bang"
  26 + fill_in "user_username", with: "bang"
26 27 fill_in "user_email", with: "bigbang@mail.com"
27 28 fill_in "user_password", with: @password
28 29 fill_in "user_password_confirmation", with: @password
... ...
spec/requests/api/issues_spec.rb
... ... @@ -28,7 +28,7 @@ describe Gitlab::API do
28 28  
29 29 describe "GET /projects/:id/issues" do
30 30 it "should return project issues" do
31   - get api("/projects/#{project.code}/issues", user)
  31 + get api("/projects/#{project.path}/issues", user)
32 32 response.status.should == 200
33 33 json_response.should be_an Array
34 34 json_response.first['title'].should == issue.title
... ... @@ -37,7 +37,7 @@ describe Gitlab::API do
37 37  
38 38 describe "GET /projects/:id/issues/:issue_id" do
39 39 it "should return a project issue by id" do
40   - get api("/projects/#{project.code}/issues/#{issue.id}", user)
  40 + get api("/projects/#{project.path}/issues/#{issue.id}", user)
41 41 response.status.should == 200
42 42 json_response['title'].should == issue.title
43 43 end
... ... @@ -45,7 +45,7 @@ describe Gitlab::API do
45 45  
46 46 describe "POST /projects/:id/issues" do
47 47 it "should create a new project issue" do
48   - post api("/projects/#{project.code}/issues", user),
  48 + post api("/projects/#{project.path}/issues", user),
49 49 title: 'new issue', labels: 'label, label2'
50 50 response.status.should == 201
51 51 json_response['title'].should == 'new issue'
... ... @@ -56,7 +56,7 @@ describe Gitlab::API do
56 56  
57 57 describe "PUT /projects/:id/issues/:issue_id" do
58 58 it "should update a project issue" do
59   - put api("/projects/#{project.code}/issues/#{issue.id}", user),
  59 + put api("/projects/#{project.path}/issues/#{issue.id}", user),
60 60 title: 'updated title', labels: 'label2', closed: 1
61 61 response.status.should == 200
62 62 json_response['title'].should == 'updated title'
... ... @@ -67,7 +67,7 @@ describe Gitlab::API do
67 67  
68 68 describe "DELETE /projects/:id/issues/:issue_id" do
69 69 it "should delete a project issue" do
70   - delete api("/projects/#{project.code}/issues/#{issue.id}", user)
  70 + delete api("/projects/#{project.path}/issues/#{issue.id}", user)
71 71 response.status.should == 405
72 72 end
73 73 end
... ...
spec/requests/api/merge_requests_spec.rb
... ... @@ -11,14 +11,14 @@ describe Gitlab::API do
11 11 describe "GET /projects/:id/merge_requests" do
12 12 context "when unauthenticated" do
13 13 it "should return authentication error" do
14   - get api("/projects/#{project.code}/merge_requests")
  14 + get api("/projects/#{project.path}/merge_requests")
15 15 response.status.should == 401
16 16 end
17 17 end
18 18  
19 19 context "when authenticated" do
20 20 it "should return an array of merge_requests" do
21   - get api("/projects/#{project.code}/merge_requests", user)
  21 + get api("/projects/#{project.path}/merge_requests", user)
22 22 response.status.should == 200
23 23 json_response.should be_an Array
24 24 json_response.first['title'].should == merge_request.title
... ... @@ -28,7 +28,7 @@ describe Gitlab::API do
28 28  
29 29 describe "GET /projects/:id/merge_request/:merge_request_id" do
30 30 it "should return merge_request" do
31   - get api("/projects/#{project.code}/merge_request/#{merge_request.id}", user)
  31 + get api("/projects/#{project.path}/merge_request/#{merge_request.id}", user)
32 32 response.status.should == 200
33 33 json_response['title'].should == merge_request.title
34 34 end
... ... @@ -36,7 +36,7 @@ describe Gitlab::API do
36 36  
37 37 describe "POST /projects/:id/merge_requests" do
38 38 it "should return merge_request" do
39   - post api("/projects/#{project.code}/merge_requests", user),
  39 + post api("/projects/#{project.path}/merge_requests", user),
40 40 title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user
41 41 response.status.should == 201
42 42 json_response['title'].should == 'Test merge_request'
... ... @@ -45,7 +45,7 @@ describe Gitlab::API do
45 45  
46 46 describe "PUT /projects/:id/merge_request/:merge_request_id" do
47 47 it "should return merge_request" do
48   - put api("/projects/#{project.code}/merge_request/#{merge_request.id}", user), title: "New title"
  48 + put api("/projects/#{project.path}/merge_request/#{merge_request.id}", user), title: "New title"
49 49 response.status.should == 200
50 50 json_response['title'].should == 'New title'
51 51 end
... ... @@ -53,7 +53,7 @@ describe Gitlab::API do
53 53  
54 54 describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
55 55 it "should return comment" do
56   - post api("/projects/#{project.code}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
  56 + post api("/projects/#{project.path}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
57 57 response.status.should == 201
58 58 json_response['note'].should == 'My comment'
59 59 end
... ...
spec/requests/api/milestones_spec.rb
... ... @@ -11,7 +11,7 @@ describe Gitlab::API do
11 11  
12 12 describe "GET /projects/:id/milestones" do
13 13 it "should return project milestones" do
14   - get api("/projects/#{project.code}/milestones", user)
  14 + get api("/projects/#{project.path}/milestones", user)
15 15 response.status.should == 200
16 16 json_response.should be_an Array
17 17 json_response.first['title'].should == milestone.title
... ... @@ -20,7 +20,7 @@ describe Gitlab::API do
20 20  
21 21 describe "GET /projects/:id/milestones/:milestone_id" do
22 22 it "should return a project milestone by id" do
23   - get api("/projects/#{project.code}/milestones/#{milestone.id}", user)
  23 + get api("/projects/#{project.path}/milestones/#{milestone.id}", user)
24 24 response.status.should == 200
25 25 json_response['title'].should == milestone.title
26 26 end
... ... @@ -28,7 +28,7 @@ describe Gitlab::API do
28 28  
29 29 describe "POST /projects/:id/milestones" do
30 30 it "should create a new project milestone" do
31   - post api("/projects/#{project.code}/milestones", user),
  31 + post api("/projects/#{project.path}/milestones", user),
32 32 title: 'new milestone'
33 33 response.status.should == 201
34 34 json_response['title'].should == 'new milestone'
... ... @@ -38,7 +38,7 @@ describe Gitlab::API do
38 38  
39 39 describe "PUT /projects/:id/milestones/:milestone_id" do
40 40 it "should update a project milestone" do
41   - put api("/projects/#{project.code}/milestones/#{milestone.id}", user),
  41 + put api("/projects/#{project.path}/milestones/#{milestone.id}", user),
42 42 title: 'updated title'
43 43 response.status.should == 200
44 44 json_response['title'].should == 'updated title'
... ...
spec/requests/api/projects_spec.rb
... ... @@ -33,7 +33,7 @@ describe Gitlab::API do
33 33 end
34 34  
35 35 describe "POST /projects" do
36   - it "should create new project without code and path" do
  36 + it "should create new project without path" do
37 37 expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1)
38 38 end
39 39  
... ... @@ -53,8 +53,6 @@ describe Gitlab::API do
53 53  
54 54 it "should assign attributes to project" do
55 55 project = attributes_for(:project, {
56   - path: 'path',
57   - code: 'code',
58 56 description: Faker::Lorem.sentence,
59 57 default_branch: 'stable',
60 58 issues_enabled: false,
... ... @@ -79,8 +77,8 @@ describe Gitlab::API do
79 77 json_response['owner']['email'].should == user.email
80 78 end
81 79  
82   - it "should return a project by code name" do
83   - get api("/projects/#{project.code}", user)
  80 + it "should return a project by path name" do
  81 + get api("/projects/#{project.path}", user)
84 82 response.status.should == 200
85 83 json_response['name'].should == project.name
86 84 end
... ... @@ -94,7 +92,7 @@ describe Gitlab::API do
94 92  
95 93 describe "GET /projects/:id/repository/branches" do
96 94 it "should return an array of project branches" do
97   - get api("/projects/#{project.code}/repository/branches", user)
  95 + get api("/projects/#{project.path}/repository/branches", user)
98 96 response.status.should == 200
99 97 json_response.should be_an Array
100 98 json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
... ... @@ -103,7 +101,7 @@ describe Gitlab::API do
103 101  
104 102 describe "GET /projects/:id/repository/branches/:branch" do
105 103 it "should return the branch information for a single branch" do
106   - get api("/projects/#{project.code}/repository/branches/new_design", user)
  104 + get api("/projects/#{project.path}/repository/branches/new_design", user)
107 105 response.status.should == 200
108 106  
109 107 json_response['name'].should == 'new_design'
... ... @@ -113,7 +111,7 @@ describe Gitlab::API do
113 111  
114 112 describe "GET /projects/:id/members" do
115 113 it "should return project team members" do
116   - get api("/projects/#{project.code}/members", user)
  114 + get api("/projects/#{project.path}/members", user)
117 115 response.status.should == 200
118 116 json_response.should be_an Array
119 117 json_response.count.should == 2
... ... @@ -123,7 +121,7 @@ describe Gitlab::API do
123 121  
124 122 describe "GET /projects/:id/members/:user_id" do
125 123 it "should return project team member" do
126   - get api("/projects/#{project.code}/members/#{user.id}", user)
  124 + get api("/projects/#{project.path}/members/#{user.id}", user)
127 125 response.status.should == 200
128 126 json_response['email'].should == user.email
129 127 json_response['access_level'].should == UsersProject::MASTER
... ... @@ -133,7 +131,7 @@ describe Gitlab::API do
133 131 describe "POST /projects/:id/members" do
134 132 it "should add user to project team" do
135 133 expect {
136   - post api("/projects/#{project.code}/members", user), user_id: user2.id,
  134 + post api("/projects/#{project.path}/members", user), user_id: user2.id,
137 135 access_level: UsersProject::DEVELOPER
138 136 }.to change { UsersProject.count }.by(1)
139 137  
... ... @@ -145,7 +143,7 @@ describe Gitlab::API do
145 143  
146 144 describe "PUT /projects/:id/members/:user_id" do
147 145 it "should update project team member" do
148   - put api("/projects/#{project.code}/members/#{user3.id}", user), access_level: UsersProject::MASTER
  146 + put api("/projects/#{project.path}/members/#{user3.id}", user), access_level: UsersProject::MASTER
149 147 response.status.should == 200
150 148 json_response['email'].should == user3.email
151 149 json_response['access_level'].should == UsersProject::MASTER
... ... @@ -155,14 +153,14 @@ describe Gitlab::API do
155 153 describe "DELETE /projects/:id/members/:user_id" do
156 154 it "should remove user from project team" do
157 155 expect {
158   - delete api("/projects/#{project.code}/members/#{user3.id}", user)
  156 + delete api("/projects/#{project.path}/members/#{user3.id}", user)
159 157 }.to change { UsersProject.count }.by(-1)
160 158 end
161 159 end
162 160  
163 161 describe "GET /projects/:id/hooks" do
164 162 it "should return project hooks" do
165   - get api("/projects/#{project.code}/hooks", user)
  163 + get api("/projects/#{project.path}/hooks", user)
166 164  
167 165 response.status.should == 200
168 166  
... ... @@ -174,7 +172,7 @@ describe Gitlab::API do
174 172  
175 173 describe "GET /projects/:id/hooks/:hook_id" do
176 174 it "should return a project hook" do
177   - get api("/projects/#{project.code}/hooks/#{hook.id}", user)
  175 + get api("/projects/#{project.path}/hooks/#{hook.id}", user)
178 176 response.status.should == 200
179 177 json_response['url'].should == hook.url
180 178 end
... ... @@ -183,7 +181,7 @@ describe Gitlab::API do
183 181 describe "POST /projects/:id/hooks" do
184 182 it "should add hook to project" do
185 183 expect {
186   - post api("/projects/#{project.code}/hooks", user),
  184 + post api("/projects/#{project.path}/hooks", user),
187 185 "url" => "http://example.com"
188 186 }.to change {project.hooks.count}.by(1)
189 187 end
... ... @@ -191,7 +189,7 @@ describe Gitlab::API do
191 189  
192 190 describe "PUT /projects/:id/hooks/:hook_id" do
193 191 it "should update an existing project hook" do
194   - put api("/projects/#{project.code}/hooks/#{hook.id}", user),
  192 + put api("/projects/#{project.path}/hooks/#{hook.id}", user),
195 193 url: 'http://example.org'
196 194 response.status.should == 200
197 195 json_response['url'].should == 'http://example.org'
... ... @@ -202,7 +200,7 @@ describe Gitlab::API do
202 200 describe "DELETE /projects/:id/hooks" do
203 201 it "should delete hook from project" do
204 202 expect {
205   - delete api("/projects/#{project.code}/hooks", user),
  203 + delete api("/projects/#{project.path}/hooks", user),
206 204 hook_id: hook.id
207 205 }.to change {project.hooks.count}.by(-1)
208 206 end
... ... @@ -210,7 +208,7 @@ describe Gitlab::API do
210 208  
211 209 describe "GET /projects/:id/repository/tags" do
212 210 it "should return an array of project tags" do
213   - get api("/projects/#{project.code}/repository/tags", user)
  211 + get api("/projects/#{project.path}/repository/tags", user)
214 212 response.status.should == 200
215 213 json_response.should be_an Array
216 214 json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
... ... @@ -222,7 +220,7 @@ describe Gitlab::API do
222 220 before { project.add_access(user2, :read) }
223 221  
224 222 it "should return project commits" do
225   - get api("/projects/#{project.code}/repository/commits", user)
  223 + get api("/projects/#{project.path}/repository/commits", user)
226 224 response.status.should == 200
227 225  
228 226 json_response.should be_an Array
... ... @@ -232,7 +230,7 @@ describe Gitlab::API do
232 230  
233 231 context "unauthorized user" do
234 232 it "should not return project commits" do
235   - get api("/projects/#{project.code}/repository/commits")
  233 + get api("/projects/#{project.path}/repository/commits")
236 234 response.status.should == 401
237 235 end
238 236 end
... ... @@ -240,7 +238,7 @@ describe Gitlab::API do
240 238  
241 239 describe "GET /projects/:id/snippets" do
242 240 it "should return an array of project snippets" do
243   - get api("/projects/#{project.code}/snippets", user)
  241 + get api("/projects/#{project.path}/snippets", user)
244 242 response.status.should == 200
245 243 json_response.should be_an Array
246 244 json_response.first['title'].should == snippet.title
... ... @@ -249,7 +247,7 @@ describe Gitlab::API do
249 247  
250 248 describe "GET /projects/:id/snippets/:snippet_id" do
251 249 it "should return a project snippet" do
252   - get api("/projects/#{project.code}/snippets/#{snippet.id}", user)
  250 + get api("/projects/#{project.path}/snippets/#{snippet.id}", user)
253 251 response.status.should == 200
254 252 json_response['title'].should == snippet.title
255 253 end
... ... @@ -257,7 +255,7 @@ describe Gitlab::API do
257 255  
258 256 describe "POST /projects/:id/snippets" do
259 257 it "should create a new project snippet" do
260   - post api("/projects/#{project.code}/snippets", user),
  258 + post api("/projects/#{project.path}/snippets", user),
261 259 title: 'api test', file_name: 'sample.rb', code: 'test'
262 260 response.status.should == 201
263 261 json_response['title'].should == 'api test'
... ... @@ -266,7 +264,7 @@ describe Gitlab::API do
266 264  
267 265 describe "PUT /projects/:id/snippets/:shippet_id" do
268 266 it "should update an existing project snippet" do
269   - put api("/projects/#{project.code}/snippets/#{snippet.id}", user),
  267 + put api("/projects/#{project.path}/snippets/#{snippet.id}", user),
270 268 code: 'updated code'
271 269 response.status.should == 200
272 270 json_response['title'].should == 'example'
... ... @@ -277,31 +275,31 @@ describe Gitlab::API do
277 275 describe "DELETE /projects/:id/snippets/:snippet_id" do
278 276 it "should delete existing project snippet" do
279 277 expect {
280   - delete api("/projects/#{project.code}/snippets/#{snippet.id}", user)
  278 + delete api("/projects/#{project.path}/snippets/#{snippet.id}", user)
281 279 }.to change { Snippet.count }.by(-1)
282 280 end
283 281 end
284 282  
285 283 describe "GET /projects/:id/snippets/:snippet_id/raw" do
286 284 it "should get a raw project snippet" do
287   - get api("/projects/#{project.code}/snippets/#{snippet.id}/raw", user)
  285 + get api("/projects/#{project.path}/snippets/#{snippet.id}/raw", user)
288 286 response.status.should == 200
289 287 end
290 288 end
291 289  
292 290 describe "GET /projects/:id/:sha/blob" do
293 291 it "should get the raw file contents" do
294   - get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.md", user)
  292 + get api("/projects/#{project.path}/repository/commits/master/blob?filepath=README.md", user)
295 293 response.status.should == 200
296 294 end
297 295  
298 296 it "should return 404 for invalid branch_name" do
299   - get api("/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md", user)
  297 + get api("/projects/#{project.path}/repository/commits/invalid_branch_name/blob?filepath=README.md", user)
300 298 response.status.should == 404
301 299 end
302 300  
303 301 it "should return 404 for invalid file" do
304   - get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid", user)
  302 + get api("/projects/#{project.path}/repository/commits/master/blob?filepath=README.invalid", user)
305 303 response.status.should == 404
306 304 end
307 305 end
... ...
spec/requests/projects_spec.rb
... ... @@ -3,16 +3,6 @@ require &#39;spec_helper&#39;
3 3 describe "Projects" do
4 4 before { login_as :user }
5 5  
6   - describe 'GET /project/new' do
7   - it "should work autocomplete", :js => true do
8   - visit new_project_path
9   -
10   - fill_in 'project_name', with: 'Awesome'
11   - find("#project_path").value.should == 'awesome'
12   - find("#project_code").value.should == 'awesome'
13   - end
14   - end
15   -
16 6 describe "GET /projects/show" do
17 7 before do
18 8 @project = create(:project, owner: @user)
... ... @@ -53,7 +43,6 @@ describe &quot;Projects&quot; do
53 43 visit edit_project_path(@project)
54 44  
55 45 fill_in 'project_name', with: 'Awesome'
56   - fill_in 'project_code', with: 'gitlabhq'
57 46 click_button "Save"
58 47 @project = @project.reload
59 48 end
... ...
spec/routing/admin_routing_spec.rb
... ... @@ -78,14 +78,6 @@ describe Admin::ProjectsController, &quot;routing&quot; do
78 78 get("/admin/projects").should route_to('admin/projects#index')
79 79 end
80 80  
81   - it "to #create" do
82   - post("/admin/projects").should route_to('admin/projects#create')
83   - end
84   -
85   - it "to #new" do
86   - get("/admin/projects/new").should route_to('admin/projects#new')
87   - end
88   -
89 81 it "to #edit" do
90 82 get("/admin/projects/gitlab/edit").should route_to('admin/projects#edit', id: 'gitlab')
91 83 end
... ...
spec/support/namespaces_stub.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +require 'namespace'
  2 +require 'gitlab/project_mover'
  3 +
  4 +class Namespace
  5 + def ensure_dir_exist
  6 + true
  7 + end
  8 +
  9 + def move_dir
  10 + true
  11 + end
  12 +end
  13 +
  14 +class Gitlab::ProjectMover
  15 + def execute
  16 + true
  17 + end
  18 +end
... ...