summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBaldinof <baldinof@gmail.com>2016-03-09 22:54:53 +0100
committerBaldinof <baldinof@gmail.com>2016-03-09 22:54:53 +0100
commita5079d219548c4e94eb78953811158c346225174 (patch)
treecf122ee677415a6891c60c244766680bdc3e288d
parenta603422edb69248c822dd6c4a9d7860194d89a3d (diff)
parentd9042e8b399608fa189ffdb02efa8de30604088d (diff)
downloadgitlab-ce-a5079d219548c4e94eb78953811158c346225174.tar.gz
Merge branch 'master' into markdown_preview_shortcut
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--CHANGELOG15
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock10
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/milestone.js.coffee7
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss9
-rw-r--r--app/assets/stylesheets/pages/milestone.scss16
-rw-r--r--app/assets/stylesheets/pages/snippets.scss2
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/concerns/filter_projects.rb15
-rw-r--r--app/controllers/dashboard/projects_controller.rb24
-rw-r--r--app/controllers/explore/projects_controller.rb11
-rw-r--r--app/controllers/groups_controller.rb6
-rw-r--r--app/controllers/projects/avatars_controller.rb3
-rw-r--r--app/controllers/projects/milestones_controller.rb4
-rw-r--r--app/controllers/projects/raw_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb6
-rw-r--r--app/finders/snippets_finder.rb6
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb21
-rw-r--r--app/helpers/commits_helper.rb11
-rw-r--r--app/helpers/explore_helper.rb12
-rw-r--r--app/helpers/labels_helper.rb15
-rw-r--r--app/helpers/milestones_helper.rb26
-rw-r--r--app/helpers/snippets_helper.rb10
-rw-r--r--app/helpers/sorting_helper.rb10
-rw-r--r--app/models/ability.rb5
-rw-r--r--app/models/blob.rb3
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/concerns/milestoneish.rb25
-rw-r--r--app/models/global_label.rb7
-rw-r--r--app/models/global_milestone.rb55
-rw-r--r--app/models/label.rb44
-rw-r--r--app/models/merge_request.rb25
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/milestone.rb27
-rw-r--r--app/models/personal_snippet.rb1
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_snippet.rb3
-rw-r--r--app/models/repository.rb55
-rw-r--r--app/models/snippet.rb7
-rw-r--r--app/services/git_push_service.rb18
-rw-r--r--app/services/git_tag_push_service.rb2
-rw-r--r--app/services/merge_requests/post_merge_service.rb4
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/dashboard/milestones/_issue.html.haml10
-rw-r--r--app/views/dashboard/milestones/_issues.html.haml6
-rw-r--r--app/views/dashboard/milestones/_merge_request.html.haml10
-rw-r--r--app/views/dashboard/milestones/_merge_requests.html.haml6
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml31
-rw-r--r--app/views/dashboard/milestones/show.html.haml106
-rw-r--r--app/views/dashboard/todos/_todo.html.haml5
-rw-r--r--app/views/explore/projects/_dropdown.html.haml20
-rw-r--r--app/views/explore/projects/_filter.html.haml10
-rw-r--r--app/views/groups/_projects.html.haml9
-rw-r--r--app/views/groups/milestones/_issue.html.haml10
-rw-r--r--app/views/groups/milestones/_issues.html.haml6
-rw-r--r--app/views/groups/milestones/_merge_request.html.haml10
-rw-r--r--app/views/groups/milestones/_merge_requests.html.haml6
-rw-r--r--app/views/groups/milestones/_milestone.html.haml34
-rw-r--r--app/views/groups/milestones/show.html.haml114
-rw-r--r--app/views/projects/blob/_image.html.haml2
-rw-r--r--app/views/projects/branches/destroy.js.haml2
-rw-r--r--app/views/projects/commits/_commit_list.html.haml11
-rw-r--r--app/views/projects/commits/_commits.html.haml8
-rw-r--r--app/views/projects/commits/_head.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/milestones/_issue.html.haml10
-rw-r--r--app/views/projects/milestones/_issues.html.haml7
-rw-r--r--app/views/projects/milestones/_merge_request.html.haml8
-rw-r--r--app/views/projects/milestones/_merge_requests.html.haml5
-rw-r--r--app/views/projects/milestones/_milestone.html.haml36
-rw-r--r--app/views/projects/milestones/show.html.haml99
-rw-r--r--app/views/shared/milestones/_issuable.html.haml25
-rw-r--r--app/views/shared/milestones/_issuables.html.haml16
-rw-r--r--app/views/shared/milestones/_issues_tab.html.haml10
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml18
-rw-r--r--app/views/shared/milestones/_merge_requests_tab.haml12
-rw-r--r--app/views/shared/milestones/_milestone.html.haml45
-rw-r--r--app/views/shared/milestones/_participants_tab.html.haml8
-rw-r--r--app/views/shared/milestones/_summary.html.haml28
-rw-r--r--app/views/shared/milestones/_tabs.html.haml30
-rw-r--r--app/views/shared/milestones/_top.html.haml58
-rw-r--r--app/views/shared/projects/_dropdown.html.haml22
-rw-r--r--app/views/shared/projects/_project.html.haml3
-rw-r--r--config/environments/test.rb2
-rw-r--r--db/migrate/20160229193553_add_main_language_to_repository.rb5
-rw-r--r--db/migrate/20160305220806_remove_expires_at_from_snippets.rb5
-rw-r--r--db/migrate/20160309140734_fix_todos.rb16
-rw-r--r--db/schema.rb5
-rw-r--r--doc/api/commits.md3
-rw-r--r--doc/api/notes.md1
-rw-r--r--doc/api/project_snippets.md1
-rw-r--r--doc/api/users.md2
-rw-r--r--doc/ci/api/README.md88
-rw-r--r--doc/ci/api/builds.md118
-rw-r--r--doc/ci/api/commits.md108
-rw-r--r--doc/ci/api/projects.md149
-rw-r--r--doc/ci/api/runners.md71
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/sql.md219
-rw-r--r--doc/hooks/custom_hooks.md2
-rw-r--r--doc/install/installation.md11
-rw-r--r--doc/integration/README.md31
-rw-r--r--doc/integration/ldap.md22
-rw-r--r--doc/integration/saml.md38
-rw-r--r--doc/integration/slack.md12
-rw-r--r--doc/markdown/markdown.md1
-rw-r--r--doc/project_services/jira.md13
-rw-r--r--doc/update/8.4-to-8.5.md3
-rw-r--r--doc/update/patch_versions.md6
-rw-r--r--doc/update/upgrader.md2
-rw-r--r--doc/web_hooks/web_hooks.md1
-rw-r--r--doc/workflow/lfs/lfs_administration.md8
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md85
-rw-r--r--features/dashboard/archived_projects.feature5
-rw-r--r--features/explore/projects.feature2
-rw-r--r--features/group/milestones.feature17
-rw-r--r--features/project/issues/issues.feature8
-rw-r--r--features/project/merge_requests.feature29
-rw-r--r--features/steps/dashboard/archived_projects.rb4
-rw-r--r--features/steps/group/milestones.rb47
-rw-r--r--features/steps/project/issues/issues.rb4
-rw-r--r--features/steps/project/issues/milestones.rb2
-rw-r--r--features/steps/project/merge_requests.rb29
-rw-r--r--features/steps/project/merge_requests/acceptance.rb2
-rw-r--r--features/steps/project/merge_requests/revert.rb2
-rw-r--r--features/steps/shared/note.rb7
-rw-r--r--features/steps/shared/paths.rb19
-rw-r--r--features/support/env.rb1
-rw-r--r--features/support/rerun.rb14
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb2
-rw-r--r--lib/banzai/filter/label_reference_filter.rb79
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb2
-rw-r--r--lib/tasks/gemojione.rake121
-rw-r--r--lib/tasks/spinach.rake62
-rwxr-xr-xscripts/prepare_build.sh28
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb2
-rw-r--r--spec/factories/emails.rb10
-rw-r--r--spec/factories/merge_requests.rb10
-rw-r--r--spec/factories/personal_snippets.rb11
-rw-r--r--spec/factories/project_members.rb22
-rw-r--r--spec/factories/project_snippets.rb6
-rw-r--r--spec/factories/services.rb2
-rw-r--r--spec/factories/snippets.rb12
-rw-r--r--spec/finders/snippets_finder_spec.rb25
-rw-r--r--spec/fixtures/markdown.md.erb2
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb6
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb27
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb88
-rw-r--r--spec/models/label_spec.rb30
-rw-r--r--spec/models/merge_request_spec.rb64
-rw-r--r--spec/models/milestone_spec.rb3
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/project_snippet_spec.rb1
-rw-r--r--spec/models/project_spec.rb3
-rw-r--r--spec/models/repository_spec.rb98
-rw-r--r--spec/models/snippet_spec.rb1
-rw-r--r--spec/requests/api/branches_spec.rb4
-rw-r--r--spec/requests/api/builds_spec.rb4
-rw-r--r--spec/requests/api/commit_status_spec.rb10
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/fork_spec.rb2
-rw-r--r--spec/requests/api/project_members_spec.rb4
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/requests/api/repositories_spec.rb4
-rw-r--r--spec/requests/api/runners_spec.rb6
-rw-r--r--spec/requests/api/tags_spec.rb4
-rw-r--r--spec/requests/api/triggers_spec.rb4
-rw-r--r--spec/requests/api/users_spec.rb2
-rw-r--r--spec/requests/api/variables_spec.rb4
-rw-r--r--spec/services/delete_tag_service_spec.rb26
-rw-r--r--spec/services/git_push_service_spec.rb32
-rw-r--r--spec/services/projects/update_service_spec.rb4
-rw-r--r--spec/spec_helper.rb4
178 files changed, 2006 insertions, 1471 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c477721f9da..d21785f7af2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,16 +12,18 @@ cache:
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
+ # retry tests only in CI environment
+ RSPEC_RETRY_RETRY_COUNT: "3"
before_script:
- source ./scripts/prepare_build.sh
- ruby -v
- which ruby
- - gem install bundler --no-ri --no-rdoc
+ - retry gem install bundler --no-ri --no-rdoc
- cp config/gitlab.yml.example config/gitlab.yml
- touch log/application.log
- touch log/test.log
- - bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
+ - retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
stages:
diff --git a/CHANGELOG b/CHANGELOG
index e939b632d0d..9474ca50b24 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,16 +3,31 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased)
- Contributions to forked projects are included in calendar
- Improve the formatting for the user page bio (Connor Shea)
+ - Removed the default password from the initial admin account created during
+ setup. A password can be provided during setup (see installation docs), or
+ GitLab will ask the user to create a new one upon first visit.
- Fix issue when pushing to projects ending in .wiki
- Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
- Don't load all of GitLab in mail_room
+ - Memoize @group in Admin::GroupsController (Yatish Mehta)
+ - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
- Strip leading and trailing spaces in URL validator (evuez)
+ - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez)
- Return empty array instead of 404 when commit has no statuses in commit status API
+ - Add support for cross-project label references
- Update documentation to reflect Guest role not being enforced on internal projects
- Allow search for logged out users
+ - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
- Don't show Issues/MRs from archived projects in Groups view
- Increase the notes polling timeout over time (Roberto Dip)
- Add shortcut to toggle markdown preview (Florent Baldino)
+ - Show labels in dashboard and group milestone views
+ - Add main language of a project in the list of projects (Tiago Botelho)
+ - Add ability to show archived projects on dashboard, explore and group pages
+
+v 8.5.5
+ - Ensure removing a project removes associated Todo entries.
+ - Prevent a 500 error in Todos when author was removed.
v 8.5.4
- Do not cache requests for badges (including builds badge)
diff --git a/Gemfile b/Gemfile
index c66ef3cffad..7e70761a77a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -263,7 +263,9 @@ group :development, :test do
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.3.0'
+ gem 'rspec-retry'
gem 'spinach-rails', '~> 0.2.1'
+ gem 'spinach-rerun-reporter', '~> 0.0.2'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
@@ -273,7 +275,7 @@ group :development, :test do
gem 'capybara', '~> 2.4.0'
gem 'capybara-screenshot', '~> 1.0.0'
- gem 'poltergeist', '~> 1.8.1'
+ gem 'poltergeist', '~> 1.9.0'
gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 22c86e4ae8f..432bdc344ad 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -552,7 +552,7 @@ GEM
parser (2.2.3.0)
ast (>= 1.1, < 3.0)
pg (0.18.4)
- poltergeist (1.8.1)
+ poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
@@ -679,6 +679,8 @@ GEM
rspec-expectations (~> 3.3.0)
rspec-mocks (~> 3.3.0)
rspec-support (~> 3.3.0)
+ rspec-retry (0.4.5)
+ rspec-core
rspec-support (3.3.0)
rubocop (0.35.1)
astrolabe (~> 1.3)
@@ -764,6 +766,8 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
+ spinach-rerun-reporter (0.0.2)
+ spinach (~> 0.8)
spring (1.6.4)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
@@ -978,7 +982,7 @@ DEPENDENCIES
org-ruby (~> 0.9.12)
paranoia (~> 2.0)
pg (~> 0.18.2)
- poltergeist (~> 1.8.1)
+ poltergeist (~> 1.9.0)
pry-rails
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.1)
@@ -999,6 +1003,7 @@ DEPENDENCIES
rouge (~> 1.10.1)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0)
+ rspec-retry
rubocop (~> 0.35.0)
ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0)
@@ -1017,6 +1022,7 @@ DEPENDENCIES
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1)
+ spinach-rerun-reporter (~> 0.0.2)
spring (~> 1.6.4)
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index d7feb5d5c87..54b28f2dd8d 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -23,7 +23,7 @@ class Dispatcher
new Issue()
shortcut_handler = new ShortcutsIssuable()
new ZenMode()
- when 'projects:milestones:show'
+ when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
index e6d8518bec8..0037a3a21c2 100644
--- a/app/assets/javascripts/milestone.js.coffee
+++ b/app/assets/javascripts/milestone.js.coffee
@@ -69,7 +69,7 @@ class @Milestone
@bindIssuesSorting()
@bindMergeRequestSorting()
- @bindTabsSwitching
+ @bindTabsSwitching()
bindIssuesSorting: ->
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
@@ -104,7 +104,7 @@ class @Milestone
).disableSelection()
- bindMergeRequestSorting: ->
+ bindTabsSwitching: ->
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
currentTabClass = $(e.target).data('show')
previousTabClass = $(e.relatedTarget).data('show')
@@ -112,7 +112,8 @@ class @Milestone
$(previousTabClass).hide()
$(currentTabClass).removeClass('hidden')
$(currentTabClass).show()
-
+
+ bindMergeRequestSorting: ->
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
connectWith: ".merge_requests-sortable-list",
dropOnEmpty: true,
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d4878b333f9..3dc524ccca4 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -81,8 +81,9 @@
&::before {
content: "\f00c";
position: absolute;
- left: 4px;
- top: 8px;
+ left: 5px;
+ top: 50%;
+ margin-top: -7px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
@@ -94,8 +95,8 @@
}
.dropdown-header {
- padding-left: 10px;
- padding-right: 10px;
+ padding-left: 5px;
+ padding-right: 5px;
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index d24adbf67e6..d0e72a4422c 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -19,10 +19,11 @@ li.milestone {
width: 105px;
}
- .issue-row {
+ .issuable-row {
.color-label {
border-radius: 2px;
padding: 3px !important;
+ margin-right: 7px;
}
// Issue title
@@ -44,20 +45,15 @@ li.milestone {
}
}
-.issues-sortable-list {
- .issue-detail {
+.issues-sortable-list, .merge_requests-sortable-list {
+ .issuable-detail {
display: block;
+ margin-top: 7px;
- .issue-number{
+ .issuable-number {
color: rgba(0,0,0,0.44);
margin-right: 5px;
}
- .color-label {
- padding: 6px 10px;
- margin-right: 7px;
- margin-top: 10px;
- }
-
.avatar {
float: none;
}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 0161642d871..7d414ae003d 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -26,5 +26,5 @@
margin-right: 10px;
font-size: $gl-font-size;
border: 1px solid;
- line-height: 40px;
+ line-height: 32px;
}
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 4d3e48f7f81..668396a0f20 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -55,7 +55,7 @@ class Admin::GroupsController < Admin::ApplicationController
private
def group
- @group = Group.find_by(path: params[:id])
+ @group ||= Group.find_by(path: params[:id])
end
def group_params
diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb
new file mode 100644
index 00000000000..f63b703d101
--- /dev/null
+++ b/app/controllers/concerns/filter_projects.rb
@@ -0,0 +1,15 @@
+# == FilterProjects
+#
+# Controller concern to handle projects filtering
+# * by name
+# * by archived state
+#
+module FilterProjects
+ extend ActiveSupport::Concern
+
+ def filter_projects(projects)
+ projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ projects = projects.non_archived if params[:archived].blank?
+ projects
+ end
+end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index dc880b634e5..fc51c3241af 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -1,18 +1,15 @@
class Dashboard::ProjectsController < Dashboard::ApplicationController
+ include FilterProjects
+
before_action :event_filter
def index
- @projects = current_user.authorized_projects.sorted_by_activity.non_archived
- @projects = @projects.sort(@sort = params[:sort])
+ @projects = current_user.authorized_projects.sorted_by_activity
+ @projects = filter_projects(@projects)
@projects = @projects.includes(:namespace)
+ @projects = @projects.sort(@sort = params[:sort])
+ @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
- terms = params[:filter_projects]
-
- if terms.present?
- @projects = @projects.search(terms)
- end
-
- @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
@last_push = current_user.recent_push
respond_to do |format|
@@ -32,16 +29,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def starred
@projects = current_user.starred_projects.sorted_by_activity
+ @projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
+ @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
- terms = params[:filter_projects]
-
- if terms.present?
- @projects = @projects.search(terms)
- end
-
- @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
@last_push = current_user.recent_push
@groups = []
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index a384f3004db..5b811db3068 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,12 +1,12 @@
class Explore::ProjectsController < Explore::ApplicationController
+ include FilterProjects
+
def index
@projects = ProjectsFinder.new.execute(current_user)
@tags = @projects.tags_on(:tags)
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
- @projects = @projects.non_archived
- @projects = @projects.search(params[:search]) if params[:search].present?
- @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ @projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
@@ -22,8 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
@projects = TrendingProjectsFinder.new.execute(current_user)
- @projects = @projects.non_archived
- @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ @projects = filter_projects(@projects)
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
@@ -38,7 +37,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def starred
@projects = ProjectsFinder.new.execute(current_user)
- @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ @projects = filter_projects(@projects)
@projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index ca5ce1e2046..f05c29e9974 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,4 +1,5 @@
class GroupsController < Groups::ApplicationController
+ include FilterProjects
include IssuesAction
include MergeRequestsAction
@@ -41,7 +42,8 @@ class GroupsController < Groups::ApplicationController
def show
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
- @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ @projects = filter_projects(@projects)
+ @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
@@ -98,7 +100,7 @@ class GroupsController < Groups::ApplicationController
end
def load_projects
- @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
+ @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity
end
# Dont allow unauthorized access to group
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index b64dbbd89ce..a6bebc46b06 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -7,6 +7,9 @@ class Projects::AvatarsController < Projects::ApplicationController
@blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
+
+ return if cached_blob?
+
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(@blob)
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 21f30f278c8..da46731d945 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def show
- @issues = @milestone.issues
- @users = @milestone.participants.uniq
- @merge_requests = @milestone.merge_requests
- @labels = @milestone.labels
end
def create
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index d9723acb1d9..10de0e60530 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -13,6 +13,8 @@ class Projects::RawController < Projects::ApplicationController
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
+ return if cached_blob?
+
if @blob.lfs_pointer?
send_lfs_object
else
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f7240edd618..c88a420b412 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -263,11 +263,9 @@ class IssuableFinder
def by_label(items)
if labels?
if filter_by_no_label?
- items = items.
- joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
- where(label_links: { id: nil })
+ items = items.without_label
else
- items = items.joins(:labels).where(labels: { title: label_names })
+ items = items.with_label(label_names)
if projects
items = items.where(labels: { project_id: projects })
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index 07b5759443b..a41172816b8 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -4,7 +4,7 @@ class SnippetsFinder
case filter
when :all then
- snippets(current_user).fresh.non_expired
+ snippets(current_user).fresh
when :by_user then
by_user(current_user, params[:user], params[:scope])
when :by_project
@@ -27,7 +27,7 @@ class SnippetsFinder
end
def by_user(current_user, user, scope)
- snippets = user.snippets.fresh.non_expired
+ snippets = user.snippets.fresh
return snippets.are_public unless current_user
@@ -48,7 +48,7 @@ class SnippetsFinder
end
def by_project(current_user, project)
- snippets = project.snippets.fresh.non_expired
+ snippets = project.snippets.fresh
if current_user
if project.team.member?(current_user.id)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f0aa2b57121..368969c6472 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -72,7 +72,7 @@ module ApplicationHelper
if user_or_email.is_a?(User)
user = user_or_email
else
- user = User.find_by(email: user_or_email.downcase)
+ user = User.find_by(email: user_or_email.try(:downcase))
end
if user
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7f63a2e2cb4..0f77b3b299a 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -152,4 +152,25 @@ module BlobHelper
'application/octet-stream'
end
end
+
+ def cached_blob?
+ stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
+
+ # Because we are opionated we set the cache headers ourselves.
+ response.cache_control[:public] = @project.public?
+
+ if @ref && @commit && @ref == @commit.id
+ # This is a link to a commit by its commit SHA. That means that the blob
+ # is immutable. The only reason to invalidate the cache is if the commit
+ # was deleted or if the user lost access to the repository.
+ response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
+ else
+ # A branch or tag points at this blob. That means that the expected blob
+ # value may change over time.
+ response.cache_control[:max_age] = Blob::CACHE_TIME
+ end
+
+ response.etag = @blob.id
+ !stale
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index a09e91578b6..f994c9e6170 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -211,4 +211,15 @@ module CommitsHelper
def clean(string)
Sanitize.clean(string, remove_contents: true)
end
+
+ def limited_commits(commits)
+ if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ [
+ commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE),
+ commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE
+ ]
+ else
+ [commits, 0]
+ end
+ end
end
diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb
index 3648757428b..337b0aacbb5 100644
--- a/app/helpers/explore_helper.rb
+++ b/app/helpers/explore_helper.rb
@@ -1,5 +1,5 @@
module ExploreHelper
- def explore_projects_filter_path(options={})
+ def filter_projects_path(options={})
exist_opts = {
sort: params[:sort],
scope: params[:scope],
@@ -9,15 +9,7 @@ module ExploreHelper
}
options = exist_opts.merge(options)
-
- path = if explore_controller?
- explore_projects_path
- elsif current_action?(:starred)
- starred_dashboard_projects_path
- else
- dashboard_projects_path
- end
-
+ path = request.path
path << "?#{options.to_param}"
path
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 1c7fcc13b42..89a054289e8 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -50,19 +50,25 @@ module LabelsHelper
@project.labels.pluck(:title)
end
- def render_colored_label(label)
+ def render_colored_label(label, label_suffix = '')
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="label color-label") +
- %( style="background-color: #{label_color}; color: #{text_color}">) +
- escape_once(label.name) + '</span>'
+ %(style="background-color: #{label_color}; color: #{text_color}">) +
+ %(#{escape_once(label.name)}#{label_suffix}</span>)
span.html_safe
end
+ def render_colored_cross_project_label(label)
+ label_suffix = label.project.name_with_namespace
+ label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
+ render_colored_label(label, label_suffix)
+ end
+
def suggested_colors
[
'#0033CC',
@@ -119,5 +125,6 @@ module LabelsHelper
end
# Required for Banzai::Filter::LabelReferenceFilter
- module_function :render_colored_label, :text_color_for_bg, :escape_once
+ module_function :render_colored_label, :render_colored_cross_project_label,
+ :text_color_for_bg, :escape_once
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 7de81d8dfdb..e3e7daa49c5 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -9,6 +9,32 @@ module MilestonesHelper
end
end
+ def milestones_label_path(opts = {})
+ if @project
+ namespace_project_issues_path(@project.namespace, @project, opts)
+ elsif @group
+ issues_group_path(@group, opts)
+ else
+ issues_dashboard_path(opts)
+ end
+ end
+
+ def milestones_browse_issuables_path(milestone, type:)
+ opts = { milestone_title: milestone.title }
+
+ if @project
+ polymorphic_path([@project.namespace.becomes(Namespace), @project, type], opts)
+ elsif @group
+ polymorphic_url([type, @group], opts)
+ else
+ polymorphic_url([type, :dashboard], opts)
+ end
+ end
+
+ def milestone_issues_by_label_count(milestone, label, state:)
+ milestone.issues.with_label(label.title).send(state).size
+ end
+
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 41ae4048992..0a5a8eb5aee 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,14 +1,4 @@
module SnippetsHelper
- def lifetime_select_options
- options = [
- ['forever', nil],
- ['1 day', "#{Date.current + 1.day}"],
- ['1 week', "#{Date.current + 1.week}"],
- ['1 month', "#{Date.current + 1.month}"]
- ]
- options_for_select(options)
- end
-
def reliable_snippet_path(snippet)
if snippet.project_id?
namespace_project_snippet_path(snippet.project.namespace,
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index f9026b887da..2f2d2721d6d 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -16,6 +16,16 @@ module SortingHelper
}
end
+ def projects_sort_options_hash
+ {
+ sort_value_name => sort_title_name,
+ sort_value_recently_updated => sort_title_recently_updated,
+ sort_value_oldest_updated => sort_title_oldest_updated,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_oldest_created => sort_title_oldest_created,
+ }
+ end
+
def sort_title_oldest_updated
'Oldest updated'
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index f34554d557c..fe9e0aab717 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -9,6 +9,7 @@ class Ability
when CommitStatus then commit_status_abilities(user, subject)
when Project then project_abilities(user, subject)
when Issue then issue_abilities(user, subject)
+ when ExternalIssue then external_issue_abilities(user, subject)
when Note then note_abilities(user, subject)
when ProjectSnippet then project_snippet_abilities(user, subject)
when PersonalSnippet then personal_snippet_abilities(user, subject)
@@ -424,6 +425,10 @@ class Ability
end
end
+ def external_issue_abilities(user, subject)
+ project_abilities(user, subject.project)
+ end
+
private
def named_abilities(name)
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 8ee9f3006b2..72e6c5fa3fd 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -1,5 +1,8 @@
# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
class Blob < SimpleDelegator
+ CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
+ CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
+
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
#
# This method prevents the decorated object from evaluating to "truthy" when
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 286d6655861..27b97944e38 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -29,12 +29,15 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
+ scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :opened, -> { with_state(:opened, :reopened) }
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
+ scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
+ scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
new file mode 100644
index 00000000000..d67df7c1d9c
--- /dev/null
+++ b/app/models/concerns/milestoneish.rb
@@ -0,0 +1,25 @@
+module Milestoneish
+ def closed_items_count
+ issues.closed.size + merge_requests.closed_and_merged.size
+ end
+
+ def total_items_count
+ issues.size + merge_requests.size
+ end
+
+ def complete?
+ total_items_count == closed_items_count
+ end
+
+ def percent_complete
+ ((closed_items_count * 100) / total_items_count).abs
+ rescue ZeroDivisionError
+ 0
+ end
+
+ def remaining_days
+ return 0 if !due_date || expired?
+
+ (due_date - Date.today).to_i
+ end
+end
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index 0171f7d54b7..ddd4bad5c21 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -2,16 +2,19 @@ class GlobalLabel
attr_accessor :title, :labels
alias_attribute :name, :title
+ delegate :color, :description, to: :@first_label
+
def self.build_collection(labels)
labels = labels.group_by(&:title)
- labels.map do |title, label|
- new(title, label)
+ labels.map do |title, labels|
+ new(title, labels)
end
end
def initialize(title, labels)
@title = title
@labels = labels
+ @first_label = labels.find { |lbl| lbl.description.present? } || labels.first
end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 7ee276255a0..97bd79af083 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -1,4 +1,6 @@
class GlobalMilestone
+ include Milestoneish
+
attr_accessor :title, :milestones
alias_attribute :name, :title
@@ -28,33 +30,7 @@ class GlobalMilestone
end
def projects
- milestones.map { |milestone| milestone.project }
- end
-
- def issue_count
- milestones.map { |milestone| milestone.issues.count }.sum
- end
-
- def merge_requests_count
- milestones.map { |milestone| milestone.merge_requests.count }.sum
- end
-
- def open_items_count
- milestones.map { |milestone| milestone.open_items_count }.sum
- end
-
- def closed_items_count
- milestones.map { |milestone| milestone.closed_items_count }.sum
- end
-
- def total_items_count
- milestones.map { |milestone| milestone.total_items_count }.sum
- end
-
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
- rescue ZeroDivisionError
- 0
+ @projects ||= Project.for_milestones(milestones.map(&:id))
end
def state
@@ -76,35 +52,20 @@ class GlobalMilestone
end
def issues
- @issues ||= milestones.map(&:issues).flatten.group_by(&:state)
+ @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
end
def merge_requests
- @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
+ @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
end
def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq
end
- def opened_issues
- issues.values_at("opened", "reopened").compact.flatten
- end
-
- def closed_issues
- issues['closed']
- end
-
- def opened_merge_requests
- merge_requests.values_at("opened", "reopened").compact.flatten
- end
-
- def closed_merge_requests
- merge_requests.values_at("closed", "merged", "locked").compact.flatten
- end
-
- def complete?
- total_items_count == closed_items_count
+ def labels
+ @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
+ .sort_by!(&:title)
end
def due_date
diff --git a/app/models/label.rb b/app/models/label.rb
index c34f4e4ba60..5ff644b8426 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -48,10 +48,15 @@ class Label < ActiveRecord::Base
'~'
end
+ ##
# Pattern used to extract label references from text
+ #
+ # This pattern supports cross-project references.
+ #
def self.reference_pattern
%r{
- #{reference_prefix}
+ (#{Project.reference_pattern})?
+ #{Regexp.escape(reference_prefix)}
(?:
(?<label_id>\d+) | # Integer-based label ID, or
(?<label_name>
@@ -62,24 +67,31 @@ class Label < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ nil
+ end
+
+ ##
# Returns the String necessary to reference this Label in Markdown
#
# format - Symbol format to use (default: :id, optional: :name)
#
- # Note that its argument differs from other objects implementing Referable. If
- # a non-Symbol argument is given (such as a Project), it will default to :id.
- #
# Examples:
#
- # Label.first.to_reference # => "~1"
- # Label.first.to_reference(:name) # => "~\"bug\""
+ # Label.first.to_reference # => "~1"
+ # Label.first.to_reference(format: :name) # => "~\"bug\""
+ # Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
#
# Returns a String
- def to_reference(format = :id)
- if format == :name && !name.include?('"')
- %(#{self.class.reference_prefix}"#{name}")
+ #
+ def to_reference(from_project = nil, format: :id)
+ format_reference = label_format_reference(format)
+ reference = "#{self.class.reference_prefix}#{format_reference}"
+
+ if cross_project_reference?(from_project)
+ project.to_reference + reference
else
- "#{self.class.reference_prefix}#{id}"
+ reference
end
end
@@ -98,4 +110,16 @@ class Label < ActiveRecord::Base
def template?
template
end
+
+ private
+
+ def label_format_reference(format = :id)
+ raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
+
+ if format == :name && !name.include?('"')
+ %("#{name}")
+ else
+ id
+ end
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index cf26183c254..c1e18bb3cc5 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -137,9 +137,7 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
- scope :opened, -> { with_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) }
- scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :join_project, -> { joins(:target_project) }
@@ -537,6 +535,29 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def diverged_commits_count
+ cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")
+
+ if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha
+ cache = {
+ source_sha: source_sha,
+ target_sha: target_sha,
+ diverged_commits_count: compute_diverged_commits_count
+ }
+ Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
+ end
+
+ cache[:diverged_commits_count]
+ end
+
+ def compute_diverged_commits_count
+ Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
+ end
+
+ def diverged_from_target_branch?
+ diverged_commits_count > 0
+ end
+
def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index df08d3a6dfb..33884118595 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -17,7 +17,7 @@ class MergeRequestDiff < ActiveRecord::Base
include Sortable
# Prevent store of diff if commits amount more then 500
- COMMITS_SAFE_SIZE = 500
+ COMMITS_SAFE_SIZE = 100
belongs_to :merge_request
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 7dc2f909b2f..e3969f32dd6 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base
include Sortable
include Referable
include StripAttribute
+ include Milestoneish
belongs_to :project
has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
- has_many :participants, through: :issues, source: :assignee
+ has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
@@ -92,30 +93,6 @@ class Milestone < ActiveRecord::Base
end
end
- def open_items_count
- self.issues.opened.count + self.merge_requests.opened.count
- end
-
- def closed_items_count
- self.issues.closed.count + self.merge_requests.closed_and_merged.count
- end
-
- def total_items_count
- self.issues.count + self.merge_requests.count
- end
-
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
- rescue ZeroDivisionError
- 0
- end
-
- def remaining_days
- return 0 if !due_date || expired?
-
- (due_date - Date.today).to_i
- end
-
def expires_at
if due_date
if due_date.past?
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index 9cee3b70cb3..452f3913eef 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
diff --git a/app/models/project.rb b/app/models/project.rb
index 148eab692ff..426464dee81 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -151,6 +151,7 @@ class Project < ActiveRecord::Base
has_many :releases, dependent: :destroy
has_many :lfs_objects_projects, dependent: :destroy
has_many :lfs_objects, through: :lfs_objects_projects
+ has_many :todos, dependent: :destroy
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
@@ -215,6 +216,7 @@ class Project < ActiveRecord::Base
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
+ scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
state_machine :import_status, initial: :none do
event :import_start do
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 9e2c1b0e18e..1f7d85a5f3d 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
@@ -23,6 +22,4 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
- scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
- scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c135ab61f6a..6441cd87e87 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -133,18 +133,18 @@ class Repository
rugged.branches.create(branch_name, target)
end
- expire_branches_cache
+ after_create_branch
find_branch(branch_name)
end
def add_tag(tag_name, ref, message = nil)
- expire_tags_cache
+ before_push_tag
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(user, branch_name)
- expire_branches_cache
+ before_remove_branch
branch = find_branch(branch_name)
oldrev = branch.try(:target)
@@ -155,12 +155,12 @@ class Repository
rugged.branches.delete(branch_name)
end
- expire_branches_cache
+ after_remove_branch
true
end
def rm_tag(tag_name)
- expire_tags_cache
+ before_remove_tag
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
@@ -183,6 +183,14 @@ class Repository
end
end
+ def branch_count
+ @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
+ end
+
+ def tag_count
+ @tag_count ||= cache.fetch(:tag_count) { raw_repository.rugged.tags.count }
+ end
+
# Return repo size in megabytes
# Cached in redis
def size
@@ -278,6 +286,16 @@ class Repository
@has_visible_content = nil
end
+ def expire_branch_count_cache
+ cache.expire(:branch_count)
+ @branch_count = nil
+ end
+
+ def expire_tag_count_cache
+ cache.expire(:tag_count)
+ @tag_count = nil
+ end
+
def rebuild_cache
cache_keys.each do |key|
cache.expire(key)
@@ -313,9 +331,17 @@ class Repository
expire_root_ref_cache
end
- # Runs code before creating a new tag.
- def before_create_tag
+ # Runs code before pushing (= creating or removing) a tag.
+ def before_push_tag
expire_cache
+ expire_tags_cache
+ expire_tag_count_cache
+ end
+
+ # Runs code before removing a tag.
+ def before_remove_tag
+ expire_tags_cache
+ expire_tag_count_cache
end
# Runs code after a repository has been forked/imported.
@@ -330,12 +356,21 @@ class Repository
# Runs code after a new branch has been created.
def after_create_branch
+ expire_branches_cache
expire_has_visible_content_cache
+ expire_branch_count_cache
+ end
+
+ # Runs code before removing an existing branch.
+ def before_remove_branch
+ expire_branches_cache
end
# Runs code after an existing branch has been removed.
def after_remove_branch
expire_has_visible_content_cache
+ expire_branch_count_cache
+ expire_branches_cache
end
def method_missing(m, *args, &block)
@@ -812,6 +847,12 @@ class Repository
raw_repository.ls_files(actual_ref)
end
+ def main_language
+ unless empty?
+ Linguist::Repository.new(rugged, rugged.head.target_id).language
+ end
+ end
+
private
def cache
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f876be7a4c8..dd3925c7a7d 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
@@ -46,8 +45,6 @@ class Snippet < ActiveRecord::Base
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") }
- scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
- scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
participant :author, :notes
@@ -111,10 +108,6 @@ class Snippet < ActiveRecord::Base
nil
end
- def expired?
- expires_at && expires_at < Time.current
- end
-
def visibility_level_field
visibility_level
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 9ba200f7bde..93a16e88967 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -14,6 +14,7 @@ class GitPushService < BaseService
# 3. Recognizes cross-references from commit messages
# 4. Executes the project's web hooks
# 5. Executes the project's services
+ # 6. Checks if the project's main language has changed
#
def execute
@project.repository.after_push_commit(branch_name)
@@ -42,11 +43,24 @@ class GitPushService < BaseService
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages
end
+ # Checks if the main language has changed in the project and if so
+ # it updates it accordingly
+ update_main_language
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
update_merge_requests
end
+ def update_main_language
+ current_language = @project.repository.main_language
+
+ unless current_language == @project.main_language
+ return @project.update_attributes(main_language: current_language)
+ end
+
+ true
+ end
+
protected
def update_merge_requests
@@ -96,7 +110,9 @@ class GitPushService < BaseService
# a different branch.
closed_issues = commit.closes_issues(current_user)
closed_issues.each do |issue|
- Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
+ if can?(current_user, :update_issue, issue)
+ Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
+ end
end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index a62c5fc4fc4..c88c7672805 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,7 +2,7 @@ class GitTagPushService
attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref)
- project.repository.before_create_tag
+ project.repository.before_push_tag
@project, @user = project, user
@push_data = build_push_data(oldrev, newrev, ref)
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index 8f25c5e2496..ebb67c7db65 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -21,7 +21,9 @@ module MergeRequests
closed_issues = merge_request.closes_issues(current_user)
closed_issues.each do |issue|
- Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+ if can?(current_user, :update_issue, issue)
+ Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+ end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index edced010811..58a861ee08e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -66,7 +66,7 @@ class SystemNoteService
def self.change_label(noteable, project, author, added_labels, removed_labels)
labels_count = added_labels.count + removed_labels.count
- references = ->(label) { label.to_reference(:id) }
+ references = ->(label) { label.to_reference(format: :id) }
added_labels = added_labels.map(&references).join(' ')
removed_labels = removed_labels.map(&references).join(' ')
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 40f88261c10..9da3fcbd986 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -15,7 +15,7 @@
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
- = render 'explore/projects/dropdown'
+ = render 'shared/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
= icon('plus')
diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml
deleted file mode 100644
index 1408ebdd5dc..00000000000
--- a/app/views/dashboard/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
- %span.milestone-row
- - project = issue.project
- %strong #{project.name_with_namespace} &middot;
- = link_to [project.namespace.becomes(Namespace), project, issue] do
- %span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
- .pull-right.assignee-icon
- - if issue.assignee
- = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_issues.html.haml b/app/views/dashboard/milestones/_issues.html.haml
deleted file mode 100644
index 9f350b772bd..00000000000
--- a/app/views/dashboard/milestones/_issues.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list issues-sortable-list" }
- - if issues
- - issues.each do |issue|
- = render 'issue', issue: issue
diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml
deleted file mode 100644
index 77c46de030b..00000000000
--- a/app/views/dashboard/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
- %span.milestone-row
- - project = merge_request.project
- %strong #{project.name_with_namespace} &middot;
- = link_to [project.namespace.becomes(Namespace), project, merge_request] do
- %span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
- .pull-right.assignee-icon
- - if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_merge_requests.html.haml b/app/views/dashboard/milestones/_merge_requests.html.haml
deleted file mode 100644
index 50057e2c636..00000000000
--- a/app/views/dashboard/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list merge_requests-sortable-list" }
- - if merge_requests
- - merge_requests.each do |merge_request|
- = render 'merge_request', merge_request: merge_request
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 7c882a32702..6173ca6ab9b 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -1,25 +1,6 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .row
- .col-sm-6
- %strong
- = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
- .col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
- .row
- .col-sm-6
- = link_to issues_dashboard_path(milestone_title: milestone.title) do
- = pluralize milestone.issue_count, 'Issue'
- &middot;
- = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
- = pluralize milestone.merge_requests_count, 'Merge Request'
- .col-sm-6
- = milestone_progress_bar(milestone)
- .row
- .col-sm-6
- .expiration
- = render 'shared/milestone_expired', milestone: milestone
- .projects
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name_with_namespace
+= render 'shared/milestones/milestone',
+ milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title),
+ issues_path: issues_dashboard_path(milestone_title: milestone.title),
+ merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
+ milestone: milestone,
+ dashboard: true
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 3810267577c..60c84a26420 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -1,105 +1,5 @@
-- page_title @milestone.title, "Milestones"
- header_title "Milestones", dashboard_milestones_path
-.detail-page-header
- .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
- %span.identifier
- Milestone #{@milestone.title}
-
-.detail-page-description.gray-content-block.second-block
- %h2.title
- = markdown escape_once(@milestone.title), pipeline: :single_line
-
-- if @milestone.complete? && @milestone.active?
- .alert.alert-success.prepend-top-default
- %span All issues for this milestone are closed. Navigate to the project to close the milestone.
-
-.table-holder
- %table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
-
-.context
- %p.lead
- Progress:
- #{@milestone.closed_items_count} closed
- &ndash;
- #{@milestone.open_items_count} open
- = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
- %li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab' do
- Issues
- %span.badge= @milestone.issue_count
- %li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
- Merge Requests
- %span.badge= @milestone.merge_requests_count
- %li
- = link_to '#tab-participants', 'data-toggle' => 'tab' do
- Participants
- %span.badge= @milestone.participants.count
-
-.tab-content
- .tab-pane.active#tab-issues
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All issues in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'issues', title: "Open", issues: @milestone.opened_issues
- .col-md-6
- = render 'issues', title: "Closed", issues: @milestone.closed_issues
-
- .tab-pane#tab-merge-requests
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All merge requests in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
- .col-md-6
- = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
-
- .tab-pane#tab-participants
- .gray-content-block.middle-block
- .oneline
- All participants to this milestone
- %ul.bordered-list
- - @milestone.participants.each do |user|
- %li
- = link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user, 32), class: "avatar s32"
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.username
+= render 'shared/milestones/top', milestone: @milestone
+= render 'shared/milestones/summary', milestone: @milestone
+= render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index f878d36e739..45cfe3da188 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -4,7 +4,10 @@
.todo-title
%span.author-name
- = link_to_author todo
+ - if todo.author
+ = link_to_author(todo)
+ - else
+ (removed)
%span.todo-label
= todo_action_name(todo)
= todo_target_link(todo)
diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml
deleted file mode 100644
index a4b4cd8d6c7..00000000000
--- a/app/views/explore/projects/_dropdown.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.dropdown.inline
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light
- - if @sort.present?
- = sort_options_hash[@sort]
- - else
- = sort_title_recently_updated
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- %li
- = link_to explore_projects_filter_path(sort: sort_value_name) do
- = sort_title_name
- = link_to explore_projects_filter_path(sort: sort_value_recently_created) do
- = sort_title_recently_created
- = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
- = sort_title_oldest_created
- = link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
- = sort_title_recently_updated
- = link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
- = sort_title_oldest_updated
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 39e3e8e2738..cd485da5104 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -10,11 +10,11 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to explore_projects_filter_path(visibility_level: nil) do
+ = link_to filter_projects_path(visibility_level: nil) do
Any
- Gitlab::VisibilityLevel.values.each do |level|
%li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
- = link_to explore_projects_filter_path(visibility_level: level) do
+ = link_to filter_projects_path(visibility_level: level) do
= visibility_level_icon(level)
= visibility_level_label(level)
@@ -30,11 +30,11 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to explore_projects_filter_path(tag: nil) do
+ = link_to filter_projects_path(tag: nil) do
Any
- @tags.each do |tag|
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
- = link_to explore_projects_filter_path(tag: tag.name) do
- %i.fa.fa-tag
+ = link_to filter_projects_path(tag: tag.name) do
+ = icon('tag')
= tag.name
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 794aa57b55a..7cd8e9bea46 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -3,9 +3,10 @@
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
- if @projects.present?
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- - if can? current_user, :create_projects, @group
- = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
- = icon('plus')
- New Project
+ = render 'shared/projects/dropdown'
+ - if can? current_user, :create_projects, @group
+ = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
+ = icon('plus')
+ New Project
= render 'shared/projects/list', projects: @projects, stars: false, skip_namespace: true
diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml
deleted file mode 100644
index 9b85d83d6d8..00000000000
--- a/app/views/groups/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
- %span.milestone-row
- - project = issue.project
- %strong #{project.name} &middot;
- = link_to [project.namespace.becomes(Namespace), project, issue] do
- %span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
- .pull-right.assignee-icon
- - if issue.assignee
- = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_issues.html.haml b/app/views/groups/milestones/_issues.html.haml
deleted file mode 100644
index 9f350b772bd..00000000000
--- a/app/views/groups/milestones/_issues.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list issues-sortable-list" }
- - if issues
- - issues.each do |issue|
- = render 'issue', issue: issue
diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml
deleted file mode 100644
index e3aa4aad198..00000000000
--- a/app/views/groups/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
- %span.milestone-row
- - project = merge_request.project
- %strong #{project.name} &middot;
- = link_to [project.namespace.becomes(Namespace), project, merge_request] do
- %span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
- .pull-right.assignee-icon
- - if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_merge_requests.html.haml b/app/views/groups/milestones/_merge_requests.html.haml
deleted file mode 100644
index 50057e2c636..00000000000
--- a/app/views/groups/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list merge_requests-sortable-list" }
- - if merge_requests
- - merge_requests.each do |merge_request|
- = render 'merge_request', merge_request: merge_request
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index a20bf75bc39..4c4e0a26728 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -1,29 +1,5 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .row
- .col-sm-6
- %strong
- = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
- .col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
- .row
- .col-sm-6
- = link_to issues_group_path(@group, milestone_title: milestone.title) do
- = pluralize milestone.issue_count, 'Issue'
- &middot;
- = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
- = pluralize milestone.merge_requests_count, 'Merge Request'
- .col-sm-6
- = milestone_progress_bar(milestone)
- .row
- .col-sm-6
- %div
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name
- .col-sm-6
- - if can?(current_user, :admin_milestones, @group)
- - if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
- - else
- = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+= render 'shared/milestones/milestone',
+ milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
+ issues_path: issues_group_path(@group, milestone_title: milestone.title),
+ merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
+ milestone: milestone
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 1233da85524..fb6f0da28f8 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,112 +1,4 @@
-- page_title @milestone.title, "Milestones"
= render "header_title"
-
-.detail-page-header
- .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
- %span.identifier
- Milestone #{@milestone.title}
- .pull-right
- - if can?(current_user, :admin_milestones, @group)
- - if @milestone.active?
- = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- - else
- = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
-
-.detail-page-description.gray-content-block.second-block
- %h2.title
- = markdown escape_once(@milestone.title), pipeline: :single_line
-
-- if @milestone.complete? && @milestone.active?
- .alert.alert-success.prepend-top-default
- %span All issues for this milestone are closed. You may close the milestone now.
-
-.table-holder
- %table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
-
-.context
- %p.lead
- Progress:
- #{@milestone.closed_items_count} closed
- &ndash;
- #{@milestone.open_items_count} open
- = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
- %li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab' do
- Issues
- %span.badge= @milestone.issue_count
- %li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
- Merge Requests
- %span.badge= @milestone.merge_requests_count
- %li
- = link_to '#tab-participants', 'data-toggle' => 'tab' do
- Participants
- %span.badge= @milestone.participants.count
-
-.tab-content
- .tab-pane.active#tab-issues
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All issues in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'issues', title: "Open", issues: @milestone.opened_issues
- .col-md-6
- = render 'issues', title: "Closed", issues: @milestone.closed_issues
-
- .tab-pane#tab-merge-requests
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All merge requests in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
- .col-md-6
- = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
-
- .tab-pane#tab-participants
- .gray-content-block.middle-block
- .oneline
- All participants to this milestone
-
- %ul.bordered-list
- - @milestone.participants.each do |user|
- %li
- = link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user, 32), class: "avatar s32"
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.username
+= render 'shared/milestones/top', milestone: @milestone, group: @group
+= render 'shared/milestones/summary', milestone: @milestone
+= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index 3c11b97921f..18caddabd39 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -6,4 +6,4 @@
- blob = sanitize_svg(blob)
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
- else
- %img{src: namespace_project_raw_path(@project.namespace, @project, @id)}
+ %img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
diff --git a/app/views/projects/branches/destroy.js.haml b/app/views/projects/branches/destroy.js.haml
index 882a4d0c5e2..a21ddaf4930 100644
--- a/app/views/projects/branches/destroy.js.haml
+++ b/app/views/projects/branches/destroy.js.haml
@@ -1 +1 @@
-$('.js-totalbranch-count').html("#{@repository.branches.size}")
+$('.js-totalbranch-count').html("#{@repository.branch_count}")
diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml
index ce60fbdf032..bac9e244d36 100644
--- a/app/views/projects/commits/_commit_list.html.haml
+++ b/app/views/projects/commits/_commit_list.html.haml
@@ -1,11 +1,14 @@
+- commits, hidden = limited_commits(@commits)
+- commits = Commit.decorate(commits, @project)
+
%div.panel.panel-default
.panel-heading
Commits (#{@commits.count})
- - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ - if hidden > 0
%ul.well-list
- - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), @project).each do |commit|
+ - commits.each do |commit|
= render "projects/commits/inline_commit", commit: commit, project: @project
%li.warning-row.unstyled
- other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
+ #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else
- %ul.well-list= render Commit.decorate(@commits, @project), project: @project
+ %ul.well-list= render commits, project: @project
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 6c631228002..a7e3c2478c2 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,7 +1,9 @@
- unless defined?(project)
- project = @project
-- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
+- commits, hidden = limited_commits(@commits)
+
+- commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
.row.commits-row
.col-md-2.hidden-xs.hidden-sm
%h5.commits-row-date
@@ -13,3 +15,7 @@
%ul.bordered-list
= render commits, project: project
%hr.lists-separator
+
+- if hidden > 0
+ .alert.alert-warning
+ #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 498c5e05b32..7a5b0d993db 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -15,9 +15,9 @@
= nav_link(html_options: {class: branches_tab_class}) do
= link_to namespace_project_branches_path(@project.namespace, @project) do
Branches
- %span.badge.js-totalbranch-count= @repository.branches.size
+ %span.badge.js-totalbranch-count= @repository.branch_count
= nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
- %span.badge.js-totaltags-count= @repository.tags.length
+ %span.badge.js-totaltags-count= @repository.tag_count
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 7b5e2991c09..b262892ac65 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -34,6 +34,8 @@
%span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
+ - if @merge_request.open? && @merge_request.diverged_from_target_branch?
+ %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
deleted file mode 100644
index ca51b8c745d..00000000000
--- a/app/views/projects/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
- %span
- = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
- .issue-detail
- = link_to [@project.namespace.becomes(Namespace), @project, issue] do
- %span.issue-number ##{issue.iid}
- - issue.labels.each do |label|
- = render_colored_label(label)
- - if issue.assignee
- = image_tag avatar_icon(issue.assignee, 16), class: "avatar s24", alt: ''
diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml
deleted file mode 100644
index 6f8a341e478..00000000000
--- a/app/views/projects/milestones/_issues.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.panel.panel-default
- .panel-heading
- = title
- .pull-right= issues.size
- %ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id }
- - issues.sort_by(&:position).each do |issue|
- = render 'issue', issue: issue
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
deleted file mode 100644
index a1033607c5d..00000000000
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) }
- %span.str-truncated
- = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do
- %span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
- .pull-right.assignee-icon
- - if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/projects/milestones/_merge_requests.html.haml b/app/views/projects/milestones/_merge_requests.html.haml
deleted file mode 100644
index 9a5a02af215..00000000000
--- a/app/views/projects/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list merge_requests-sortable-list", id: "merge_requests-list-#{id}", "data-state" => id }
- - merge_requests.sort_by(&:position).each do |merge_request|
- = render 'merge_request', merge_request: merge_request
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 67d95ab0364..77b566db6b6 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,31 +1,5 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
- .row
- .col-sm-6
- %strong
- = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
-
- .col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
- .row
- .col-sm-6
- = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
- = pluralize milestone.issues.count, 'Issue'
- &middot;
- = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
- = pluralize milestone.merge_requests.count, 'Merge Request'
- .col-sm-6
- = milestone_progress_bar(milestone)
-
- .row
- .col-sm-6
- = render 'shared/milestone_expired', milestone: milestone
- .col-sm-6
- - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
- = icon('pencil-square-o')
- Edit
- \
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
- = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
- = icon('trash-o')
- Delete
+= render 'shared/milestones/milestone',
+ milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
+ issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+ merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+ milestone: milestone
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 2cae1ac4e2c..b4597043a27 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -42,102 +42,9 @@
= preserve do
= markdown @milestone.description
-- if @milestone.issues.any? && @milestone.can_be_closed?
+- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
-.context.prepend-top-default
- .milestone-summary
- %h4 Progress
- %strong= @milestone.issues.count
- issues:
- %span.milestone-stat
- %strong= @milestone.open_items_count
- open and
- %strong= @milestone.closed_items_count
- closed
- %span.milestone-stat
- %strong== #{@milestone.percent_complete}%
- complete
- %span.milestone-stat
- %span.remaining-days= milestone_remaining_days(@milestone)
- %span.pull-right.tab-issues-buttons
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
- %i.fa.fa-plus
- New Issue
- - if can?(current_user, :read_issue, @project)
- = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
- %span.pull-right.tab-merge-requests-buttons.hidden
- - if can?(current_user, :read_merge_request, @project)
- = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
-
- = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
- %li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
- Issues
- %span.badge= @issues.count
- %li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
- Merge Requests
- %span.badge= @merge_requests.count
- %li
- = link_to '#tab-participants', 'data-toggle' => 'tab' do
- Participants
- %span.badge= @users.count
- %li
- = link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
- Labels
- %span.badge= @labels.count
-
-.tab-content.milestone-content
- .tab-pane.active#tab-issues
- .row.prepend-top-default
- .col-md-4
- = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
- .col-md-4
- = render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned, id: 'ongoing')
- .col-md-4
- = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
-
- .tab-pane#tab-merge-requests
- .row.prepend-top-default
- .col-md-3
- = render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
- .col-md-3
- = render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
- .col-md-3
- = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
- .col-md-3
- .panel.panel-primary
- .panel-heading Merged
- %ul.well-list
- - @merge_requests.merged.each do |merge_request|
- = render 'merge_request', merge_request: merge_request
-
- .tab-pane#tab-participants
- %ul.bordered-list
- - @users.each do |user|
- %li
- = link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user, 32), class: "avatar s32"
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.username
-
- .tab-pane#tab-labels
- %ul.bordered-list.manage-labels-list
- - @labels.each do |label|
- %li
- = render_colored_label(label)
- - args = [@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title, label_name: label.title]
- - options = args.extract_options!
-
- %span.issues-count
- = link_to namespace_project_issues_path(*args, options.merge(state: 'opened')) do
- = pluralize label.open_issues_count, 'open issue'
- %span.issues-count
- = link_to namespace_project_issues_path(*args, options.merge(state: 'closed')) do
- = pluralize label.closed_issues_count, 'closed issue'
+= render 'shared/milestones/summary', milestone: @milestone, project: @project
+= render 'shared/milestones/tabs', milestone: @milestone
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
new file mode 100644
index 00000000000..f7c6fc14adf
--- /dev/null
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -0,0 +1,25 @@
+-# @project is present when viewing Project's milestone
+- project = @project || issuable.project
+- assignee = issuable.assignee
+- issuable_type = issuable.class.table_name
+- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
+
+%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) }
+ %span
+ - if show_project_name
+ %strong #{project.name} &middot;
+ - elsif show_full_project_name
+ %strong #{project.name_with_namespace} &middot;
+ = link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
+ %div{class: 'issuable-detail'}
+ = link_to [project.namespace.becomes(Namespace), project, issuable] do
+ %span{ class: 'issuable-number' }>= issuable.to_reference
+
+ - issuable.labels.each do |label|
+ = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
+ - render_colored_label(label)
+
+ - if assignee
+ = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
+ class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
+ - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
new file mode 100644
index 00000000000..8619939dde7
--- /dev/null
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -0,0 +1,16 @@
+- show_counter = local_assigns.fetch(:show_counter, false)
+- primary = local_assigns.fetch(:primary, false)
+- panel_class = primary ? 'panel-primary' : 'panel-default'
+
+.panel{ class: panel_class }
+ .panel-heading
+ = title
+ - if show_counter
+ .pull-right= issuables.size
+
+ - class_prefix = dom_class(issuables).pluralize
+ %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
+ = render partial: 'shared/milestones/issuable',
+ collection: issuables.sort_by(&:position),
+ as: :issuable,
+ locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
diff --git a/app/views/shared/milestones/_issues_tab.html.haml b/app/views/shared/milestones/_issues_tab.html.haml
new file mode 100644
index 00000000000..a8db7f8a556
--- /dev/null
+++ b/app/views/shared/milestones/_issues_tab.html.haml
@@ -0,0 +1,10 @@
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
+ show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+
+.row.prepend-top-default
+ .col-md-4
+ = render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true)
+ .col-md-4
+ = render 'shared/milestones/issuables', args.merge(title: 'Ongoing Issues (open and assigned)', issuables: issues.opened.assigned, id: 'ongoing', show_counter: true)
+ .col-md-4
+ = render 'shared/milestones/issuables', args.merge(title: 'Completed Issues (closed)', issuables: issues.closed, id: 'closed', show_counter: true)
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
new file mode 100644
index 00000000000..ba27bafd1bc
--- /dev/null
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -0,0 +1,18 @@
+%ul.bordered-list.manage-labels-list
+ - labels.each do |label|
+ - options = { milestone_title: @milestone.title, label_name: label.title }
+
+ %li
+ %span.label-row
+ = link_to milestones_label_path(options) do
+ - render_colored_label(label)
+ %span.prepend-left-10
+ = markdown(label.description, pipeline: :single_line)
+
+ .pull-right
+ %strong.issues-count
+ = link_to milestones_label_path(options.merge(state: 'opened')) do
+ - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
+ %strong.issues-count
+ = link_to milestones_label_path(options.merge(state: 'closed')) do
+ - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
diff --git a/app/views/shared/milestones/_merge_requests_tab.haml b/app/views/shared/milestones/_merge_requests_tab.haml
new file mode 100644
index 00000000000..c29d8ee6737
--- /dev/null
+++ b/app/views/shared/milestones/_merge_requests_tab.haml
@@ -0,0 +1,12 @@
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
+ show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+
+.row.prepend-top-default
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned')
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing')
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed')
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
new file mode 100644
index 00000000000..f01138af3f0
--- /dev/null
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -0,0 +1,45 @@
+- dashboard = local_assigns[:dashboard]
+- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first)
+
+%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
+ .row
+ .col-sm-6
+ %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
+ .row
+ .col-sm-6
+ = link_to pluralize(milestone.issues.size, 'Issue'), issues_path
+ &middot;
+ = link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
+ .col-sm-6= milestone_progress_bar(milestone)
+ - if milestone.is_a?(GlobalMilestone)
+ .row
+ .col-sm-6
+ .expiration= render('shared/milestone_expired', milestone: milestone)
+ .projects
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = dashboard ? milestone.project.name_with_namespace : milestone.project.name
+ - if @group
+ .col-sm-6
+ - if can?(current_user, :admin_milestones, @group)
+ - if milestone.closed?
+ = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
+ - else
+ = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+
+ - if @project
+ .row
+ .col-sm-6= render('shared/milestone_expired', milestone: milestone)
+ .col-sm-6
+ - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
+ = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
+ = icon('pencil-square-o')
+ Edit
+ \
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
+ = icon('trash-o')
+ Delete
diff --git a/app/views/shared/milestones/_participants_tab.html.haml b/app/views/shared/milestones/_participants_tab.html.haml
new file mode 100644
index 00000000000..67ae85ac276
--- /dev/null
+++ b/app/views/shared/milestones/_participants_tab.html.haml
@@ -0,0 +1,8 @@
+%ul.bordered-list
+ - users.each do |user|
+ %li
+ = link_to user, title: user.name, class: "darken" do
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
+ %strong= truncate(user.name, lenght: 40)
+ %br
+ %small.cgray= user.username
diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml
new file mode 100644
index 00000000000..59d4ae29f79
--- /dev/null
+++ b/app/views/shared/milestones/_summary.html.haml
@@ -0,0 +1,28 @@
+- project = local_assigns[:project]
+
+.context.prepend-top-default
+ .milestone-summary
+ %h4 Progress
+ %strong= milestone.issues.size
+ issues:
+ %span.milestone-stat
+ %strong= milestone.issues.opened.size
+ open and
+ %strong= milestone.issues.closed.size
+ closed
+ %span.milestone-stat
+ %strong== #{milestone.percent_complete}%
+ complete
+
+ %span.milestone-stat
+ %span.remaining-days= milestone_remaining_days(milestone)
+ %span.pull-right.tab-issues-buttons
+ - if project && can?(current_user, :create_issue, project)
+ = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do
+ %i.fa.fa-plus
+ New Issue
+ = link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped"
+ %span.pull-right.tab-merge-requests-buttons.hidden
+ = link_to 'Browse Merge Requests', milestones_browse_issuables_path(milestone, type: :merge_requests), class: "btn btn-grouped"
+
+ = milestone_progress_bar(milestone)
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
new file mode 100644
index 00000000000..57d7ee85a3b
--- /dev/null
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -0,0 +1,30 @@
+%ul.nav-links.no-top.no-bottom
+ %li.active
+ = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
+ Issues
+ %span.badge= milestone.issues.size
+ %li
+ = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
+ Merge Requests
+ %span.badge= milestone.merge_requests.size
+ %li
+ = link_to '#tab-participants', 'data-toggle' => 'tab' do
+ Participants
+ %span.badge= milestone.participants.count
+ %li
+ = link_to '#tab-labels', 'data-toggle' => 'tab' do
+ Labels
+ %span.badge= milestone.labels.count
+
+- show_project_name = local_assigns.fetch(:show_project_name, false)
+- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
+
+.tab-content.milestone-content
+ .tab-pane.active#tab-issues
+ = render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ .tab-pane#tab-merge-requests
+ = render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ .tab-pane#tab-participants
+ = render 'shared/milestones/participants_tab', users: milestone.participants
+ .tab-pane#tab-labels
+ = render 'shared/milestones/labels_tab', labels: milestone.labels
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
new file mode 100644
index 00000000000..4cf1d948b5b
--- /dev/null
+++ b/app/views/shared/milestones/_top.html.haml
@@ -0,0 +1,58 @@
+- page_title milestone.title, "Milestones"
+
+- group = local_assigns[:group]
+
+.detail-page-header
+ .status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
+ - if milestone.closed?
+ Closed
+ - elsif milestone.expired?
+ Expired
+ - else
+ Open
+ %span.identifier
+ Milestone #{milestone.title}
+ - if milestone.expires_at
+ %span.creator
+ &middot;
+ = milestone.expires_at
+ - if group
+ .pull-right
+ - if can?(current_user, :admin_milestones, group)
+ - if milestone.active?
+ = link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
+ - else
+ = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(milestone.title), pipeline: :single_line
+
+- if milestone.complete? && milestone.active?
+ .alert.alert-success.prepend-top-default
+ - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
+ %span All issues for this milestone are closed. #{close_msg}
+
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - milestone.milestones.each do |ms|
+ %tr
+ %td
+ - project_name = group ? ms.project.name : ms.project.name_with_namespace
+ = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
+ %td
+ = ms.issues.opened.count
+ %td
+ - if ms.closed?
+ Closed
+ - else
+ Open
+ %td
+ = ms.expires_at
+
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
new file mode 100644
index 00000000000..e7e04621ff4
--- /dev/null
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -0,0 +1,22 @@
+- @sort ||= sort_value_recently_updated
+- archived = params[:archived]
+.dropdown.inline
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light
+ = projects_sort_options_hash[@sort]
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
+ %li.dropdown-header
+ Sort by
+ - projects_sort_options_hash.each do |value, title|
+ %li
+ = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
+ = title
+
+ %li.divider
+ %li
+ = link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do
+ Hide archived projects
+ %li
+ = link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
+ Show archived projects
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 99e48e86e38..97cfb76cdb0 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -28,6 +28,9 @@
= project.name
.controls
+ - if project.main_language
+ %span
+ = project.main_language
- if ci_commit
%span
= render_ci_status(ci_commit)
diff --git a/config/environments/test.rb b/config/environments/test.rb
index d6842affa6c..f96ac6f9753 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -7,8 +7,6 @@ Rails.application.configure do
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = false
- config.cache_store = :null_store
-
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_files = true
config.static_cache_control = "public, max-age=3600"
diff --git a/db/migrate/20160229193553_add_main_language_to_repository.rb b/db/migrate/20160229193553_add_main_language_to_repository.rb
new file mode 100644
index 00000000000..b5446c6a447
--- /dev/null
+++ b/db/migrate/20160229193553_add_main_language_to_repository.rb
@@ -0,0 +1,5 @@
+class AddMainLanguageToRepository < ActiveRecord::Migration
+ def change
+ add_column :projects, :main_language, :string
+ end
+end
diff --git a/db/migrate/20160305220806_remove_expires_at_from_snippets.rb b/db/migrate/20160305220806_remove_expires_at_from_snippets.rb
new file mode 100644
index 00000000000..fc12b5b09e6
--- /dev/null
+++ b/db/migrate/20160305220806_remove_expires_at_from_snippets.rb
@@ -0,0 +1,5 @@
+class RemoveExpiresAtFromSnippets < ActiveRecord::Migration
+ def change
+ remove_column :snippets, :expires_at, :datetime
+ end
+end
diff --git a/db/migrate/20160309140734_fix_todos.rb b/db/migrate/20160309140734_fix_todos.rb
new file mode 100644
index 00000000000..ebe0fc82305
--- /dev/null
+++ b/db/migrate/20160309140734_fix_todos.rb
@@ -0,0 +1,16 @@
+class FixTodos < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM todos
+ WHERE todos.target_type IN ('Commit', 'ProjectSnippet')
+ OR NOT EXISTS (
+ SELECT *
+ FROM projects
+ WHERE projects.id = todos.project_id
+ )
+ SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 71d9257a31e..a74b86d8e2f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160222153918) do
+ActiveRecord::Schema.define(version: 20160309140734) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -697,6 +697,7 @@ ActiveRecord::Schema.define(version: 20160222153918) do
t.integer "build_timeout", default: 3600, null: false
t.boolean "pending_delete", default: false
t.boolean "public_builds", default: true, null: false
+ t.string "main_language"
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -777,7 +778,6 @@ ActiveRecord::Schema.define(version: 20160222153918) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "file_name"
- t.datetime "expires_at"
t.string "type"
t.integer "visibility_level", default: 0, null: false
end
@@ -785,7 +785,6 @@ ActiveRecord::Schema.define(version: 20160222153918) do
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
- add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
diff --git a/doc/api/commits.md b/doc/api/commits.md
index e4d436b8e52..6341440c58b 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -213,8 +213,7 @@ Example response:
## Commit status
-Since GitLab 8.1, this is the new commit status API. The documentation in
-[ci/api/commits](../ci/api/commits.md) is deprecated.
+Since GitLab 8.1, this is the new commit status API.
### Get the status of a commit
diff --git a/doc/api/notes.md b/doc/api/notes.md
index d4d63e825ab..85d4f0bafa2 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -145,7 +145,6 @@ Parameters:
"state": "active",
"created_at": "2013-09-30T13:46:01Z"
},
- "expires_at": null,
"updated_at": "2013-10-02T07:34:20Z",
"created_at": "2013-10-02T07:34:20Z"
}
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index a7acf37b5bc..fb802102e3a 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -51,7 +51,6 @@ Parameters:
"state": "active",
"created_at": "2012-05-23T08:00:58Z"
},
- "expires_at": null,
"updated_at": "2012-06-28T10:52:04Z",
"created_at": "2012-06-28T10:52:04Z"
}
diff --git a/doc/api/users.md b/doc/api/users.md
index b7fc903825e..82c57a2fd43 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -151,6 +151,8 @@ Parameters:
"name": "John Smith",
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
+ "confirmed_at": "2012-05-23T08:00:58Z",
+ "last_sign_in_at": "2015-03-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
diff --git a/doc/ci/api/README.md b/doc/ci/api/README.md
index cf9710ede57..aea808007fc 100644
--- a/doc/ci/api/README.md
+++ b/doc/ci/api/README.md
@@ -1,86 +1,22 @@
# GitLab CI API
-## Resources
-
-- [Projects](projects.md)
-- [Runners](runners.md)
-- [Commits](commits.md)
-- [Builds](builds.md)
-
-
-## Authentication
-
-GitLab CI API uses different types of authentication depends on what API you use.
-Each API document has section with information about authentication you need to use.
-
-GitLab CI API has 4 authentication methods:
-
-* GitLab user token & GitLab url
-* GitLab CI project token
-* GitLab CI runners registration token
-* GitLab CI runner token
-
-
-### Authentication #1: GitLab user token & GitLab url
-
-Authentication is done by
-sending the `private-token` of a valid user and the `url` of an
-authorized GitLab instance via a query string along with the API
-request:
-
- GET http://gitlab.example.com/ci/api/v1/projects?private_token=QVy1PB7sTxfy4pqfZM1U&url=http://demo.gitlab.com/
+## Purpose
-If preferred, you may instead send the `private-token` as a header in
-your request:
+Main purpose of GitLab CI API is to provide necessary data and context for
+GitLab CI Runners.
- curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://gitlab.example.com/ci/api/v1/projects?url=http://demo.gitlab.com/"
+For consumer API take a look at this [documentation](../../api/README.md) where
+you will find all relevant information.
+## API Prefix
-### Authentication #2: GitLab CI project token
+Current CI API prefix is `/ci/api/v1`.
-Each project in GitLab CI has it own token.
-It can be used to get project commits and builds information.
-You can use project token only for certain project.
+You need to prepend this prefix to all examples in this documentation, like:
-### Authentication #3: GitLab CI runners registration token
+ GET /ci/api/v1/builds/:id/artifacts
-This token is not persisted and is generated on each application start.
-It can be used only for registering new runners in system. You can find it on
-GitLab CI Runners web page https://gitlab-ci.example.com/admin/runners
-
-### Authentication #4: GitLab CI runner token
-
-Every GitLab CI runner has it own token that allow it to receive and update
-GitLab CI builds. This token exists of internal purposes and should be used only
-by runners
-
-## JSON
-
-All API requests are serialized using JSON. You don't need to specify
-`.json` at the end of API URL.
-
-## Status codes
-
-The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
-
-API request types:
-
-- `GET` requests access one or more resources and return the result as JSON
-- `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
-- `GET`, `PUT` and `DELETE` return `200 OK` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
-- `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
-
-The following list shows the possible return codes for API requests.
-
-Return values:
+## Resources
-- `200 OK` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
-- `201 Created` - The `POST` request was successful and the resource is returned as JSON
-- `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
-- `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
-- `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
-- `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
-- `405 Method Not Allowed` - The request is not supported
-- `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
-- `422 Unprocessable` - The entity could not be processed
-- `500 Server Error` - While handling the request something went wrong on the server side
+- [Builds](builds.md)
+- [Runners](runners.md)
diff --git a/doc/ci/api/builds.md b/doc/ci/api/builds.md
index 018ca22dbbd..d100e261178 100644
--- a/doc/ci/api/builds.md
+++ b/doc/ci/api/builds.md
@@ -1,85 +1,73 @@
# Builds API
-This API used by runners to receive and update builds.
+API used by runners to receive and update builds.
-__Authentication is done by runner token__
+_**Note:** This API is intended to be used only by Runners as their own
+communication channel. For the consumer API see the
+[Builds API](../../api/builds.md)._
+
+## Authentication
+
+This API uses two types of authentication:
+
+1. Unique runner's token
+
+ Token assigned to runner after it has been registered.
+
+2. Using build authorization token
+
+ This is project's CI token that can be found in Continuous Integration
+ project settings.
+
+ Build authorization token can be passed as a parameter or a value of
+ `BUILD-TOKEN` header. This method are interchangeable.
## Builds
### Runs oldest pending build by runner
- POST /ci/builds/register
+ POST /ci/api/v1/builds/register
Parameters:
- * `token` (required) - The unique token of runner
-
-Returns:
-
-```json
-{
- "id": 48584,
- "ref": "0.1.1",
- "tag": true,
- "sha": "d63117656af6ff57d99e50cc270f854691f335ad",
- "status": "success",
- "name": "pages",
- "token": "9dd60b4f1a439d1765357446c1084c",
- "stage": "test",
- "project_id": 479,
- "project_name": "test",
- "commands": "echo commands",
- "repo_url": "http://gitlab-ci-token:token@gitlab.example/group/test.git",
- "before_sha": "0000000000000000000000000000000000000000",
- "allow_git_fetch": false,
- "options": {
- "image": "docker:image",
- "artifacts": {
- "paths": [
- "public"
- ]
- },
- "cache": {
- "paths": [
- "vendor"
- ]
- }
- },
- "timeout": 3600,
- "variables": [
- {
- "key": "CI_BUILD_TAG",
- "value": "0.1.1",
- "public": true
- }
- ],
- "depends_on_builds": [
- {
- "id": 48584,
- "ref": "0.1.1",
- "tag": true,
- "sha": "d63117656af6ff57d99e50cc270f854691f335ad",
- "status": "success",
- "name": "build",
- "token": "9dd60b4f1a439d1765357446c1084c",
- "stage": "build",
- "project_id": 479,
- "project_name": "test",
- "artifacts_file": {
- "filename": "artifacts.zip",
- "size": 0
- }
- }
- ]
-}
-```
+ * `token` (required) - Unique runner token
+
### Update details of an existing build
- PUT /ci/builds/:id
+ PUT /ci/api/v1/builds/:id
Parameters:
* `id` (required) - The ID of a project
+ * `token` (required) - Unique runner token
* `state` (optional) - The state of a build
* `trace` (optional) - The trace of a build
+
+### Upload artifacts to build
+
+ POST /ci/api/v1/builds/:id/artifacts
+
+Parameters:
+
+ * `id` (required) - The ID of a build
+ * `token` (required) - The build authorization token
+ * `file` (required) - Artifacts file
+
+### Download the artifacts file from build
+
+ GET /ci/api/v1/builds/:id/artifacts
+
+Parameters:
+
+ * `id` (required) - The ID of a build
+ * `token` (required) - The build authorization token
+
+### Remove the artifacts file from build
+
+ DELETE /ci/api/v1/builds/:id/artifacts
+
+Parameters:
+
+ * ` id` (required) - The ID of a build
+ * `token` (required) - The build authorization token
diff --git a/doc/ci/api/commits.md b/doc/ci/api/commits.md
deleted file mode 100644
index 871de7abcce..00000000000
--- a/doc/ci/api/commits.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# Commits API
-
-**DEPRECATED**
-
-Since GitLab 8.1, there is a new commit status API. Please see the [revised
-documentation](../../api/commits.md#commit-status).
-
----
-
-__Authentication is done by GitLab CI project token__
-
-## Commits
-
-### Retrieve all commits per project
-
-Get list of commits per project
-
- GET /ci/commits
-
-Parameters:
-
- * `project_id` (required) - The ID of a project
- * `project_token` (requires) - Project token
- * `page` (optional)
- * `per_page` (optional) - items per request (default is 20)
-
-Returns:
-
-```json
-[{
- "id": 3,
- "ref": "master",
- "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
- "project_id": 2,
- "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
- "created_at": "2014-11-05T09:46:35.247Z",
- "status": "success",
- "finished_at": "2014-11-05T09:46:44.254Z",
- "duration": 5.062692165374756,
- "git_commit_message": "wow\n",
- "git_author_name": "Administrator",
- "git_author_email": "admin@example.com",
- "builds": [{
- "id": 7,
- "project_id": 2,
- "ref": "master",
- "status": "success",
- "finished_at": "2014-11-05T09:46:44.254Z",
- "created_at": "2014-11-05T09:46:35.259Z",
- "updated_at": "2014-11-05T09:46:44.255Z",
- "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
- "started_at": "2014-11-05T09:46:39.192Z",
- "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
- "runner_id": 1,
- "coverage": null,
- "commit_id": 3
- }]
-}]
-```
-
-### Create commit
-
-Inform GitLab CI about new commit you want it to build.
-
-__If commit already exists in GitLab CI it will not be created__
-
-
- POST /ci/commits
-
-Parameters:
-
- * `project_id` (required) - The ID of a project
- * `project_token` (requires) - Project token
- * `data` (required) - Push data. For example see comment in `lib/api/commits.rb`
-
-Returns:
-
-```json
-{
- "id": 3,
- "ref": "master",
- "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
- "project_id": 2,
- "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
- "created_at": "2014-11-05T09:46:35.247Z",
- "status": "success",
- "finished_at": "2014-11-05T09:46:44.254Z",
- "duration": 5.062692165374756,
- "git_commit_message": "wow\n",
- "git_author_name": "Administrator",
- "git_author_email": "admin@example.com",
- "builds": [{
- "id": 7,
- "project_id": 2,
- "ref": "master",
- "status": "success",
- "finished_at": "2014-11-05T09:46:44.254Z",
- "created_at": "2014-11-05T09:46:35.259Z",
- "updated_at": "2014-11-05T09:46:44.255Z",
- "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
- "started_at": "2014-11-05T09:46:39.192Z",
- "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
- "runner_id": 1,
- "coverage": null,
- "commit_id": 3
- }]
-}
-```
diff --git a/doc/ci/api/projects.md b/doc/ci/api/projects.md
deleted file mode 100644
index fe6b1c01352..00000000000
--- a/doc/ci/api/projects.md
+++ /dev/null
@@ -1,149 +0,0 @@
-# Projects API
-
-This API is intended to aid in the setup and configuration of
-projects on GitLab CI.
-
-__Authentication is done by GitLab user token & GitLab url__
-
-## Projects
-
-### List Authorized Projects
-
-Lists all projects that the authenticated user has access to.
-
-```
-GET /ci/projects
-```
-
-Returns:
-
-```json
-[
- {
- "id" : 271,
- "name" : "gitlabhq",
- "timeout" : 1800,
- "token" : "iPWx6WM4lhHNedGfBpPJNP",
- "default_ref" : "master",
- "gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
- "path" : "gitlab/gitlab-shell",
- "always_build" : false,
- "polling_interval" : null,
- "public" : false,
- "ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
- "gitlab_id" : 3
- },
- {
- "id" : 272,
- "name" : "gitlab-ci",
- "timeout" : 1800,
- "token" : "iPWx6WM4lhHNedGfBpPJNP",
- "default_ref" : "master",
- "gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
- "path" : "gitlab/gitlab-shell",
- "always_build" : false,
- "polling_interval" : null,
- "public" : false,
- "ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
- "gitlab_id" : 4
- }
-]
-```
-
-### List Owned Projects
-
-Lists all projects that the authenticated user owns.
-
-```
-GET /ci/projects/owned
-```
-
-Returns:
-
-```json
-[
- {
- "id" : 272,
- "name" : "gitlab-ci",
- "timeout" : 1800,
- "token" : "iPWx6WM4lhHNedGfBpPJNP",
- "default_ref" : "master",
- "gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
- "path" : "gitlab/gitlab-shell",
- "always_build" : false,
- "polling_interval" : null,
- "public" : false,
- "ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
- "gitlab_id" : 4
- }
-]
-```
-
-### Single Project
-
-Returns information about a single project for which the user is
-authorized.
-
- GET /ci/projects/:id
-
-Parameters:
-
- * `id` (required) - The ID of the GitLab CI project
-
-### Create Project
-
-Creates a GitLab CI project using GitLab project details.
-
- POST /ci/projects
-
-Parameters:
-
- * `name` (required) - The name of the project
- * `gitlab_id` (required) - The ID of the project on the GitLab instance
- * `default_ref` (optional) - The branch to run on (default to `master`)
-
-### Update Project
-
-Updates a GitLab CI project using GitLab project details that the
-authenticated user has access to.
-
- PUT /ci/projects/:id
-
-Parameters:
-
- * `name` - The name of the project
- * `default_ref` - The branch to run on (default to `master`)
-
-### Remove Project
-
-Removes a GitLab CI project that the authenticated user has access to.
-
- DELETE /ci/projects/:id
-
-Parameters:
-
- * `id` (required) - The ID of the GitLab CI project
-
-### Link Project to Runner
-
-Links a runner to a project so that it can make builds (only via
-authorized user).
-
- POST /ci/projects/:id/runners/:runner_id
-
-Parameters:
-
- * `id` (required) - The ID of the GitLab CI project
- * `runner_id` (required) - The ID of the GitLab CI runner
-
-### Remove Project from Runner
-
-Removes a runner from a project so that it can not make builds (only
-via authorized user).
-
- DELETE /ci/projects/:id/runners/:runner_id
-
-Parameters:
-
- * `id` (required) - The ID of the GitLab CI project
- * `runner_id` (required) - The ID of the GitLab CI runner \ No newline at end of file
diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md
index e9033aeacd5..2f01da4bd76 100644
--- a/doc/ci/api/runners.md
+++ b/doc/ci/api/runners.md
@@ -1,81 +1,46 @@
# Runners API
+API used by runners to register and delete themselves.
+
_**Note:** This API is intended to be used only by Runners as their own
communication channel. For the consumer API see the
[new Runners API](../../api/runners.md)._
-## Runners
-
-### Retrieve all runners
+## Authentication
-__Authentication is done by GitLab user token & GitLab url__
+This API uses two types of authentication:
-Used to get information about all runners registered on the GitLab CI
-instance.
+1. Unique runner's token
- GET /ci/runners
+ Token assigned to runner after it has been registered.
-Returns:
+2. Using runners' registration token
-```json
-[
- {
- "id" : 85,
- "token" : "12b68e90394084703135"
- },
- {
- "id" : 86,
- "token" : "76bf894e969364709864"
- },
-]
-```
+ This is a token that can be found in project's settings.
+ It can be also found in Admin area &raquo; Runners settings.
-### Register a new runner
+ There are two types of tokens you can pass - shared runner registration
+ token or project specific registration token.
+## Runners
-__Authentication is done with a Shared runner registration token or a project Specific runner registration token__
+### Register a new runner
Used to make GitLab CI aware of available runners.
- POST /ci/runners/register
+ POST /ci/api/v1/runners/register
Parameters:
- * `token` (required) - The registration token. It is 2 types of token you can pass here.
+ * `token` (required) - Registration token
-1. Shared runner registration token
-2. Project specific registration token
-
-Returns:
-
-```json
-{
- "id" : 85,
- "token" : "12b68e90394084703135"
-}
-```
### Delete a runner
+Used to remove runner.
-__Authentication is done by runner token__
-
-Used to removing runners.
-
- DELETE /ci/runners/delete
+ DELETE /ci/api/v1/runners/delete
Parameters:
- * `token` (required) - The runner token.
-
-Returns:
-
-```json
-{
- "id" : 1,
- "token" : "d14963981a428f70121777e50643d1",
- "created_at" : "2015-02-26T11:39:39.232Z",
- "updated_at" : "2015-02-26T11:39:39.232Z",
- "description" : "awesome runner"
-}
-```
+ * `token` (required) - Unique runner token
diff --git a/doc/development/README.md b/doc/development/README.md
index b9a0d81e5ba..f5c3107ff44 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -9,4 +9,5 @@
- [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md)
+- [SQL guidelines](sql.md) for SQL guidelines
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
diff --git a/doc/development/sql.md b/doc/development/sql.md
new file mode 100644
index 00000000000..23fd7604957
--- /dev/null
+++ b/doc/development/sql.md
@@ -0,0 +1,219 @@
+# SQL Query Guidelines
+
+This document describes various guidelines to follow when writing SQL queries,
+either using ActiveRecord/Arel or raw SQL queries.
+
+## Using LIKE Statements
+
+The most common way to search for data is using the `LIKE` statement. For
+example, to get all issues with a title starting with "WIP:" you'd write the
+following query:
+
+```sql
+SELECT *
+FROM issues
+WHERE title LIKE 'WIP:%';
+```
+
+On PostgreSQL the `LIKE` statement is case-sensitive. On MySQL this depends on
+the case-sensitivity of the collation, which is usually case-insensitive. To
+perform a case-insensitive `LIKE` on PostgreSQL you have to use `ILIKE` instead.
+This statement in turn isn't supported on MySQL.
+
+To work around this problem you should write `LIKE` queries using Arel instead
+of raw SQL fragments as Arel automatically uses `ILIKE` on PostgreSQL and `LIKE`
+on MySQL. This means that instead of this:
+
+```ruby
+Issue.where('title LIKE ?', 'WIP:%')
+```
+
+You'd write this instead:
+
+```ruby
+Issue.where(Issue.arel_table[:title].matches('WIP:%'))
+```
+
+Here `matches` generates the correct `LIKE` / `ILIKE` statement depending on the
+database being used.
+
+If you need to chain multiple `OR` conditions you can also do this using Arel:
+
+```ruby
+table = Issue.arel_table
+
+Issue.where(table[:title].matches('WIP:%').or(table[:foo].matches('WIP:%')))
+```
+
+For PostgreSQL this produces:
+
+```sql
+SELECT *
+FROM issues
+WHERE (title ILIKE 'WIP:%' OR foo ILIKE 'WIP:%')
+```
+
+In turn for MySQL this produces:
+
+```sql
+SELECT *
+FROM issues
+WHERE (title LIKE 'WIP:%' OR foo LIKE 'WIP:%')
+```
+
+## LIKE & Indexes
+
+Neither PostgreSQL nor MySQL use any indexes when using `LIKE` / `ILIKE` with a
+wildcard at the start. For example, this will not use any indexes:
+
+```sql
+SELECT *
+FROM issues
+WHERE title ILIKE '%WIP:%';
+```
+
+Because the value for `ILIKE` starts with a wildcard the database is not able to
+use an index as it doesn't know where to start scanning the indexes.
+
+MySQL provides no known solution to this problem. Luckily PostgreSQL _does_
+provide a solution: trigram GIN indexes. These indexes can be created as
+follows:
+
+```sql
+CREATE INDEX [CONCURRENTLY] index_name_here
+ON table_name
+USING GIN(column_name gin_trgm_ops);
+```
+
+The key here is the `GIN(column_name gin_trgm_ops)` part. This creates a [GIN
+index][gin-index] with the operator class set to `gin_trgm_ops`. These indexes
+_can_ be used by `ILIKE` / `LIKE` and can lead to greatly improved performance.
+One downside of these indexes is that they can easily get quite large (depending
+on the amount of data indexed).
+
+To keep naming of these indexes consistent please use the following naming
+pattern:
+
+ index_TABLE_on_COLUMN_trigram
+
+For example, a GIN/trigram index for `issues.title` would be called
+`index_issues_on_title_trigram`.
+
+Due to these indexes taking quite some time to be built they should be built
+concurrently. This can be done by using `CREATE INDEX CONCURRENTLY` instead of
+just `CREATE INDEX`. Concurrent indexes can _not_ be created inside a
+transaction. Transactions for migrations can be disabled using the following
+pattern:
+
+```ruby
+class MigrationName < ActiveRecord::Migration
+ disable_ddl_transaction!
+end
+```
+
+For example:
+
+```ruby
+class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_username ON users (LOWER(username));'
+ execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_email ON users (LOWER(email));'
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ remove_index :users, :index_on_users_lower_username
+ remove_index :users, :index_on_users_lower_email
+ end
+end
+```
+
+## Plucking IDs
+
+This can't be stressed enough: **never** use ActiveRecord's `pluck` to pluck a
+set of values into memory only to use them as an argument for another query. For
+example, this will make the database **very** sad:
+
+```ruby
+projects = Project.all.pluck(:id)
+
+MergeRequest.where(source_project_id: projects)
+```
+
+Instead you can just use sub-queries which perform far better:
+
+```ruby
+MergeRequest.where(source_project_id: Project.all.select(:id))
+```
+
+The _only_ time you should use `pluck` is when you actually need to operate on
+the values in Ruby itself (e.g. write them to a file). In almost all other cases
+you should ask yourself "Can I not just use a sub-query?".
+
+## Use UNIONs
+
+UNIONs aren't very commonly used in most Rails applications but they're very
+powerful and useful. In most applications queries tend to use a lot of JOINs to
+get related data or data based on certain criteria, but JOIN performance can
+quickly deteriorate as the data involved grows.
+
+For example, if you want to get a list of projects where the name contains a
+value _or_ the name of the namespace contains a value most people would write
+the following query:
+
+```sql
+SELECT *
+FROM projects
+JOIN namespaces ON namespaces.id = projects.namespace_id
+WHERE projects.name ILIKE '%gitlab%'
+OR namespaces.name ILIKE '%gitlab%';
+```
+
+Using a large database this query can easily take around 800 milliseconds to
+run. Using a UNION we'd write the following instead:
+
+```sql
+SELECT projects.*
+FROM projects
+WHERE projects.name ILIKE '%gitlab%'
+
+UNION
+
+SELECT projects.*
+FROM projects
+JOIN namespaces ON namespaces.id = projects.namespace_id
+WHERE namespaces.name ILIKE '%gitlab%';
+```
+
+This query in turn only takes around 15 milliseconds to complete while returning
+the exact same records.
+
+This doesn't mean you should start using UNIONs everywhere, but it's something
+to keep in mind when using lots of JOINs in a query and filtering out records
+based on the joined data.
+
+GitLab comes with a `Gitlab::SQL::Union` class that can be used to build a UNION
+of multiple `ActiveRecord::Relation` objects. You can use this class as
+follows:
+
+```ruby
+union = Gitlab::SQL::Union.new([projects, more_projects, ...])
+
+Project.from("(#{union.to_sql}) projects")
+```
+
+## Ordering by Creation Date
+
+When ordering records based on the time they were created you can simply order
+by the `id` column instead of ordering by `created_at`. Because IDs are always
+unique and incremented in the order that rows are created this will produce the
+exact same results. This also means there's no need to add an index on
+`created_at` to ensure consistent performance as `id` is already indexed by
+default.
+
+[gin-index]: http://www.postgresql.org/docs/current/static/gin.html
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index 0f2665a3bf7..5a4b2f43ba7 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -2,7 +2,7 @@
**Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
-Please explore webhooks as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
+Please explore [web hooks](doc/web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index c1787a7c6a8..0fd54be58b0 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -467,12 +467,15 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
### Initial Login
-Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
+Visit YOUR_SERVER in your web browser for your first GitLab login.
- root
- 5iveL!fe
+If you didn't [provide a root password during setup](#initialize-database-and-activate-advanced-features),
+you'll be redirected to a password reset screen to provide the password for the
+initial administrator account. Enter your desired password and you'll be
+redirected back to the login screen.
-**Important Note:** On login you'll be prompted to change the password.
+The default account's username is **root**. Provide the password you created
+earlier and login. After login you can change the username if you wish.
**Enjoy!**
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 281eea8363d..7c8f785a61f 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -39,3 +39,34 @@ please see the [project_services directory][projects-code].
[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
[Project Service]: ../project_services/project_services.md
[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
+
+## SSL certificate errors
+
+When trying to integrate GitLab with services that are using self-signed certificates,
+it is very likely that SSL certificate errors will occur on different parts of the
+application, most likely Sidekiq. There are 2 approaches you can take to solve this:
+
+1. Add the root certificate to the trusted chain of the OS.
+1. If using Omnibus, you can add the certificate to GitLab's trusted certificates.
+
+**OS main trusted chain**
+
+This [resource](http://kb.kerio.com/product/kerio-connect/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html)
+has all the information you need to add a certificate to the main trusted chain.
+
+This [answer](http://superuser.com/questions/437330/how-do-you-add-a-certificate-authority-ca-to-ubuntu)
+at SuperUser also has relevant information.
+
+**Omnibus Trusted Chain**
+
+It is enough to concatenate the certificate to the main trusted certificate:
+
+```bash
+cat jira.pem >> /opt/gitlab/embedded/ssl/certs/cacert.pem
+```
+
+After that restart GitLab with:
+
+```bash
+sudo gitlab-ctl restart
+```
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index f256477196b..cf1f98492ea 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -204,3 +204,25 @@ When setting `method: ssl`, the underlying authentication method used by
`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
the LDAP server before any LDAP-protocol data is exchanged but no validation of
the LDAP server's SSL certificate is performed.
+
+## Troubleshooting
+
+### Invalid credentials when logging in
+
+Make sure the user you are binding with has enough permissions to read the user's
+tree and traverse it.
+
+Also make sure that the `user_filter` is not blocking otherwise valid users.
+
+To make sure that the LDAP settings are correct and GitLab can see your users,
+execute the following command:
+
+
+```bash
+# For Omnibus installations
+sudo gitlab-rake gitlab:ldap:check
+
+# For installations from source
+sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
+```
+
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index c84113556cd..148c4ac1886 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -133,12 +133,16 @@ will be returned to GitLab and will be signed in.
## Troubleshooting
+### 500 error after login
+
If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page,
this likely indicates that GitLab could not get the email address for the SAML user.
Make sure the IdP provides a claim containing the user's email address, using claim name
`email` or `mail`.
+### Redirect back to login screen with no evident error
+
If after signing in into your SAML server you are redirected back to the sign in page and
no error is displayed, check your `production.log` file. It will most likely contain the
message `Can't verify CSRF token authenticity`. This means that there is an error during
@@ -147,4 +151,36 @@ the SAML request, but this error never reaches GitLab due to the CSRF check.
To bypass this you can add `skip_before_action :verify_authenticity_token` to the
`omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab,
where it can then be seen in the usual logs, or as a flash message in the login
-screen. \ No newline at end of file
+screen.
+
+### Invalid audience
+
+This error means that the IdP doesn't recognize GitLab as a valid sender and
+receiver of SAML requests. Make sure to add the GitLab callback URL to the approved
+audiences of the IdP server.
+
+### Missing claims
+
+The IdP server needs to pass certain information in order for GitLab to either
+create an account, or match the login information to an existing account. `email`
+is the minimum amount of information that needs to be passed. If the IdP server
+is not providing this information, all SAML requests will fail.
+
+Make sure this information is provided.
+
+### Key validation error, Digest mismatch or Fingerprint mismatch
+
+These errors all come from a similar place, the SAML certificate. SAML requests
+need to be validated using a fingerprint, a certificate or a validator.
+
+For this you need take the following into account:
+
+- If no certificate is provided in the settings, a fingerprint or fingerprint
+ validator needs to be provided and the response from the server must contain
+ a certificate (`<ds:KeyInfo><ds:X509Data><ds:X509Certificate>`)
+- If a certificate is provided in the settings, it is no longer necessary for
+ the request to contain one. In this case the fingerprint or fingerprint
+ validators are optional
+
+Make sure that one of the above described scenarios is valid, or the requests will
+fail with one of the mentioned errors. \ No newline at end of file
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index ecbe0d3e887..f6ba80f46d5 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -2,19 +2,11 @@
## On Slack
-To enable Slack integration you must create an Incoming WebHooks integration on Slack;
+To enable Slack integration you must create an Incoming WebHooks integration on Slack:
1. [Sign in to Slack](https://slack.com/signin)
-1. Select **Apps & Custom Integrations** from the dropdown next to your team name.
-
-1. Click the **Configure** link (right-upper corner).
-
-1. Select the **Custom integrations** tab.
-
-1. Click the **Incoming WebHooks** row.
-
-1. Click the **Add configuration** button.
+1. Visit [Incoming WebHooks](https://my.slack.com/services/new/incoming-webhook/)
1. Choose the channel name you want to send notifications to.
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index c400cdac64d..cbf57db5684 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -207,6 +207,7 @@ GFM also recognizes certain cross-project references:
| `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
+| `namespace/project~"Some label"` | issues with given label |
## Task Lists
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
index 7c12557a321..27170c1eb19 100644
--- a/doc/project_services/jira.md
+++ b/doc/project_services/jira.md
@@ -219,3 +219,16 @@ You can see from the above image that there are four references to GitLab:
[JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website"
[jira-ce]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2146 "MR - Backport JIRA service"
[8_3_post]: https://about.gitlab.com/2015/12/22/gitlab-8-3-released/ "GitLab 8.3 release post"
+
+## Troubleshooting
+
+### GitLab is unable to comment on a ticket
+
+Make sure that the user you set up for GitLab to communicate with JIRA has the
+correct access permission to post comments on a ticket and to also transition the
+ticket, if you'd like GitLab to also take care of closing them.
+
+### GitLab is unable to close a ticket
+
+Make sure the the `Transition ID` you set within the JIRA settings matches the
+one your project needs to close a ticket.
diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md
index 408a17ac348..0a9cb5683e7 100644
--- a/doc/update/8.4-to-8.5.md
+++ b/doc/update/8.4-to-8.5.md
@@ -64,6 +64,9 @@ sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index a10e62877ba..f446ed0a35b 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -62,7 +62,13 @@ sudo -u git -H bundle install --without development test mysql --deployment
# MySQL
sudo -u git -H bundle install --without development test postgres --deployment
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index fd0327686b1..5fa39ef1b0a 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -4,7 +4,7 @@
Although deprecated, if someone wants to make this script into a gem or otherwise improve it merge requests are welcome.
-*Make sure you view this [upgrade guide from the 'master' branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.*
+*Make sure you view this [upgrade guide from the 'master' branch](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md) for the most up to date instructions.*
GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version.
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index b82306bd1da..e2b53c45ab1 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -582,7 +582,6 @@ X-Gitlab-Event: Note Hook
"created_at": "2015-04-09 02:40:38 UTC",
"updated_at": "2015-04-09 02:40:38 UTC",
"file_name": "test.rb",
- "expires_at": null,
"type": "ProjectSnippet",
"visibility_level": 0
}
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 5076b2697a3..36cb9da2380 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -9,7 +9,8 @@ Documentation on how to use Git LFS are under [Managing large binary files with
## Configuration
-Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on.
+Git LFS objects can be large in size. By default, they are stored on the server
+GitLab is installed on.
There are two configuration options to help GitLab server administrators:
@@ -37,5 +38,8 @@ In `config/gitlab.yml`:
## Known limitations
-* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported
+* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
+ is not supported
* Currently, removing LFS objects from GitLab Git LFS storage is not supported
+* LFS authentications via SSH is not supported for the time being
+* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2.
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index b59e92cb317..ba91685a20b 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -1,17 +1,21 @@
# Git LFS
-Managing large files such as audio, video and graphics files has always been one of the shortcomings of Git.
-The general recommendation is to not have Git repositories larger than 1GB to preserve performance.
+Managing large files such as audio, video and graphics files has always been one
+of the shortcomings of Git. The general recommendation is to not have Git repositories
+larger than 1GB to preserve performance.
-GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) (EE only), however in certain
-environments it is not always convenient to use different commands to differentiate between the large files and regular ones.
+GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html)
+(EE only), however in certain environments it is not always convenient to use
+different commands to differentiate between the large files and regular ones.
-Git LFS makes this simpler for the end user by removing the requirement to learn new commands.
+Git LFS makes this simpler for the end user by removing the requirement to
+learn new commands.
## How it works
-Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests.
-Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file.
+Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
+to authorize client requests. Once the request is authorized, Git LFS client receives
+instructions from where to fetch or where to push the large file.
## GitLab server configuration
@@ -24,15 +28,19 @@ Documentation for GitLab instance administrators is under [LFS administration do
## Known limitations
-* Git LFS v1 original API is not supported since it was deprecated early in LFS development
+* Git LFS v1 original API is not supported since it was deprecated early in LFS
+ development
* When SSH is set as a remote, Git LFS objects still go through HTTPS
-* Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended
-* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting)
+* Any Git LFS request will ask for HTTPS credentials to be provided so good Git
+ credentials store is recommended
+* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
+ to add the URL to Git config manually (see #troubleshooting)
## Using Git LFS
-Lets take a look at the workflow when you need to check large files into your Git repository with Git LFS:
-For example, if you want to upload a very large file and check it into your Git repository:
+Lets take a look at the workflow when you need to check large files into your Git
+repository with Git LFS. For example, if you want to upload a very large file and
+check it into your Git repository:
```bash
git clone git@gitlab.example.com:group/project.git
@@ -40,7 +48,8 @@ git lfs init # initialize the Git LFS project project
git lfs track "*.iso" # select the file extensions that you want to treat as large files
```
-Once a certain file extension is marked for tracking as a LFS object you can use Git as usual without having to redo the command to track a file with the same extension:
+Once a certain file extension is marked for tracking as a LFS object you can use
+Git as usual without having to redo the command to track a file with the same extension:
```bash
cp ~/tmp/debian.iso ./ # copy a large file into the current directory
@@ -49,13 +58,17 @@ git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server
```
-Cloning the repository works the same as before. Git automatically detects the LFS-tracked files and clones them via HTTP. If you performed the git clone command with a SSH URL, you have to enter your GitLab credentials for HTTP authentication.
+Cloning the repository works the same as before. Git automatically detects the
+LFS-tracked files and clones them via HTTP. If you performed the git clone
+command with a SSH URL, you have to enter your GitLab credentials for HTTP
+authentication.
```bash
git clone git@gitlab.example.com:group/project.git
```
-If you already cloned the repository and you want to get the latest LFS object that are on the remote repository, eg. from branch `master`:
+If you already cloned the repository and you want to get the latest LFS object
+that are on the remote repository, eg. from branch `master`:
```bash
git lfs fetch master
@@ -73,8 +86,8 @@ Check if you have permissions to push to the project or fetch from the project.
* Project is not allowed to access the LFS object
-LFS object you are trying to push to the project or fetch from the project is not available to the project anymore.
-Probably the object was removed from the server.
+LFS object you are trying to push to the project or fetch from the project is not
+available to the project anymore. Probably the object was removed from the server.
* Local git repository is using deprecated LFS API
@@ -89,16 +102,26 @@ git lfs logs last
If the status `error 501` is shown, it is because:
-* Git LFS support is not enabled on the GitLab server. Check with your GitLab administrator why Git LFS is not enabled on the server. See [LFS administration documentation](lfs_administration.md) for instructions on how to enable LFS support.
+* Git LFS support is not enabled on the GitLab server. Check with your GitLab
+ administrator why Git LFS is not enabled on the server. See
+ [LFS administration documentation](lfs_administration.md) for instructions
+ on how to enable LFS support.
-* Git LFS client version is not supported by GitLab server. Check your Git LFS version with `git lfs version`. Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try to update your Git LFS client. Only version 1.0.1 and newer are supported.
+* Git LFS client version is not supported by GitLab server. Check your Git LFS
+ version with `git lfs version`. Check the Git config of the project for traces
+ of deprecated API with `git lfs -l`. If `batch = false` is set in the config,
+ remove the line and try to update your Git LFS client. Only version 1.0.1 and
+ newer are supported.
### getsockopt: connection refused
-If you push a LFS object to a project and you receive an error similar to: `Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`,
-the LFS client is trying to reach GitLab through HTTPS. However, your GitLab instance is being served on HTTP.
+If you push a LFS object to a project and you receive an error similar to:
+`Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`,
+the LFS client is trying to reach GitLab through HTTPS. However, your GitLab
+instance is being served on HTTP.
-This behaviour is caused by Git LFS using HTTPS connections by default when a `lfsurl` is not set in the Git config.
+This behaviour is caused by Git LFS using HTTPS connections by default when a
+`lfsurl` is not set in the Git config.
To prevent this from happening, set the lfs url in project Git config:
@@ -109,18 +132,24 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/o
### Credentials are always required when pushing an object
-Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required.
+Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
+the LFS object on every push for every object, user HTTPS credentials are required.
-By default, Git has support for remembering the credentials for each repository you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials).
+By default, Git has support for remembering the credentials for each repository
+you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials).
-For example, you can tell Git to remember the password for a period of time in which you expect to push the objects:
+For example, you can tell Git to remember the password for a period of time in
+which you expect to push the objects:
```bash
git config --global credential.helper 'cache --timeout=3600'
```
-This will remember the credentials for an hour after which Git operations will require re-authentication.
+This will remember the credentials for an hour after which Git operations will
+require re-authentication.
-If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
+If you are using OS X you can use `osxkeychain` to store and encrypt your credentials.
+For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
-More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file
+More details about various methods of storing the user credentials can be found
+on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file
diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature
index 69b3a776441..bed9282f1c6 100644
--- a/features/dashboard/archived_projects.feature
+++ b/features/dashboard/archived_projects.feature
@@ -10,3 +10,8 @@ Feature: Dashboard Archived Projects
Scenario: I should see non-archived projects on dashboard
Then I should see "Shop" project link
And I should not see "Forum" project link
+
+ Scenario: I toggle show of archived projects on dashboard
+ When I click "Show archived projects" link
+ Then I should see "Shop" project link
+ And I should see "Forum" project link
diff --git a/features/explore/projects.feature b/features/explore/projects.feature
index 7df6b6f09ba..092e18d1b86 100644
--- a/features/explore/projects.feature
+++ b/features/explore/projects.feature
@@ -140,4 +140,4 @@ Feature: Explore Projects
When I visit the explore starred projects
Then I should see project "Community"
And I should see project "Internal"
- And I should see project "Archive"
+ And I should not see project "Archive"
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
index 62ea66a783c..d6c05df9840 100644
--- a/features/group/milestones.feature
+++ b/features/group/milestones.feature
@@ -28,3 +28,20 @@ Feature: Group Milestones
And I fill milestone name
When I press create mileston button
Then milestone in each project should be created
+
+ Scenario: I should see Issues listed with labels
+ Given Group has projects with milestones
+ When I visit group "Owned" page
+ And I click on group milestones
+ And I click on one group milestone
+ Then I should see the "bug" label
+ And I should see the "feature" label
+ And I should see the project name in the Issue row
+
+ Scenario: I should see the Labels tab
+ Given Group has projects with milestones
+ When I visit group "Owned" page
+ And I click on group milestones
+ And I click on one group milestone
+ And I click on the "Labels" tab
+ Then I should see the list of labels
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 89af58dcef3..ff21c7d1b83 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -59,14 +59,6 @@ Feature: Project Issues
And I should see an error alert section within the comment form
@javascript
- Scenario: Visiting Issues after leaving a comment
- Given I visit issue page "Release 0.4"
- And I leave a comment like "XML attached"
- And I visit project "Shop" issues page
- And I sort the list by "Last updated"
- Then I should see "Release 0.4" at the top
-
- @javascript
Scenario: Visiting Issues after being sorted the list
Given I visit project "Shop" issues page
And I sort the list by "Oldest updated"
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 495f25f28e7..f8d9fe1854d 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -26,6 +26,16 @@ Feature: Project Merge Requests
When I visit project "Shop" merge requests page
Then I should see "other_branch" branch
+ Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target
+ Given project "Shop" have "Bug NS-07" open merge request with rebased branch
+ When I visit merge request page "Bug NS-07"
+ Then I should not see the diverged commits count
+
+ Scenario: I should see the numbers of diverged commits if the branch diverged from the target
+ Given project "Shop" have "Bug NS-08" open merge request with diverged branch
+ When I visit merge request page "Bug NS-08"
+ Then I should see the diverged commits count
+
Scenario: I should see rejected merge requests
Given I click link "Closed"
Then I should see "Feature NS-03" in merge requests
@@ -77,15 +87,6 @@ Feature: Project Merge Requests
Then I should see comment "XML attached"
@javascript
- Scenario: Visiting Merge Requests after leaving a comment
- Given project "Shop" have "Bug NS-05" open merge request with diffs inside
- And I visit merge request page "Bug NS-04"
- And I leave a comment like "XML attached"
- And I visit project "Shop" merge requests page
- And I sort the list by "Last updated"
- Then I should see "Bug NS-04" at the top
-
- @javascript
Scenario: Visiting Merge Requests after being sorted the list
Given I visit project "Shop" merge requests page
And I sort the list by "Oldest updated"
@@ -119,16 +120,6 @@ Feature: Project Merge Requests
Then The list should be sorted by "Least popular"
@javascript
- Scenario: Visiting Merge Requests after commenting on diffs
- Given project "Shop" have "Bug NS-05" open merge request with diffs inside
- And I visit merge request page "Bug NS-05"
- And I click on the Changes tab
- And I leave a comment like "Line is wrong" on diff
- And I visit project "Shop" merge requests page
- And I sort the list by "Last updated"
- Then I should see "Bug NS-05" at the top
-
- @javascript
Scenario: I comment on a merge request diff
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
diff --git a/features/steps/dashboard/archived_projects.rb b/features/steps/dashboard/archived_projects.rb
index 36e092f50c6..6510f8d9b32 100644
--- a/features/steps/dashboard/archived_projects.rb
+++ b/features/steps/dashboard/archived_projects.rb
@@ -19,4 +19,8 @@ class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
step 'I should see "Forum" project link' do
expect(page).to have_link "Forum"
end
+
+ step 'I click "Show archived projects" link' do
+ click_link "Show archived projects"
+ end
end
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 2363ad797fa..a167d259837 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -24,6 +24,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
step 'I click on one group milestone' do
+ milestones = Milestone.where(title: 'GL-113')
+ @global_milestone = GlobalMilestone.new('GL-113', milestones)
+
click_link 'GL-113'
end
@@ -33,7 +36,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
step 'I should see group milestone with all issues and MRs assigned to that milestone' do
expect(page).to have_content('Milestone GL-113')
- expect(page).to have_content('Progress: 0 closed – 3 open')
+ expect(page).to have_content('3 issues: 3 open and 0 closed')
issue = Milestone.find_by(name: 'GL-113').issues.first
expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
end
@@ -60,6 +63,39 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
end
+ step 'I should see the "bug" label' do
+ page.within('#tab-issues') do
+ expect(page).to have_content 'bug'
+ end
+ end
+
+ step 'I should see the "feature" label' do
+ page.within('#tab-issues') do
+ expect(page).to have_content 'bug'
+ end
+ end
+
+ step 'I should see the project name in the Issue row' do
+ page.within('#tab-issues') do
+ @global_milestone.projects.each do |project|
+ expect(page).to have_content project.name
+ end
+ end
+ end
+
+ step 'I click on the "Labels" tab' do
+ page.within('.nav-links') do
+ page.find(:xpath, "//a[@href='#tab-labels']").click
+ end
+ end
+
+ step 'I should see the list of labels' do
+ page.within('#tab-labels') do
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'feature'
+ end
+ end
+
private
def group_milestone
@@ -68,6 +104,10 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
%w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
project = create :project, path: path, group: group
milestone = create :milestone, title: "Version 7.2", project: project
+
+ create(:label, project: project, title: 'bug')
+ create(:label, project: project, title: 'feature')
+
create :issue,
project: project,
assignee: current_user,
@@ -80,11 +120,14 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
due_date: '2114-08-20',
description: 'Lorem Ipsum is simply dummy text'
- create :issue,
+ issue = create :issue,
project: project,
assignee: current_user,
author: current_user,
milestone: milestone
+
+ issue.labels << project.labels.find_by(title: 'bug')
+ issue.labels << project.labels.find_by(title: 'feature')
end
end
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 565bf088b41..d9842ccf95e 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -355,10 +355,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
end
- step 'I should see "Release 0.4" at the top' do
- expect(page.find('ul.content-list.issues-list li.issue:first-child')).to have_content("Release 0.4")
- end
-
def filter_issue(text)
fill_in 'issue_search', with: text
end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index e2eda511497..4faa0f4707c 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
end
step 'I should see 3 issues' do
- expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
+ expect(page).to have_selector('#tab-issues li.issuable-row', count: 4)
end
step 'I click link to remove milestone' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index dde864f5180..c19b15bc9ed 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -60,7 +60,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(page).not_to have_content "Feature NS-03"
end
-
step 'I should not see "Bug NS-04" in merge requests' do
expect(page).not_to have_content "Bug NS-04"
end
@@ -121,6 +120,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'project "Shop" have "Bug NS-07" open merge request with rebased branch' do
+ create(:merge_request, :rebased,
+ title: "Bug NS-07",
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
+ end
+
+ step 'project "Shop" have "Bug NS-08" open merge request with diverged branch' do
+ create(:merge_request, :diverged,
+ title: "Bug NS-08",
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
+ end
+
step 'project "Shop" have "Feature NS-03" closed merge request' do
create(:closed_merge_request,
title: "Feature NS-03",
@@ -490,12 +505,16 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
- step 'I should see "Bug NS-05" at the top' do
- expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-05")
+ step 'I should see the diverged commits count' do
+ page.within ".mr-source-target" do
+ expect(page).to have_content /([0-9]+ commits behind)/
+ end
end
- step 'I should see "Bug NS-04" at the top' do
- expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-04")
+ step 'I should not see the diverged commits count' do
+ page.within ".mr-source-target" do
+ expect(page).not_to have_content /([0-9]+ commit[s]? behind)/
+ end
end
def merge_request
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 2685f5fd6b4..4fda0731e2f 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -29,7 +29,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
step 'There is an open Merge Request' do
@user = create(:user)
@project = create(:project, :public)
- @project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER)
+ @project_member = create(:project_member, :developer, user: @user, project: @project)
@merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
end
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
index c5a4cfce6f0..efbc4831ce1 100644
--- a/features/steps/project/merge_requests/revert.rb
+++ b/features/steps/project/merge_requests/revert.rb
@@ -36,7 +36,7 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
step 'There is an open Merge Request' do
@user = create(:user)
@project = create(:project, :public)
- @project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER)
+ @project_member = create(:project_member, :developer, user: @user, project: @project)
@merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index eb6df61b8e6..444d6726f99 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -144,11 +144,4 @@ module SharedNote
expect(page).to have_content("+1 Awesome!")
end
end
-
- step 'I sort the list by "Last updated"' do
- find('button.dropdown-toggle.btn').click
- page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
- click_link "Last updated"
- end
- end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 6432a786bfc..da9d1503ebc 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -388,13 +388,19 @@ module SharedPaths
end
step 'I visit merge request page "Bug NS-04"' do
- mr = MergeRequest.find_by(title: "Bug NS-04")
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit merge_request_path("Bug NS-04")
end
step 'I visit merge request page "Bug NS-05"' do
- mr = MergeRequest.find_by(title: "Bug NS-05")
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit merge_request_path("Bug NS-05")
+ end
+
+ step 'I visit merge request page "Bug NS-07"' do
+ visit merge_request_path("Bug NS-07")
+ end
+
+ step 'I visit merge request page "Bug NS-08"' do
+ visit merge_request_path("Bug NS-08")
end
step 'I visit merge request page "Bug CO-01"' do
@@ -503,6 +509,11 @@ module SharedPaths
Project.find_by!(name: 'Shop')
end
+ def merge_request_path(title)
+ mr = MergeRequest.find_by(title: title)
+ namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ end
+
# ----------------------------------------
# Errors
# ----------------------------------------
diff --git a/features/support/env.rb b/features/support/env.rb
index 62c80b9c948..357d164d87f 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -14,6 +14,7 @@ require 'sidekiq/testing/inline'
require_relative 'capybara'
require_relative 'db_cleaner'
+require_relative 'rerun'
%w(select2_helper test_env repo_helpers).each do |f|
require Rails.root.join('spec', 'support', f)
diff --git a/features/support/rerun.rb b/features/support/rerun.rb
new file mode 100644
index 00000000000..8b176c5be89
--- /dev/null
+++ b/features/support/rerun.rb
@@ -0,0 +1,14 @@
+# The spinach-rerun-reporter doesn't define the on_undefined_step
+# See it here: https://github.com/javierav/spinach-rerun-reporter/blob/master/lib/spinach/reporter/rerun.rb
+module Spinach
+ class Reporter
+ class Rerun
+ def on_undefined_step(step_data, failure, step_definitions = nil)
+ super step_data, failure, step_definitions
+
+ # save feature file and scenario line
+ @rerun << "#{current_feature.filename}:#{current_scenario.line}"
+ end
+ end
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b021db8fa5b..5b5b8bd044b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -23,6 +23,8 @@ module API
end
class UserFull < User
+ expose :last_sign_in_at
+ expose :confirmed_at
expose :email
expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
expose :identities, using: Entities::Identity
@@ -141,7 +143,7 @@ module API
class ProjectSnippet < Grape::Entity
expose :id, :title, :file_name
expose :author, using: Entities::UserBasic
- expose :expires_at, :updated_at, :created_at
+ expose :updated_at, :created_at
end
class ProjectEntity < Grape::Entity
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index cdbaecf8d90..34c38913474 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -94,6 +94,8 @@ module Banzai
object_link_filter(link, object_class.link_reference_pattern, link_text: text)
end
end
+
+ doc
end
# Replace references (like `!123` for merge requests) in text with links
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 95e7d209119..8147e5ed3c7 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -1,78 +1,47 @@
module Banzai
module Filter
# HTML filter that replaces label references with links.
- class LabelReferenceFilter < ReferenceFilter
- # Public: Find label references in text
- #
- # LabelReferenceFilter.references_in(text) do |match, id, name|
- # "<a href=...>#{Label.find(id)}</a>"
- # end
- #
- # text - String text to search.
- #
- # Yields the String match, an optional Integer label ID, and an optional
- # String label name.
- #
- # Returns a String replaced with the return of the block.
- def self.references_in(text)
- text.gsub(Label.reference_pattern) do |match|
- yield match, $~[:label_id].to_i, $~[:label_name]
- end
+ class LabelReferenceFilter < AbstractReferenceFilter
+ def self.object_class
+ Label
end
- def self.referenced_by(node)
- { label: LazyReference.new(Label, node.attr("data-label")) }
+ def find_object(project, id)
+ project.labels.find(id)
end
- def call
- replace_text_nodes_matching(Label.reference_pattern) do |content|
- label_link_filter(content)
- end
-
- replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
- label_link_filter(link, link_text: text)
+ def self.references_in(text, pattern = Label.reference_pattern)
+ text.gsub(pattern) do |match|
+ yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~
end
end
- # Replace label references in text with links to the label specified.
- #
- # text - String text to replace references in.
- #
- # Returns a String with label references replaced with links. All links
- # have `gfm` and `gfm-label` class names attached for styling.
- def label_link_filter(text, link_text: nil)
- project = context[:project]
-
- self.class.references_in(text) do |match, id, name|
- params = label_params(id, name)
-
- if label = project.labels.find_by(params)
- url = url_for_label(project, label)
- klass = reference_class(:label)
- data = data_attribute(
- original: link_text || match,
- project: project.id,
- label: label.id
- )
+ def references_in(text, pattern = Label.reference_pattern)
+ text.gsub(pattern) do |match|
+ project = project_from_ref($~[:project])
+ params = label_params($~[:label_id].to_i, $~[:label_name])
+ label = project.labels.find_by(params)
- text = link_text || render_colored_label(label)
-
- %(<a href="#{url}" #{data}
- class="#{klass}">#{escape_once(text)}</a>)
+ if label
+ yield match, label.id, $~[:project], $~
else
match
end
end
end
- def url_for_label(project, label)
+ def url_for_object(label, project)
h = Gitlab::Application.routes.url_helpers
- h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
- only_path: context[:only_path])
+ h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
+ only_path: context[:only_path])
end
- def render_colored_label(label)
- LabelsHelper.render_colored_label(label)
+ def object_link_text(object, matches)
+ if context[:project] == object.project
+ LabelsHelper.render_colored_label(object)
+ else
+ LabelsHelper.render_colored_cross_project_label(object)
+ end
end
# Parameters to pass to `Label.find_by` based on the given arguments
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 3f483847efa..46e51a4bf6d 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -76,7 +76,7 @@ module Gitlab
project.issues.create!(
description: body,
title: issue["title"],
- state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened',
+ state: %w(resolved invalid duplicate wontfix closed).include?(issue["status"]) ? 'closed' : 'opened',
author_id: gl_user_id(project, reporter)
)
end
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
new file mode 100644
index 00000000000..ebe301c1fc7
--- /dev/null
+++ b/lib/tasks/gemojione.rake
@@ -0,0 +1,121 @@
+# This task will generate a standard and Retina sprite of all of the current
+# Gemojione Emojis, with the accompanying SCSS map.
+#
+# It will not appear in `rake -T` output, and the dependent gems are not
+# included in the Gemfile by default, because this task will only be needed
+# occasionally, such as when new Emojis are added to Gemojione.
+
+begin
+ require 'sprite_factory'
+ require 'rmagick'
+rescue LoadError
+ # noop
+end
+
+namespace :gemojione do
+ task sprite: :environment do
+ check_requirements!
+
+ SIZE = 20
+ RETINA = SIZE * 2
+
+ Dir.mktmpdir do |tmpdir|
+ # Copy the Gemojione assets to the temporary folder for resizing
+ FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+
+ Dir.chdir(tmpdir) do
+ Dir["**/*.png"].each do |png|
+ resize!(File.join(tmpdir, png), SIZE)
+ end
+ end
+
+ style_path = Rails.root.join(*%w(app assets stylesheets pages emojis.scss))
+
+ # Combine the resized assets into a packed sprite and re-generate the SCSS
+ SpriteFactory.cssurl = "image-url('$IMAGE')"
+ SpriteFactory.run!(File.join(tmpdir, 'images'), {
+ output_style: style_path,
+ output_image: "app/assets/images/emoji.png",
+ selector: '.emoji-',
+ style: :scss,
+ nocomments: true,
+ pngcrush: true,
+ layout: :packed
+ })
+
+ # SpriteFactory's SCSS is a bit too verbose for our purposes here, so
+ # let's simplify it
+ system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path}))
+ system(%Q(sed -i '' "s/ no-repeat//" #{style_path}))
+
+ # Append a generic rule that applies to all Emojis
+ File.open(style_path, 'a') do |f|
+ f.puts
+ f.puts <<-CSS.strip_heredoc
+ .emoji-icon {
+ background-image: image-url('emoji.png');
+ background-repeat: no-repeat;
+ height: #{SIZE}px;
+ width: #{SIZE}px;
+
+ @media only screen and (-webkit-min-device-pixel-ratio: 2),
+ only screen and (min--moz-device-pixel-ratio: 2),
+ only screen and (-o-min-device-pixel-ratio: 2/1),
+ only screen and (min-device-pixel-ratio: 2),
+ only screen and (min-resolution: 192dpi),
+ only screen and (min-resolution: 2dppx) {
+ background-image: image-url('emoji@2x.png');
+ background-size: 840px 820px;
+ }
+ }
+ CSS
+ end
+ end
+
+ # Now do it again but for Retina
+ Dir.mktmpdir do |tmpdir|
+ # Copy the Gemojione assets to the temporary folder for resizing
+ FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+
+ Dir.chdir(tmpdir) do
+ Dir["**/*.png"].each do |png|
+ resize!(File.join(tmpdir, png), RETINA)
+ end
+ end
+
+ # Combine the resized assets into a packed sprite and re-generate the SCSS
+ SpriteFactory.run!(File.join(tmpdir, 'images'), {
+ output_image: "app/assets/images/emoji@2x.png",
+ style: false,
+ nocomments: true,
+ pngcrush: true,
+ layout: :packed
+ })
+ end
+ end
+
+ def check_requirements!
+ return if defined?(SpriteFactory) && defined?(Magick)
+
+ puts <<-MSG.strip_heredoc
+ This task is disabled by default and should only be run when the Gemojione
+ gem is updated with new Emojis.
+
+ To enable this task, *temporarily* add the following lines to Gemfile and
+ re-bundle:
+
+ gem 'sprite-factory'
+ gem 'rmagick'
+ MSG
+
+ exit 1
+ end
+
+ def resize!(image_path, size)
+ # Resize the image in-place, save it, and free the object
+ image = Magick::Image.read(image_path).first
+ image.resize!(size, size)
+ image.write(image_path) { self.quality = 100 }
+ image.destroy!
+ end
+end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index 3acfc6e2075..01d23b89bb7 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -4,53 +4,59 @@ namespace :spinach do
namespace :project do
desc "GitLab | Spinach | Run project commits, issues and merge requests spinach features"
task :half do
- cmds = [
- %W(rake gitlab:setup),
- %W(spinach --tags @project_commits,@project_issues,@project_merge_requests),
- ]
- run_commands(cmds)
+ run_spinach_tests('@project_commits,@project_issues,@project_merge_requests')
end
desc "GitLab | Spinach | Run remaining project spinach features"
task :rest do
- cmds = [
- %W(rake gitlab:setup),
- %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets,~@project_commits,~@project_issues,~@project_merge_requests),
- ]
- run_commands(cmds)
+ run_spinach_tests('~@admin,~@dashboard,~@profile,~@public,~@snippets,~@project_commits,~@project_issues,~@project_merge_requests')
end
end
desc "GitLab | Spinach | Run project spinach features"
task :project do
- cmds = [
- %W(rake gitlab:setup),
- %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets),
- ]
- run_commands(cmds)
+ run_spinach_tests('~@admin,~@dashboard,~@profile,~@public,~@snippets')
end
desc "GitLab | Spinach | Run other spinach features"
task :other do
- cmds = [
- %W(rake gitlab:setup),
- %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets),
- ]
- run_commands(cmds)
+ run_spinach_tests('@admin,@dashboard,@profile,@public,@snippets')
+ end
+
+ desc "GitLab | Spinach | Run other spinach features"
+ task :builds do
+ run_spinach_tests('@builds')
end
end
desc "GitLab | Run spinach"
task :spinach do
- cmds = [
- %W(rake gitlab:setup),
- %W(spinach),
- ]
- run_commands(cmds)
+ run_spinach_tests(nil)
+end
+
+def run_command(cmd)
+ system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
end
-def run_commands(cmds)
- cmds.each do |cmd|
- system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!")
+def run_spinach_command(args)
+ run_command(%w(spinach -r rerun) + args)
+end
+
+def run_spinach_tests(tags)
+ #run_command(%w(rake gitlab:setup)) or raise('gitlab:setup failed!')
+
+ success = run_spinach_command(%W(--tags #{tags}))
+ 3.times do |_|
+ break if success
+ break unless File.exists?('tmp/spinach-rerun.txt')
+
+ tests = File.foreach('tmp/spinach-rerun.txt').map(&:chomp)
+ puts ''
+ puts "Spinach tests for #{tags}: Retrying tests... #{tests}".red
+ puts ''
+ sleep(3)
+ success = run_spinach_command(tests)
end
+
+ raise("spinach tests for #{tags} failed!") unless success
end
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index b6f076a90c3..4a7ee7dbb64 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,16 +1,30 @@
#!/bin/bash
+retry() {
+ for i in $(seq 1 3); do
+ if eval "$@"; then
+ return 0
+ fi
+ sleep 3s
+ echo "Retrying..."
+ done
+ return 1
+}
+
if [ -f /.dockerinit ]; then
mkdir -p vendor
- if [ ! -e vendor/phantomjs_1.9.8-0jessie_amd64.deb ]; then
+
+ # Install phantomjs package
+ pushd vendor
+ if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then
wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
- mv phantomjs_1.9.8-0jessie_amd64.deb vendor/
fi
- dpkg -i vendor/phantomjs_1.9.8-0jessie_amd64.deb
+ dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
+ popd
- apt-get update -qq
- apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
- libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip
+ # Try to install packages
+ retry 'apt-get update -yqqq; apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
+ libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip'
cp config/database.yml.mysql config/database.yml
sed -i 's/username:.*/username: root/g' config/database.yml
@@ -20,7 +34,7 @@ if [ -f /.dockerinit ]; then
cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml
- export FLAGS=(--path vendor)
+ export FLAGS=(--path vendor --retry 3)
else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index 883bbaedd4e..70ed8f3a62e 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Projects::ForksController do
let(:user) { create(:user) }
- let(:project) { create(:project, visibility_level: Project::PUBLIC) }
+ let(:project) { create(:project, :public) }
let(:forked_project) { Projects::ForkService.new(project, user).execute }
let(:group) { create(:group, owner: forked_project.creator) }
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
index ed072213153..9794772ac7d 100644
--- a/spec/factories/emails.rb
+++ b/spec/factories/emails.rb
@@ -1,14 +1,6 @@
FactoryGirl.define do
factory :email do
user
- email do
- FFaker::Internet.email('alias')
- end
-
- factory :another_email do
- email do
- FFaker::Internet.email('another.alias')
- end
- end
+ email { FFaker::Internet.email('alias') }
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 00de7bb5294..ca1c636fce4 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -69,6 +69,16 @@ FactoryGirl.define do
target_branch "master"
end
+ trait :rebased do
+ source_branch "markdown"
+ target_branch "improve/awesome"
+ end
+
+ trait :diverged do
+ source_branch "feature"
+ target_branch "master"
+ end
+
trait :merge_when_build_succeeds do
merge_when_build_succeeds true
merge_user author
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
index b493a6968ff..0f13b2c1020 100644
--- a/spec/factories/personal_snippets.rb
+++ b/spec/factories/personal_snippets.rb
@@ -1,15 +1,4 @@
FactoryGirl.define do
factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
- trait :public do
- visibility_level PersonalSnippet::PUBLIC
- end
-
- trait :internal do
- visibility_level PersonalSnippet::INTERNAL
- end
-
- trait :private do
- visibility_level PersonalSnippet::PRIVATE
- end
end
end
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index 70fb7595c74..cf3659ba275 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -2,6 +2,26 @@ FactoryGirl.define do
factory :project_member do
user
project
- access_level { ProjectMember::MASTER }
+ master
+
+ trait :guest do
+ access_level ProjectMember::GUEST
+ end
+
+ trait :reporter do
+ access_level ProjectMember::REPORTER
+ end
+
+ trait :developer do
+ access_level ProjectMember::DEVELOPER
+ end
+
+ trait :master do
+ access_level ProjectMember::MASTER
+ end
+
+ trait :owner do
+ access_level ProjectMember::OWNER
+ end
end
end
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
index 154442bd3db..d681a2c8483 100644
--- a/spec/factories/project_snippets.rb
+++ b/spec/factories/project_snippets.rb
@@ -1,9 +1,5 @@
FactoryGirl.define do
- factory :project_snippet do
+ factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
project
- author
- title
- content
- file_name
end
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index f7c285cff3a..9de78d68280 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -1,7 +1,5 @@
FactoryGirl.define do
factory :service do
- type ""
- title "GitLab CI"
project
end
end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index b9127b3d75e..365f12a0c95 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -12,5 +12,17 @@ FactoryGirl.define do
title
content
file_name
+
+ trait :public do
+ visibility_level Snippet::PUBLIC
+ end
+
+ trait :internal do
+ visibility_level Snippet::INTERNAL
+ end
+
+ trait :private do
+ visibility_level Snippet::PRIVATE
+ end
end
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 1b4ffc2d717..7fdc5e5d7aa 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -5,15 +5,14 @@ describe SnippetsFinder do
let(:user1) { create :user }
let(:group) { create :group }
- let(:project1) { create(:empty_project, :public, group: group) }
- let(:project2) { create(:empty_project, :private, group: group) }
-
+ let(:project1) { create(:empty_project, :public, group: group) }
+ let(:project2) { create(:empty_project, :private, group: group) }
context ':all filter' do
before do
- @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE)
- @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL)
- @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC)
+ @snippet1 = create(:personal_snippet, :private)
+ @snippet2 = create(:personal_snippet, :internal)
+ @snippet3 = create(:personal_snippet, :public)
end
it "returns all private and internal snippets" do
@@ -31,9 +30,9 @@ describe SnippetsFinder do
context ':by_user filter' do
before do
- @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user)
- @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user)
- @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user)
+ @snippet1 = create(:personal_snippet, :private, author: user)
+ @snippet2 = create(:personal_snippet, :internal, author: user)
+ @snippet3 = create(:personal_snippet, :public, author: user)
end
it "returns all public and internal snippets" do
@@ -75,9 +74,9 @@ describe SnippetsFinder do
context 'by_project filter' do
before do
- @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1)
- @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1)
- @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1)
+ @snippet1 = create(:project_snippet, :private, project: project1)
+ @snippet2 = create(:project_snippet, :internal, project: project1)
+ @snippet3 = create(:project_snippet, :public, project: project1)
end
it "returns public snippets for unauthorized user" do
@@ -93,7 +92,7 @@ describe SnippetsFinder do
end
it "returns all snippets for project members" do
- project1.team << [user, :developer]
+ project1.team << [user, :developer]
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
expect(snippets).to include(@snippet1, @snippet2, @snippet3)
end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index fe6d42acee2..1772cc3f6a4 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -209,7 +209,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Label by ID: <%= simple_label.to_reference %>
- Label by name: <%= Label.reference_prefix %><%= simple_label.name %>
-- Label by name in quotes: <%= label.to_reference(:name) %>
+- Label by name in quotes: <%= label.to_reference(format: :name) %>
- Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
- Link to label by reference: [Label](<%= label.to_reference %>)
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index aafc24397a9..cd7596a763d 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -58,7 +58,7 @@ describe VisibilityLevelHelper do
describe "skip_level?" do
describe "forks" do
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:project) { create(:project, :internal) }
let(:fork_project) { create(:forked_project_with_submodules) }
before do
@@ -74,7 +74,7 @@ describe VisibilityLevelHelper do
end
describe "non-forked project" do
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:project) { create(:project, :internal) }
it "skips levels" do
expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
@@ -84,7 +84,7 @@ describe VisibilityLevelHelper do
end
describe "Snippet" do
- let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:snippet) { create(:snippet, :internal) }
it "skips levels" do
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index b46ccc47605..e2d21f53b7e 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -111,7 +111,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
context 'String-based multi-word references in quotes' do
let(:label) { create(:label, name: 'gfm references', project: project) }
- let(:reference) { label.to_reference(:name) }
+ let(:reference) { label.to_reference(format: :name) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
@@ -176,4 +176,29 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
expect(result[:references][:label]).to eq [label]
end
end
+
+ describe 'cross project label references' do
+ let(:another_project) { create(:empty_project, :public) }
+ let(:project_name) { another_project.name_with_namespace }
+ let(:label) { create(:label, project: another_project, color: '#00ff00') }
+ let(:reference) { label.to_reference(project) }
+
+ let!(:result) { reference_filter("See #{reference}") }
+
+ it 'points to referenced project issues page' do
+ expect(result.css('a').first.attr('href'))
+ .to eq urls.namespace_project_issues_url(another_project.namespace,
+ another_project,
+ label_name: label.name)
+ end
+
+ it 'has valid color' do
+ expect(result.css('a span').first.attr('style'))
+ .to match /background-color: #00ff00/
+ end
+
+ it 'contains cross project content' do
+ expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
+ end
+ end
end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
new file mode 100644
index 00000000000..c413132abe5
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Gitlab::BitbucketImport::Importer, lib: true do
+ before do
+ Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket")
+ end
+
+ let(:statuses) do
+ [
+ "open",
+ "resolved",
+ "on hold",
+ "invalid",
+ "duplicate",
+ "wontfix",
+ "closed" # undocumented status
+ ]
+ end
+ let(:sample_issues_statuses) do
+ issues = []
+
+ statuses.map.with_index do |status, index|
+ issues << {
+ local_id: index,
+ status: status,
+ title: "Issue #{index}",
+ content: "Some content to issue #{index}"
+ }
+ end
+
+ issues
+ end
+
+ let(:project_identifier) { 'namespace/repo' }
+ let(:data) do
+ {
+ bb_session: {
+ bitbucket_access_token: "123456",
+ bitbucket_access_token_secret: "secret"
+ }
+ }
+ end
+ let(:project) do
+ create(
+ :project,
+ import_source: project_identifier,
+ import_data: ProjectImportData.new(data: data)
+ )
+ end
+ let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
+ let(:issues_statuses_sample_data) do
+ {
+ count: sample_issues_statuses.count,
+ issues: sample_issues_statuses
+ }
+ end
+
+ context 'issues statuses' do
+ before do
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}"
+ ).to_return(status: 200, body: { has_issues: true }.to_json)
+
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues?limit=50&sort=utc_created_on&start=0"
+ ).to_return(status: 200, body: issues_statuses_sample_data.to_json)
+
+ sample_issues_statuses.each_with_index do |issue, index|
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues/#{issue[:local_id]}/comments"
+ ).to_return(
+ status: 200,
+ body: [{ author_info: { username: "username" }, utc_created_on: index }].to_json
+ )
+ end
+ end
+
+ it 'map statuses to open or closed' do
+ importer.execute
+
+ expect(project.issues.where(state: "closed").size).to eq(5)
+ expect(project.issues.where(state: "opened").size).to eq(2)
+ end
+ end
+end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 696fbf7e0aa..0614ca1e7c9 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -59,18 +59,42 @@ describe Label, models: true do
context 'using id' do
it 'returns a String reference to the object' do
expect(label.to_reference).to eq "~#{label.id}"
- expect(label.to_reference(double('project'))).to eq "~#{label.id}"
end
end
context 'using name' do
it 'returns a String reference to the object' do
- expect(label.to_reference(:name)).to eq %(~"#{label.name}")
+ expect(label.to_reference(format: :name)).to eq %(~"#{label.name}")
end
it 'uses id when name contains double quote' do
label = create(:label, name: %q{"irony"})
- expect(label.to_reference(:name)).to eq "~#{label.id}"
+ expect(label.to_reference(format: :name)).to eq "~#{label.id}"
+ end
+ end
+
+ context 'using invalid format' do
+ it 'raises error' do
+ expect { label.to_reference(format: :invalid) }
+ .to raise_error StandardError, /Unknown format/
+ end
+ end
+
+ context 'cross project reference' do
+ let(:project) { create(:project) }
+
+ context 'using name' do
+ it 'returns cross reference with label name' do
+ expect(label.to_reference(project, format: :name))
+ .to eq %Q(#{label.project.to_reference}~"#{label.name}")
+ end
+ end
+
+ context 'using id' do
+ it 'returns cross reference with label id' do
+ expect(label.to_reference(project, format: :id))
+ .to eq %Q(#{label.project.to_reference}~#{label.id})
+ end
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c51f34034d7..59c40922abb 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -274,6 +274,70 @@ describe MergeRequest, models: true do
end
end
+ describe '#diverged_commits_count' do
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+
+ context 'diverged on same repository' do
+ subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(5)
+ end
+ end
+
+ context 'diverged on fork' do
+ subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(5)
+ end
+ end
+
+ context 'rebased on fork' do
+ subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: fork_project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(0)
+ end
+ end
+
+ describe 'caching' do
+ before(:example) do
+ allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
+ end
+
+ it 'caches the output' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ once.
+ and_return(2)
+
+ subject.diverged_commits_count
+ subject.diverged_commits_count
+ end
+
+ it 'invalidates the cache when the source sha changes' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ twice.
+ and_return(2)
+
+ subject.diverged_commits_count
+ allow(subject).to receive(:source_sha).and_return('123abc')
+ subject.diverged_commits_count
+ end
+
+ it 'invalidates the cache when the target sha changes' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ twice.
+ and_return(2)
+
+ subject.diverged_commits_count
+ allow(subject).to receive(:target_sha).and_return('123abc')
+ subject.diverged_commits_count
+ end
+ end
+ end
+
it_behaves_like 'an editable mentionable' do
subject { create(:merge_request) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 1b1380ce4e2..28f13100d15 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -60,7 +60,7 @@ describe Milestone, models: true do
end
it "should recover from dividing by zero" do
- expect(milestone.issues).to receive(:count).and_return(0)
+ expect(milestone.issues).to receive(:size).and_return(0)
expect(milestone.percent_complete).to eq(0)
end
end
@@ -114,7 +114,6 @@ describe Milestone, models: true do
end
it { expect(milestone.closed_items_count).to eq(1) }
- it { expect(milestone.open_items_count).to eq(2) }
it { expect(milestone.total_items_count).to eq(3) }
it { expect(milestone.is_empty?).to be_falsey }
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 583937ca748..33085dac4ea 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -24,7 +24,7 @@ require 'spec_helper'
describe Note, models: true do
describe 'associations' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:noteable) }
+ it { is_expected.to belong_to(:noteable).touch(true) }
it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to have_many(:todos).dependent(:destroy) }
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index cc92eb0bd9f..e0feb606f78 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f9842d23afa..2fa38a5d3d3 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -68,6 +68,7 @@ describe Project, models: true do
it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'modules' do
@@ -561,7 +562,7 @@ describe Project, models: true do
end
describe '#visibility_level_allowed?' do
- let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:project) { create(:project, :internal) }
context 'when checking on non-forked project' do
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 1c7d66398cb..34866be3395 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -148,6 +148,12 @@ describe Repository, models: true do
expect(branch.name).to eq('new_feature')
end
+
+ it 'calls the after_create_branch hook' do
+ expect(repository).to receive(:after_create_branch)
+
+ repository.add_branch(user, 'new_feature', 'master')
+ end
end
context 'when pre hooks failed' do
@@ -405,7 +411,7 @@ describe Repository, models: true do
end
end
- describe '#expire_branch_ache' do
+ describe '#expire_branch_cache' do
# This method is private but we need it for testing purposes. Sadly there's
# no other proper way of testing caching operations.
let(:cache) { repository.send(:cache) }
@@ -556,11 +562,12 @@ describe Repository, models: true do
end
end
- describe '#before_create_tag' do
+ describe '#before_push_tag' do
it 'flushes the cache' do
expect(repository).to receive(:expire_cache)
+ expect(repository).to receive(:expire_tag_count_cache)
- repository.before_create_tag
+ repository.before_push_tag
end
end
@@ -595,4 +602,89 @@ describe Repository, models: true do
repository.after_remove_branch
end
end
+
+ describe "#main_language" do
+ it 'shows the main language of the project' do
+ expect(repository.main_language).to eq("Ruby")
+ end
+
+ it 'returns nil when the repository is empty' do
+ allow(repository).to receive(:empty?).and_return(true)
+
+ expect(repository.main_language).to be_nil
+ end
+ end
+
+ describe '#before_remove_tag' do
+ it 'flushes the tag cache' do
+ expect(repository).to receive(:expire_tag_count_cache)
+
+ repository.before_remove_tag
+ end
+ end
+
+ describe '#branch_count' do
+ it 'returns the number of branches' do
+ expect(repository.branch_count).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '#tag_count' do
+ it 'returns the number of tags' do
+ expect(repository.tag_count).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '#expire_branch_count_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:branch_count)
+
+ repository.expire_branch_count_cache
+ end
+ end
+
+ describe '#expire_tag_count_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:tag_count)
+
+ repository.expire_tag_count_cache
+ end
+ end
+
+ describe '#add_tag' do
+ it 'adds a tag' do
+ expect(repository).to receive(:before_push_tag)
+
+ expect_any_instance_of(Gitlab::Shell).to receive(:add_tag).
+ with(repository.path_with_namespace, '8.5', 'master', 'foo')
+
+ repository.add_tag('8.5', 'master', 'foo')
+ end
+ end
+
+ describe '#rm_branch' do
+ let(:user) { create(:user) }
+
+ it 'removes a branch' do
+ expect(repository).to receive(:before_remove_branch)
+ expect(repository).to receive(:after_remove_branch)
+
+ repository.rm_branch(user, 'feature')
+ end
+ end
+
+ describe '#rm_tag' do
+ it 'removes a tag' do
+ expect(repository).to receive(:before_remove_tag)
+
+ expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+ with(repository.path_with_namespace, '8.5')
+
+ repository.rm_tag('8.5')
+ end
+ end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index eb2dbbdc5a4..7e5b5499aea 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 36461e84c3a..55582aa53d2 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -7,8 +7,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:branch_name) { 'feature' }
let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 175ee861a71..967c34800d0 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -7,8 +7,8 @@ describe API::API, api: true do
let(:api_user) { user }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:developer) { create(:project_member, user: user, project: project, access_level: ProjectMember::DEVELOPER) }
- let!(:reporter) { create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER) }
+ let!(:developer) { create(:project_member, :developer, user: user, project: project) }
+ let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
let(:commit) { create(:ci_commit, project: project)}
let(:build) { create(:ci_build, commit: commit) }
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
index 8017ed97d88..429a24109fd 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_status_spec.rb
@@ -6,9 +6,9 @@ describe API::CommitStatus, api: true do
let!(:project) { create(:project) }
let(:commit) { project.repository.commit }
let(:commit_status) { create(:commit_status, commit: ci_commit) }
- let(:guest) { create_user(ProjectMember::GUEST) }
- let(:reporter) { create_user(ProjectMember::REPORTER) }
- let(:developer) { create_user(ProjectMember::DEVELOPER) }
+ let(:guest) { create_user(:guest) }
+ let(:reporter) { create_user(:reporter) }
+ let(:developer) { create_user(:developer) }
let(:sha) { commit.id }
@@ -201,9 +201,9 @@ describe API::CommitStatus, api: true do
end
end
- def create_user(access_level)
+ def create_user(access_level_trait)
user = create(:user)
- create(:project_member, user: user, project: project, access_level: access_level)
+ create(:project_member, access_level_trait, user: user, project: project)
user
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 49acc3368f4..7ff21175c1b 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index 3fe7efff5ba..fa94e03ec32 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -12,7 +12,7 @@ describe API::API, api: true do
end
let(:project_user2) do
- create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST)
+ create(:project_member, :guest, user: user2, project: project)
end
describe 'POST /projects/fork/:id' do
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
index 6358f6a2a4a..4301588b16a 100644
--- a/spec/requests/api/project_members_spec.rb
+++ b/spec/requests/api/project_members_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
describe "GET /projects/:id/members" do
before { project_member }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 2a310f3834d..9f2365a4832 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -12,8 +12,8 @@ describe API::API, api: true do
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
let(:project3) do
create(:project,
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 0ae63b0afec..7cf4a01d76b 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -9,8 +9,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tree" do
context "authorized user" do
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 78484747d6a..3af61d4b335 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -28,9 +28,9 @@ describe API::Runners, api: true do
before do
# Set project access for users
- create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER)
- create(:project_member, user: user, project: project2, access_level: ProjectMember::MASTER)
- create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER)
+ create(:project_member, :master, user: user, project: project)
+ create(:project_member, :master, user: user, project: project2)
+ create(:project_member, :reporter, user: user2, project: project)
end
describe 'GET /runners' do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index f966e38cd3e..a15be07ed57 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -8,8 +8,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tags" do
let(:tag_name) { project.repository.tag_names.sort.reverse.first }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 2a86b60bc4d..0510b77a39b 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -8,8 +8,8 @@ describe API::API do
let!(:trigger_token) { 'secure_token' }
let!(:trigger_token_2) { 'secure_token_2' }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::DEVELOPER) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b82c5c7685f..96e8c8c51f8 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -47,6 +47,8 @@ describe API::API, api: true do
expect(json_response.first.keys).to include 'identities'
expect(json_response.first.keys).to include 'can_create_project'
expect(json_response.first.keys).to include 'two_factor_enabled'
+ expect(json_response.first.keys).to include 'last_sign_in_at'
+ expect(json_response.first.keys).to include 'confirmed_at'
end
end
end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 9744729ba0c..b1e1053d037 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::DEVELOPER) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:variable) { create(:ci_variable, project: project) }
describe 'GET /projects/:id/variables' do
diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/delete_tag_service_spec.rb
new file mode 100644
index 00000000000..5b7ba521812
--- /dev/null
+++ b/spec/services/delete_tag_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DeleteTagService, services: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ let(:tag) { double(:tag, name: '8.5', target: 'abc123') }
+
+ describe '#execute' do
+ before do
+ allow(repository).to receive(:find_tag).and_return(tag)
+ end
+
+ it 'removes the tag' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+ and_return(true)
+
+ expect(repository).to receive(:before_remove_tag)
+ expect(service).to receive(:success)
+
+ service.execute('8.5')
+ end
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 994585fb32c..82813cee227 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -155,6 +155,23 @@ describe GitPushService, services: true do
end
end
+ describe "Updates main language" do
+
+ context "before push" do
+ it { expect(project.main_language).to eq(nil) }
+ end
+
+ context "after push" do
+ before do
+ @service = execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it { expect(@service.update_main_language).to eq(true) }
+ it { expect(project.main_language).to eq("Ruby") }
+ end
+ end
+
+
describe "Web Hooks" do
context "execute web hooks" do
it "when pushing a branch for the first time" do
@@ -254,22 +271,24 @@ describe GitPushService, services: true do
allow(project.repository).to receive(:commits_between).
and_return([closing_commit])
+
+ project.team << [commit_author, :master]
end
context "to default branches" do
it "closes issues" do
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_closed
end
it "adds a note indicating that the issue is now closed" do
expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
end
it "doesn't create additional cross-reference notes" do
expect(SystemNoteService).not_to receive(:cross_reference)
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
end
it "doesn't close issues when external issue tracker is in use" do
@@ -277,7 +296,7 @@ describe GitPushService, services: true do
# The push still shouldn't create cross-reference notes.
expect do
- execute_service(project, user, @oldrev, @newrev, 'refs/heads/hurf' )
+ execute_service(project, commit_author, @oldrev, @newrev, 'refs/heads/hurf' )
end.not_to change { Note.where(project_id: project.id, system: true).count }
end
end
@@ -299,7 +318,6 @@ describe GitPushService, services: true do
end
end
- # EE-only tests
context "for jira issue tracker" do
include JiraServiceHelper
@@ -349,7 +367,7 @@ describe GitPushService, services: true do
}
}.to_json
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
body: transition_body
).once
@@ -360,7 +378,7 @@ describe GitPushService, services: true do
body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
}.to_json
- execute_service(project, user, @oldrev, @newrev, @ref )
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: comment_body
).once
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 3c06a890163..e8b9e6b9238 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -102,8 +102,8 @@ describe Projects::UpdateService, services: true do
describe :visibility_level do
let(:user) { create :user, admin: true }
- let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
- let(:forked_project) { create :forked_project_with_submodules, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:project) { create(:project, :internal) }
+ let(:forked_project) { create(:forked_project_with_submodules, :internal) }
let(:opts) { {} }
before do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8f381f46e57..159fb964171 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -15,6 +15,7 @@ require 'rspec/rails'
require 'shoulda/matchers'
require 'sidekiq/testing/inline'
require 'benchmark/ips'
+require 'rspec/retry'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
@@ -25,6 +26,9 @@ RSpec.configure do |config|
config.use_instantiated_fixtures = false
config.mock_with :rspec
+ config.verbose_retry = true
+ config.display_try_failure_messages = true
+
config.include Devise::TestHelpers, type: :controller
config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request