diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index 935d24e58ff3737983cd5613ddb7322abf4b4930..ce897292c37cd759377533934ab4afefe37f2f5e 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -365,6 +365,10 @@ table {
   &.input-large {
     width: 210px;
   }
+
+  &.input-clamp {
+    max-width: 100%;
+  }
 }
 
 .user-result {
diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss
index b5fd1fce30b40435d9047855273e80f977d66cf1..26fe02e4928ee18ba251e81fc20d65d0e078a9f5 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/common.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss
@@ -6,6 +6,7 @@
 .cblue { color: #29A }
 .cblack { color: #111 }
 .cdark { color: #444 }
+.camber { color: #ffc000 }
 .cwhite { color: #fff!important }
 .bgred { background: #F2DEDE!important }
 
diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss
index 82556e91da3f5eb88da29016e57dd1595ca33482..8ad9bc732b268cbc61b2d9e7b82393f282864b90 100644
--- a/app/assets/stylesheets/sections/admin.scss
+++ b/app/assets/stylesheets/sections/admin.scss
@@ -20,6 +20,15 @@
   label { width: 110px; }
   .controls { margin-left: 130px; }
   .form-actions { padding-left: 130px; background: #fff }
+  .visibility-levels {
+    .controls {
+        margin-bottom: 9px;
+    }
+
+    i {
+      color: inherit;
+    }
+  }
 }
 
 .broadcast-messages {
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss
index e9162b3c1679a5f7c7860990ad343c37f353feff..22bd6fb1807bbcd271709f990d5bfe1bcf3c2d4a 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/sections/projects.scss
@@ -18,6 +18,12 @@
   border-bottom: 1px solid #DDD;
   padding-bottom: 25px;
   margin-bottom: 30px;
+  
+  &.empty-project {
+      border-bottom: 0px;
+      padding-bottom: 15px;
+      margin-bottom: 0px;
+  }
 
   .project-home-title {
     font-size: 18px;
@@ -45,7 +51,7 @@
     }
   }
 
-  .public-label {
+  .visibility-level-label {
     font-size: 14px;
     background: #f1f1f1;
     padding: 8px 10px;
@@ -53,6 +59,10 @@
     margin-left: 10px;
     color: #888;
     text-shadow: 0 1px 1px #FFF;
+
+    i {
+      color: inherit;
+    }
   }
 }
 
@@ -87,9 +97,33 @@
   }
 }
 
-.project-public-holder {
-  .help-inline {
-    padding-top: 7px;
+.project-visibility-level-holder {
+  .controls {
+    padding-bottom: 9px;
+  }
+
+  .controls {
+    input {
+      float: left;
+    }
+    .descr {
+      display: block;
+      margin-left: 1.5em;
+      &.restricted {
+        color: #888;
+      }
+    }
+    .info {
+      display: block;
+      margin-top: 5px;
+    }
+    strong {
+      display: inline-block;
+      width: 4em;
+    }
+  }
+  i {
+    color: inherit;
   }
 }
 
@@ -130,7 +164,8 @@ ul.nav.nav-projects-tabs {
   margin: 0px;
 }
 
-.my-projects {
+.my-projects,
+.public-projects {
   li {
     .project-info {
       margin-bottom: 10px;
diff --git a/app/contexts/projects/create_context.rb b/app/contexts/projects/create_context.rb
index 904c60a0f23afeb96c4e50785916605fe093272b..2acb9fbfe14bbc37bd5cf352827d9daf6f80137b 100644
--- a/app/contexts/projects/create_context.rb
+++ b/app/contexts/projects/create_context.rb
@@ -8,6 +8,11 @@ module Projects
       # get namespace id
       namespace_id = params.delete(:namespace_id)
 
+      # check that user is allowed to set specified visibility_level
+      unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+        params.delete(:visibility_level)
+      end
+
       # Load default feature settings
       default_features = Gitlab.config.gitlab.default_projects_features
 
@@ -17,7 +22,7 @@ module Projects
         wall_enabled: default_features.wall,
         snippets_enabled: default_features.snippets,
         merge_requests_enabled: default_features.merge_requests,
-        public: default_features.public
+        visibility_level: default_features.visibility_level
       }.stringify_keys
 
       @project = Project.new(default_opts.merge(params))
diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb
index 9564dd94688bec9f44f273191071b7c394655e8e..27d7a95724ae9f314179d430650fc5191093ef54 100644
--- a/app/contexts/projects/update_context.rb
+++ b/app/contexts/projects/update_context.rb
@@ -2,7 +2,11 @@ module Projects
   class UpdateContext < BaseContext
     def execute(role = :default)
       params[:project].delete(:namespace_id)
-      params[:project].delete(:public) unless can?(current_user, :change_public_mode, project)
+      # check that user is allowed to set specified visibility_level
+      unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:project][:visibility_level])
+        params[:project].delete(:visibility_level)
+      end
+
       new_branch = params[:project].delete(:default_branch)
 
       if project.repository.exists? && new_branch != project.repository.root_ref
diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb
index 2f9438f6bb4a4346cf8566a793e891d3949a27dd..5985ab1fb0c1ddfc1359bf539ac2bb2e3b1c6746 100644
--- a/app/contexts/search_context.rb
+++ b/app/contexts/search_context.rb
@@ -1,8 +1,8 @@
 class SearchContext
-  attr_accessor :project_ids, :params
+  attr_accessor :project_ids, :current_user, :params
 
-  def initialize(project_ids, params)
-    @project_ids, @params = project_ids, params.dup
+  def initialize(project_ids, user, params)
+    @project_ids, @current_user, @params = project_ids, user, params.dup
   end
 
   def execute
@@ -10,7 +10,8 @@ class SearchContext
     query = Shellwords.shellescape(query) if query.present?
 
     return result unless query.present?
-    result[:projects] = Project.where("projects.id in (?) OR projects.public = true", project_ids).search(query).limit(20)
+    visibility_levels = @current_user ? [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] : [ Gitlab::VisibilityLevel::PUBLIC ]
+    result[:projects] = Project.where("projects.id in (?) OR projects.visibility_level in (?)", project_ids, visibility_levels).search(query).limit(20)
 
     # Search inside single project
     single_project_search(Project.where(id: project_ids), query)
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 4de0a65b5fed0a0d23b9cfbcc53c802ba4ff774e..0e8335f3d8bbacc7ed40f14d1d71434375954462 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -8,7 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController
     user = User.find_by_id(owner_id)
 
     @projects = user ? user.owned_projects : Project.scoped
-    @projects = @projects.where(public: true) if params[:public_only].present?
+    @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
     @projects = @projects.with_push if params[:with_push].present?
     @projects = @projects.abandoned if params[:abandoned].present?
     @projects = @projects.search(params[:name]) if params[:name].present?
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index cfa3cac5e88e55e17166b09e1308efa1e200ea97..68ea636ccfe86f963469a1e9e480c79eb46d00db 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -102,7 +102,7 @@ class ApplicationController < ActionController::Base
   end
 
   def authorize_code_access!
-    return access_denied! unless can?(current_user, :download_code, project) or project.public?
+    return access_denied! unless can?(current_user, :download_code, project)
   end
 
   def authorize_push!
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 80aeb5cd6cc04240aaf308d157a0dfbb429d9db5..7e4580017dd7e6c26373a1b80186f7dd518a71b0 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -10,7 +10,7 @@ class Projects::ApplicationController < ApplicationController
       id = params[:project_id] || params[:id]
       @project = Project.find_with_namespace(id)
 
-      return if @project && @project.public
+      return if @project && @project.public?
     end
 
     super
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 66d6edd810c26375cea45686255069fc315ded49..60003187a9d9972a2805c17e3eb9821668fc587f 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -55,7 +55,7 @@ class ProjectsController < ApplicationController
   end
 
   def show
-    return authenticate_user! unless @project.public || current_user
+    return authenticate_user! unless @project.public? || current_user
 
     limit = (params[:limit] || 20).to_i
     @events = @project.events.recent
diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb
index 87e903a1d2df38b327c26adc147840dc89580ebb..8d66250d7b6d6e61bf1ea7d3c313b1362c877138 100644
--- a/app/controllers/public/projects_controller.rb
+++ b/app/controllers/public/projects_controller.rb
@@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController
   layout 'public'
 
   def index
-    @projects = Project.public_only
+    @projects = Project.public_or_internal_only(current_user)
     @projects = @projects.search(params[:search]) if params[:search].present?
     @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
   end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index f5c3bb133ed74bee9a0bc42da816c5b9bc13a4c9..2a2748dc1fb0ddab3222dacfe46b7b3e35ad052c 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -14,7 +14,7 @@ class SearchController < ApplicationController
       project_ids.select! { |id| id == project_id.to_i}
     end
 
-    result = SearchContext.new(project_ids, params).execute
+    result = SearchContext.new(project_ids, current_user, params).execute
 
     @projects       = result[:projects]
     @merge_requests = result[:merge_requests]
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index e4dfc236ca1840f96a918dd2ce77551b1a37ea54..1688cfc40b1089315aac4b4db761b3a71672c7b7 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -11,6 +11,10 @@ module IconsHelper
     content_tag :i, nil, class: 'icon-globe cblue'
   end
 
+  def internal_icon
+    content_tag :i, nil, class: 'icon-shield camber'
+  end
+
   def private_icon
     content_tag :i, nil, class: 'icon-lock cgreen'
   end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 33c5e4fb9db2e65493a1e2102e76cc267956cbfb..8ff0bc67b71a9e4651b8b1a8270331f281a73f52 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -1,10 +1,10 @@
 module SearchHelper
   def search_autocomplete_source
     return unless current_user
-
     [
       groups_autocomplete,
       projects_autocomplete,
+      public_projects_autocomplete,
       default_autocomplete,
       project_autocomplete,
       help_autocomplete
@@ -75,4 +75,11 @@ module SearchHelper
       { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
     end
   end
+
+  # Autocomplete results for the current user's projects
+  def public_projects_autocomplete
+    Project.public_or_internal_only(current_user).map do |p|
+      { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
+    end
+  end
 end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5cec7f8b4e65d22f29b09ee5f6a55c7dd27c195f
--- /dev/null
+++ b/app/helpers/visibility_level_helper.rb
@@ -0,0 +1,55 @@
+module VisibilityLevelHelper
+  def visibility_level_color(level)
+    case level
+    when Gitlab::VisibilityLevel::PRIVATE
+      'cgreen'
+    when Gitlab::VisibilityLevel::INTERNAL
+      'camber'
+    when Gitlab::VisibilityLevel::PUBLIC
+      'cblue'
+    end
+  end
+
+  def visibility_level_description(level)
+    capture_haml do
+      haml_tag :span do
+        case level
+        when Gitlab::VisibilityLevel::PRIVATE
+          haml_concat "Project access must be granted explicitly for each user."
+        when Gitlab::VisibilityLevel::INTERNAL
+          haml_concat "The project can be cloned by"
+          haml_tag :em, "any logged in user."
+          haml_concat "It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users."
+          haml_tag :em, "Any logged in user"
+          haml_concat "will have #{link_to "Guest", help_permissions_path} permissions on the repository."
+        when Gitlab::VisibilityLevel::PUBLIC
+          haml_concat "The project can be cloned"
+          haml_tag :em, "without any"
+          haml_concat "authentication."
+          haml_concat "It will also be listed on the #{link_to "public access directory", public_root_path}."
+          haml_tag :em, "Any logged in user"
+          haml_concat "will have #{link_to "Guest", help_permissions_path} permissions on the repository."
+        end
+      end
+    end
+  end
+
+  def visibility_level_icon(level)
+    case level
+    when Gitlab::VisibilityLevel::PRIVATE
+      private_icon
+    when Gitlab::VisibilityLevel::INTERNAL
+      internal_icon
+    when Gitlab::VisibilityLevel::PUBLIC
+      public_icon
+    end
+  end
+
+  def visibility_level_label(level)
+    Project.visibility_levels.key(level)
+  end
+  
+  def restricted_visibility_levels
+    current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels
+  end
+end
\ No newline at end of file
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 85476089145fab798b5251fb20ed174302109932..6df56eed5b8fd803336f7ecd980db73cfe785b86 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -29,7 +29,7 @@ class Ability
                   nil
                 end
 
-      if project && project.public
+      if project && project.public?
         [
           :read_project,
           :read_wiki,
@@ -71,7 +71,7 @@ class Ability
         rules << project_guest_rules
       end
 
-      if project.public?
+      if project.public? || project.internal?
         rules << public_project_rules
       end
 
@@ -89,7 +89,7 @@ class Ability
     def public_project_rules
       project_guest_rules + [
         :download_code,
-        :fork_project,
+        :fork_project
       ]
     end
 
@@ -145,7 +145,7 @@ class Ability
     def project_admin_rules
       project_master_rules + [
         :change_namespace,
-        :change_public_mode,
+        :change_visibility_level,
         :rename_project,
         :remove_project
       ]
diff --git a/app/models/project.rb b/app/models/project.rb
index eab7c14d6c6479fd09380e5409550bfb865d0920..9ac078fcfa61810483da36f731f7c736650a5642 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -14,24 +14,25 @@
 #  merge_requests_enabled :boolean          default(TRUE), not null
 #  wiki_enabled           :boolean          default(TRUE), not null
 #  namespace_id           :integer
-#  public                 :boolean          default(FALSE), not null
 #  issues_tracker         :string(255)      default("gitlab"), not null
 #  issues_tracker_id      :string(255)
 #  snippets_enabled       :boolean          default(TRUE), not null
 #  last_activity_at       :datetime
 #  imported               :boolean          default(FALSE), not null
 #  import_url             :string(255)
+#  visibility_level       :integer          default(0), not null
 #
 
 class Project < ActiveRecord::Base
   include Gitlab::ShellAdapter
+  include Gitlab::VisibilityLevel
   extend Enumerize
    
   ActsAsTaggableOn.strict_case_match = true
 
   attr_accessible :name, :path, :description, :issues_tracker, :label_list,
     :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
-    :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin]
+    :wiki_enabled, :visibility_level, :import_url, :last_activity_at, as: [:default, :admin]
 
   attr_accessible :namespace_id, :creator_id, as: :admin
 
@@ -108,7 +109,8 @@ class Project < ActiveRecord::Base
   scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
   scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
   scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
-  scope :public_only, -> { where(public: true) }
+  scope :public_only, -> { where(visibility_level: PUBLIC) }
+  scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) }
 
   enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
 
@@ -140,6 +142,10 @@ class Project < ActiveRecord::Base
         where(path: id, namespace_id: nil).last
       end
     end
+    
+    def visibility_levels
+      Gitlab::VisibilityLevel.options
+    end
   end
 
   def team
@@ -451,4 +457,8 @@ class Project < ActiveRecord::Base
   def default_branch
     @default_branch ||= repository.root_ref if repository.exists?
   end
+  
+  def visibility_level_field
+    visibility_level
+  end
 end
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index bc297209ae5d4537dfb65699b20ca1349c9abdac..05236e320b522555f1f4136d2f8d22a925625f81 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -10,11 +10,15 @@
         .control-group
           = label_tag :owner_id, 'Owner:', class: 'control-label'
           .controls
-            = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large'
-        .control-group
-          = label_tag :public_only, 'Public Only', class: 'control-label'
-          .controls
-            = check_box_tag :public_only, 1, params[:public_only]
+            = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large input-clamp'
+        .control-group.visibility-levels
+          = label_tag :visibility_level, 'Visibility Levels', class: 'control-label'
+          - Project.visibility_levels.each do |label, level|
+            .controls
+              = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s)
+              %span.descr
+                = visibility_level_icon(level)
+                = label
         .control-group
           = label_tag :with_push, 'Not empty', class: 'control-label'
           .controls
@@ -42,10 +46,7 @@
       %ul.well-list
         - @projects.each do |project|
           %li
-            - if project.public
-              = public_icon
-            - else
-              = private_icon
+            = visibility_level_icon(project.visibility_level)
             = link_to project.name_with_namespace, [:admin, project]
             .pull-right
               = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index c9c9c38d9f522019e77d74fbadb53fb8221e7106..42c427aad635ba6983a73479a8b2510b1d65442a 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -66,14 +66,10 @@
         %li
           %span.light access:
           %strong
-            - if @project.public
-              %span.cblue
-                %i.icon-share
-                Public
-            - else
-              %span.cgreen
-                %i.icon-lock
-                Private
+            %span{ class: visibility_level_color(@project.visibility_level) }
+              = visibility_level_icon(@project.visibility_level)
+              = visibility_level_label(@project.visibility_level)
+
     .ui-box
       .title
         Transfer project
@@ -88,9 +84,6 @@
             .controls
               = f.submit 'Transfer', class: 'btn btn-primary'
 
-
-
-
   .span6
     - if @group
       .ui-box
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 904ac2d00a2b8c9d2e3de9a2b8849080ebd14466..5ac90593c3a75e1b04092f126d9cb90b1968d55b 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -58,10 +58,10 @@
           %h4.project-title
             = link_to project_path(project), class: dom_class(project) do
               = project.name_with_namespace
-            - if project.public
+            - unless project.private?
               %small.access-icon
-                = public_icon
-                Public
+                = visibility_level_icon(project.visibility_level)
+                = visibility_level_label(project.visibility_level)
 
             - if current_user.can_leave_project?(project)
               .pull-right
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 10b974ea222a70b6793160c6484e37c87ae7c35f..5b5f8a20c197bd8cb6e4cb1684d26ebe01690c31 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -51,10 +51,7 @@
           %ul.well-list
             - @group.projects.each do |project|
               %li
-                - if project.public
-                  = public_icon
-                - else
-                  = private_icon
+                = visibility_level_icon(project.visibility_level)
                 = link_to project.name_with_namespace, project
                 .pull-right
                   = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml
index df35f41fc9046c6383220b28274c34f9ceb02606..15e3bf3a135c755af28a4b9999fde3abe7a8a811 100644
--- a/app/views/help/permissions.html.haml
+++ b/app/views/help/permissions.html.haml
@@ -143,7 +143,7 @@
         %td.permission-x &#10003;
         %td.permission-x &#10003;
       %tr
-        %td Switch public mode
+        %td Switch visibility level
         %td
         %td
         %td
diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml
index c67402ee319610a1cc54f13fbe474ca1275037d0..3a8aedc780ee7206666202074770e282530262f6 100644
--- a/app/views/help/public_access.html.haml
+++ b/app/views/help/public_access.html.haml
@@ -2,14 +2,20 @@
   %h3.page-title Public Access
 
   %p
-    GitLab allows you to open selected projects to be accessed publicly.
-    These projects will be cloneable
+    GitLab allows you to open selected projects to be accessed publicly or internally.
+    Projects with either of these visibility levels will be listed in the #{link_to "public access directory", public_root_path}. Internal projects will only be available to authenticated users.
+  %p
+    = public_icon
+    Public projects will be cloneable
     %em without any
     authentication.
-    Also they will be listed on the #{link_to "public access directory", public_root_path}.
+  %p
+    = internal_icon
+    Internal projects will be cloneable by
+    %em any authenticated user.
 
   %ol
     %li Go to your project dashboard
     %li Click on the "Edit" tab
-    %li Select "Public clone access"
+    %li Change "Visibility Level"
 
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..19c150b54fb2f8f835fb43447d7139cb9261d04a
--- /dev/null
+++ b/app/views/projects/_home_panel.html.haml
@@ -0,0 +1,31 @@
+- empty_repo = @project.empty_repo?
+.project-home-panel{:class => ("empty-project" if empty_repo)}
+  .row
+    .span5
+      %h4.project-home-title
+        = @project.name_with_namespace
+        %span.visibility-level-label
+          = visibility_level_icon(@project.visibility_level)
+          = visibility_level_label(@project.visibility_level)
+
+    .span7
+      - unless empty_repo
+        .project-home-dropdown
+          = render "dropdown"
+      .form-horizontal
+        = render "shared/clone_panel"
+
+  .project-home-extra.clearfix
+    .project-home-desc
+      - if @project.description.present?
+        = @project.description
+      - if can?(current_user, :admin_project, @project)
+        &ndash;
+        %strong= link_to 'Edit', edit_project_path
+
+    - unless empty_repo
+      .project-home-links
+        = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
+        = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project)
+        = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project)
+        %span.light.prepend-left-20= repository_size
\ No newline at end of file
diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..1bce3ba8cdaacca6ed1a4cbb31895d7e6ba9fc31
--- /dev/null
+++ b/app/views/projects/_visibility_level.html.haml
@@ -0,0 +1,23 @@
+.control-group.project-visibility-level-holder
+  = f.label :visibility_level, "Visibility Level"
+  - if can_change_visibility_level
+    - Gitlab::VisibilityLevel.values.each do |level|
+      - restricted = restricted_visibility_levels.include?(level)
+      .controls
+        = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted
+        %span.descr{:class => ("restricted" if restricted)}
+          = visibility_level_icon(level)
+          %strong
+            = visibility_level_label(level)
+          = visibility_level_description(level)
+    - unless restricted_visibility_levels.empty?
+      .controls
+        %span.info
+          Some visibility level settings have been restricted by the administrator.
+  - else
+    .controls
+      %span.info
+        = visibility_level_icon(visibility_level)
+        %strong
+          = visibility_level_label(visibility_level)
+        = visibility_level_description(visibility_level)
\ No newline at end of file
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3afc607d90ffcde3a0b3d18a1856180ad3d320e4..d282fc626e1eff76597e19d9be8b184671520ec2 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -29,22 +29,7 @@
                 .controls= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'})
 
 
-          - if can?(current_user, :change_public_mode, @project)
-            %fieldset.public-mode
-              %legend
-                Public mode:
-              .control-group
-                = f.label :public, class: 'control-label' do
-                  %span Public access
-                .controls
-                  = f.check_box :public
-                  %span.descr
-                    If checked, this project can be cloned
-                    %em without any
-                    authentication.
-                    It will also be listed on the #{link_to "public access directory", public_root_path}.
-                    %em Any
-                    user will have #{link_to "Guest", help_permissions_path} permissions on the repository.
+          = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project)
 
           %fieldset.features
             %legend
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 04fc0c31733dfea69f40e915ab79276590c9dc0c..3ed22015c0bcde0abe207f50ee6ef08846338e36 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,7 +1,4 @@
-%h3.page-title
-  = @project.name_with_namespace
-  .form-horizontal.pull-right
-    = render "shared/clone_panel"
+= render "home_panel"
 
 - if @project.import? && !@project.imported
   .save-project-loader
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 466a63dee83b10bc247f070232ca3b8a72c932a6..ee6c42b6ea84740fef47901d5038a93df5f63a43 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -47,12 +47,7 @@
           %span.light (optional)
         .controls
           = f.text_area :description, placeholder: "Awesome project", class: "input-xlarge", rows: 3, maxlength: 250, tabindex: 3
-      .control-group.project-public-holder
-        = f.label :public do
-          %span Public project
-        .controls
-          = f.check_box :public, { checked: gitlab_config.default_projects_features.public }, true, false
-          %span.help-inline Make project visible to everyone
+      = render "visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true
 
       .form-actions
         = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 1f4b5839175cd2d0ff54b4eadb63eeeff610bafd..41035d9175627a2a4033b822260db93bf502534f 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,32 +1,4 @@
-.project-home-panel
-  .row
-    .span5
-      %h4.project-home-title
-        = @project.name_with_namespace
-        - if @project.public
-          %span.public-label Public
-        - else
-          %span.public-label Private
-
-    .span7
-      .project-home-dropdown
-        = render "dropdown"
-      .form-horizontal
-        = render "shared/clone_panel"
-
-  .project-home-extra.clearfix
-    .project-home-desc
-      - if @project.description.present?
-        = @project.description
-      - if can?(current_user, :admin_project, @project)
-        &ndash;
-        %strong= link_to 'Edit', edit_project_path
-
-    .project-home-links
-      = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
-      = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project)
-      = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project)
-      %span.light.prepend-left-20= repository_size
+= render "home_panel"
 
 .row
   .span9
diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml
index 21aee6445795df849988cbf5d7cb24784c55c9bd..b88169add3c83334b61d57fc728a14cfc8529a02 100644
--- a/app/views/public/projects/index.html.haml
+++ b/app/views/public/projects/index.html.haml
@@ -19,6 +19,10 @@
         %h4
           = link_to project_path(project) do
             = project.name_with_namespace
+          - if project.internal?
+            %small.access-icon
+              = internal_icon
+              Internal
           .pull-right
             %pre.public-clone git clone #{project.http_url_to_repo}
 
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 759d7434230e12f63b6871ab49106eb2338b3c79..6b527cf0212de651568ef9392adf85686035ce5b 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -55,6 +55,10 @@ production: &base
     # default: false - Account passwords are not sent via the email if signup is enabled. 
     # signup_enabled: true
 
+    # Restrict setting visibility levels for non-admin users.
+    # The default is to allow all levels.
+    #restricted_visibility_levels: [ "public" ]
+
     ## Automatic issue closing
     # If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
     # This happens when the commit is pushed or merged into the default branch of a project.
@@ -68,7 +72,7 @@ production: &base
       wiki: true
       wall: false
       snippets: false
-      public: false
+      visibility_level: "private"  # can be "private" | "internal" | "public"
 
   ## External issues trackers
   issues_tracker:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 942b77ffd2e2377444f4461dbaac461b6b65106f..06e05714fdf8719ed78d336bc42eec2d3f9b5bc8 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -30,6 +30,29 @@ class Settings < Settingslogic
         gitlab.relative_url_root
       ].join('')
     end
+    
+    # check that values in `current` (string or integer) is a contant in `modul`.
+    def verify_constant_array(modul, current, default)
+      values = default || []
+      if !current.nil?
+        values = []
+        current.each do |constant|
+          values.push(verify_constant(modul, constant, nil))
+        end
+        values.delete_if { |value| value.nil? }
+      end
+      values
+    end
+
+    # check that `current` (string or integer) is a contant in `modul`.
+    def verify_constant(modul, current, default)
+      constant = modul.constants.find{ |name| modul.const_get(name) == current }
+      value = constant.nil? ? default : modul.const_get(constant)
+      if current.is_a? String
+        value = modul.const_get(current.upcase) rescue default
+      end
+      value
+    end
   end
 end
 
@@ -68,6 +91,7 @@ rescue ArgumentError # no user configured
   '/home/' + Settings.gitlab['user']
 end
 Settings.gitlab['signup_enabled'] ||= false
+Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
 Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
 Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
 Settings.gitlab['default_projects_features'] ||= {}
@@ -76,7 +100,7 @@ Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.g
 Settings.gitlab.default_projects_features['wiki']           = true if Settings.gitlab.default_projects_features['wiki'].nil?
 Settings.gitlab.default_projects_features['wall']           = false if Settings.gitlab.default_projects_features['wall'].nil?
 Settings.gitlab.default_projects_features['snippets']       = false if Settings.gitlab.default_projects_features['snippets'].nil?
-Settings.gitlab.default_projects_features['public']         = false if Settings.gitlab.default_projects_features['public'].nil?
+Settings.gitlab.default_projects_features['visibility_level']    = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
 
 #
 # Gravatar
diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cf1e9f912a04957ee92486d189ae1edf74ddd83f
--- /dev/null
+++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
@@ -0,0 +1,13 @@
+class AddVisibilityLevelToProjects < ActiveRecord::Migration
+  def self.up
+    add_column :projects, :visibility_level, :integer, :default => 0, :null => false
+    Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+    remove_column :projects, :public
+  end
+
+  def self.down
+    add_column :projects, :public, :boolean, :default => false, :null => false
+    Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true)
+    remove_column :projects, :visibility_level
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a03e471318811a23f7de2dfb6437eea68bf0f212..24d2fef4a19de5b3a071dd1bed837399aa2ac214 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20131112114325) do
+ActiveRecord::Schema.define(:version => 20131112220935) do
 
   create_table "broadcast_messages", :force => true do |t|
     t.text     "message",    :null => false
@@ -185,13 +185,13 @@ ActiveRecord::Schema.define(:version => 20131112114325) do
     t.boolean  "merge_requests_enabled", :default => true,     :null => false
     t.boolean  "wiki_enabled",           :default => true,     :null => false
     t.integer  "namespace_id"
-    t.boolean  "public",                 :default => false,    :null => false
     t.string   "issues_tracker",         :default => "gitlab", :null => false
     t.string   "issues_tracker_id"
     t.boolean  "snippets_enabled",       :default => true,     :null => false
     t.datetime "last_activity_at"
     t.boolean  "imported",               :default => false,    :null => false
     t.string   "import_url"
+    t.integer  "visibility_level",       :default => 0,        :null => false
   end
 
   add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 9b5d62ac832ca3d1a7f5aadf8c24596345a6aefe..5ec4c4a74e5d9c3e4b7a7b30a796ac701c37b482 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -15,6 +15,7 @@ GET /projects
     "description": null,
     "default_branch": "master",
     "public": false,
+    "visibility_level": 0,
     "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
     "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
     "web_url": "http://example.com/diaspora/diaspora-client",
@@ -49,6 +50,7 @@ GET /projects
     "description": null,
     "default_branch": "master",
     "public": false,
+    "visibility_level": 0,
     "ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
     "http_url_to_repo": "http://example.com/brightbox/puppet.git",
     "web_url": "http://example.com/brightbox/puppet",
@@ -117,6 +119,7 @@ Parameters:
   "description": null,
   "default_branch": "master",
   "public": false,
+  "visibility_level": 0,
   "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
   "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
   "web_url": "http://example.com/diaspora/diaspora-project-site",
@@ -234,7 +237,8 @@ Parameters:
 + `merge_requests_enabled` (optional)
 + `wiki_enabled` (optional) 
 + `snippets_enabled` (optional)
-+ `public` (optional)
++ `public` (optional) - if `true` same as setting visibility_level = 20
++ `visibility_level` (optional)
 
 
 ### Create project for user
@@ -256,7 +260,8 @@ Parameters:
 + `merge_requests_enabled` (optional)
 + `wiki_enabled` (optional) 
 + `snippets_enabled` (optional)
-+ `public` (optional)
++ `public` (optional) - if `true` same as setting visibility_level = 20
++ `visibility_level` (optional)
 
 
 ## Remove project
diff --git a/features/public/public_projects.feature b/features/public/public_projects.feature
index 178a769194c38b50e0dcfd31256b7a1e5179b88c..86bb888fdb6b66cfdf94468de5fdaedcd34d2572 100644
--- a/features/public/public_projects.feature
+++ b/features/public/public_projects.feature
@@ -1,18 +1,40 @@
 Feature: Public Projects Feature
   Background:
     Given public project "Community"
+    And internal project "Internal"
     And private project "Enterprise"
 
   Scenario: I visit public area
     When I visit the public projects area
     Then I should see project "Community"
+    And I should not see project "Internal"
     And I should not see project "Enterprise"
 
   Scenario: I visit public project page
     When I visit project "Community" page
     Then I should see project "Community" home page
 
+  Scenario: I visit internal project page
+    When I visit project "Internal" page
+    Then page status code should be 404
+
+  Scenario: I visit private project page
+    When I visit project "Enterprise" page
+    Then page status code should be 404
+
   Scenario: I visit an empty public project page
     Given public empty project "Empty Public Project"
     When I visit empty project page
     Then I should see empty public project details
+
+  Scenario: I visit public area as user
+    Given I sign in as a user
+    When I visit the public projects area
+    Then I should see project "Community"
+    And I should see project "Internal"
+    And I should not see project "Enterprise"
+
+  Scenario: I visit internal project page as user
+    Given I sign in as a user
+    When I visit project "Internal" page
+    Then I should see project "Internal" home page
diff --git a/features/steps/public/projects_feature.rb b/features/steps/public/projects_feature.rb
index e5292380c554e652321467b520853ae056de2644..8b61eba3ffb0e912877e3a4352921921a709e23c 100644
--- a/features/steps/public/projects_feature.rb
+++ b/features/steps/public/projects_feature.rb
@@ -1,5 +1,7 @@
 class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
+  include SharedAuthentication
   include SharedPaths
+  include SharedProject
 
   step 'I should see project "Community"' do
     page.should have_content "Community"
@@ -23,11 +25,11 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
   end
 
   step 'public project "Community"' do
-    create :project_with_code, name: 'Community', public: true
+    create :project_with_code, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC
   end
 
   step 'public empty project "Empty Public Project"' do
-    create :project, name: 'Empty Public Project', public: true
+    create :project, name: 'Empty Public Project', visibility_level: Gitlab::VisibilityLevel::PUBLIC
   end
 
   step 'I visit empty project page' do
@@ -48,16 +50,38 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
     create :project, name: 'Enterprise'
   end
 
+  step 'I visit project "Enterprise" page' do
+    project = Project.find_by_name('Enterprise')
+    visit project_path(project)
+  end
+
   step 'I should see project "Community" home page' do
     within '.project-home-title' do
       page.should have_content 'Community'
     end
   end
 
-  private
+  step 'internal project "Internal"' do
+    create :project_with_code, name: 'Internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL
+  end
 
-  def project
-    @project ||= Project.find_by_name("Community")
+  step 'I should see project "Internal"' do
+    page.should have_content "Internal"
+  end
+
+  step 'I should not see project "Internal"' do
+    page.should_not have_content "Internal"
+  end
+
+  step 'I visit project "Internal" page' do
+    project = Project.find_by_name('Internal')
+    visit project_path(project)
+  end
+
+  step 'I should see project "Internal" home page' do
+    within '.project-home-title' do
+      page.should have_content 'Internal'
+    end
   end
 end
 
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 2bdcbdc8c7f65fdd0d57af865aea60288d7c50f3..90cb69760a91dbf45a7c95a17fece6b09ed449e7 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -31,11 +31,13 @@ module API
     end
 
     class Project < Grape::Entity
-      expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
+      expose :id, :description, :default_branch
+      expose :public?, as: :public
+      expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
       expose :owner, using: Entities::UserBasic
       expose :name, :name_with_namespace
       expose :path, :path_with_namespace
-      expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public
+      expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at
       expose :namespace
       expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
     end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index b927e63f4a48cb30754fcb5d08ab7d2e13145893..003533fb59a388ec773fee876340858bd4202b96 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -11,6 +11,13 @@ module API
           end
           not_found!
         end
+        
+        def map_public_to_visibility_level(attrs)
+          publik = attrs.delete(:public)
+          publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik)
+          attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
+          attrs
+        end
       end
 
       # Get a projects list for authenticated user
@@ -76,7 +83,8 @@ module API
       #   wiki_enabled (optional)
       #   snippets_enabled (optional)
       #   namespace_id (optional) - defaults to user namespace
-      #   public (optional) - false by default
+      #   public (optional) - if true same as setting visibility_level = 20
+      #   visibility_level (optional) - 0 by default
       # Example Request
       #   POST /projects
       post do
@@ -90,7 +98,9 @@ module API
                                      :wiki_enabled,
                                      :snippets_enabled,
                                      :namespace_id,
-                                     :public]
+                                     :public,
+                                     :visibility_level]
+        attrs = map_public_to_visibility_level(attrs)
         @project = ::Projects::CreateContext.new(current_user, attrs).execute
         if @project.saved?
           present @project, with: Entities::Project
@@ -114,7 +124,8 @@ module API
       #   merge_requests_enabled (optional)
       #   wiki_enabled (optional)
       #   snippets_enabled (optional)
-      #   public (optional)
+      #   public (optional) - if true same as setting visibility_level = 20
+      #   visibility_level (optional)
       # Example Request
       #   POST /projects/user/:user_id
       post "user/:user_id" do
@@ -128,7 +139,9 @@ module API
                                      :merge_requests_enabled,
                                      :wiki_enabled,
                                      :snippets_enabled,
-                                     :public]
+                                     :public,
+                                     :visibility_level]
+        attrs = map_public_to_visibility_level(attrs)
         @project = ::Projects::CreateContext.new(user, attrs).execute
         if @project.saved?
           present @project, with: Entities::Project
@@ -290,7 +303,8 @@ module API
       #   GET /projects/search/:query
       get "/search/:query" do
         ids = current_user.authorized_projects.map(&:id)
-        projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%")
+        visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
+        projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
         present paginate(projects), with: Entities::Project
       end
     end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index e2349495b57f809b85ef46eff4a5dd83009f5567..c629144118c2c92e6ce6827fdf481cf7235df6a5 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -58,7 +58,7 @@ module Grack
         end
 
       else
-        return unauthorized unless project.public
+        return unauthorized unless project.public?
       end
 
       if authorized_git_request?
@@ -80,7 +80,7 @@ module Grack
     def authorize_request(service)
       case service
       when 'git-upload-pack'
-        project.public || can?(user, :download_code, project)
+        can?(user, :download_code, project)
       when'git-receive-pack'
         refs.each do |ref|
           action = if project.protected_branch?(ref)
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eada9bcddf5b9ded74c0570e91ef9cebf01aa97f
--- /dev/null
+++ b/lib/gitlab/visibility_level.rb
@@ -0,0 +1,42 @@
+# Gitlab::VisibilityLevel module
+#
+# Define allowed public modes that can be used for
+# GitLab projects to determine project public mode
+#
+module Gitlab
+  module VisibilityLevel
+    PRIVATE  = 0
+    INTERNAL = 10
+    PUBLIC   = 20
+
+    class << self
+      def values
+        options.values
+      end
+
+      def options
+        {
+          'Private'  => PRIVATE,
+          'Internal' => INTERNAL,
+          'Public'   => PUBLIC
+        }
+      end
+      
+      def allowed_for?(user, level)
+        user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level)
+      end
+    end
+
+    def private?
+      visibility_level_field == PRIVATE
+    end
+
+    def internal?
+      visibility_level_field == INTERNAL
+    end
+
+    def public?
+      visibility_level_field == PUBLIC
+    end
+  end
+end
diff --git a/spec/contexts/projects_create_context_spec.rb b/spec/contexts/projects_create_context_spec.rb
index 8b2a49dbee575551a16dd33d2a962ffd67a2db40..d5b1cb83510566f9b51056323fdeb947a36bf463 100644
--- a/spec/contexts/projects_create_context_spec.rb
+++ b/spec/contexts/projects_create_context_spec.rb
@@ -7,6 +7,7 @@ describe Projects::CreateContext do
   describe :create_by_user do
     before do
       @user = create :user
+      @admin = create :user, admin: true
       @opts = {
         name: "GitLab",
         namespace: @user.namespace
@@ -37,7 +38,7 @@ describe Projects::CreateContext do
       it { @project.namespace.should == @group }
     end
 
-    context 'respect configured public setting' do
+    context 'respect configured visibility setting' do
       before(:each) do
         @settings = double("settings")
         @settings.stub(:issues) { true }
@@ -46,25 +47,90 @@ describe Projects::CreateContext do
         @settings.stub(:wall) { true }
         @settings.stub(:snippets) { true }
         stub_const("Settings", Class.new)
+        @restrictions = double("restrictions")
+        @restrictions.stub(:restricted_visibility_levels) { [] }
+        Settings.stub_chain(:gitlab).and_return(@restrictions)
         Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
       end
 
       context 'should be public when setting is public' do
         before do
-          @settings.stub(:public) { true }
+          @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
           @project = create_project(@user, @opts)
         end
 
-        it { @project.public.should be_true }
+        it { @project.public?.should be_true }
       end
 
-      context 'should be private when setting is not public' do
+      context 'should be private when setting is private' do
         before do
-          @settings.stub(:public) { false }
+          @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
           @project = create_project(@user, @opts)
         end
 
-        it { @project.public.should be_false }
+        it { @project.private?.should be_true }
+      end
+
+      context 'should be internal when setting is internal' do
+        before do
+          @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::INTERNAL }
+          @project = create_project(@user, @opts)
+        end
+
+        it { @project.internal?.should be_true }
+      end
+    end
+
+    context 'respect configured visibility restrictions setting' do
+      before(:each) do
+        @settings = double("settings")
+        @settings.stub(:issues) { true }
+        @settings.stub(:merge_requests) { true }
+        @settings.stub(:wiki) { true }
+        @settings.stub(:wall) { true }
+        @settings.stub(:snippets) { true }
+        @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
+        stub_const("Settings", Class.new)
+        @restrictions = double("restrictions")
+        @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] }
+        Settings.stub_chain(:gitlab).and_return(@restrictions)
+        Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
+      end
+
+      context 'should be private when option is public' do
+        before do
+          @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+          @project = create_project(@user, @opts)
+        end
+
+        it { @project.private?.should be_true }
+      end
+
+      context 'should be public when option is public for admin' do
+        before do
+          @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+          @project = create_project(@admin, @opts)
+        end
+
+        it { @project.public?.should be_true }
+      end
+
+      context 'should be private when option is private' do
+        before do
+          @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+          @project = create_project(@user, @opts)
+        end
+
+        it { @project.private?.should be_true }
+      end
+
+      context 'should be internal when option is internal' do
+        before do
+          @opts.merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+          @project = create_project(@user, @opts)
+        end
+
+        it { @project.internal?.should be_true }
       end
     end
   end
@@ -73,3 +139,4 @@ describe Projects::CreateContext do
     Projects::CreateContext.new(user, opts).execute
   end
 end
+
diff --git a/spec/contexts/projects_update_context_spec.rb b/spec/contexts/projects_update_context_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..edcaf844e5db61683712c86eef856abe2d939a97
--- /dev/null
+++ b/spec/contexts/projects_update_context_spec.rb
@@ -0,0 +1,111 @@
+require 'spec_helper'
+
+describe Projects::UpdateContext do
+  before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
+  after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
+
+  describe :update_by_user do
+    before do
+      @user = create :user
+      @admin = create :user, admin: true
+      @project = create :project, creator_id: @user.id, namespace: @user.namespace
+      @opts = { project: {} }
+    end
+
+    context 'should be private when updated to private' do
+      before do
+       @created_private = @project.private?
+
+        @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+        update_project(@project, @user, @opts)
+      end
+
+      it { @created_private.should be_true }
+      it { @project.private?.should be_true }
+    end
+
+    context 'should be internal when updated to internal' do
+      before do
+        @created_private = @project.private?
+
+        @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+        update_project(@project, @user, @opts)
+      end
+
+      it { @created_private.should be_true }
+      it { @project.internal?.should be_true }
+    end
+
+    context 'should be public when updated to public' do
+      before do
+        @created_private = @project.private?
+
+        @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+        update_project(@project, @user, @opts)
+      end
+
+      it { @created_private.should be_true }
+      it { @project.public?.should be_true }
+    end
+
+    context 'respect configured visibility restrictions setting' do
+      before(:each) do
+        @restrictions = double("restrictions")
+        @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] }
+        Settings.stub_chain(:gitlab).and_return(@restrictions)
+      end
+
+      context 'should be private when updated to private' do
+        before do
+          @created_private = @project.private?
+
+          @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+          update_project(@project, @user, @opts)
+        end
+
+        it { @created_private.should be_true }
+        it { @project.private?.should be_true }
+      end
+
+      context 'should be internal when updated to internal' do
+        before do
+          @created_private = @project.private?
+
+          @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+          update_project(@project, @user, @opts)
+        end
+
+        it { @created_private.should be_true }
+        it { @project.internal?.should be_true }
+      end
+
+      context 'should be private when updated to public' do
+        before do
+          @created_private = @project.private?
+
+          @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+          update_project(@project, @user, @opts)
+        end
+
+        it { @created_private.should be_true }
+        it { @project.private?.should be_true }
+      end
+
+      context 'should be public when updated to public by admin' do
+        before do
+          @created_private = @project.private?
+
+          @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+          update_project(@project, @admin, @opts)
+        end
+
+        it { @created_private.should be_true }
+        it { @project.public?.should be_true }
+      end
+    end
+  end
+
+  def update_project(project, user, opts)
+    Projects::UpdateContext.new(project, user, opts).execute
+  end
+end
\ No newline at end of file
diff --git a/spec/contexts/search_context_spec.rb b/spec/contexts/search_context_spec.rb
index 58f747e87252f872912d7e282dbfbf919b783bca..c25743e0032a02ea2a04c1bedd3114961d2ca106 100644
--- a/spec/contexts/search_context_spec.rb
+++ b/spec/contexts/search_context_spec.rb
@@ -3,23 +3,39 @@ require 'spec_helper'
 describe SearchContext do
   let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') }
   let(:user) { create(:user, namespace: found_namespace) }
-  let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, public: false) }
+  let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
 
   let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') }
-  let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, public: false) }
-  let(:public_namespace) { create(:namespace, path: 'something_else',name: 'searchable public namespace') }
-  let(:other_user) { create(:user, namespace: public_namespace) }
-  let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: other_user.id, namespace: public_namespace, public: true) }
+  let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+  
+  let(:internal_namespace) { create(:namespace, path: 'something_internal',name: 'searchable internal namespace') }
+  let(:internal_user) { create(:user, namespace: internal_namespace) }
+  let!(:internal_project) { create(:project, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+  
+  let(:public_namespace) { create(:namespace, path: 'something_public',name: 'searchable public namespace') }
+  let(:public_user) { create(:user, namespace: public_namespace) }
+  let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
 
   describe '#execute' do
     it 'public projects should be searchable' do
-      context = SearchContext.new([found_project.id], {search_code:  false, search: "searchable"})
+      context = SearchContext.new([found_project.id], nil, {search_code:  false, search: "searchable"})
       results = context.execute
       results[:projects].should == [found_project, public_project]
     end
 
+    it 'internal projects should be searchable' do
+      context = SearchContext.new([found_project.id], user, {search_code:  false, search: "searchable"})
+      results = context.execute
+      # can't seem to rely on the return order, so check this way
+      #subject { results[:projects] }
+      results[:projects].should have(3).items
+      results[:projects].should include(found_project)
+      results[:projects].should include(internal_project)
+      results[:projects].should include(public_project)
+    end
+
     it 'namespace name should be searchable' do
-      context = SearchContext.new([found_project.id], {search_code:  false, search: "searchable namespace"})
+      context = SearchContext.new([found_project.id], user, {search_code:  false, search: "searchable namespace"})
       results = context.execute
       results[:projects].should == [found_project]
     end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5abccd259d424c2a4d401034a7ae429956d3c729
--- /dev/null
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -0,0 +1,251 @@
+require 'spec_helper'
+
+describe "Internal Project Access" do
+  let(:project) { create(:project_with_code) }
+
+  let(:master) { create(:user) }
+  let(:guest) { create(:user) }
+  let(:reporter) { create(:user) }
+
+  before do
+    # internal project
+    project.visibility_level = Gitlab::VisibilityLevel::INTERNAL
+    project.save!
+
+    # full access
+    project.team << [master, :master]
+
+    # readonly
+    project.team << [reporter, :reporter]
+
+  end
+
+  describe "Project should be internal" do
+    subject { project }
+
+    its(:internal?) { should be_true }
+  end
+
+  describe "GET /:project_path" do
+    subject { project_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/tree/master" do
+    subject { project_tree_path(project, project.repository.root_ref) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/commits/master" do
+    subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/commit/:sha" do
+    subject { project_commit_path(project, project.repository.commit) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/compare" do
+    subject { project_compare_index_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/team" do
+    subject { project_team_index_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_denied_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_denied_for guest }
+    it { should be_denied_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/wall" do
+    subject { project_wall_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/blob" do
+    before do
+      commit = project.repository.commit
+      path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
+      @blob_path = project_blob_path(project, File.join(commit.id, path))
+    end
+
+    it { @blob_path.should be_allowed_for master }
+    it { @blob_path.should be_allowed_for reporter }
+    it { @blob_path.should be_allowed_for :admin }
+    it { @blob_path.should be_allowed_for guest }
+    it { @blob_path.should be_allowed_for :user }
+    it { @blob_path.should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/edit" do
+    subject { edit_project_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_denied_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_denied_for guest }
+    it { should be_denied_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/deploy_keys" do
+    subject { project_deploy_keys_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_denied_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_denied_for guest }
+    it { should be_denied_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/issues" do
+    subject { project_issues_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/snippets" do
+    subject { project_snippets_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/snippets/new" do
+    subject { new_project_snippet_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_denied_for guest }
+    it { should be_denied_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/merge_requests" do
+    subject { project_merge_requests_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/merge_requests/new" do
+    subject { new_project_merge_request_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_denied_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_denied_for guest }
+    it { should be_denied_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/branches/recent" do
+    subject { recent_project_branches_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/branches" do
+    subject { project_branches_path(project) }
+
+    before do
+      # Speed increase
+      Project.any_instance.stub(:branches).and_return([])
+    end
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/tags" do
+    subject { project_tags_path(project) }
+
+    before do
+      # Speed increase
+      Project.any_instance.stub(:tags).and_return([])
+    end
+
+    it { should be_allowed_for master }
+    it { should be_allowed_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_allowed_for guest }
+    it { should be_allowed_for :user }
+    it { should be_denied_for :visitor }
+  end
+
+  describe "GET /:project_path/hooks" do
+    subject { project_hooks_path(project) }
+
+    it { should be_allowed_for master }
+    it { should be_denied_for reporter }
+    it { should be_allowed_for :admin }
+    it { should be_denied_for guest }
+    it { should be_denied_for :user }
+    it { should be_denied_for :visitor }
+  end
+end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 7f3f8c50f02b2f30d3e67c45a2f00f4eec73381e..481d8cec41698e4e9a0cd121d1bfa7b03333544c 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -15,6 +15,12 @@ describe "Private Project Access" do
     project.team << [reporter, :reporter]
   end
 
+  describe "Project should be private" do
+    subject { project }
+
+    its(:private?) { should be_true }
+  end
+
   describe "GET /:project_path" do
     subject { project_path(project) }
 
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 267643fd8ef2d485002e6f0da81b141b7716bf02..3f1016473f5c41f7ea96edc412c8b94e2d9e8363 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -9,7 +9,7 @@ describe "Public Project Access" do
 
   before do
     # public project
-    project.public = true
+    project.visibility_level = Gitlab::VisibilityLevel::PUBLIC
     project.save!
 
     # full access
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d5803d8cec3c42039724dbabefa638fdf49c4f0d..0167d51dd39d81504e8a53c77a4541857067fdc1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -14,13 +14,13 @@
 #  merge_requests_enabled :boolean          default(TRUE), not null
 #  wiki_enabled           :boolean          default(TRUE), not null
 #  namespace_id           :integer
-#  public                 :boolean          default(FALSE), not null
 #  issues_tracker         :string(255)      default("gitlab"), not null
 #  issues_tracker_id      :string(255)
 #  snippets_enabled       :boolean          default(TRUE), not null
 #  last_activity_at       :datetime
 #  imported               :boolean          default(FALSE), not null
 #  import_url             :string(255)
+#  visibility_level       :integer          default(0), not null
 #
 
 require 'spec_helper'
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index e4cef6c587c0a702c30f90d6d8db01da3a65047b..7322d793c95c09162ea8ef3b14b4630385bbdd8f 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -132,15 +132,45 @@ describe API::API do
     end
 
     it "should set a project as public" do
+      project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC })
+      post api("/projects", user), project
+      json_response['public'].should be_true
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+    end
+
+    it "should set a project as public using :public" do
       project = attributes_for(:project, { public: true })
       post api("/projects", user), project
       json_response['public'].should be_true
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+    end
+
+    it "should set a project as internal" do
+      project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL })
+      post api("/projects", user), project
+      json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
+    end
+
+    it "should set a project as internal overriding :public" do
+      project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL })
+      post api("/projects", user), project
+      json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
     end
 
     it "should set a project as private" do
+      project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+      post api("/projects", user), project
+      json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
+    end
+
+    it "should set a project as private using :public" do
       project = attributes_for(:project, { public: false })
       post api("/projects", user), project
       json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
     end
   end
 
@@ -183,19 +213,46 @@ describe API::API do
     end
 
     it "should set a project as public" do
+      project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC })
+      post api("/projects/user/#{user.id}", admin), project
+      json_response['public'].should be_true
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+    end
+
+    it "should set a project as public using :public" do
       project = attributes_for(:project, { public: true })
       post api("/projects/user/#{user.id}", admin), project
       json_response['public'].should be_true
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+    end
 
+    it "should set a project as internal" do
+      project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL })
+      post api("/projects/user/#{user.id}", admin), project
+      json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
     end
 
-    it "should set a project as private" do
-      project = attributes_for(:project, { public: false })
+    it "should set a project as internal overriding :public" do
+      project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL })
       post api("/projects/user/#{user.id}", admin), project
       json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
+    end
 
+    it "should set a project as private" do
+      project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+      post api("/projects/user/#{user.id}", admin), project
+      json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
     end
 
+    it "should set a project as private using :public" do
+      project = attributes_for(:project, { public: false })
+      post api("/projects/user/#{user.id}", admin), project
+      json_response['public'].should be_false
+      json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
+    end
   end
 
   describe "GET /projects/:id" do
@@ -649,10 +706,10 @@ describe API::API do
 
   describe :fork_admin do
     let(:project_fork_target) { create(:project) }
-    let(:project_fork_source) { create(:project, public: true) }
+    let(:project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
 
     describe "POST /projects/:id/fork/:forked_from_id" do
-      let(:new_project_fork_source) { create(:project, public: true) }
+      let(:new_project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
 
       it "shouldn't available for non admin users" do
         post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
@@ -721,8 +778,10 @@ describe API::API do
     let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) }
     let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) }
     let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) }
-    let!(:public) { create(:project, name: "another #{query}",public: true) }
-    let!(:unfound_public) { create(:project, name: 'unfound public', public: true) }
+    let!(:internal) { create(:project, name: "internal #{query}", visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+    let!(:unfound_internal) { create(:project, name: 'unfound internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+    let!(:public) { create(:project, name: "public #{query}", visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+    let!(:unfound_public) { create(:project, name: 'unfound public', visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
 
     context "when unauthenticated" do
       it "should return authentication error" do
@@ -736,7 +795,7 @@ describe API::API do
         get api("/projects/search/#{query}",user)
         response.status.should == 200
         json_response.should be_an Array
-        json_response.size.should == 5
+        json_response.size.should == 6
         json_response.each {|project| project['name'].should =~ /.*query.*/}
       end
     end
@@ -746,8 +805,8 @@ describe API::API do
         get api("/projects/search/#{query}", user2)
         response.status.should == 200
         json_response.should be_an Array
-        json_response.size.should == 1
-        json_response.first['name'].should == "another #{query}"
+        json_response.size.should == 2
+        json_response.each {|project| project['name'].should =~ /(internal|public) query/}
       end
     end
   end