summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Vosmaer <contact@jacobvosmaer.nl>2015-01-08 10:57:08 +0100
committerJacob Vosmaer <contact@jacobvosmaer.nl>2015-01-08 10:57:08 +0100
commitdec168932e87e80d1763931df30ecc0300bbc7e2 (patch)
treefa6fe8fa9f12d37e8112019033d9c612e4fbaab2
parentaf56c1dd323ee418eb8dbfa9eb35c7ec9ac58a66 (diff)
parentd02a22ba21f91d2aa4f9cf716dc3aefcf7e7495e (diff)
downloadgitlab-ce-dec168932e87e80d1763931df30ecc0300bbc7e2.tar.gz
Merge remote-tracking branch 'dev_gitlab_org/master' into git-http-blacklist
Conflicts: CHANGELOG
-rw-r--r--.gitignore56
-rw-r--r--.ruby-version2
-rw-r--r--CHANGELOG43
-rw-r--r--CONTRIBUTING.md11
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock37
-rw-r--r--PROCESS.md7
-rw-r--r--Procfile2
-rw-r--r--README.md51
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/activities.js.coffee2
-rw-r--r--app/assets/javascripts/api.js.coffee29
-rw-r--r--app/assets/javascripts/application.js.coffee6
-rw-r--r--app/assets/javascripts/groups_select.js.coffee41
-rw-r--r--app/assets/javascripts/issue.js.coffee4
-rw-r--r--app/assets/javascripts/merge_request.js.coffee7
-rw-r--r--app/assets/javascripts/notes.js.coffee2
-rw-r--r--app/assets/javascripts/protected_branches.js.coffee21
-rw-r--r--app/assets/javascripts/stat_graph_contributors_graph.js.coffee4
-rw-r--r--app/assets/stylesheets/generic/common.scss38
-rw-r--r--app/assets/stylesheets/generic/forms.scss10
-rw-r--r--app/assets/stylesheets/generic/issue_box.scss122
-rw-r--r--app/assets/stylesheets/generic/markdown_area.scss1
-rw-r--r--app/assets/stylesheets/generic/selects.scss12
-rw-r--r--app/assets/stylesheets/generic/tables.scss20
-rw-r--r--app/assets/stylesheets/generic/timeline.scss36
-rw-r--r--app/assets/stylesheets/gl_bootstrap.scss4
-rw-r--r--app/assets/stylesheets/main/layout.scss4
-rw-r--r--app/assets/stylesheets/main/variables.scss4
-rw-r--r--app/assets/stylesheets/sections/dashboard.scss14
-rw-r--r--app/assets/stylesheets/sections/events.scss40
-rw-r--r--app/assets/stylesheets/sections/header.scss26
-rw-r--r--app/assets/stylesheets/sections/issues.scss4
-rw-r--r--app/assets/stylesheets/sections/login.scss89
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss47
-rw-r--r--app/assets/stylesheets/sections/nav.scss96
-rw-r--r--app/assets/stylesheets/sections/notes.scss18
-rw-r--r--app/assets/stylesheets/sections/projects.scss7
-rw-r--r--app/assets/stylesheets/sections/sidebar.scss161
-rw-r--r--app/assets/stylesheets/sections/tree.scss13
-rw-r--r--app/assets/stylesheets/sections/votes.scss10
-rw-r--r--app/assets/stylesheets/themes/ui_basic.scss12
-rw-r--r--app/assets/stylesheets/themes/ui_color.scss5
-rw-r--r--app/assets/stylesheets/themes/ui_gray.scss5
-rw-r--r--app/assets/stylesheets/themes/ui_mars.scss5
-rw-r--r--app/assets/stylesheets/themes/ui_modern.scss5
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/admin/keys_controller.rb34
-rw-r--r--app/controllers/admin/users_controller.rb3
-rw-r--r--app/controllers/application_controller.rb59
-rw-r--r--app/controllers/dashboard_controller.rb12
-rw-r--r--app/controllers/groups_controller.rb20
-rw-r--r--app/controllers/oauth/applications_controller.rb41
-rw-r--r--app/controllers/oauth/authorizations_controller.rb57
-rw-r--r--app/controllers/oauth/authorized_applications_controller.rb8
-rw-r--r--app/controllers/profiles_controller.rb5
-rw-r--r--app/controllers/projects/application_controller.rb27
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/milestones_controller.rb2
-rw-r--r--app/controllers/projects/protected_branches_controller.rb20
-rw-r--r--app/controllers/projects/services_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb3
-rw-r--r--app/helpers/application_helper.rb39
-rw-r--r--app/helpers/dashboard_helper.rb54
-rw-r--r--app/helpers/diff_helper.rb18
-rw-r--r--app/helpers/events_helper.rb13
-rw-r--r--app/helpers/groups_helper.rb18
-rw-r--r--app/helpers/milestones_helper.rb9
-rw-r--r--app/helpers/notes_helper.rb7
-rw-r--r--app/helpers/projects_helper.rb42
-rw-r--r--app/helpers/selects_helper.rb9
-rw-r--r--app/helpers/tab_helper.rb52
-rw-r--r--app/helpers/tree_helper.rb38
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/notification.rb10
-rw-r--r--app/models/project.rb13
-rw-r--r--app/models/project_services/hipchat_service.rb4
-rw-r--r--app/models/project_services/slack_message.rb16
-rw-r--r--app/models/project_services/teamcity_service.rb116
-rw-r--r--app/models/user.rb5
-rw-r--r--app/services/merge_requests/auto_merge_service.rb7
-rw-r--r--app/services/merge_requests/base_merge_service.rb13
-rw-r--r--app/services/merge_requests/build_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb7
-rw-r--r--app/services/merge_requests/refresh_service.rb4
-rw-r--r--app/services/notes/update_service.rb25
-rw-r--r--app/services/notification_service.rb33
-rw-r--r--app/services/oauth2/access_token_validation_service.rb41
-rw-r--r--app/services/projects/create_service.rb17
-rw-r--r--app/views/admin/groups/_form.html.haml11
-rw-r--r--app/views/admin/keys/show.html.haml1
-rw-r--r--app/views/admin/users/show.html.haml4
-rw-r--r--app/views/dashboard/_groups.html.haml11
-rw-r--r--app/views/dashboard/_projects.html.haml11
-rw-r--r--app/views/dashboard/_projects_filter.html.haml145
-rw-r--r--app/views/dashboard/_sidebar.html.haml9
-rw-r--r--app/views/dashboard/_zero_authorized_projects.html.haml6
-rw-r--r--app/views/dashboard/issues.atom.builder2
-rw-r--r--app/views/dashboard/issues.html.haml10
-rw-r--r--app/views/dashboard/merge_requests.html.haml10
-rw-r--r--app/views/dashboard/projects.html.haml104
-rw-r--r--app/views/dashboard/show.atom.builder2
-rw-r--r--[-rwxr-xr-x]app/views/devise/confirmations/new.html.haml0
-rw-r--r--[-rwxr-xr-x]app/views/devise/passwords/new.html.haml0
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml1
-rw-r--r--app/views/devise/sessions/new.html.haml52
-rw-r--r--app/views/devise/shared/_oauth_box.html.haml (renamed from app/views/devise/sessions/_oauth_providers.html.haml)4
-rw-r--r--app/views/devise/shared/_signin_box.html.haml25
-rw-r--r--app/views/devise/shared/_signup_box.html.haml17
-rw-r--r--app/views/doorkeeper/applications/_delete_form.html.haml4
-rw-r--r--app/views/doorkeeper/applications/_form.html.haml24
-rw-r--r--app/views/doorkeeper/applications/edit.html.haml2
-rw-r--r--app/views/doorkeeper/applications/index.html.haml16
-rw-r--r--app/views/doorkeeper/applications/new.html.haml2
-rw-r--r--app/views/doorkeeper/applications/show.html.haml26
-rw-r--r--app/views/doorkeeper/authorizations/error.html.haml3
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml28
-rw-r--r--app/views/doorkeeper/authorizations/show.html.haml3
-rw-r--r--app/views/doorkeeper/authorized_applications/_delete_form.html.haml4
-rw-r--r--app/views/doorkeeper/authorized_applications/index.html.haml16
-rw-r--r--app/views/groups/_filter.html.haml12
-rw-r--r--app/views/groups/_settings_nav.html.haml9
-rw-r--r--app/views/groups/edit.html.haml70
-rw-r--r--app/views/groups/issues.html.haml10
-rw-r--r--app/views/groups/merge_requests.html.haml10
-rw-r--r--app/views/groups/milestones/index.html.haml72
-rw-r--r--app/views/groups/milestones/show.html.haml70
-rw-r--r--app/views/groups/projects.html.haml52
-rw-r--r--app/views/groups/show.atom.builder2
-rw-r--r--app/views/groups/show.html.haml57
-rw-r--r--app/views/layouts/_broadcast.html.haml4
-rw-r--r--app/views/layouts/_head_panel.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml16
-rw-r--r--app/views/layouts/_public_head_panel.html.haml14
-rw-r--r--app/views/layouts/admin.html.haml10
-rw-r--r--app/views/layouts/application.html.haml9
-rw-r--r--app/views/layouts/devise.html.haml46
-rw-r--r--app/views/layouts/doorkeeper/admin.html.haml22
-rw-r--r--app/views/layouts/doorkeeper/application.html.haml15
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/explore.html.haml2
-rw-r--r--app/views/layouts/group.html.haml11
-rw-r--r--app/views/layouts/nav/_admin.html.haml41
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml31
-rw-r--r--app/views/layouts/nav/_group.html.haml47
-rw-r--r--app/views/layouts/nav/_profile.html.haml54
-rw-r--r--app/views/layouts/nav/_project.html.haml63
-rw-r--r--app/views/layouts/navless.html.haml2
-rw-r--r--app/views/layouts/profile.html.haml9
-rw-r--r--app/views/layouts/project_settings.html.haml16
-rw-r--r--app/views/layouts/projects.html.haml12
-rw-r--r--app/views/layouts/public_group.html.haml7
-rw-r--r--app/views/layouts/public_projects.html.haml7
-rw-r--r--app/views/layouts/public_users.html.haml5
-rw-r--r--app/views/layouts/search.html.haml2
-rw-r--r--app/views/notify/repository_push_email.html.haml4
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/profiles/applications.html.haml47
-rw-r--r--app/views/profiles/keys/_key.html.haml21
-rw-r--r--app/views/profiles/keys/_key_details.html.haml22
-rw-r--r--app/views/profiles/keys/_key_table.html.haml19
-rw-r--r--app/views/profiles/keys/index.html.haml14
-rw-r--r--app/views/profiles/keys/show.html.haml23
-rw-r--r--app/views/profiles/notifications/show.html.haml7
-rw-r--r--app/views/profiles/update.js.erb4
-rw-r--r--app/views/projects/_issuable_filter.html.haml72
-rw-r--r--app/views/projects/_issuable_form.html.haml13
-rw-r--r--app/views/projects/_issues_nav.html.haml11
-rw-r--r--app/views/projects/_settings_nav.html.haml20
-rw-r--r--app/views/projects/blob/_actions.html.haml8
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/commits/_commits.html.haml2
-rw-r--r--app/views/projects/commits/show.html.haml4
-rw-r--r--app/views/projects/diffs/_diffs.html.haml12
-rw-r--r--app/views/projects/diffs/_file.html.haml8
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/issues/_discussion.html.haml37
-rw-r--r--app/views/projects/issues/_form.html.haml6
-rw-r--r--app/views/projects/issues/_issue_context.html.haml43
-rw-r--r--app/views/projects/issues/_issues.html.haml2
-rw-r--r--app/views/projects/issues/index.html.haml11
-rw-r--r--app/views/projects/issues/show.html.haml74
-rw-r--r--app/views/projects/issues/update.js.haml2
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml31
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml147
-rw-r--r--app/views/projects/merge_requests/_show.html.haml57
-rw-r--r--app/views/projects/merge_requests/index.html.haml30
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml31
-rw-r--r--app/views/projects/merge_requests/show/_context.html.haml41
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml35
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml26
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml49
-rw-r--r--app/views/projects/merge_requests/show/_participants.html.haml5
-rw-r--r--app/views/projects/merge_requests/update.js.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml32
-rw-r--r--app/views/projects/milestones/show.html.haml62
-rw-r--r--app/views/projects/new.html.haml25
-rw-r--r--app/views/projects/notes/_note.html.haml11
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml34
-rw-r--r--app/views/projects/protected_branches/index.html.haml31
-rw-r--r--app/views/projects/show.html.haml3
-rw-r--r--app/views/projects/transfer.js.haml7
-rw-r--r--app/views/projects/wikis/_form.html.haml12
-rw-r--r--app/views/shared/_event_filter.html.haml16
-rw-r--r--app/views/shared/_filter.html.haml50
-rw-r--r--app/views/shared/_group_form.html.haml25
-rw-r--r--app/views/shared/_issuable_filter.html.haml112
-rw-r--r--app/views/shared/_milestones_filter.html.haml16
-rw-r--r--app/views/shared/_no_ssh.html.haml20
-rw-r--r--app/views/shared/_outdated_browser.html.haml8
-rw-r--r--app/views/shared/_project_filter.html.haml64
-rw-r--r--app/views/shared/_sort_dropdown.html.haml12
-rw-r--r--app/views/users/show.atom.builder2
-rw-r--r--app/workers/project_service_worker.rb1
-rw-r--r--app/workers/project_web_hook_worker.rb3
-rw-r--r--config/initializers/6_rack_profiler.rb2
-rw-r--r--config/initializers/doorkeeper.rb91
-rw-r--r--config/locales/doorkeeper.en.yml73
-rw-r--r--config/routes.rb13
-rw-r--r--db/fixtures/development/05_users.rb15
-rw-r--r--db/migrate/20141121161704_add_identity_table.rb8
-rw-r--r--db/migrate/20141216155758_create_doorkeeper_tables.rb42
-rw-r--r--db/migrate/20141217125223_add_owner_to_application.rb7
-rw-r--r--db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb5
-rw-r--r--db/schema.rb52
-rw-r--r--doc/README.md1
-rw-r--r--doc/api/groups.md2
-rw-r--r--doc/api/notes.md47
-rw-r--r--doc/api/projects.md10
-rw-r--r--doc/install/installation.md18
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--doc/operations/README.md4
-rw-r--r--doc/operations/cleaning_up_redis_sessions.md52
-rw-r--r--doc/operations/sidekiq_memory_killer.md38
-rw-r--r--doc/permissions/permissions.md6
-rw-r--r--doc/project_services/project_services.md1
-rw-r--r--doc/raketasks/README.md3
-rw-r--r--doc/release/monthly.md31
-rw-r--r--doc/update/6.x-or-7.x-to-7.6.md (renamed from doc/update/6.x-or-7.x-to-7.5.md)22
-rw-r--r--doc/update/7.5-to-7.6.md6
-rw-r--r--doc/update/mysql_to_postgresql.md4
-rw-r--r--doc/update/upgrader.md2
-rw-r--r--doc/workflow/README.md3
-rw-r--r--doc/workflow/gitlab_flow.md42
-rw-r--r--doc/workflow/protected_branches.md33
-rw-r--r--doc/workflow/protected_branches/protected_branches1.pngbin0 -> 170113 bytes
-rw-r--r--doc/workflow/protected_branches/protected_branches2.pngbin0 -> 25851 bytes
-rw-r--r--docker/Dockerfile8
-rw-r--r--features/admin/users.feature10
-rw-r--r--features/explore/groups.feature4
-rw-r--r--features/profile/profile.feature16
-rw-r--r--features/project/service.feature7
-rw-r--r--features/project/source/browse_files.feature10
-rw-r--r--features/steps/admin/groups.rb2
-rw-r--r--features/steps/admin/users.rb32
-rw-r--r--features/steps/dashboard/issues.rb14
-rw-r--r--features/steps/dashboard/merge_requests.rb14
-rw-r--r--features/steps/groups.rb7
-rw-r--r--features/steps/profile/profile.rb50
-rw-r--r--features/steps/profile/ssh_keys.rb4
-rw-r--r--features/steps/project/commits/commits.rb6
-rw-r--r--features/steps/project/create.rb2
-rw-r--r--features/steps/project/merge_requests.rb20
-rw-r--r--features/steps/project/services.rb20
-rw-r--r--features/steps/project/source/browse_files.rb8
-rw-r--r--features/steps/shared/active_tab.rb8
-rw-r--r--features/steps/shared/issuable.rb2
-rw-r--r--features/steps/shared/paths.rb19
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/api_guard.rb175
-rw-r--r--lib/api/branches.rb9
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/files.rb4
-rw-r--r--lib/api/groups.rb17
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/milestones.rb4
-rw-r--r--lib/api/notes.rb35
-rw-r--r--lib/api/project_hooks.rb4
-rw-r--r--lib/api/project_members.rb2
-rw-r--r--lib/api/projects.rb45
-rw-r--r--lib/api/repositories.rb2
-rw-r--r--lib/gitlab/git_access.rb30
-rw-r--r--lib/gitlab/theme.rb14
-rw-r--r--lib/tasks/gitlab/check.rake8
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/atom/users_spec.rb9
-rw-r--r--spec/lib/gitlab/git_access_spec.rb24
-rw-r--r--spec/requests/api/api_helpers_spec.rb1
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb31
-rw-r--r--spec/requests/api/fork_spec.rb4
-rw-r--r--spec/requests/api/groups_spec.rb3
-rw-r--r--spec/requests/api/notes_spec.rb54
-rw-r--r--spec/requests/api/projects_spec.rb6
-rw-r--r--spec/routing/project_routing_spec.rb20
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb8
-rw-r--r--spec/services/notification_service_spec.rb16
-rw-r--r--[-rwxr-xr-x]vendor/assets/javascripts/chart-lib.min.js0
-rw-r--r--vendor/plugins/.gitkeep0
305 files changed, 4142 insertions, 2226 deletions
diff --git a/.gitignore b/.gitignore
index 2c6b65b7b7d..7a7b5c93936 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,42 +1,42 @@
+*.log
+*.swp
+.DS_Store
.bundle
+.chef
+.directory
+.envrc
+.gitlab_shell_secret
+.idea
+.rbenv-version
.rbx/
-db/*.sqlite3
-db/*.sqlite3-journal
-log/*.log*
-tmp/
-.sass-cache/
-coverage/*
-backups/*
-*.swp
-public/uploads/
-.ruby-version
.ruby-gemset
+.ruby-version
.rvmrc
-.rbenv-version
-.directory
-nohup.out
-Vagrantfile
+.sass-cache/
+.secret
.vagrant
-config/gitlab.yml
+Vagrantfile
+backups/*
+config/aws.yml
config/database.yml
+config/gitlab.yml
config/initializers/omniauth.rb
config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb
-config/unicorn.rb
config/resque.yml
-config/aws.yml
+config/unicorn.rb
+coverage/*
+db/*.sqlite3
+db/*.sqlite3-journal
db/data.yml
-.idea
-.DS_Store
-.chef
-vendor/bundle/*
-rails_best_practices_output.html
doc/code/*
-.secret
-*.log
-public/uploads.*
-public/assets/
-.envrc
dump.rdb
+log/*.log*
+nohup.out
+public/assets/
+public/uploads.*
+public/uploads/
+rails_best_practices_output.html
tags
-.gitlab_shell_secret
+tmp/
+vendor/bundle/*
diff --git a/.ruby-version b/.ruby-version
index ac2cdeba013..cd57a8b95d6 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.1.3
+2.1.5
diff --git a/CHANGELOG b/CHANGELOG
index e4d180359b7..945df335e52 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,23 +1,40 @@
v 7.7.0
+ -
+ -
+ - Add Jetbrains Teamcity CI service (Jason Lippert)
+ -
+ -
+ - Mention notification level
+ - Markdown preview in wiki (Yuriy Glukhov)
+ - Raise group avatar filesize limit to 200kb
+ - OAuth applications feature
+ - Show user SSH keys in admin area
+ - Developer can push to protected branches option
+ - Set project path instead of project name in create form
- Block Git HTTP access after 10 failed authentication attempts
+ -
+ -
+ - Updates to the messages returned by API (sponsored by O'Reilly Media)
+ - New UI layout with side navigation
+ -
+ -
+ -
+ - Add alert message in case of outdated browser (IE < 10)
+ -
+ - Added API support for sorting projects
+ - Update gitlab_git to version 7.0.0.rc13
v 7.6.0
- Fork repository to groups
- New rugged version
- Add CRON=1 backup setting for quiet backups
- Fix failing wiki restore
- -
- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
- -
- -
- Monokai highlighting style now more faithful to original design (Mark Riedesel)
- Create project with repository in synchrony
- Added ability to create empty repo or import existing one if project does not have repository
- -
- -
- Reactivate highlight.js language autodetection
- Mobile UI improvements
- -
- Change maximum avatar file size from 100KB to 200KB
- Strict validation for snippet file names
- Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
@@ -25,9 +42,21 @@ v 7.6.0
- Update Sidekiq to version 2.17.8
- Add author filter to project issues and merge requests pages
- Atom feed for user activity
+ - Support multiple omniauth providers for the same user
+ - Rendering cross reference in issue title and tooltip for merge request
+ - Show username in comments
+ - Possibility to create Milestones or Labels when Issues are disabled
+ - Fix bug with showing gpg signature in tag
+
+v 7.5.3
+ - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
v 7.5.2
- Don't log Sidekiq arguments by default
+ - Fix restore of wiki repositories from backups
+
+v 7.5.1
+ - Add missing timestamps to 'members' table
v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert)
@@ -47,7 +76,7 @@ v 7.5.0
- Performance improvements
- Fix post-receive issue for projects with deleted forks
- New gitlab-shell version with custom hooks support
- - Improve code
+ - Improve code
- GitLab CI 5.2+ support (does not support older versions)
- Fixed bug when you can not push commits starting with 000000 to protected branches
- Added a password strength indicator
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9531b27089b..c82a4c623e0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -101,6 +101,16 @@ Please ensure you support the feature you contribute through all of these steps.
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.)
+If you add a dependency in GitLab (such as an operating system package) please consider updating the following and note the applicability of each in your merge request:
+
+1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
+1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
+1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
+1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
+1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
+1. Test suite https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/configure_a_runner_to_run_the_gitlab_ce_test_suite.md
+1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
+
## Merge request description format
1. What does this MR do?
@@ -118,6 +128,7 @@ Please ensure you support the feature you contribute through all of these steps.
1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
+1. Migrations should do only one thing (eg: either create a table, move data to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
diff --git a/Gemfile b/Gemfile
index b4ca5969277..46ba460506b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -28,10 +28,16 @@ gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
gem 'omniauth-shibboleth'
+gem 'omniauth-kerberos'
+gem 'doorkeeper', '2.0.1'
+gem "rack-oauth2", "~> 1.0.5"
+
+# Browser detection
+gem "browser"
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '7.0.0.rc12'
+gem "gitlab_git", '7.0.0.rc13'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
@@ -89,7 +95,7 @@ gem "github-markup"
gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
-gem 'org-ruby', '= 0.9.9'
+gem 'org-ruby', '= 0.9.12'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 045b2b60fed..3452fc24e91 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -37,6 +37,7 @@ GEM
rake (>= 0.8.7)
arel (5.0.1.20140414130214)
asciidoctor (0.1.4)
+ attr_required (1.0.0)
awesome_print (1.2.0)
axiom-types (0.0.5)
descendants_tracker (~> 0.0.1)
@@ -49,6 +50,7 @@ GEM
debug_inspector (>= 0.0.1)
bootstrap-sass (3.0.3.0)
sass (~> 3.2)
+ browser (0.7.2)
builder (3.2.2)
capybara (2.2.1)
mime-types (>= 1.16)
@@ -107,6 +109,8 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.3)
docile (1.1.5)
+ doorkeeper (2.0.1)
+ railties (>= 3.1)
dotenv (0.9.0)
dropzonejs-rails (0.4.14)
rails (> 3.1)
@@ -120,7 +124,7 @@ GEM
equalizer (0.0.8)
erubis (2.7.0)
escape_utils (0.2.4)
- eventmachine (1.0.3)
+ eventmachine (1.0.4)
excon (0.32.1)
execjs (2.0.2)
expression_parser (0.9.0)
@@ -158,7 +162,7 @@ GEM
dotenv (>= 0.7)
thor (>= 0.13.6)
formatador (0.2.4)
- gemnasium-gitlab-service (0.2.2)
+ gemnasium-gitlab-service (0.2.3)
rugged (~> 0.19)
gherkin-ruby (0.3.1)
racc
@@ -179,7 +183,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
- gitlab_git (7.0.0.rc12)
+ gitlab_git (7.0.0.rc13)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -250,6 +254,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpauth (0.2.1)
+ httpclient (2.5.3.3)
i18n (0.6.11)
ice_nine (0.10.0)
jasmine (2.0.2)
@@ -275,7 +280,7 @@ GEM
kaminari (0.15.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
- kgio (2.8.1)
+ kgio (2.9.2)
launchy (2.4.2)
addressable (~> 2.3)
letter_opener (1.1.2)
@@ -322,6 +327,11 @@ GEM
omniauth-google-oauth2 (0.2.5)
omniauth (> 1.0)
omniauth-oauth2 (~> 1.1)
+ omniauth-kerberos (0.2.0)
+ omniauth-multipassword
+ timfel-krb5-auth (~> 0.8)
+ omniauth-multipassword (0.4.1)
+ omniauth (~> 1.0)
omniauth-oauth (1.0.1)
oauth
omniauth (~> 1.0)
@@ -333,7 +343,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
- org-ruby (0.9.9)
+ org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
pg (0.15.1)
@@ -363,6 +373,12 @@ GEM
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
+ rack-oauth2 (1.0.8)
+ activesupport (>= 2.3)
+ attr_required (>= 0.0.5)
+ httpclient (>= 2.2.0.2)
+ multi_json (>= 1.3.6)
+ rack (>= 1.1)
rack-protection (1.5.1)
rack
rack-test (0.6.2)
@@ -393,7 +409,7 @@ GEM
activesupport (= 4.1.1)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- raindrops (0.12.0)
+ raindrops (0.13.0)
rake (10.3.2)
raphael-rails (2.1.2)
rb-fsevent (0.9.3)
@@ -531,6 +547,7 @@ GEM
thread_safe (0.3.4)
tilt (1.4.1)
timers (1.1.0)
+ timfel-krb5-auth (0.8)
tinder (1.9.3)
eventmachine (~> 1.0)
faraday (~> 0.8)
@@ -598,6 +615,7 @@ DEPENDENCIES
better_errors
binding_of_caller
bootstrap-sass (~> 3.0)
+ browser
capybara (~> 2.2.1)
carrierwave
coffee-rails
@@ -610,6 +628,7 @@ DEPENDENCIES
devise (= 3.2.4)
devise-async (= 0.9.0)
diffy (~> 3.0.3)
+ doorkeeper (= 2.0.1)
dropzonejs-rails
email_spec
enumerize
@@ -624,7 +643,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1)
- gitlab_git (= 7.0.0.rc12)
+ gitlab_git (= 7.0.0.rc13)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0)
@@ -655,9 +674,10 @@ DEPENDENCIES
omniauth (~> 1.1.3)
omniauth-github
omniauth-google-oauth2
+ omniauth-kerberos
omniauth-shibboleth
omniauth-twitter
- org-ruby (= 0.9.9)
+ org-ruby (= 0.9.12)
pg
poltergeist (~> 1.5.1)
pry
@@ -665,6 +685,7 @@ DEPENDENCIES
rack-attack
rack-cors
rack-mini-profiler
+ rack-oauth2 (~> 1.0.5)
rails (~> 4.1.0)
rails_autolink (~> 1.1)
rails_best_practices
diff --git a/PROCESS.md b/PROCESS.md
index 1dd28d6b670..5cc25de05a4 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -104,3 +104,10 @@ This merge request has been closed because a request for more information has no
### Accepting merge requests
Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first.
+
+### Only accepting merge requests with green tests
+
+We can only accept a merge request if all the tests are green. I've just
+restarted the build. When the tests are still not passing after this restart and
+you're sure that is does not have anything to do with your code changes, please
+rebase with master to see if that solves the issue.
diff --git a/Procfile b/Procfile
index a5693f8dbc5..a0ab4a734a4 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,2 @@
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
-worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell
+worker: bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default
diff --git a/README.md b/README.md
index f303e8e7383..afcaaf0f0fa 100644
--- a/README.md
+++ b/README.md
@@ -52,69 +52,30 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
+You can access new installation with the login `root` and password `5iveL!fe`, after login you are required to set a unique password.
## Third-party applications
There are a lot of applications and API wrappers for GitLab.
Find them [on our website](https://about.gitlab.com/applications/).
-### New versions
+## New versions
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
-### Upgrading
+## Upgrading
For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update).
-## Run in production mode
-
-The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually:
-
- sudo service gitlab start
-
-or by directly calling the script:
-
- sudo /etc/init.d/gitlab start
-
-Please login with `root` / `5iveL!fe`
-
## Install a development environment
We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
-If you do not use the development kit you might need to copy the example development unicorn configuration file
+If you do not use the GitLab Development Development kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
+One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
cp config/unicorn.rb.example.development config/unicorn.rb
-## Run in development mode
-
-Start it with [Foreman](https://github.com/ddollar/foreman)
-
- bundle exec foreman start -p 3000
-
-or start each component separately:
-
- bundle exec rails s
- bin/background_jobs start
-
-And surf to [localhost:3000](http://localhost:3000/) and login with `root` / `5iveL!fe`.
-
-## Run the tests
-
-- Run all tests:
-
- bundle exec rake test
-
-- [RSpec](http://rspec.info/) unit and functional tests.
-
- All RSpec tests: `bundle exec rake spec`
-
- Single RSpec file: `bundle exec rspec spec/controllers/commit_controller_spec.rb`
-
-- [Spinach](https://github.com/codegram/spinach) integration tests.
-
- All Spinach tests: `bundle exec rake spinach`
-
- Single Spinach test: `bundle exec spinach features/project/issues/milestones.feature`
+Instructions on how to start Gitlab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
## Documentation
diff --git a/VERSION b/VERSION
index a28398aef42..550b62480c3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.6.0.pre
+7.7.0.pre
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
index 4f76d8ce486..777c62dc1b7 100644
--- a/app/assets/javascripts/activities.js.coffee
+++ b/app/assets/javascripts/activities.js.coffee
@@ -12,7 +12,7 @@ class @Activities
toggleFilter: (sender) ->
- sender.parent().toggleClass "inactive"
+ sender.parent().toggleClass "active"
event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0]
if event_filters
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index fafa5cdfaa4..27d04e7cac6 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -1,4 +1,6 @@
@Api =
+ groups_path: "/api/:version/groups.json"
+ group_path: "/api/:version/groups/:id.json"
users_path: "/api/:version/users.json"
user_path: "/api/:version/users/:id.json"
notes_path: "/api/:version/projects/:id/notes.json"
@@ -51,6 +53,33 @@
).done (users) ->
callback(users)
+ group: (group_id, callback) ->
+ url = Api.buildUrl(Api.group_path)
+ url = url.replace(':id', group_id)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ dataType: "json"
+ ).done (group) ->
+ callback(group)
+
+ # Return groups list. Filtered by query
+ # Only active groups retrieved
+ groups: (query, skip_ldap, callback) ->
+ url = Api.buildUrl(Api.groups_path)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ dataType: "json"
+ ).done (groups) ->
+ callback(groups)
+
# Return project users list. Filtered by query
# Only active users retrieved
projectUsers: (project_id, query, callback) ->
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index e9a28c12159..4cda8b75d8e 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -51,12 +51,6 @@ window.ajaxGet = (url) ->
window.showAndHide = (selector) ->
-window.errorMessage = (message) ->
- ehtml = $("<p>")
- ehtml.addClass("error_message")
- ehtml.html(message)
- ehtml
-
window.split = (val) ->
return val.split( /,\s*/ )
diff --git a/app/assets/javascripts/groups_select.js.coffee b/app/assets/javascripts/groups_select.js.coffee
new file mode 100644
index 00000000000..1084e2a17d1
--- /dev/null
+++ b/app/assets/javascripts/groups_select.js.coffee
@@ -0,0 +1,41 @@
+class @GroupsSelect
+ constructor: ->
+ $('.ajax-groups-select').each (i, select) =>
+ skip_ldap = $(select).hasClass('skip_ldap')
+
+ $(select).select2
+ placeholder: "Search for a group"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.groups query.term, skip_ldap, (groups) ->
+ data = { results: groups }
+ query.callback(data)
+
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ Api.group(id, callback)
+
+
+ formatResult: (args...) =>
+ @formatResult(args...)
+ formatSelection: (args...) =>
+ @formatSelection(args...)
+ dropdownCssClass: "ajax-groups-dropdown"
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
+
+ formatResult: (group) ->
+ if group.avatar_url
+ avatar = group.avatar_url
+ else
+ avatar = gon.default_avatar_url
+
+ "<div class='group-result'>
+ <div class='group-name'>#{group.name}</div>
+ <div class='group-path'>#{group.path}</div>
+ </div>"
+
+ formatSelection: (group) ->
+ group.name
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 597b4695a6d..45c248e6fb6 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,9 +1,9 @@
class @Issue
constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide()
- $(".issue-box .inline-update").on "change", "select", ->
+ $(".context .inline-update").on "change", "select", ->
$(this).submit()
- $(".issue-box .inline-update").on "change", "#issue_assignee_id", ->
+ $(".context .inline-update").on "change", "#issue_assignee_id", ->
$(this).submit()
if $("a.btn-close").length
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 46e06424e5a..9e3ca45ce04 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -26,9 +26,9 @@ class @MergeRequest
initContextWidget: ->
$('.edit-merge_request.inline-update input[type="submit"]').hide()
- $(".issue-box .inline-update").on "change", "select", ->
+ $(".context .inline-update").on "change", "select", ->
$(this).submit()
- $(".issue-box .inline-update").on "change", "#merge_request_assignee_id", ->
+ $(".context .inline-update").on "change", "#merge_request_assignee_id", ->
$(this).submit()
initMergeWidget: ->
@@ -89,6 +89,9 @@ class @MergeRequest
this.$('.merge-request-tabs .diffs-tab').addClass 'active'
this.loadDiff() unless @diffs_loaded
this.$('.diffs').show()
+ when 'commits'
+ this.$('.merge-request-tabs .commits-tab').addClass 'active'
+ this.$('.commits').show()
else
this.$('.merge-request-tabs .notes-tab').addClass 'active'
this.$('.notes').show()
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 30f8530dfda..4d1c81d91d4 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -375,7 +375,7 @@ class @Notes
###
addDiffNote: (e) =>
e.preventDefault()
- link = e.target
+ link = e.currentTarget
form = $(".js-new-note-form")
row = $(link).closest("tr")
nextRow = row.next()
diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee
new file mode 100644
index 00000000000..691fd4f10d8
--- /dev/null
+++ b/app/assets/javascripts/protected_branches.js.coffee
@@ -0,0 +1,21 @@
+$ ->
+ $(":checkbox").change ->
+ name = $(this).attr("name")
+ if name == "developers_can_push"
+ id = $(this).val()
+ checked = $(this).is(":checked")
+ url = $(this).data("url")
+ $.ajax
+ type: "PUT"
+ url: url
+ dataType: "json"
+ data:
+ id: id
+ developers_can_push: checked
+
+ success: ->
+ new Flash("Branch updated.", "notice")
+ location.reload true
+
+ error: ->
+ new Flash("Failed to update branch!", "alert")
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
index 9952fa0b00a..8b82d20c6c2 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -46,7 +46,7 @@ class @ContributorsGraph
class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
- @width = $('.container').width() - 70
+ @width = $('.container').width() - 345
@height = 200
@x = null
@y = null
@@ -119,7 +119,7 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
- @width = $('.container').width()/2 - 100
+ @width = $('.container').width()/2 - 225
@height = 200
@x = null
@y = null
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index 2fc738c18d8..da708c96b09 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -207,24 +207,16 @@ li.note {
}
}
-.no-ssh-key-message {
- padding: 10px 0;
- background: #C67;
- margin: 0;
- color: #FFF;
- margin-top: -1px;
+.browser-alert {
+ padding: 10px;
text-align: center;
-
+ background: #C67;
+ color: #fff;
+ font-weight: bold;
a {
color: #fff;
text-decoration: underline;
}
-
- .links-xs {
- text-align: center;
- font-size: 16px;
- padding: 5px;
- }
}
.warning_message {
@@ -282,7 +274,7 @@ img.emoji {
}
.navless-container {
- margin-top: 20px;
+ margin-top: 68px;
}
.description-block {
@@ -300,11 +292,17 @@ table {
.dashboard-intro-icon {
float: left;
+ text-align: center;
font-size: 32px;
color: #AAA;
- padding: 5px 0;
- width: 50px;
- min-height: 100px;
+ width: 60px;
+}
+
+.dashboard-intro-text {
+ display: inline-block;
+ margin-left: -60px;
+ padding-left: 60px;
+ width: 100%;
}
.broadcast-message {
@@ -355,3 +353,9 @@ table {
.task-status {
margin-left: 10px;
}
+
+#nprogress .spinner {
+ top: auto !important;
+ bottom: 20px !important;
+ left: 20px !important;
+}
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss
index e8b23090b0f..1a832569953 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -31,7 +31,12 @@ fieldset legend {
margin-bottom: 18px;
background-color: whitesmoke;
border-top: 1px solid #e5e5e5;
- padding-left: 17%;
+}
+
+@media (min-width: $screen-sm-min) {
+ .form-actions {
+ padding-left: 17%;
+ }
}
label {
@@ -88,7 +93,8 @@ label {
@include box-shadow(none);
}
-.issuable-description {
+.issuable-description,
+.wiki-content {
margin-top: 35px;
}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 79fbad4b946..2563ab516e2 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -1,128 +1,32 @@
/**
- * Issue box:
- * Huge block (one per page) for storing title, descripion and other information.
+ * Issue box for showing Open/Closed state:
* Used for Issue#show page, MergeRequest#show page etc
*
- * CLasses:
- * .issue-box - Regular box
*/
.issue-box {
- color: #555;
- margin:20px 0;
- background: $box_bg;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
+ display: inline-block;
+ padding: 7px 13px;
+ font-weight: normal;
+ margin-right: 5px;
&.issue-box-closed {
- .state {
- background-color: #F3CECE;
- border-color: $border_danger;
- }
- .state-label {
- background-color: $bg_danger;
- color: #FFF;
- }
+ background-color: $bg_danger;
+ color: #FFF;
}
&.issue-box-merged {
- .state {
- background-color: #B7CEE7;
- border-color: $border_primary;
- }
- .state-label {
- background-color: $bg_primary;
- color: #FFF;
- }
+ background-color: $bg_primary;
+ color: #FFF;
}
&.issue-box-open {
- .state {
- background-color: #D6F1D7;
- border-color: $bg_success;
- }
- .state-label {
- background-color: $bg_success;
- color: #FFF;
- }
+ background-color: $bg_success;
+ color: #FFF;
}
&.issue-box-expired {
- .state {
- background-color: #EEE9B3;
- border-color: #faebcc;
- }
- .state-label {
- background: #cea61b;
- color: #FFF;
- }
- }
-
- .control-group {
- margin-bottom: 0;
- }
-
- .state {
- background-color: #f9f9f9;
- }
-
- .title {
- font-size: 28px;
- font-weight: normal;
- line-height: 1.5;
- margin: 0;
- color: #333;
- padding: 10px 15px;
- }
-
- .context {
- border: none;
- border-top: 1px solid #eee;
- padding: 10px 15px;
-
- // Reset text align for children
- .text-right > * { text-align: left; }
-
- @media (max-width: $screen-xs-max) {
- // Don't right align on mobile
- .text-right { text-align: left; }
-
- .row .col-md-6 {
- padding-top: 5px;
- }
- }
- }
-
- .description {
- padding: 0 15px 10px 15px;
-
- code {
- white-space: pre-wrap;
- }
- }
-
- .title, .context, .description {
- .clearfix {
- margin: 0;
- }
- }
-
- .state-label {
- font-size: 14px;
- float: left;
- font-weight: bold;
- padding: 10px 15px;
- }
-
- .cross-project-ref {
- float: left;
- padding: 10px 15px;
- }
-
- .creator {
- float: right;
- padding: 10px 15px;
- a {
- text-decoration: underline;
- }
+ background: #cea61b;
+ color: #FFF;
}
}
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index 4168e235cae..5a87cc6c612 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -65,6 +65,7 @@
.edit_note,
.issuable-description,
.milestone-description,
+.wiki-content,
.merge-request-form {
.nav-tabs {
margin-bottom: 0;
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss
index e0f508d2695..d85e80a512b 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/generic/selects.scss
@@ -116,6 +116,18 @@ select {
}
}
+.group-result {
+ .group-image {
+ float: left;
+ }
+ .group-name {
+ font-weight: bold;
+ }
+ .group-path {
+ color: #999;
+ }
+}
+
.user-result {
.user-image {
float: left;
diff --git a/app/assets/stylesheets/generic/tables.scss b/app/assets/stylesheets/generic/tables.scss
new file mode 100644
index 00000000000..71a7d4abaee
--- /dev/null
+++ b/app/assets/stylesheets/generic/tables.scss
@@ -0,0 +1,20 @@
+table {
+ &.table {
+ tr {
+ td, th {
+ padding: 8px 10px;
+ line-height: 20px;
+ vertical-align: middle;
+ }
+ th {
+ font-weight: normal;
+ font-size: 15px;
+ border-bottom: 1px solid #CCC !important;
+ }
+ td {
+ border-color: #F1F1F1 !important;
+ border-bottom: 1px solid;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss
index 57e9e8ae5c5..82ee41b71bd 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/generic/timeline.scss
@@ -74,6 +74,42 @@
}
}
}
+
+ .system-note .timeline-entry-inner {
+ .timeline-icon {
+ background: none;
+ margin-left: 12px;
+ margin-top: 0;
+ @include box-shadow(none);
+
+ span {
+ margin: 0 2px;
+ font-size: 16px;
+ color: #eeeeee;
+ }
+ }
+
+ .timeline-content {
+ background: none;
+ margin-left: 45px;
+ padding: 0px 15px;
+
+ &:after { border: 0; }
+
+ .note-header {
+ span { font-size: 12px; }
+
+ .avatar {
+ margin-right: 5px;
+ }
+ }
+
+ .note-text {
+ font-size: 12px;
+ margin-left: 20px;
+ }
+ }
+ }
}
@media (max-width: $screen-xs-max) {
diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss
index 9c5e76ab8e2..2a68d922bb7 100644
--- a/app/assets/stylesheets/gl_bootstrap.scss
+++ b/app/assets/stylesheets/gl_bootstrap.scss
@@ -148,6 +148,10 @@ $list-group-active-bg: $bg_primary;
color: #666;
}
+.nav-compact > li > a {
+ padding: 6px 12px;
+}
+
.nav-small > li > a {
padding: 3px 5px;
font-size: 12px;
diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/main/layout.scss
index 2800feb81f2..71522443f10 100644
--- a/app/assets/stylesheets/main/layout.scss
+++ b/app/assets/stylesheets/main/layout.scss
@@ -4,10 +4,6 @@ html {
&.touch .tooltip { display: none !important; }
}
-body {
- padding-bottom: 20px;
-}
-
.container {
padding-top: 0;
z-index: 5;
diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss
index c71984a5665..92b220f8019 100644
--- a/app/assets/stylesheets/main/variables.scss
+++ b/app/assets/stylesheets/main/variables.scss
@@ -44,6 +44,6 @@ $added: #63c363;
$deleted: #f77;
/**
- *
+ * NProgress customize
*/
-$nprogress-color: #3498db;
+$nprogress-color: #c0392b;
diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss
index d181d83e857..e540f7ff940 100644
--- a/app/assets/stylesheets/sections/dashboard.scss
+++ b/app/assets/stylesheets/sections/dashboard.scss
@@ -23,20 +23,6 @@
}
}
-.dashboard {
- .dash-filter {
- width: 205px;
- float: left;
- height: inherit;
- }
-}
-
-@media (max-width: 1200px) {
- .dashboard .dash-filter {
- width: 140px;
- }
-}
-
.dash-sidebar-tabs {
margin-bottom: 2px;
border: none;
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss
index a766d6e77ab..3c3a0d92c6e 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/sections/events.scss
@@ -140,43 +140,17 @@
}
}
-/**
- * Event filter
- *
- */
-.event_filter {
- position: absolute;
- width: 40px;
- margin-left: -55px;
-
- .filter_icon {
- a {
- text-align:center;
- background: $bg_primary;
- margin-bottom: 10px;
- float: left;
- padding: 9px 6px;
- font-size: 18px;
- width: 40px;
- color: #FFF;
- @include border-radius(3px);
- }
-
- &.inactive {
- a {
- color: #DDD;
- background: #f9f9f9;
- }
- }
- }
-}
/*
* Last push widget
*/
.event-last-push {
+ overflow: auto;
.event-last-push-text {
- @include str-truncated(75%);
+ @include str-truncated(100%);
+ float:left;
+ margin-right: -150px;
+ padding-right: 150px;
line-height: 24px;
}
}
@@ -203,3 +177,7 @@
}
}
}
+
+.event_filter li a {
+ padding: 5px 10px;
+}
diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss
index 9ad1a1db2cd..32b0b10c649 100644
--- a/app/assets/stylesheets/sections/header.scss
+++ b/app/assets/stylesheets/sections/header.scss
@@ -4,9 +4,13 @@
*/
header {
&.navbar-gitlab {
+ z-index: 100;
margin-bottom: 0;
min-height: 40px;
border: none;
+ position: fixed;
+ top: 0;
+ width: 100%;
.navbar-inner {
filter: none;
@@ -52,8 +56,6 @@ header {
border-width: 0;
font-size: 18px;
- .app_logo { margin-left: -15px; }
-
.title {
@include str-truncated(70%);
}
@@ -84,7 +86,10 @@ header {
}
}
- z-index: 10;
+ .container {
+ width: 100% !important;
+ padding-left: 0px;
+ }
/**
*
@@ -232,21 +237,6 @@ header {
color: #fff;
}
}
-
- .app_logo {
- .separator {
- margin-left: 0;
- margin-right: 0;
- }
- }
-
- .separator {
- float: left;
- height: 46px;
- width: 2px;
- margin-left: 10px;
- margin-right: 10px;
- }
}
.search .search-input {
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index 9a5400fffbc..929838379cb 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -162,3 +162,7 @@ form.edit-issue {
}
}
}
+
+.issue-title {
+ margin-top: 0;
+}
diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss
index 1bcb1f6d68e..901733ef9ff 100644
--- a/app/assets/stylesheets/sections/login.scss
+++ b/app/assets/stylesheets/sections/login.scss
@@ -1,48 +1,66 @@
/* Login Page */
.login-page {
- h1 {
- font-size: 3em;
- font-weight: 200;
+ .container {
+ max-width: 960px;
}
- .login-box{
- padding: 0 15px;
+ .navbar-gitlab .container {
+ max-width: none;
+ }
- .login-heading h3 {
- font-weight: 300;
- line-height: 2;
- }
+ .brand-holder {
+ font-size: 18px;
+ line-height: 1.5;
- .login-footer {
- margin-top: 10px;
+ p {
+ color: #888;
}
- .btn {
- padding: 12px !important;
- @extend .btn-block;
+ h1:first-child {
+ font-weight: normal;
+ margin-bottom: 30px;
}
- }
- .brand-image {
img {
max-width: 100%;
- margin-bottom: 20px;
+ margin-bottom: 30px;
}
- &.default-brand-image {
- margin: 0 80px;
+ a {
+ font-weight: bold;
}
}
- .login-logo {
- margin: 10px 0 30px 0;
- display: block;
+ .login-box{
+ background: #fafafa;
+ border-radius: 10px;
+ box-shadow: 0 0px 2px #CCC;
+ padding: 15px;
+
+ .login-heading h3 {
+ font-weight: 300;
+ line-height: 1.5;
+ margin: 0;
+ display: none;
+ }
+
+ .login-footer {
+ margin-top: 10px;
+ }
+
+ a.forgot {
+ float: right;
+ padding-top: 6px
+ }
+
+ .nav .active a {
+ background: transparent;
+ }
}
.form-control {
- background-color: #F5F5F5;
- font-size: 16px;
- padding: 14px 10px;
+ font-size: 14px;
+ padding: 10px 8px;
width: 100%;
height: auto;
@@ -68,11 +86,6 @@
}
}
- .login-box a.forgot {
- float: right;
- padding-top: 6px
- }
-
.devise-errors {
h2 {
font-size: 14px;
@@ -80,7 +93,19 @@
}
}
- .brand-holder {
- border-right: 1px solid #EEE;
+ .remember-me {
+ margin-top: -10px;
+
+ label {
+ font-weight: normal;
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .login-page {
+ .col-sm-5.pull-right {
+ float: none !important;
+ }
}
}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index ec844cc00b0..8445b77c1a8 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -11,25 +11,40 @@
}
}
- .accept-group {
- label {
- margin: 5px;
+ .accept-merge-holder {
+ margin-top: 5px;
+
+ .accept-action {
+ display: inline-block;
+
+ .accept_merge_request {
+ padding: 10px 20px;
+ }
+ }
+
+ .accept-control {
+ display: inline-block;
margin-left: 20px;
+ padding: 10px 0;
+ line-height: 20px;
+ font-weight: bold;
+
+ .checkbox {
+ margin: 0;
+ }
}
}
}
-.merge-request .merge-request-tabs{
- border-bottom: 2px solid $border_primary;
- margin: 20px 0;
+@media(min-width: $screen-sm-max) {
+ .merge-request .merge-request-tabs{
+ margin: 20px 0;
- li {
- a {
- padding: 15px 40px;
- font-size: 14px;
- margin-bottom: -2px;
- border-bottom: 2px solid $border_primary;
- @include border-radius(0px);
+ li {
+ a {
+ padding: 15px 40px;
+ font-size: 14px;
+ }
}
}
}
@@ -106,6 +121,7 @@
.mr-state-widget {
background: $box_bg;
margin-bottom: 20px;
+ color: #666;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget {
@@ -150,7 +166,6 @@
padding: 10px 15px;
h4 {
- font-size: 20px;
font-weight: normal;
}
@@ -172,7 +187,3 @@
.merge-request-show-labels .label {
padding: 6px 10px;
}
-
-.mr-commits .commit {
- padding: 10px 15px;
-}
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
deleted file mode 100644
index ccd672c5f67..00000000000
--- a/app/assets/stylesheets/sections/nav.scss
+++ /dev/null
@@ -1,96 +0,0 @@
-.main-nav {
- background: #f5f5f5;
- margin: 20px 0;
- margin-top: 0;
- padding-top: 4px;
- border-bottom: 1px solid #E9E9E9;
-
- ul {
- padding: 0;
- margin: auto;
- .count {
- font-weight: normal;
- display: inline-block;
- height: 15px;
- padding: 1px 6px;
- height: auto;
- font-size: 0.82em;
- line-height: 14px;
- text-align: center;
- color: #777;
- background: #eee;
- @include border-radius(8px);
- }
- .label {
- background: $hover;
- text-shadow: none;
- color: $style_color;
- }
- li {
- list-style-type: none;
- margin: 0;
- display: table-cell;
- width: 1%;
- &.active {
- a {
- color: $link_color;
- font-weight: bold;
- border-bottom: 3px solid $link_color;
- }
- }
-
- &:hover {
- a {
- color: $link_hover_color;
- border-bottom: 3px solid $link_hover_color;
- }
- }
- }
- a {
- display: block;
- text-align: center;
- font-weight: bold;
- height: 42px;
- line-height: 39px;
- color: #777;
- text-shadow: 0 1px 1px white;
- text-decoration: none;
- overflow: hidden;
- margin-bottom: -1px;
- }
- }
-
- @media (max-width: $screen-xs-max) {
- font-size: 18px;
- margin: 0;
- max-height: none;
-
- &, .container {
- padding: 0;
- border-top: 0;
- }
-
- ul {
- height: auto;
-
- li {
- display: list-item;
- width: auto;
- padding: 5px 0;
-
- &.active {
- background-color: $link_hover_color;
-
- a {
- color: #fff;
- font-weight: normal;
- text-shadow: none;
- border: none;
-
- &:after { display: none; }
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index e1f9c0cb258..1550e30fe53 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -62,6 +62,7 @@ ul.notes {
}
.note-body {
@include md-typography;
+ overflow: auto;
}
.note-header {
padding-bottom: 3px;
@@ -155,19 +156,26 @@ ul.notes {
}
.add-diff-note {
- background: image-url("diff_note_add.png") no-repeat left 0;
- border: none;
- height: 22px;
- margin-left: -65px;
+ margin-top: -4px;
+ @include border-radius(40px);
+ background: #FFF;
+ padding: 4px;
+ font-size: 16px;
+ color: $link_color;
+ margin-left: -60px;
position: absolute;
- width: 22px;
z-index: 10;
+ transition: all 0.2s ease;
+
// "hide" it by default
opacity: 0.0;
filter: alpha(opacity=0);
&:hover {
+ font-size: 24px;
+ background: $bg_primary;
+ color: #FFF;
@include show-add-diff-note;
}
}
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss
index 7b894cf00bb..fbfe9ad4c93 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/sections/projects.scss
@@ -308,3 +308,10 @@ ul.nav.nav-projects-tabs {
display: none;
}
}
+
+
+table.table.protected-branches-list tr.no-border {
+ th, td {
+ border: 0;
+ }
+}
diff --git a/app/assets/stylesheets/sections/sidebar.scss b/app/assets/stylesheets/sections/sidebar.scss
new file mode 100644
index 00000000000..79441eba6db
--- /dev/null
+++ b/app/assets/stylesheets/sections/sidebar.scss
@@ -0,0 +1,161 @@
+.page-with-sidebar {
+ background: #F5F5F5;
+}
+
+.sidebar-wrapper {
+ z-index: 99;
+ overflow-y: auto;
+ background: #F5F5F5;
+}
+
+.content-wrapper {
+ width: 100%;
+ padding: 15px;
+ background: #FFF;
+ margin-top: 48px;
+}
+
+.nav-sidebar {
+ margin: 0;
+ list-style: none;
+
+ &.navbar-collapse {
+ padding: 0px !important;
+ }
+}
+
+.nav-sidebar li a .count {
+ float: right;
+ background: #eee;
+ padding: 0px 8px;
+ @include border-radius(6px);
+}
+
+.nav-sidebar li {
+ &.active a {
+ color: #111;
+ background: #EEE;
+ font-weight: bold;
+
+ &.no-highlight {
+ background: none;
+ }
+
+ i {
+ color: #444;
+ }
+ }
+}
+
+.nav-sidebar li {
+ &.separate-item {
+ border-top: 1px solid #ddd;
+ padding-top: 10px;
+ margin-top: 10px;
+ }
+
+ a {
+ color: #555;
+ display: block;
+ text-decoration: none;
+ padding: 6px 15px;
+ font-size: 13px;
+ line-height: 20px;
+ text-shadow: 0 1px 2px #FFF;
+ padding-left: 20px;
+
+ &:hover {
+ text-decoration: none;
+ color: #333;
+ background: #DDD;
+ }
+
+ &:active, &:focus {
+ text-decoration: none;
+ }
+
+ i {
+ width: 20px;
+ color: #888;
+ margin-right: 23px;
+ }
+ }
+}
+
+.sidebar-subnav {
+ margin-left: 0px;
+ padding-left: 0px;
+
+ li {
+ list-style: none;
+ }
+}
+
+@mixin expanded-sidebar {
+ .page-with-sidebar {
+ padding-left: 250px;
+ }
+
+ .sidebar-wrapper {
+ width: 250px;
+ position: fixed;
+ left: 250px;
+ height: 100%;
+ margin-left: -250px;
+ border-right: 1px solid #EAEAEA;
+
+ .nav-sidebar {
+ margin-top: 20px;
+ position: fixed;
+ top: 45px;
+ width: 250px;
+ }
+ }
+
+ .content-wrapper {
+ padding: 20px;
+ }
+}
+
+@mixin folded-sidebar {
+ .page-with-sidebar {
+ padding-left: 50px;
+ }
+
+ .sidebar-wrapper {
+ width: 52px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ border-right: 1px solid #EAEAEA;
+ overflow-x: hidden;
+
+ .nav-sidebar {
+ margin-top: 20px;
+ position: absolute;
+ top: 45px;
+ width: 52px;
+
+ li a {
+ padding-left: 18px;
+ font-size: 14px;
+ padding: 10px 15px;
+ text-align: center;
+
+ & > span {
+ display: none;
+ }
+ }
+ }
+ }
+}
+
+@media (max-width: $screen-sm-max) {
+ @include folded-sidebar;
+}
+
+@media(min-width: $screen-sm-max) {
+ @include expanded-sidebar;
+}
+
diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss
index 678a6cd716d..bc7451e2d53 100644
--- a/app/assets/stylesheets/sections/tree.scss
+++ b/app/assets/stylesheets/sections/tree.scss
@@ -17,19 +17,6 @@
@include border-radius(0);
tr {
- td, th {
- padding: 8px 10px;
- line-height: 20px;
- }
- th {
- font-weight: normal;
- font-size: 15px;
- border-bottom: 1px solid #CCC !important;
- }
- td {
- border-color: #F1F1F1 !important;
- border-bottom: 1px solid;
- }
&:hover {
td {
background: $hover;
diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/sections/votes.scss
index d683e33e1f0..ba0a519dca6 100644
--- a/app/assets/stylesheets/sections/votes.scss
+++ b/app/assets/stylesheets/sections/votes.scss
@@ -37,13 +37,3 @@
margin: 0 8px;
}
-.votes-holder {
- float: right;
- width: 250px;
-
- @media (max-width: $screen-xs-max) {
- width: 100%;
- margin-top: 5px;
- margin-bottom: 10px;
- }
-}
diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss
index 3e3744fdc33..0dad9917b55 100644
--- a/app/assets/stylesheets/themes/ui_basic.scss
+++ b/app/assets/stylesheets/themes/ui_basic.scss
@@ -9,17 +9,15 @@
.navbar-inner {
background: #F1F1F1;
border-bottom: 1px solid #DDD;
+
+ .app_logo {
+ background-color: #DDD;
+ }
+
.nav > li > a {
color: $style_color;
}
- .separator {
- background: #F9F9F9;
- border-left: 1px solid #DDD;
- }
}
}
}
- .main-nav {
- background: #FFF;
- }
}
diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss
index a08f3ff3d48..3c441a8e098 100644
--- a/app/assets/stylesheets/themes/ui_color.scss
+++ b/app/assets/stylesheets/themes/ui_color.scss
@@ -23,9 +23,8 @@
background-color: #436;
}
}
- .separator {
- background: #436;
- border-left: 1px solid #659;
+ .app_logo {
+ background-color: #325;
}
.nav > li > a {
color: #98C;
diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss
index 959febad6fe..8df08ccaeec 100644
--- a/app/assets/stylesheets/themes/ui_gray.scss
+++ b/app/assets/stylesheets/themes/ui_gray.scss
@@ -23,9 +23,8 @@
background-color: #272727;
}
}
- .separator {
- background: #272727;
- border-left: 1px solid #474747;
+ .app_logo {
+ background-color: #222;
}
}
}
diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss
index 9af5adbf10a..b08cbda6c4f 100644
--- a/app/assets/stylesheets/themes/ui_mars.scss
+++ b/app/assets/stylesheets/themes/ui_mars.scss
@@ -23,9 +23,8 @@
background-color: #373D47;
}
}
- .separator {
- background: #373D47;
- border-left: 1px solid #575D67;
+ .app_logo {
+ background-color: #24272D;
}
.nav > li > a {
color: #979DA7;
diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss
index 308a03477db..34f39614ca4 100644
--- a/app/assets/stylesheets/themes/ui_modern.scss
+++ b/app/assets/stylesheets/themes/ui_modern.scss
@@ -23,9 +23,8 @@
background-color: #018865;
}
}
- .separator {
- background: #018865;
- border-left: 1px solid #11A885;
+ .app_logo {
+ background-color: #017855;
}
.nav > li > a {
color: #ADC;
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index e6d0c9323c1..8c7d90a5d9f 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -21,7 +21,7 @@ class Admin::GroupsController < Admin::ApplicationController
def create
@group = Group.new(group_params)
- @group.path = @group.name.dup.parameterize if @group.name
+ @group.name = @group.path.dup unless @group.name
if @group.save
@group.add_owner(current_user)
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
new file mode 100644
index 00000000000..21111bb44f5
--- /dev/null
+++ b/app/controllers/admin/keys_controller.rb
@@ -0,0 +1,34 @@
+class Admin::KeysController < Admin::ApplicationController
+ before_filter :user, only: [:show, :destroy]
+
+ def show
+ @key = user.keys.find(params[:id])
+
+ respond_to do |format|
+ format.html
+ format.js { render nothing: true }
+ end
+ end
+
+ def destroy
+ key = user.keys.find(params[:id])
+
+ respond_to do |format|
+ if key.destroy
+ format.html { redirect_to [:admin, user], notice: 'User key was successfully removed.' }
+ else
+ format.html { redirect_to [:admin, user], alert: 'Failed to remove user key.' }
+ end
+ end
+ end
+
+ protected
+
+ def user
+ @user ||= User.find_by!(username: params[:user_id])
+ end
+
+ def key_params
+ params.require(:user_id, :id)
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index baad9095b70..aea8545d38e 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -11,6 +11,7 @@ class Admin::UsersController < Admin::ApplicationController
def show
@personal_projects = user.personal_projects
@joined_projects = user.projects.joined(@user)
+ @keys = user.keys.order('id DESC')
end
def new
@@ -118,7 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
:email, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password,
:extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key,
- :projects_limit, :can_create_group, :admin
+ :projects_limit, :can_create_group, :admin, :key_id
)
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index f1e1bebe5ce..4b8cae469e3 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -239,4 +239,63 @@ class ApplicationController < ActionController::Base
redirect_to profile_path, notice: 'Please complete your profile with email address' and return
end
end
+
+ def set_filters_params
+ params[:sort] ||= 'newest'
+ params[:scope] = 'all' if params[:scope].blank?
+ params[:state] = 'opened' if params[:state].blank?
+
+ @filter_params = params.dup
+
+ if @project
+ @filter_params[:project_id] = @project.id
+ elsif @group
+ @filter_params[:group_id] = @group.id
+ else
+ # TODO: this filter ignore issues/mr created in public or
+ # internal repos where you are not a member. Enable this filter
+ # or improve current implementation to filter only issues you
+ # created or assigned or mentioned
+ #@filter_params[:authorized_only] = true
+ end
+
+ @filter_params
+ end
+
+ def set_filter_values(collection)
+ assignee_id = @filter_params[:assignee_id]
+ author_id = @filter_params[:author_id]
+ milestone_id = @filter_params[:milestone_id]
+
+ @sort = @filter_params[:sort].try(:humanize)
+ @assignees = User.where(id: collection.pluck(:assignee_id))
+ @authors = User.where(id: collection.pluck(:author_id))
+ @milestones = Milestone.where(id: collection.pluck(:milestone_id))
+
+ if assignee_id.present? && !assignee_id.to_i.zero?
+ @assignee = @assignees.find_by(id: assignee_id)
+ end
+
+ if author_id.present? && !author_id.to_i.zero?
+ @author = @authors.find_by(id: author_id)
+ end
+
+ if milestone_id.present? && !milestone_id.to_i.zero?
+ @milestone = @milestones.find_by(id: milestone_id)
+ end
+ end
+
+ def get_issues_collection
+ set_filters_params
+ issues = IssuesFinder.new.execute(current_user, @filter_params)
+ set_filter_values(issues)
+ issues
+ end
+
+ def get_merge_requests_collection
+ set_filters_params
+ merge_requests = MergeRequestsFinder.new.execute(current_user, @filter_params)
+ set_filter_values(merge_requests)
+ merge_requests
+ end
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 5aff526d1b5..cd876024ba3 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -3,8 +3,6 @@ class DashboardController < ApplicationController
before_filter :load_projects, except: [:projects]
before_filter :event_filter, only: :show
- before_filter :default_filter, only: [:issues, :merge_requests]
-
def show
# Fetch only 30 projects.
@@ -55,13 +53,13 @@ class DashboardController < ApplicationController
end
def merge_requests
- @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
+ @merge_requests = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(20)
@merge_requests = @merge_requests.preload(:author, :target_project)
end
def issues
- @issues = IssuesFinder.new.execute(current_user, params)
+ @issues = get_issues_collection
@issues = @issues.page(params[:page]).per(20)
@issues = @issues.preload(:author, :project)
@@ -76,10 +74,4 @@ class DashboardController < ApplicationController
def load_projects
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
end
-
- def default_filter
- params[:scope] = 'assigned-to-me' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
- params[:authorized_only] = true
- end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 36222758eb2..aad3709090e 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -11,8 +11,6 @@ class GroupsController < ApplicationController
# Load group projects
before_filter :load_projects, except: [:new, :create, :projects, :edit, :update]
- before_filter :default_filter, only: [:issues, :merge_requests]
-
layout :determine_layout
before_filter :set_title, only: [:new, :create]
@@ -23,7 +21,7 @@ class GroupsController < ApplicationController
def create
@group = Group.new(group_params)
- @group.path = @group.name.dup.parameterize if @group.name
+ @group.name = @group.path.dup unless @group.name
if @group.save
@group.add_owner(current_user)
@@ -47,13 +45,13 @@ class GroupsController < ApplicationController
end
def merge_requests
- @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
+ @merge_requests = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(20)
@merge_requests = @merge_requests.preload(:author, :target_project)
end
def issues
- @issues = IssuesFinder.new.execute(current_user, params)
+ @issues = get_issues_collection
@issues = @issues.page(params[:page]).per(20)
@issues = @issues.preload(:author, :project)
@@ -148,18 +146,6 @@ class GroupsController < ApplicationController
end
end
- def default_filter
- if params[:scope].blank?
- if current_user
- params[:scope] = 'assigned-to-me'
- else
- params[:scope] = 'all'
- end
- end
- params[:state] = 'opened' if params[:state].blank?
- params[:group_id] = @group.id
- end
-
def group_params
params.require(:group).permit(:name, :description, :path, :avatar)
end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
new file mode 100644
index 00000000000..3407490e498
--- /dev/null
+++ b/app/controllers/oauth/applications_controller.rb
@@ -0,0 +1,41 @@
+class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
+ before_filter :authenticate_user!
+ layout "profile"
+
+ def index
+ head :forbidden and return
+ end
+
+ def create
+ @application = Doorkeeper::Application.new(application_params)
+
+ if Doorkeeper.configuration.confirm_application_owner?
+ @application.owner = current_user
+ end
+
+ if @application.save
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+ redirect_to oauth_application_url(@application)
+ else
+ render :new
+ end
+ end
+
+ def destroy
+ if @application.destroy
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :destroy])
+ end
+
+ redirect_to applications_profile_url
+ end
+
+ private
+
+ def set_application
+ @application = current_user.oauth_applications.find(params[:id])
+ end
+
+ rescue_from ActiveRecord::RecordNotFound do |exception|
+ render "errors/not_found", layout: "errors", status: 404
+ end
+end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
new file mode 100644
index 00000000000..a57b4a60c24
--- /dev/null
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -0,0 +1,57 @@
+class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
+ before_filter :authenticate_resource_owner!
+ layout "profile"
+
+ def new
+ if pre_auth.authorizable?
+ if skip_authorization? || matching_token?
+ auth = authorization.authorize
+ redirect_to auth.redirect_uri
+ else
+ render "doorkeeper/authorizations/new"
+ end
+ else
+ render "doorkeeper/authorizations/error"
+ end
+ end
+
+ # TODO: Handle raise invalid authorization
+ def create
+ redirect_or_render authorization.authorize
+ end
+
+ def destroy
+ redirect_or_render authorization.deny
+ end
+
+ private
+
+ def matching_token?
+ Doorkeeper::AccessToken.matching_token_for(pre_auth.client,
+ current_resource_owner.id,
+ pre_auth.scopes)
+ end
+
+ def redirect_or_render(auth)
+ if auth.redirectable?
+ redirect_to auth.redirect_uri
+ else
+ render json: auth.body, status: auth.status
+ end
+ end
+
+ def pre_auth
+ @pre_auth ||=
+ Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration,
+ server.client_via_uid,
+ params)
+ end
+
+ def authorization
+ @authorization ||= strategy.request
+ end
+
+ def strategy
+ @strategy ||= server.authorization_request(pre_auth.response_type)
+ end
+end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
new file mode 100644
index 00000000000..0b27ce7da72
--- /dev/null
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -0,0 +1,8 @@
+class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
+ layout "profile"
+
+ def destroy
+ Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner)
+ redirect_to applications_profile_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy])
+ end
+end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index e877f9b9049..c0b7e2223a2 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -13,6 +13,11 @@ class ProfilesController < ApplicationController
def design
end
+ def applications
+ @applications = current_user.oauth_applications
+ @authorized_tokens = current_user.oauth_authorized_tokens
+ end
+
def update
user_params.except!(:email) if @user.ldap_user?
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 6b7fe06d59f..7e4580017dd 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -29,31 +29,4 @@ class Projects::ApplicationController < ApplicationController
redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch"
end
end
-
- def set_filter_variables(collection)
- params[:sort] ||= 'newest'
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
-
- @sort = params[:sort].humanize
-
- assignee_id = params[:assignee_id]
- author_id = params[:author_id]
- milestone_id = params[:milestone_id]
-
- if assignee_id.present? && !assignee_id.to_i.zero?
- @assignee = @project.team.find(assignee_id)
- end
-
- if author_id.present? && !author_id.to_i.zero?
- @author = @project.team.find(assignee_id)
- end
-
- if milestone_id.present? && !milestone_id.to_i.zero?
- @milestone = @project.milestones.find(milestone_id)
- end
-
- @assignees = User.where(id: collection.pluck(:assignee_id))
- @authors = User.where(id: collection.pluck(:author_id))
- end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 22235123826..42e207cf376 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -18,9 +18,7 @@ class Projects::IssuesController < Projects::ApplicationController
def index
terms = params['issue_search']
- set_filter_variables(@project.issues)
-
- @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
+ @issues = get_issues_collection
@issues = @issues.full_search(terms) if terms.present?
@issues = @issues.page(params[:page]).per(20)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4d6f41e9de5..d23461821d7 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -17,9 +17,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index
- set_filter_variables(@project.merge_requests)
-
- @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
+ @merge_requests = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(20)
end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index f362f449e70..95801f8b8fb 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -11,7 +11,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html
def index
- @milestones = case params[:f]
+ @milestones = case params[:state]
when 'all'; @project.milestones.order("state, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC")
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index bd31b1d3c54..02160d973b3 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -15,6 +15,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
redirect_to project_protected_branches_path(@project)
end
+ def update
+ protected_branch = @project.protected_branches.find(params[:id])
+
+ if protected_branch &&
+ protected_branch.update_attributes(
+ developers_can_push: params[:developers_can_push]
+ )
+
+ respond_to do |format|
+ format.json { render :json => protected_branch, status: :ok }
+ end
+ else
+ respond_to do |format|
+ format.json { render json: protected_branch.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
def destroy
@project.protected_branches.find(params[:id]).destroy
@@ -27,6 +45,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
private
def protected_branch_params
- params.require(:protected_branch).permit(:name)
+ params.require(:protected_branch).permit(:name, :developers_can_push)
end
end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index c50a1f1e75b..ef4d2609147 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
- :build_key, :server
+ :build_key, :server, :teamcity_url, :build_type
)
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index fcff6952d38..e541b6fd872 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -44,6 +44,9 @@ class ProjectsController < ApplicationController
def transfer
::Projects::TransferService.new(project, current_user, project_params).execute
+ if @project.errors[:namespace_id].present?
+ flash[:alert] = @project.errors[:namespace_id].first
+ end
end
def show
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 021bd0a494c..f21b0bd1f50 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -114,6 +114,10 @@ module ApplicationHelper
Gitlab::Theme.css_class_by_id(current_user.try(:theme_id))
end
+ def theme_type
+ Gitlab::Theme.type_css_class_by_id(current_user.try(:theme_id))
+ end
+
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
@@ -271,4 +275,39 @@ module ApplicationHelper
def promo_url
'https://' + promo_host
end
+
+ def page_filter_path(options={})
+ exist_opts = {
+ state: params[:state],
+ scope: params[:scope],
+ label_name: params[:label_name],
+ milestone_id: params[:milestone_id],
+ assignee_id: params[:assignee_id],
+ author_id: params[:author_id],
+ sort: params[:sort],
+ }
+
+ options = exist_opts.merge(options)
+
+ path = request.path
+ path << "?#{options.to_param}"
+ path
+ end
+
+ def outdated_browser?
+ browser.ie? && browser.version.to_i < 10
+ end
+
+ def path_to_key(key, admin = false)
+ if admin
+ admin_user_key_path(@user, key)
+ else
+ profile_key_path(key)
+ end
+ end
+
+ def redirect_from_root?
+ request.env['rack.session']['user_return_to'] ==
+ '/'
+ end
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index acc0eeb76b3..4dae96644c8 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -1,32 +1,11 @@
module DashboardHelper
- def filter_path(entity, options={})
- exist_opts = {
- state: params[:state],
- scope: params[:scope],
- project_id: params[:project_id],
- }
-
- options = exist_opts.merge(options)
-
- path = request.path
- path << "?#{options.to_param}"
- path
- end
-
- def entities_per_project(project, entity)
- case entity.to_sym
- when :issue then @issues.where(project_id: project.id)
- when :merge_request then @merge_requests.where(target_project_id: project.id)
- else
- []
- end.count
- end
-
def projects_dashboard_filter_path(options={})
exist_opts = {
sort: params[:sort],
scope: params[:scope],
group: params[:group],
+ tag: params[:tag],
+ visibility_level: params[:visibility_level],
}
options = exist_opts.merge(options)
@@ -36,32 +15,11 @@ module DashboardHelper
path
end
- def assigned_entities_count(current_user, entity, scope = nil)
- items = current_user.send('assigned_' + entity.pluralize)
- get_count(items, scope)
+ def assigned_issues_dashboard_path
+ issues_dashboard_path(assignee_id: current_user.id)
end
- def authored_entities_count(current_user, entity, scope = nil)
- items = current_user.send(entity.pluralize)
- get_count(items, scope)
- end
-
- def authorized_entities_count(current_user, entity, scope = nil)
- items = entity.classify.constantize
- get_count(items, scope, true, current_user)
- end
-
- protected
-
- def get_count(items, scope, get_authorized = false, current_user = nil)
- items = items.opened
- if scope.kind_of?(Group)
- items = items.of_group(scope)
- elsif scope.kind_of?(Project)
- items = items.of_projects(scope)
- elsif get_authorized
- items = items.of_projects(current_user.authorized_projects)
- end
- items.count
+ def assigned_mrs_dashboard_path
+ merge_requests_dashboard_path(assignee_id: current_user.id)
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index cb50d89cba8..a15af0be01a 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -117,4 +117,22 @@ module DiffHelper
[comments_left, comments_right]
end
+
+ def inline_diff_btn
+ params_copy = params.dup
+ params_copy[:view] = 'inline'
+
+ link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do
+ 'Inline'
+ end
+ end
+
+ def parallel_diff_btn
+ params_copy = params.dup
+ params_copy[:view] = 'parallel'
+
+ link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do
+ 'Side-by-side'
+ end
+ end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index a3136926b38..903a5009616 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -21,15 +21,14 @@ module EventsHelper
def event_filter_link(key, tooltip)
key = key.to_s
- inactive = if @event_filter.active? key
- nil
- else
- 'inactive'
- end
+ active = if @event_filter.active? key
+ 'active'
+ end
- content_tag :div, class: "filter_icon #{inactive}" do
+ content_tag :li, class: "filter_icon #{active}" do
link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
- content_tag :i, nil, class: icon_for_event[key]
+ content_tag(:i, nil, class: icon_for_event[key]) +
+ content_tag(:span, ' ' + tooltip)
end
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 0dc53dedeb7..03fd461a462 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -6,7 +6,7 @@ module GroupsHelper
def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?"
end
-
+
def should_user_see_group_roles?(user, group)
if user
user.is_admin? || group.members.exists?(user_id: user.id)
@@ -33,15 +33,11 @@ module GroupsHelper
title
end
- def group_filter_path(entity, options={})
- exist_opts = {
- status: params[:status]
- }
-
- options = exist_opts.merge(options)
-
- path = request.path
- path << "?#{options.to_param}"
- path
+ def group_settings_page?
+ if current_controller?('groups')
+ current_action?('edit') || current_action?('projects')
+ else
+ false
+ end
end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
new file mode 100644
index 00000000000..6847123d2d4
--- /dev/null
+++ b/app/helpers/milestones_helper.rb
@@ -0,0 +1,9 @@
+module MilestonesHelper
+ def milestones_filter_path(opts = {})
+ if @project
+ project_milestones_path(@project, opts)
+ elsif @group
+ group_milestones_path(@group, opts)
+ end
+ end
+end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 901052edec6..6d2244b8714 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -52,8 +52,11 @@ module NotesHelper
discussion_id: discussion_id
}
- button_tag '', class: 'btn add-diff-note js-add-diff-note-button',
- data: data, title: 'Add a comment to this line'
+ button_tag(class: 'btn add-diff-note js-add-diff-note-button',
+ data: data,
+ title: 'Add a comment to this line') do
+ content_tag :i, nil, class: 'fa fa-comment-o'
+ end
end
def link_to_reply_diff(note)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index fb5470d98e5..e489d431e84 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -68,48 +68,6 @@ module ProjectsHelper
project_nav_tabs.include? name
end
- def selected_label?(label_name)
- params[:label_name].to_s.split(',').include?(label_name)
- end
-
- def labels_filter_path(label_name)
- label_name =
- if selected_label?(label_name)
- params[:label_name].split(',').reject { |l| l == label_name }.join(',')
- elsif params[:label_name].present?
- "#{params[:label_name]},#{label_name}"
- else
- label_name
- end
-
- project_filter_path(label_name: label_name)
- end
-
- def label_filter_class(label_name)
- if selected_label?(label_name)
- 'label-filter-item active'
- else
- 'label-filter-item light'
- end
- end
-
- def project_filter_path(options={})
- exist_opts = {
- state: params[:state],
- scope: params[:scope],
- label_name: params[:label_name],
- milestone_id: params[:milestone_id],
- assignee_id: params[:assignee_id],
- sort: params[:sort],
- }
-
- options = exist_opts.merge(options)
-
- path = request.path
- path << "?#{options.to_param}"
- path
- end
-
def project_active_milestones
@project.milestones.active.order("due_date, title ASC")
end
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index ab24367c455..796d805f219 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -17,4 +17,13 @@ module SelectsHelper
project_id = opts[:project_id] || @project.id
hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
end
+
+ def groups_select_tag(id, opts = {})
+ css_class = "ajax-groups-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+
+ hidden_field_tag(id, value, class: css_class)
+ end
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index bc43e078568..639fc98c222 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -28,6 +28,10 @@ module TabHelper
# nav_link(controller: [:tree, :refs]) { "Hello" }
# # => '<li class="active">Hello</li>'
#
+ # # Several paths
+ # nav_link(path: ['tree#show', 'profile#show']) { "Hello" }
+ # # => '<li class="active">Hello</li>'
+ #
# # Shorthand path
# nav_link(path: 'tree#show') { "Hello" }
# # => '<li class="active">Hello</li>'
@@ -38,25 +42,7 @@ module TabHelper
#
# Returns a list item element String
def nav_link(options = {}, &block)
- if path = options.delete(:path)
- if path.respond_to?(:each)
- c = path.map { |p| p.split('#').first }
- a = path.map { |p| p.split('#').last }
- else
- c, a, _ = path.split('#')
- end
- else
- c = options.delete(:controller)
- a = options.delete(:action)
- end
-
- if c && a
- # When given both options, make sure BOTH are active
- klass = current_controller?(*c) && current_action?(*a) ? 'active' : ''
- else
- # Otherwise check EITHER option
- klass = current_controller?(*c) || current_action?(*a) ? 'active' : ''
- end
+ klass = active_nav_link?(options) ? 'active' : ''
# Add our custom class into the html_options, which may or may not exist
# and which may or may not already have a :class key
@@ -72,6 +58,34 @@ module TabHelper
end
end
+ def active_nav_link?(options)
+ if path = options.delete(:path)
+ unless path.respond_to?(:each)
+ path = [path]
+ end
+
+ path.any? do |single_path|
+ current_path?(single_path)
+ end
+ else
+ c = options.delete(:controller)
+ a = options.delete(:action)
+
+ if c && a
+ # When given both options, make sure BOTH are true
+ current_controller?(*c) && current_action?(*a)
+ else
+ # Otherwise check EITHER option
+ current_controller?(*c) || current_action?(*a)
+ end
+ end
+ end
+
+ def current_path?(path)
+ c, a, _ = path.split('#')
+ current_controller?(c) && current_action?(a)
+ end
+
def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 8e209498323..e32aeba5f8f 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -53,13 +53,41 @@ module TreeHelper
File.join(*args)
end
- def allowed_tree_edit?
- return false unless @repository.branch_names.include?(@ref)
+ def allowed_tree_edit?(project = nil, ref = nil)
+ project ||= @project
+ ref ||= @ref
+ return false unless project.repository.branch_names.include?(ref)
- if @project.protected_branch? @ref
- can?(current_user, :push_code_to_protected_branches, @project)
+ if project.protected_branch? ref
+ can?(current_user, :push_code_to_protected_branches, project)
else
- can?(current_user, :push_code, @project)
+ can?(current_user, :push_code, project)
+ end
+ end
+
+ def edit_blob_link(project, ref, path, options = {})
+ blob =
+ begin
+ project.repository.blob_at(ref, path)
+ rescue
+ nil
+ end
+
+ if blob && blob.text?
+ text = 'Edit'
+ after = options[:after] || ''
+ from_mr = options[:from_merge_request_id]
+ link_opts = {}
+ link_opts[:from_merge_request_id] = from_mr if from_mr
+ cls = 'btn btn-small'
+ if allowed_tree_edit?(project, ref)
+ link_to text, project_edit_tree_path(project, tree_join(ref, path),
+ link_opts), class: cls
+ else
+ content_tag :span, text, class: cls + ' disabled'
+ end + after.html_safe
+ else
+ ''
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 65b4c2edfee..2a6c690ab91 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -174,7 +174,7 @@ class Event < ActiveRecord::Base
def valid_push?
data[:ref] && ref_name.present?
- rescue => ex
+ rescue
false
end
diff --git a/app/models/group.rb b/app/models/group.rb
index b8ed3b8ac73..733afa2fc07 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -21,7 +21,7 @@ class Group < Namespace
has_many :users, through: :group_members
validate :avatar_type, if: ->(user) { user.avatar_changed? }
- validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
+ validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 8479d4aecf6..d1d522be194 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -48,7 +48,7 @@ class WebHook < ActiveRecord::Base
verify: false,
basic_auth: auth)
end
- rescue SocketError, Errno::ECONNREFUSED => e
+ rescue SocketError, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
false
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2cc427d35c2..de0ee0e2c5a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -189,7 +189,9 @@ class MergeRequest < ActiveRecord::Base
end
def automerge!(current_user, commit_message = nil)
- MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message)
+ MergeRequests::AutoMergeService.
+ new(target_project, current_user).
+ execute(self, commit_message)
end
def open?
diff --git a/app/models/note.rb b/app/models/note.rb
index 5bf645bbd1d..5996298be22 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -502,6 +502,6 @@ class Note < ActiveRecord::Base
end
def editable?
- !system
+ !read_attribute(:system)
end
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index b0f8ed6a4ec..1395274173d 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -6,12 +6,13 @@ class Notification
N_PARTICIPATING = 1
N_WATCH = 2
N_GLOBAL = 3
+ N_MENTION = 4
attr_accessor :target
class << self
def notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH]
+ [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION]
end
def options_with_labels
@@ -19,12 +20,13 @@ class Notification
disabled: N_DISABLED,
participating: N_PARTICIPATING,
watch: N_WATCH,
+ mention: N_MENTION,
global: N_GLOBAL
}
end
def project_notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
+ [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION]
end
end
@@ -48,6 +50,10 @@ class Notification
target.notification_level == N_GLOBAL
end
+ def mention?
+ target.notification_level == N_MENTION
+ end
+
def level
target.notification_level
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 32b0145ca24..b0c379e6157 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -66,6 +66,7 @@ class Project < ActiveRecord::Base
has_one :slack_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy
+ has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
@@ -314,7 +315,8 @@ class Project < ActiveRecord::Base
end
def available_services_names
- %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox bamboo)
+ %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla
+ emails_on_push gemnasium slack pushover buildbox bamboo teamcity)
end
def gitlab_ci?
@@ -329,11 +331,6 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first
end
- # For compatibility with old code
- def code
- path
- end
-
def items_for(entity)
case entity
when 'issue' then
@@ -470,6 +467,10 @@ class Project < ActiveRecord::Base
protected_branches_names.include?(branch_name)
end
+ def developers_can_push_to_protected_branch?(branch_name)
+ protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push }
+ end
+
def forked?
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index a848d74044c..6ef4b210c56 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -35,7 +35,7 @@ class HipchatService < Service
{ type: 'text', name: 'token', placeholder: '' },
{ type: 'text', name: 'room', placeholder: '' },
{ type: 'text', name: 'server',
- placeholder: 'Leave blank for default. https://chat.hipchat.com' }
+ placeholder: 'Leave blank for default. https://hipchat.example.com' }
]
end
@@ -47,7 +47,7 @@ class HipchatService < Service
def gate
options = { api_version: 'v2' }
- options[:server_url] = server unless server.nil?
+ options[:server_url] = server unless server.blank?
@gate ||= HipChat::Client.new(token, options)
end
diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb
index 28204e5ea60..d0ddb1f162c 100644
--- a/app/models/project_services/slack_message.rb
+++ b/app/models/project_services/slack_message.rb
@@ -1,6 +1,14 @@
require 'slack-notifier'
class SlackMessage
+ attr_reader :after
+ attr_reader :before
+ attr_reader :commits
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :ref
+ attr_reader :username
+
def initialize(params)
@after = params.fetch(:after)
@before = params.fetch(:before)
@@ -23,14 +31,6 @@ class SlackMessage
private
- attr_reader :after
- attr_reader :before
- attr_reader :commits
- attr_reader :project_name
- attr_reader :project_url
- attr_reader :ref
- attr_reader :username
-
def message
if new_branch?
new_branch_message
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
new file mode 100644
index 00000000000..52b5862e4d1
--- /dev/null
+++ b/app/models/project_services/teamcity_service.rb
@@ -0,0 +1,116 @@
+class TeamcityService < CiService
+ include HTTParty
+
+ prop_accessor :teamcity_url, :build_type, :username, :password
+
+ validates :teamcity_url, presence: true,
+ format: { with: URI::regexp }, if: :activated?
+ validates :build_type, presence: true, if: :activated?
+ validates :username, presence: true,
+ if: ->(service) { service.password? }, if: :activated?
+ validates :password, presence: true,
+ if: ->(service) { service.username? }, if: :activated?
+
+ attr_accessor :response
+
+ after_save :compose_service_hook, if: :activated?
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.save
+ end
+
+ def title
+ 'JetBrains TeamCity CI'
+ end
+
+ def description
+ 'A continuous integration and build server'
+ end
+
+ def help
+ 'The build configuration in Teamcity must use the build format '\
+ 'number %build.vcs.number% '\
+ 'you will also want to configure monitoring of all branches so merge '\
+ 'requests build, that setting is in the vsc root advanced settings.'
+ end
+
+ def to_param
+ 'teamcity'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'teamcity_url',
+ placeholder: 'TeamCity root URL like https://teamcity.example.com' },
+ { type: 'text', name: 'build_type',
+ placeholder: 'Build configuration ID' },
+ { type: 'text', name: 'username',
+ placeholder: 'A user with permissions to trigger a manual build' },
+ { type: 'password', name: 'password' },
+ ]
+ end
+
+ def build_info(sha)
+ url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
+ "branch:unspecified:any,number:#{sha}")
+ auth = {
+ username: username,
+ password: password,
+ }
+ @response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
+ end
+
+ def build_page(sha)
+ build_info(sha) if @response.nil? || !@response.code
+
+ if @response.code != 200
+ # If actual build link can't be determined,
+ # send user to build summary page.
+ "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
+ else
+ # If actual build link is available, go to build result page.
+ built_id = @response['build']['id']
+ "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
+ "&buildTypeId=#{build_type}"
+ end
+ end
+
+ def commit_status(sha)
+ build_info(sha) if @response.nil? || !@response.code
+ return :error unless @response.code == 200 || @response.code == 404
+
+ status = if @response.code == 404
+ 'Pending'
+ else
+ @response['build']['status']
+ end
+
+ if status.include?('SUCCESS')
+ 'success'
+ elsif status.include?('FAILURE')
+ 'failed'
+ elsif status.include?('Pending')
+ 'pending'
+ else
+ :error
+ end
+ end
+
+ def execute(data)
+ auth = {
+ username: username,
+ password: password,
+ }
+
+ branch = data[:ref]
+
+ self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
+ body: "<build branchName=\"#{branch}\">"\
+ "<buildType id=\"#{build_type}\"/>"\
+ '</build>',
+ headers: { 'Content-type' => 'application/xml' },
+ basic_auth: auth
+ )
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 7faeef1b5b0..7dae318e780 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -106,6 +106,7 @@ class User < ActiveRecord::Base
has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
+ has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
#
@@ -564,4 +565,8 @@ class User < ActiveRecord::Base
namespaces += masters_groups
end
end
+
+ def oauth_authorized_tokens
+ Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
+ end
end
diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb
index 20b88d1510c..b5d90a74e15 100644
--- a/app/services/merge_requests/auto_merge_service.rb
+++ b/app/services/merge_requests/auto_merge_service.rb
@@ -5,15 +5,16 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService
- def execute(merge_request, current_user, commit_message)
+ def execute(merge_request, commit_message)
merge_request.lock_mr
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge
- notification.merge_mr(merge_request, current_user)
+ notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user)
- execute_project_hooks(merge_request)
+ create_note(merge_request)
+ execute_hooks(merge_request)
true
else
diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb
index 700a21ca011..9579573adf9 100644
--- a/app/services/merge_requests/base_merge_service.rb
+++ b/app/services/merge_requests/base_merge_service.rb
@@ -1,21 +1,10 @@
module MergeRequests
- class BaseMergeService
+ class BaseMergeService < MergeRequests::BaseService
private
- def notification
- NotificationService.new
- end
-
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
-
- def execute_project_hooks(merge_request)
- if merge_request.project
- hook_data = merge_request.to_hook_data(current_user)
- merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
- end
- end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 1475973e543..859c3f56b2b 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -13,7 +13,7 @@ module MergeRequests
merge_request.target_branch ||= merge_request.target_project.default_branch
unless merge_request.target_branch && merge_request.source_branch
- return build_failed(merge_request, "You must select source and target branches")
+ return build_failed(merge_request, nil)
end
# Generate suggested MR title based on source branch name
@@ -59,7 +59,7 @@ module MergeRequests
end
def build_failed(merge_request, message)
- merge_request.errors.add(:base, message)
+ merge_request.errors.add(:base, message) unless message.nil?
merge_request.compare_commits = []
merge_request.can_be_created = false
merge_request
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 680766140bd..5de7247d617 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -6,12 +6,13 @@ module MergeRequests
# Called when you do merge via command line and push code
# to target branch
class MergeService < BaseMergeService
- def execute(merge_request, current_user, commit_message)
+ def execute(merge_request, commit_message)
merge_request.merge
- notification.merge_mr(merge_request, current_user)
+ notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user)
- execute_project_hooks(merge_request)
+ create_note(merge_request)
+ execute_hooks(merge_request)
true
rescue
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index baf0936cc3d..a6705de61f2 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -32,7 +32,9 @@ module MergeRequests
merge_requests.uniq.select(&:source_project).each do |merge_request|
- MergeRequests::MergeService.new.execute(merge_request, @current_user, nil)
+ MergeRequests::MergeService.
+ new(merge_request.target_project, @current_user).
+ execute(merge_request, nil)
end
end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
new file mode 100644
index 00000000000..63431b82471
--- /dev/null
+++ b/app/services/notes/update_service.rb
@@ -0,0 +1,25 @@
+module Notes
+ class UpdateService < BaseService
+ def execute
+ note = project.notes.find(params[:note_id])
+ note.note = params[:note]
+ if note.save
+ notification_service.new_note(note)
+
+ # Skip system notes, like status changes and cross-references.
+ unless note.system
+ event_service.leave_note(note, note.author)
+
+ # Create a cross-reference note if this Note contains GFM that
+ # names an issue, merge request, or commit.
+ note.references.each do |mentioned|
+ Note.create_cross_reference_note(mentioned, note.noteable,
+ note.author, note.project)
+ end
+ end
+ end
+
+ note
+ end
+ end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index d1aadd741e1..fb8f812dad8 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -144,6 +144,10 @@ class NotificationService
# Merge project watchers
recipients = recipients.concat(project_watchers(note.project)).compact.uniq
+ # Reject mention users unless mentioned in comment
+ recipients = reject_mention_users(recipients - note.mentioned_users, note.project)
+ recipients = recipients + note.mentioned_users
+
# Reject mutes users
recipients = reject_muted_users(recipients, note.project)
@@ -285,13 +289,39 @@ class NotificationService
end
end
+ # Remove users with notification level 'Mentioned'
+ def reject_mention_users(users, project = nil)
+ users = users.to_a.compact.uniq
+
+ users.reject do |user|
+ next user.notification.mention? unless project
+
+ tm = project.project_members.find_by(user_id: user.id)
+
+ if !tm && project.group
+ tm = project.group.group_members.find_by(user_id: user.id)
+ end
+
+ # reject users who globally set mention notification and has no membership
+ next user.notification.mention? unless tm
+
+ # reject users who set mention notification in project
+ next true if tm.notification.mention?
+
+ # reject users who have N_MENTION in project and disabled in global settings
+ tm.notification.global? && user.notification.mention?
+ end
+ end
+
def new_resource_email(target, project, method)
if target.respond_to?(:participants)
recipients = target.participants
else
recipients = []
end
+
recipients = reject_muted_users(recipients, project)
+ recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(target.author)
@@ -302,6 +332,7 @@ class NotificationService
def close_resource_email(target, project, current_user, method)
recipients = reject_muted_users([target.author, target.assignee], project)
+ recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user)
@@ -320,6 +351,7 @@ class NotificationService
# reject users with disabled notifications
recipients = reject_muted_users(recipients, project)
+ recipients = reject_mention_users(recipients, project)
# Reject me from recipients if I reassign an item
recipients.delete(current_user)
@@ -331,6 +363,7 @@ class NotificationService
def reopen_resource_email(target, project, current_user, method, status)
recipients = reject_muted_users([target.author, target.assignee], project)
+ recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user)
diff --git a/app/services/oauth2/access_token_validation_service.rb b/app/services/oauth2/access_token_validation_service.rb
new file mode 100644
index 00000000000..95283489753
--- /dev/null
+++ b/app/services/oauth2/access_token_validation_service.rb
@@ -0,0 +1,41 @@
+module Oauth2::AccessTokenValidationService
+ # Results:
+ VALID = :valid
+ EXPIRED = :expired
+ REVOKED = :revoked
+ INSUFFICIENT_SCOPE = :insufficient_scope
+
+ class << self
+ def validate(token, scopes: [])
+ if token.expired?
+ return EXPIRED
+
+ elsif token.revoked?
+ return REVOKED
+
+ elsif !self.sufficent_scope?(token, scopes)
+ return INSUFFICIENT_SCOPE
+
+ else
+ return VALID
+ end
+ end
+
+ protected
+ # True if the token's scope is a superset of required scopes,
+ # or the required scopes is empty.
+ def sufficent_scope?(token, scopes)
+ if scopes.blank?
+ # if no any scopes required, the scopes of token is sufficient.
+ return true
+ else
+ # If there are scopes required, then check whether
+ # the set of authorized scopes is a superset of the set of required scopes
+ required_scopes = Set.new(scopes)
+ authorized_scopes = Set.new(token.scopes)
+
+ return authorized_scopes >= required_scopes
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 3672b623806..31226b7504b 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -12,12 +12,17 @@ module Projects
@project.visibility_level = default_features.visibility_level
end
- # Parametrize path for project
- #
- # Ex.
- # 'GitLab HQ'.parameterize => "gitlab-hq"
- #
- @project.path = @project.name.dup.parameterize unless @project.path.present?
+ # Set project name from path
+ if @project.name.present? && @project.path.present?
+ # if both name and path set - everything is ok
+ elsif @project.path.present?
+ # Set project name from path
+ @project.name = @project.path.dup
+ elsif @project.name.present?
+ # For compatibility - set path from name
+ # TODO: remove this in 8.0
+ @project.path = @project.name.dup.parameterize
+ end
# get namespace id
namespace_id = params[:namespace_id]
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index f4d7e25fd74..86a73200609 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -21,17 +21,6 @@
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
- else
- .form-group.group_name_holder
- = f.label :path, class: 'control-label' do
- %span Group path
- .col-sm-10
- = f.text_field :path, placeholder: "example-group", class: "form-control danger"
- .bs-callout.bs-callout-danger
- %ul
- %li Changing group path can have unintended side effects.
- %li Renaming group path will rename directory for all related projects
- %li It will change web url for access group and group projects.
- %li It will change the git path to repositories under this group.
.form-actions
= f.submit 'Save changes', class: "btn btn-primary"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
diff --git a/app/views/admin/keys/show.html.haml b/app/views/admin/keys/show.html.haml
new file mode 100644
index 00000000000..5b23027b3ab
--- /dev/null
+++ b/app/views/admin/keys/show.html.haml
@@ -0,0 +1 @@
+= render "profiles/keys/key_details", admin: true
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 29717aedd80..88e71aa170f 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -20,6 +20,8 @@
%a{"data-toggle" => "tab", href: "#groups"} Groups
%li
%a{"data-toggle" => "tab", href: "#projects"} Projects
+ %li
+ %a{"data-toggle" => "tab", href: "#ssh-keys"} SSH keys
.tab-content
#account.tab-pane.active
@@ -217,3 +219,5 @@
- if tm.respond_to? :project
= link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
%i.fa.fa-times
+ #ssh-keys.tab-pane
+ = render 'profiles/keys/key_table', admin: true
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
index 5460cf56f22..ddabd6e0d52 100644
--- a/app/views/dashboard/_groups.html.haml
+++ b/app/views/dashboard/_groups.html.haml
@@ -1,10 +1,11 @@
.panel.panel-default
.panel-heading.clearfix
- = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- - if current_user.can_create_group?
- = link_to new_group_path, class: "btn btn-new pull-right" do
- %i.fa.fa-plus
- New group
+ .input-group
+ = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
+ - if current_user.can_create_group?
+ .input-group-addon
+ = link_to new_group_path, class: "" do
+ %strong New group
%ul.well-list.dash-list
- groups.each do |group|
%li.group-row
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index 3598425777f..304aa17eba8 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -1,10 +1,11 @@
.panel.panel-default
.panel-heading.clearfix
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- - if current_user.can_create_project?
- = link_to new_project_path, class: "btn btn-new pull-right" do
- %i.fa.fa-plus
- New project
+ .input-group
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
+ - if current_user.can_create_project?
+ .input-group-addon
+ = link_to new_project_path, class: "" do
+ %strong New project
%ul.well-list.dash-list
- projects.each do |project|
diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml
index b65e882e693..0e990ccfab4 100644
--- a/app/views/dashboard/_projects_filter.html.haml
+++ b/app/views/dashboard/_projects_filter.html.haml
@@ -1,55 +1,100 @@
-%fieldset
- %ul.nav.nav-pills.nav-stacked
- = nav_tab :scope, nil do
- = link_to projects_dashboard_filter_path(scope: nil) do
- All
- %span.pull-right
- = current_user.authorized_projects.count
- = nav_tab :scope, 'personal' do
- = link_to projects_dashboard_filter_path(scope: 'personal') do
- Personal
- %span.pull-right
- = current_user.personal_projects.count
- = nav_tab :scope, 'joined' do
- = link_to projects_dashboard_filter_path(scope: 'joined') do
- Joined
- %span.pull-right
- = current_user.authorized_projects.joined(current_user).count
- = nav_tab :scope, 'owned' do
- = link_to projects_dashboard_filter_path(scope: 'owned') do
- Owned
- %span.pull-right
- = current_user.owned_projects.count
+.dash-projects-filters.append-bottom-20
+ .pull-left.append-right-20
+ %ul.nav.nav-pills.nav-compact
+ = nav_tab :scope, nil do
+ = link_to projects_dashboard_filter_path(scope: nil) do
+ All
+ = nav_tab :scope, 'personal' do
+ = link_to projects_dashboard_filter_path(scope: 'personal') do
+ Personal
+ = nav_tab :scope, 'joined' do
+ = link_to projects_dashboard_filter_path(scope: 'joined') do
+ Joined
+ = nav_tab :scope, 'owned' do
+ = link_to projects_dashboard_filter_path(scope: 'owned') do
+ Owned
-%fieldset
- %legend Visibility
- %ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter
- - Gitlab::VisibilityLevel.values.each do |level|
- %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(visibility_level: level) do
- = visibility_level_icon(level)
- = visibility_level_label(level)
+ .dropdown.inline.append-right-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-globe
+ %span.light Visibility:
+ - if params[:visibility_level].present?
+ = visibility_level_label(params[:visibility_level].to_i)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to projects_dashboard_filter_path(visibility_level: nil) do
+ Any
+ - Gitlab::VisibilityLevel.values.each do |level|
+ %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
+ = link_to projects_dashboard_filter_path(visibility_level: level) do
+ = visibility_level_icon(level)
+ = visibility_level_label(level)
-- if @groups.present?
- %fieldset
- %legend Groups
- %ul.nav.nav-pills.nav-stacked.nav-small
- - @groups.each do |group|
- %li{ class: (group.name == params[:group]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(group: group.name) do
- %i.fa.fa-folder-o
- = group.name
- %small.pull-right
- = group.projects.count
+ - if @groups.present?
+ .dropdown.inline.append-right-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-group
+ %span.light Group:
+ - if params[:group].present?
+ = Group.find_by(name: params[:group]).name
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to projects_dashboard_filter_path(group: nil) do
+ Any
+ - @groups.each do |group|
+ %li{ class: (group.name == params[:group]) ? 'active' : 'light' }
+ = link_to projects_dashboard_filter_path(group: group.name) do
+ = group.name
+ %small.pull-right
+ = group.projects.count
-- if @tags.present?
- %fieldset
- %legend Tags
- %ul.nav.nav-pills.nav-stacked.nav-small
- - @tags.each do |tag|
- %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do
- %i.fa.fa-tag
- = tag.name
+ - if @tags.present?
+ .dropdown.inline.append-right-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-tags
+ %span.light Tags:
+ - if params[:tag].present?
+ = params[:tag]
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to projects_dashboard_filter_path(tag: nil) do
+ Any
+
+ - @tags.each do |tag|
+ %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
+ = link_to projects_dashboard_filter_path(tag: tag.name) do
+ %i.fa.fa-tag
+ = tag.name
+
+ .pull-right
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = @sort.humanize
+ - else
+ Name
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to projects_dashboard_filter_path(sort: nil) do
+ Name
+ = link_to projects_dashboard_filter_path(sort: 'newest') do
+ = sort_title_recently_created
+ = link_to projects_dashboard_filter_path(sort: 'oldest') do
+ = sort_title_oldest_created
+ = link_to projects_dashboard_filter_path(sort: 'recently_updated') do
+ = sort_title_recently_updated
+ = link_to projects_dashboard_filter_path(sort: 'last_updated') do
+ = sort_title_oldest_updated
diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml
index add9eb7fa29..a980f495427 100644
--- a/app/views/dashboard/_sidebar.html.haml
+++ b/app/views/dashboard/_sidebar.html.haml
@@ -15,11 +15,4 @@
= render "groups", groups: @groups
.prepend-top-20
- %span.rss-icon
- = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
- %strong
- %i.fa.fa-rss
- News Feed
-
-%hr
-= render 'shared/promo'
+ = render 'shared/promo'
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index 5d133cd8285..f78ce69ef9e 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -4,7 +4,7 @@
%div
.dashboard-intro-icon
%i.fa.fa-bookmark-o
- %div
+ .dashboard-intro-text
%p.slead
You don't have access to any projects right now.
%br
@@ -24,7 +24,7 @@
%div
.dashboard-intro-icon
%i.fa.fa-users
- %div
+ .dashboard-intro-text
%p.slead
You can create a group for several dependent projects.
%br
@@ -38,7 +38,7 @@
%div
.dashboard-intro-icon
%i.fa.fa-globe
- %div
+ .dashboard-intro-text
%p.slead
There are
%strong= @publicish_project_count
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index 66381310221..72e9e361dc3 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,5 +1,5 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html"
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 7c1f1ddbb80..db19a46cb26 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -5,10 +5,6 @@
List all issues from all projects you have access to.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'issue'
- .col-md-9
- = render 'shared/issues'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index c96584c7b6b..97a42461b4e 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -5,10 +5,6 @@
%p.light
List all merge requests from all projects you have access to.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'merge_request'
- .col-md-9
- = render 'shared/merge_requests'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/merge_requests'
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 5b7835b097b..944441669e7 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -1,74 +1,54 @@
%h3.page-title
My Projects
-.pull-right
- .dropdown.inline
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %span.light sort:
- - if @sort.present?
- = @sort.humanize
- - else
- Name
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to projects_dashboard_filter_path(sort: nil) do
- Name
- = link_to projects_dashboard_filter_path(sort: 'newest') do
- = sort_title_recently_created
- = link_to projects_dashboard_filter_path(sort: 'oldest') do
- = sort_title_oldest_created
- = link_to projects_dashboard_filter_path(sort: 'recently_updated') do
- = sort_title_recently_updated
- = link_to projects_dashboard_filter_path(sort: 'last_updated') do
- = sort_title_oldest_updated
%p.light
All projects you have access to are listed here. Public projects are not included here unless you are a member
%hr
-.row
- .col-md-3.hidden-sm.hidden-xs.side-filters
- = render "projects_filter"
- .col-md-9
- %ul.bordered-list.my-projects.top-list
- - @projects.each do |project|
- %li.my-project-row
- %h4.project-title
- .project-access-icon
- = visibility_level_icon(project.visibility_level)
- = link_to project_path(project), class: dom_class(project) do
- = project.name_with_namespace
+.side-filters
+ = render "projects_filter"
+.dash-projects
+ %ul.bordered-list.my-projects.top-list
+ - @projects.each do |project|
+ %li.my-project-row
+ %h4.project-title
+ .project-access-icon
+ = visibility_level_icon(project.visibility_level)
+ = link_to project_path(project), class: dom_class(project) do
+ = project.name_with_namespace
- - if current_user.can_leave_project?(project)
- .pull-right
- = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do
- %i.fa.fa-sign-out
- Leave
+ - if project.forked_from_project
+ &nbsp;
+ %small
+ %i.fa.fa-code-fork
+ Forked from:
+ = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
- - if project.forked_from_project
- %small.pull-right
- %i.fa.fa-code-fork
- Forked from:
- = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
- .project-info
+ - if current_user.can_leave_project?(project)
.pull-right
- - if project.archived?
- %span.label
- %i.fa.fa-archive
- Archived
- - project.tags.each do |tag|
- %span.label.label-info
- %i.fa.fa-tag
- = tag.name
- - if project.description.present?
- %p= truncate project.description, length: 100
- .last-activity
- %span.light Last activity:
- %span.date= project_last_activity(project)
+ = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do
+ %i.fa.fa-sign-out
+ Leave
+ .project-info
+ .pull-right
+ - if project.archived?
+ %span.label
+ %i.fa.fa-archive
+ Archived
+ - project.tags.each do |tag|
+ %span.label.label-info
+ %i.fa.fa-tag
+ = tag.name
+ - if project.description.present?
+ %p= truncate project.description, length: 100
+ .last-activity
+ %span.light Last activity:
+ %span.date= project_last_activity(project)
- - if @projects.blank?
- %li
- .nothing-here-block There are no projects here.
- .bottom
- = paginate @projects, theme: "gitlab"
+
+ - if @projects.blank?
+ %li
+ .nothing-here-block There are no projects here.
+ .bottom
+ = paginate @projects, theme: "gitlab"
diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder
index 70ac66f8016..da631ecb33e 100644
--- a/app/views/dashboard/show.atom.builder
+++ b/app/views/dashboard/show.atom.builder
@@ -1,5 +1,5 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_url, rel: "alternate", type: "text/html"
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
index 8d17f39eba2..8d17f39eba2 100755..100644
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index b8af1b8693a..b8af1b8693a 100755..100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index e819847e5ea..ab9085f0ba7 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -2,7 +2,7 @@
= f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus"
= f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable?
- .clearfix.append-bottom-10
+ .remember-me
%label.checkbox.remember_me{for: "user_remember_me"}
= f.check_box :remember_me
%span Remember me
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index bf8a593c254..e986989a728 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,5 +1,4 @@
= form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- %br/
= button_tag "LDAP Sign in", class: "btn-save btn"
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index ca7e9570b43..5e31d8e818a 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,43 +1,15 @@
-.login-box
- .login-heading
- %h3 Sign in
- .login-body
- - if ldap_enabled?
- %ul.nav.nav-tabs
- - @ldap_servers.each_with_index do |server, i|
- %li{class: (:active if i.zero?)}
- = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
- - if gitlab_config.signin_enabled
- %li
- = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
- .tab-content
- - @ldap_servers.each_with_index do |server, i|
- %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
- = render 'devise/sessions/new_ldap', provider: server['provider_name']
- - if gitlab_config.signin_enabled
- %div#tab-signin.tab-pane
- = render 'devise/sessions/new_base'
+%div
+ = render 'devise/shared/signin_box'
- - elsif gitlab_config.signin_enabled
- = render 'devise/sessions/new_base'
- - else
- %div
- No authentication methods configured.
+ - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
+ .prepend-top-20
+ = render 'devise/shared/oauth_box'
- = render 'devise/sessions/oauth_providers' if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
+ - if gitlab_config.signup_enabled
+ .prepend-top-20
+ = render 'devise/shared/signup_box'
- .login-footer
- - if gitlab_config.signup_enabled
- %p
- %span.light
- Don't have an account?
- %strong
- = link_to "Sign up", new_registration_path(resource_name)
-
- %p
- %span.light Did not receive confirmation email?
- = link_to "Send again", new_confirmation_path(resource_name)
-
- - if extra_config.has_key?('sign_in_text')
- %hr
- = markdown(extra_config.sign_in_text)
+.clearfix.prepend-top-20
+ %p
+ %span.light Did not receive confirmation email?
+ = link_to "Send again", new_confirmation_path(resource_name)
diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/shared/_oauth_box.html.haml
index 8d6aaefb9ff..c2e1373de30 100644
--- a/app/views/devise/sessions/_oauth_providers.html.haml
+++ b/app/views/devise/shared/_oauth_box.html.haml
@@ -1,7 +1,7 @@
- providers = additional_providers
- if providers.present?
- .bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'}
- %span Sign in with: &nbsp;
+ .login-box{:'data-no-turbolink' => 'data-no-turbolink'}
+ %span Sign in with &nbsp;
- providers.each do |provider|
%span
- if default_providers.include?(provider)
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
new file mode 100644
index 00000000000..3f2161ff6a4
--- /dev/null
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -0,0 +1,25 @@
+.login-box
+ .login-heading
+ %h3 Sign in
+ .login-body
+ - if ldap_enabled?
+ %ul.nav.nav-tabs
+ - @ldap_servers.each_with_index do |server, i|
+ %li{class: (:active if i.zero?)}
+ = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
+ - if gitlab_config.signin_enabled
+ %li
+ = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
+ .tab-content
+ - @ldap_servers.each_with_index do |server, i|
+ %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
+ = render 'devise/sessions/new_ldap', provider: server['provider_name']
+ - if gitlab_config.signin_enabled
+ %div#tab-signin.tab-pane
+ = render 'devise/sessions/new_base'
+
+ - elsif gitlab_config.signin_enabled
+ = render 'devise/sessions/new_base'
+ - else
+ %div
+ No authentication methods configured.
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
new file mode 100644
index 00000000000..5709c661288
--- /dev/null
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -0,0 +1,17 @@
+.login-box
+ .login-heading
+ %h3 Sign up
+ .login-body
+ = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
+ .devise-errors
+ = devise_error_messages!
+ %div
+ = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
+ %div
+ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
+ %div
+ = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
+ .form-group#password-strength
+ = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
+ %div
+ = f.submit "Sign up", class: "btn-create btn"
diff --git a/app/views/doorkeeper/applications/_delete_form.html.haml b/app/views/doorkeeper/applications/_delete_form.html.haml
new file mode 100644
index 00000000000..bf8098f38d0
--- /dev/null
+++ b/app/views/doorkeeper/applications/_delete_form.html.haml
@@ -0,0 +1,4 @@
+- submit_btn_css ||= 'btn btn-link btn-remove btn-small'
+= form_tag oauth_application_path(application) do
+ %input{:name => "_method", :type => "hidden", :value => "delete"}/
+ = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css \ No newline at end of file
diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml
new file mode 100644
index 00000000000..a5fec2fabdb
--- /dev/null
+++ b/app/views/doorkeeper/applications/_form.html.haml
@@ -0,0 +1,24 @@
+= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f|
+ - if application.errors.any?
+ .alert.alert-danger{"data-alert" => ""}
+ %p Whoops! Check your form for possible errors
+ = content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do
+ = f.label :name, class: 'col-sm-2 control-label'
+ .col-sm-10
+ = f.text_field :name, class: 'form-control'
+ = doorkeeper_errors_for application, :name
+ = content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do
+ = f.label :redirect_uri, class: 'col-sm-2 control-label'
+ .col-sm-10
+ = f.text_area :redirect_uri, class: 'form-control'
+ = doorkeeper_errors_for application, :redirect_uri
+ %span.help-block
+ Use one line per URI
+ - if Doorkeeper.configuration.native_redirect_uri
+ %span.help-block
+ Use
+ %code= Doorkeeper.configuration.native_redirect_uri
+ for local tests
+ .form-actions
+ = f.submit 'Submit', class: "btn btn-primary wide"
+ = link_to "Cancel", applications_profile_path, class: "btn btn-default"
diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml
new file mode 100644
index 00000000000..61584eb9c49
--- /dev/null
+++ b/app/views/doorkeeper/applications/edit.html.haml
@@ -0,0 +1,2 @@
+%h3.page-title Edit application
+= render 'form', application: @application \ No newline at end of file
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
new file mode 100644
index 00000000000..e5be4b4bcac
--- /dev/null
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -0,0 +1,16 @@
+%h3.page-title Your applications
+%p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
+%table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td= application.redirect_uri
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link'
+ %td= render 'delete_form', application: application \ No newline at end of file
diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml
new file mode 100644
index 00000000000..655845e4af5
--- /dev/null
+++ b/app/views/doorkeeper/applications/new.html.haml
@@ -0,0 +1,2 @@
+%h3.page-title New application
+= render 'form', application: @application \ No newline at end of file
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
new file mode 100644
index 00000000000..82e78b4af13
--- /dev/null
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -0,0 +1,26 @@
+%h3.page-title
+ Application: #{@application.name}
+
+
+%table.table
+ %tr
+ %td
+ Application Id
+ %td
+ %code#application_id= @application.uid
+ %tr
+ %td
+ Secret:
+ %td
+ %code#secret= @application.secret
+
+ %tr
+ %td
+ Callback url
+ %td
+ - @application.redirect_uri.split.each do |uri|
+ %div
+ %span.monospace= uri
+.form-actions
+ = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left'
+ = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml
new file mode 100644
index 00000000000..7561ec85ed9
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/error.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title An error has occurred
+%main{:role => "main"}
+ %pre= @pre_auth.error_response.body[:error_description] \ No newline at end of file
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
new file mode 100644
index 00000000000..15f9ee266c1
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -0,0 +1,28 @@
+%h3.page-title Authorize required
+%main{:role => "main"}
+ %p.h4
+ Authorize
+ %strong.text-info= @pre_auth.client.name
+ to use your account?
+ - if @pre_auth.scopes
+ #oauth-permissions
+ %p This application will be able to:
+ %ul.text-info
+ - @pre_auth.scopes.each do |scope|
+ %li= t scope, scope: [:doorkeeper, :scopes]
+ %hr/
+ .actions
+ = form_tag oauth_authorization_path, method: :post do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = submit_tag "Authorize", class: "btn btn-success wide pull-left"
+ = form_tag oauth_authorization_path, method: :delete do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = submit_tag "Deny", class: "btn btn-danger prepend-left-10" \ No newline at end of file
diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml
new file mode 100644
index 00000000000..9a402007194
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/show.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title Authorization code:
+%main{:role => "main"}
+ %code#authorization_code= params[:code] \ No newline at end of file
diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
new file mode 100644
index 00000000000..5cbb4a70c19
--- /dev/null
+++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
@@ -0,0 +1,4 @@
+- submit_btn_css ||= 'btn btn-link btn-remove'
+= form_tag oauth_authorized_application_path(application) do
+ %input{:name => "_method", :type => "hidden", :value => "delete"}/
+ = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-small' \ No newline at end of file
diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml
new file mode 100644
index 00000000000..814cdc987ef
--- /dev/null
+++ b/app/views/doorkeeper/authorized_applications/index.html.haml
@@ -0,0 +1,16 @@
+%header.page-header
+ %h1 Your authorized applications
+%main{:role => "main"}
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Application
+ %th Created At
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr
+ %td= application.name
+ %td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
+ %td= render 'delete_form', application: application \ No newline at end of file
diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml
deleted file mode 100644
index 393be3f1d12..00000000000
--- a/app/views/groups/_filter.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-= form_tag group_filter_path(entity), method: 'get' do
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if (params[:status] == 'active' || !params[:status]))}
- = link_to group_filter_path(entity, status: 'active') do
- Active
- %li{class: ("active" if params[:status] == 'closed')}
- = link_to group_filter_path(entity, status: 'closed') do
- Closed
- %li{class: ("active" if params[:status] == 'all')}
- = link_to group_filter_path(entity, status: 'all') do
- All
diff --git a/app/views/groups/_settings_nav.html.haml b/app/views/groups/_settings_nav.html.haml
index ec1fb4a2c00..35180792a0d 100644
--- a/app/views/groups/_settings_nav.html.haml
+++ b/app/views/groups/_settings_nav.html.haml
@@ -1,10 +1,11 @@
-%ul.nav.nav-pills.nav-stacked.nav-stacked-menu
+%ul.sidebar-subnav
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group) do
%i.fa.fa-pencil-square-o
- Group
+ %span
+ Group
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group) do
%i.fa.fa-folder
- Projects
-
+ %span
+ Projects
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index eb24fd65d9e..a963c59586e 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,41 +1,37 @@
-.row
- .col-md-2
- = render 'settings_nav'
- .col-md-10
- .panel.panel-default
- .panel-heading
- %strong= @group.name
- group settings:
- .panel-body
- = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- - if @group.errors.any?
- .alert.alert-danger
- %span= @group.errors.full_messages.first
- = render 'shared/group_form', f: f
+.panel.panel-default
+ .panel-heading
+ %strong= @group.name
+ group settings:
+ .panel-body
+ = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
+ - if @group.errors.any?
+ .alert.alert-danger
+ %span= @group.errors.full_messages.first
+ = render 'shared/group_form', f: f
- .form-group
- .col-sm-2
- .col-sm-10
- = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160'
- %p.light
- - if @group.avatar?
- You can change your group avatar here
- - else
- You can upload a group avatar here
- = render 'shared/choose_group_avatar_button', f: f
- - if @group.avatar?
- %hr
- = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160'
+ %p.light
+ - if @group.avatar?
+ You can change your group avatar here
+ - else
+ You can upload a group avatar here
+ = render 'shared/choose_group_avatar_button', f: f
+ - if @group.avatar?
+ %hr
+ = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
- .form-actions
- = f.submit 'Save group', class: "btn btn-save"
+ .form-actions
+ = f.submit 'Save group', class: "btn btn-save"
- .panel.panel-danger
- .panel-heading Remove group
- .panel-body
- %p
- Removing group will cause all child projects and resources to be removed.
- %br
- %strong Removed group can not be restored!
+.panel.panel-danger
+ .panel-heading Remove group
+ .panel-body
+ %p
+ Removing group will cause all child projects and resources to be removed.
+ %br
+ %strong Removed group can not be restored!
- = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
+ = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 1932ba2f644..6c0d89c4e7c 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -9,10 +9,6 @@
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'issue'
- .col-md-9
- = render 'shared/issues'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 86d5acdaa32..1ad74905636 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -8,10 +8,6 @@
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'merge_request'
- .col-md-9
- = render 'shared/merge_requests'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 2727525f070..7f0b2832cac 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -9,42 +9,38 @@
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'groups/filter', entity: 'milestone'
- .col-md-9
- .panel.panel-default
- %ul.well-list
- - if @group_milestones.blank?
- %li
- .nothing-here-block No milestones to show
- - else
- - @group_milestones.each do |milestone|
- %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .pull-right
- - if can?(current_user, :manage_group, @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-small 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-small btn-close"
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+= render 'shared/milestones_filter'
+.milestones
+ .panel.panel-default
+ %ul.well-list
+ - if @group_milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
+ - else
+ - @group_milestones.each do |milestone|
+ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
+ .pull-right
+ - if can?(current_user, :manage_group, @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-small 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-small btn-close"
+ %h4
+ = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+ %div
%div
- %div
- = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
- = pluralize milestone.issue_count, 'Issue'
- &nbsp;
- = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
- = pluralize milestone.merge_requests_count, 'Merge Request'
- &nbsp;
- %span.light #{milestone.percent_complete}% complete
- .progress.progress-info
- .progress-bar{style: "width: #{milestone.percent_complete}%;"}
- %div
- %br
- - milestone.projects.each do |project|
- %span.label.label-default
- = project.name
- = paginate @group_milestones, theme: "gitlab"
+ = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
+ = pluralize milestone.issue_count, 'Issue'
+ &nbsp;
+ = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
+ = pluralize milestone.merge_requests_count, 'Merge Request'
+ &nbsp;
+ %span.light #{milestone.percent_complete}% complete
+ .progress.progress-info
+ .progress-bar{style: "width: #{milestone.percent_complete}%;"}
+ %div
+ %br
+ - milestone.projects.each do |project|
+ %span.label.label-default
+ = project.name
+ = paginate @group_milestones, theme: "gitlab"
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 411d1822be0..7bcac56c37b 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,4 +1,9 @@
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" }
+ - if @group_milestone.closed?
+ Closed
+ - else
+ Open
Milestone #{@group_milestone.title}
.pull-right
- if can?(current_user, :manage_group, @group)
@@ -7,46 +12,41 @@
- else
= link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen"
+%hr
- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active?
.alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now.
-.back-link
- = link_to group_milestones_path(@group) do
- &larr; To milestones list
-
-.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" }
- .state.clearfix
- .state-label
- - if @group_milestone.closed?
- Closed
- - else
- Open
-
- %h4.title
- = gfm escape_once(@group_milestone.title)
-
- .description
- - @group_milestone.milestones.each do |milestone|
- %hr
- %h4
- = link_to "#{milestone.project.name} - #{milestone.title}", project_milestone_path(milestone.project, milestone)
- %span.pull-right= milestone.expires_at
+.description
+%table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - @group_milestone.milestones.each do |milestone|
+ %tr
+ %td
+ = link_to "#{milestone.project.name}", project_milestone_path(milestone.project, milestone)
+ %td
+ = milestone.issues.opened.count
+ %td
- if milestone.closed?
- %span.label.label-danger #{milestone.state}
- = preserve do
- - if milestone.description.present?
- = milestone.description
-
- .context
- %p
- Progress:
- #{@group_milestone.closed_items_count} closed
- &ndash;
- #{@group_milestone.open_items_count} open
+ Closed
+ - else
+ Open
+ %td
+ = milestone.expires_at
- .progress.progress-info
- .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"}
+.context
+ %p.lead
+ Progress:
+ #{@group_milestone.closed_items_count} closed
+ &ndash;
+ #{@group_milestone.open_items_count} open
+ .progress.progress-info
+ .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"}
%ul.nav.nav-tabs
%li.active
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 65a66355c56..40c81e8cd5b 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,29 +1,25 @@
-.row
- .col-md-2
- = render 'settings_nav'
- .col-md-10
- .panel.panel-default
- .panel-heading
- %strong= @group.name
- projects:
- - if can? current_user, :manage_group, @group
- .panel-head-actions
- = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do
- %i.fa.fa-plus
- New Project
- %ul.well-list
- - @projects.each do |project|
- %li
- .list-item-name
- = visibility_level_icon(project.visibility_level)
- %strong= link_to project.name_with_namespace, project
- %span.label.label-gray
- = repository_size(project)
- .pull-right
- = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove"
- - if @projects.blank?
- .nothing-here-block This group has no projects yet
+.panel.panel-default
+ .panel-heading
+ %strong= @group.name
+ projects:
+ - if can? current_user, :manage_group, @group
+ .panel-head-actions
+ = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do
+ %i.fa.fa-plus
+ New Project
+ %ul.well-list
+ - @projects.each do |project|
+ %li
+ .list-item-name
+ = visibility_level_icon(project.visibility_level)
+ %strong= link_to project.name_with_namespace, project
+ %span.label.label-gray
+ = repository_size(project)
+ .pull-right
+ = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
+ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
+ = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove"
+ - if @projects.blank?
+ .nothing-here-block This group has no projects yet
- = paginate @projects, theme: "gitlab"
+= paginate @projects, theme: "gitlab"
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index e765ea8338d..c78bd1bd263 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -1,5 +1,5 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Group feed - #{@group.name}"
xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml"
xml.link href: group_path(@group), rel: "alternate", type: "text/html"
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index d876e87852c..81f0e1dd2d8 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,37 +1,22 @@
.dashboard
- %section.activities.col-md-8.hidden-sm.hidden-xs
- - if current_user
- = render "events/event_last_push", event: @last_push
- = link_to dashboard_path, class: 'btn btn-tiny' do
- &larr; To dashboard
- &nbsp;
- %span.cgray
- Currently you are only seeing events from the
- = @group.name
- group
- %hr
- = render 'shared/event_filter'
- - if @events.any?
- .content_list
- - else
- .nothing-here-block Project activity will be displayed here
- = spinner
- %aside.side.col-md-4
- .light-well.append-bottom-20
- = image_tag group_icon(@group.path), class: "avatar s90"
- .clearfix.light
- %h3.page-title
- = @group.name
- - if @group.description.present?
- %p
- = escaped_autolink(@group.description)
- = render "projects", projects: @projects
- - if current_user
- .prepend-top-20
- = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
- %strong
- %i.fa.fa-rss
- News Feed
-
- %hr
- = render 'shared/promo'
+ %div
+ = image_tag group_icon(@group.path), class: "avatar s90"
+ .clearfix
+ %h2
+ = @group.name
+ - if @group.description.present?
+ %p
+ = escaped_autolink(@group.description)
+ %hr
+ .row
+ %section.activities.col-md-8.hidden-sm.hidden-xs
+ - if current_user
+ = render "events/event_last_push", event: @last_push
+ = render 'shared/event_filter'
+ - if @events.any?
+ .content_list
+ - else
+ .nothing-here-block Project activity will be displayed here
+ = spinner
+ %aside.side.col-md-4
+ = render "projects", projects: @projects
diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml
index e7d477c225e..e589e34dd23 100644
--- a/app/views/layouts/_broadcast.html.haml
+++ b/app/views/layouts/_broadcast.html.haml
@@ -2,3 +2,7 @@
.broadcast-message{ style: broadcast_styling(broadcast_message) }
%i.fa.fa-bullhorn
= broadcast_message.message
+ :css
+ .sidebar-wrapper .nav-sidebar {
+ margin-top: 58px;
+ }
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
index 5dcaee2fa02..e98b8ec631d 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -2,10 +2,8 @@
.navbar-inner
.container
%div.app_logo
- %span.separator
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
%h1 GITLAB
- %span.separator
%h1.title= title
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
@@ -46,3 +44,5 @@
%li.hidden-xs
= link_to current_user, class: "profile-pic", id: 'profile-pic' do
= image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
new file mode 100644
index 00000000000..621365fa6aa
--- /dev/null
+++ b/app/views/layouts/_page.html.haml
@@ -0,0 +1,16 @@
+- if defined?(sidebar)
+ .page-with-sidebar
+ .sidebar-wrapper
+ = render(sidebar)
+ .content-wrapper
+ .container-fluid
+ .content
+ = render "layouts/flash"
+ .clearfix
+ = yield
+- else
+ .container.navless-container
+ .content
+ = yield
+
+= yield :embedded_scripts
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
index 9bfc14d16c1..1d5bbb2aade 100644
--- a/app/views/layouts/_public_head_panel.html.haml
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -12,11 +12,13 @@
%span.sr-only Toggle navigation
%i.fa.fa-bars
- .pull-right.hidden-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new'
+ - unless current_controller?('sessions')
+ .pull-right.hidden-xs
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new'
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.visible-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav
+ %li.visible-xs
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 207ab22f4c7..fb62d5fea0a 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,13 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
- %body{class: "#{app_theme} admin", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Admin area"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/admin'
- .container
- .content
- = render "layouts/flash"
- = yield
- = yield :embedded_scripts
+ = render 'layouts/page', sidebar: 'layouts/nav/admin'
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 7d0819aa93e..d40c9753b10 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,12 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Dashboard"
- %body{class: "#{app_theme} application", :'data-page' => body_data_page }
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page }
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Dashboard"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/dashboard'
- .container
- .content
- = render "layouts/flash"
- = yield
+ = render 'layouts/page', sidebar: 'layouts/nav/dashboard'
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 06de03eadad..8b3872e535d 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,35 +1,33 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body.ui_basic.login-page
- .container
- .content
- .login-title
- %h1= brand_title
- %hr
- .container
+ %body.ui_mars.login-page.application
+ = render "layouts/broadcast"
+ = render "layouts/public_head_panel", title: ''
+ .container.navless-container
.content
- = render "layouts/flash"
- .row
- .col-md-7.brand-holder
+ - unless redirect_from_root?
+ = render "layouts/flash"
+ .row.prepend-top-20
+ .col-sm-5.pull-right
+ = yield
+ .col-sm-7.brand-holder.pull-left
+ %h1
+ = brand_title
- if brand_item
- .brand-image
- = brand_image
- .brand_text
- = brand_text
+ = brand_image
+ = brand_text
- else
- .brand-image.default-brand-image.hidden-sm.hidden-xs
- = image_tag 'brand_logo.png'
- .brand_text.hidden-xs
- %h2 Open source software to collaborate on code
+ %h3 Open source software to collaborate on code
- %p.lead
- Manage git repositories with fine grained access controls that keep your code secure.
- Perform code reviews and enhance collaboration with merge requests.
- Each project can also have an issue tracker and a wiki.
+ %p
+ Manage git repositories with fine grained access controls that keep your code secure.
+ Perform code reviews and enhance collaboration with merge requests.
+ Each project can also have an issue tracker and a wiki.
+
+ - if extra_config.has_key?('sign_in_text')
+ = markdown(extra_config.sign_in_text)
- .col-md-5
- = yield
%hr
.container
.footer-links
diff --git a/app/views/layouts/doorkeeper/admin.html.haml b/app/views/layouts/doorkeeper/admin.html.haml
new file mode 100644
index 00000000000..bd9adfab66d
--- /dev/null
+++ b/app/views/layouts/doorkeeper/admin.html.haml
@@ -0,0 +1,22 @@
+!!!
+%html
+ %head
+ %meta{:charset => "utf-8"}
+ %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}
+ %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}
+ %title Doorkeeper
+ = stylesheet_link_tag "doorkeeper/admin/application"
+ = csrf_meta_tags
+ %body
+ .navbar.navbar-inverse.navbar-fixed-top{:role => "navigation"}
+ .container
+ .navbar-header
+ = link_to 'OAuth2 Provider', oauth_applications_path, class: 'navbar-brand'
+ %ul.nav.navbar-nav
+ = content_tag :li, class: "#{'active' if request.path == oauth_applications_path}" do
+ = link_to 'Applications', oauth_applications_path
+ .container
+ - if flash[:notice].present?
+ .alert.alert-info
+ = flash[:notice]
+ = yield \ No newline at end of file
diff --git a/app/views/layouts/doorkeeper/application.html.haml b/app/views/layouts/doorkeeper/application.html.haml
new file mode 100644
index 00000000000..e5f37fad1f4
--- /dev/null
+++ b/app/views/layouts/doorkeeper/application.html.haml
@@ -0,0 +1,15 @@
+!!!
+%html
+ %head
+ %title OAuth authorize required
+ %meta{:charset => "utf-8"}
+ %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}
+ %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}
+ = stylesheet_link_tag "doorkeeper/application"
+ = csrf_meta_tags
+ %body
+ #container
+ - if flash[:notice].present?
+ .alert.alert-info
+ = flash[:notice]
+ = yield \ No newline at end of file
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 16df9c10fbb..e7d875173e6 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Error"
- %body{class: "#{app_theme} application"}
+ %body{class: "#{app_theme} #{theme_type} application"}
= render "layouts/head_panel", title: "" if current_user
.container.navless-container
= render "layouts/flash"
diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml
index d023846c5eb..9813d846542 100644
--- a/app/views/layouts/explore.html.haml
+++ b/app/views/layouts/explore.html.haml
@@ -2,7 +2,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: page_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- if current_user
= render "layouts/head_panel", title: page_title
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index f22fb236cb5..72b0d03908d 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,12 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- = render "layouts/head_panel", title: "group: #{@group.name}"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/group'
- .container
- .content
- = render "layouts/flash"
- = yield
+ = render "layouts/head_panel", title: @group.name
+ = render 'layouts/page', sidebar: 'layouts/nav/group'
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index c57216f01c8..ea503a9cc2e 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,19 +1,42 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do
- Overview
+ %i.fa.fa-dashboard
+ %span
+ Overview
= nav_link(controller: :projects) do
- = link_to "Projects", admin_projects_path
+ = link_to admin_projects_path do
+ %i.fa.fa-cube
+ %span
+ Projects
= nav_link(controller: :users) do
- = link_to "Users", admin_users_path
+ = link_to admin_users_path do
+ %i.fa.fa-users
+ %span
+ Users
= nav_link(controller: :groups) do
- = link_to "Groups", admin_groups_path
+ = link_to admin_groups_path do
+ %i.fa.fa-group
+ %span
+ Groups
= nav_link(controller: :logs) do
- = link_to "Logs", admin_logs_path
+ = link_to admin_logs_path do
+ %i.fa.fa-file-text
+ %span
+ Logs
= nav_link(controller: :broadcast_messages) do
- = link_to "Messages", admin_broadcast_messages_path
+ = link_to admin_broadcast_messages_path do
+ %i.fa.fa-bullhorn
+ %span
+ Messages
= nav_link(controller: :hooks) do
- = link_to "Hooks", admin_hooks_path
+ = link_to admin_hooks_path do
+ %i.fa.fa-external-link
+ %span
+ Hooks
= nav_link(controller: :background_jobs) do
- = link_to "Background Jobs", admin_background_jobs_path
+ = link_to admin_background_jobs_path do
+ %i.fa.fa-cog
+ %span
+ Background Jobs
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index a6e9772d93f..a2eaa2d83c5 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,18 +1,29 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to root_path, title: 'Home', class: 'shortcuts-activity' do
- Activity
+ %i.fa.fa-dashboard
+ %span
+ Activity
= nav_link(path: 'dashboard#projects') do
= link_to projects_dashboard_path, class: 'shortcuts-projects' do
- Projects
+ %i.fa.fa-cube
+ %span
+ Projects
= nav_link(path: 'dashboard#issues') do
- = link_to issues_dashboard_path, class: 'shortcuts-issues' do
- Issues
- %span.count= current_user.assigned_issues.opened.count
+ = link_to assigned_issues_dashboard_path, class: 'shortcuts-issues' do
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ %span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
- = link_to merge_requests_dashboard_path, class: 'shortcuts-merge_requests' do
- Merge Requests
- %span.count= current_user.assigned_merge_requests.opened.count
+ = link_to assigned_mrs_dashboard_path, class: 'shortcuts-merge_requests' do
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ %span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :help) do
- = link_to "Help", help_path
+ = link_to help_path do
+ %i.fa.fa-question-circle
+ %span
+ Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 9095a843c9f..54468d077ab 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,25 +1,42 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: "Home" do
- Activity
- = nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group) do
- Milestones
+ %i.fa.fa-dashboard
+ %span
+ Activity
+ - if current_user
+ = nav_link(controller: [:group, :milestones]) do
+ = link_to group_milestones_path(@group) do
+ %i.fa.fa-clock-o
+ %span
+ Milestones
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do
- Issues
- - if current_user
- %span.count= current_user.assigned_issues.opened.of_group(@group).count
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ - if current_user
+ %span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group) do
- Merge Requests
- - if current_user
- %span.count= current_user.cared_merge_requests.opened.of_group(@group).count
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ - if current_user
+ %span.count= MergeRequest.opened.of_group(@group).count
= nav_link(path: 'groups#members') do
- = link_to "Members", members_group_path(@group)
+ = link_to members_group_path(@group) do
+ %i.fa.fa-users
+ %span
+ Members
- if can?(current_user, :manage_group, @group)
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), class: "tab " do
- Settings
+ = nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do
+ = link_to edit_group_path(@group), class: "tab no-highlight" do
+ %i.fa.fa-cogs
+ %span
+ Settings
+ %i.fa.fa-angle-down
+ - if group_settings_page?
+ = render 'groups/settings_nav'
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 1de5ee99cf4..cc50b9b570a 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,26 +1,56 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: "Profile" do
- Profile
+ %i.fa.fa-user
+ %span
+ Profile
= nav_link(controller: :accounts) do
- = link_to "Account", profile_account_path
+ = link_to profile_account_path do
+ %i.fa.fa-gear
+ %span
+ Account
+ = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do
+ = link_to applications_profile_path do
+ %i.fa.fa-cloud
+ %span
+ Applications
= nav_link(controller: :emails) do
= link_to profile_emails_path do
- Emails
- %span.count= current_user.emails.count + 1
+ %i.fa.fa-envelope-o
+ %span
+ Emails
+ %span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
- = link_to "Password", edit_profile_password_path
+ = link_to edit_profile_password_path do
+ %i.fa.fa-lock
+ %span
+ Password
= nav_link(controller: :notifications) do
- = link_to "Notifications", profile_notifications_path
+ = link_to profile_notifications_path do
+ %i.fa.fa-inbox
+ %span
+ Notifications
+
= nav_link(controller: :keys) do
= link_to profile_keys_path do
- SSH Keys
- %span.count= current_user.keys.count
+ %i.fa.fa-key
+ %span
+ SSH Keys
+ %span.count= current_user.keys.count
= nav_link(path: 'profiles#design') do
- = link_to "Design", design_profile_path
+ = link_to design_profile_path do
+ %i.fa.fa-image
+ %span
+ Design
= nav_link(controller: :groups) do
- = link_to "Groups", profile_groups_path
+ = link_to profile_groups_path do
+ %i.fa.fa-group
+ %span
+ Groups
= nav_link(path: 'profiles#history') do
- = link_to "History", history_profile_path
+ = link_to history_profile_path do
+ %i.fa.fa-history
+ %span
+ History
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 6cb2a82bac8..94cee0bd50f 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,45 +1,76 @@
-%ul.project-navigation
+%ul.project-navigation.nav.nav-sidebar
= nav_link(path: 'projects#show', html_options: {class: "home"}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
- Project
+ %i.fa.fa-dashboard
+ %span
+ Project
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
- = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref), class: 'shortcuts-tree'
+ = link_to project_tree_path(@project, @ref || @repository.root_ref), class: 'shortcuts-tree' do
+ %i.fa.fa-files-o
+ %span
+ Files
+
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches)) do
- = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref), class: 'shortcuts-commits'
+ = link_to project_commits_path(@project, @ref || @repository.root_ref), class: 'shortcuts-commits' do
+ %i.fa.fa-history
+ %span
+ Commits
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
- = link_to "Network", project_network_path(@project, @ref || @repository.root_ref), class: 'shortcuts-network'
+ = link_to project_network_path(@project, @ref || @repository.root_ref), class: 'shortcuts-network' do
+ %i.fa.fa-code-fork
+ %span
+ Network
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
- = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref), class: 'shortcuts-graphs'
+ = link_to project_graph_path(@project, @ref || @repository.root_ref), class: 'shortcuts-graphs' do
+ %i.fa.fa-area-chart
+ %span
+ Graphs
- if project_nav_tab? :issues
= nav_link(controller: %w(issues milestones labels)) do
= link_to url_for_project_issues, class: 'shortcuts-issues' do
- Issues
- - if @project.used_default_issues_tracker?
- %span.count.issue_counter= @project.issues.opened.count
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ - if @project.used_default_issues_tracker?
+ %span.count.issue_counter= @project.issues.opened.count
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do
- Merge Requests
- %span.count.merge_counter= @project.merge_requests.opened.count
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ %span.count.merge_counter= @project.merge_requests.opened.count
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
- = link_to 'Wiki', project_wiki_path(@project, :home), class: 'shortcuts-wiki'
+ = link_to project_wiki_path(@project, :home), class: 'shortcuts-wiki' do
+ %i.fa.fa-book
+ %span
+ Wiki
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
- = link_to 'Snippets', project_snippets_path(@project), class: 'shortcuts-snippets'
+ = link_to project_snippets_path(@project), class: 'shortcuts-snippets' do
+ %i.fa.fa-file-text-o
+ %span
+ Snippets
- if project_nav_tab? :settings
- = nav_link(html_options: {class: "#{project_tab_class}"}) do
- = link_to edit_project_path(@project), class: "stat-tab tab " do
- Settings
+ = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
+ = link_to edit_project_path(@project), class: "stat-tab tab no-highlight" do
+ %i.fa.fa-cogs
+ %span
+ Settings
+ %i.fa.fa-angle-down
+
+ - if @project_settings_nav
+ = render 'projects/settings_nav'
diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml
index 2c5fffe384f..730f3d09277 100644
--- a/app/views/layouts/navless.html.haml
+++ b/app/views/layouts/navless.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: @title
.container.navless-container
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 1d0ab84d26f..941084cc4ad 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,12 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Profile"
- %body{class: "#{app_theme} profile", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Profile"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/profile'
- .container
- .content
- = render "layouts/flash"
- = yield
+ = render 'layouts/page', sidebar: 'layouts/nav/profile'
diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml
index c8b8f4ba971..0f20bf38bfd 100644
--- a/app/views/layouts/project_settings.html.haml
+++ b/app/views/layouts/project_settings.html.haml
@@ -1,19 +1,9 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
- %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
+ %body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
- - if can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
- .container
- .content
- = render "layouts/flash"
- .row
- .col-md-2
- = render "projects/settings_nav"
- .col-md-10
- = yield
+ - @project_settings_nav = true
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml
index 8ad2f165946..d4ee53db55c 100644
--- a/app/views/layouts/projects.html.haml
+++ b/app/views/layouts/projects.html.haml
@@ -1,16 +1,8 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: project_head_title
- %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
+ %body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
- - if can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
- .container
- .content
- = render "layouts/flash"
- = yield
- = yield :embedded_scripts
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml
index a289b784725..64794104ac5 100644
--- a/app/views/layouts/public_group.html.haml
+++ b/app/views/layouts/public_group.html.haml
@@ -1,10 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/public_head_panel", title: "group: #{@group.name}"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/group'
- .container
- .content= yield
+ = render 'layouts/page', sidebar: 'layouts/nav/group'
diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml
index 2a9230244f8..5964a29d522 100644
--- a/app/views/layouts/public_projects.html.haml
+++ b/app/views/layouts/public_projects.html.haml
@@ -1,10 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/public_head_panel", title: project_title(@project)
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
- .container
- .content= yield
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml
index 4aa258fea0d..0510ce34a7f 100644
--- a/app/views/layouts/public_users.html.haml
+++ b/app/views/layouts/public_users.html.haml
@@ -1,8 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/public_head_panel", title: @title
- .container.navless-container
- .content= yield
+ = render 'layouts/page'
diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml
index 084ff7ec830..6d001e7ee1c 100644
--- a/app/views/layouts/search.html.haml
+++ b/app/views/layouts/search.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Search"
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Search"
.container.navless-container
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 3cf50bf0826..d678147ec5d 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -6,7 +6,9 @@
- @commits.each do |commit|
%li
%strong #{link_to commit.short_id, project_commit_url(@project, commit)}
- %span by #{commit.author_name}
+ %div
+ %span by #{commit.author_name}
+ %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
%pre #{commit.safe_message}
%h4 Changes:
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a21dcff41c0..53a50f6796b 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -75,3 +75,4 @@
The following groups will be abandoned. You should transfer or remove them:
%strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
= link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
new file mode 100644
index 00000000000..cb24e4a3dde
--- /dev/null
+++ b/app/views/profiles/applications.html.haml
@@ -0,0 +1,47 @@
+%h3.page-title
+ OAuth2
+
+%fieldset.oauth-applications
+ %legend Your applications
+ %p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
+ - if @applications.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td
+ - application.redirect_uri.split.each do |uri|
+ %div= uri
+ %td= application.access_tokens.count
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-small'
+ %td= render 'doorkeeper/applications/delete_form', application: application
+
+%fieldset.oauth-authorized-applications.prepend-top-20
+ %legend Authorized applications
+
+ - if @authorized_tokens.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Authorized At
+ %th Scope
+ %th
+ %tbody
+ - @authorized_tokens.each do |token|
+ - application = token.application
+ %tr{:id => "application_#{application.id}"}
+ %td= application.name
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', application: application
+ - else
+ %p.light You dont have any authorized applications
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 81411a7565e..8892302e25d 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -1,9 +1,12 @@
-%li
- = link_to profile_key_path(key) do
- %strong= key.title
- %span
- (#{key.fingerprint})
- %span.cgray
- added #{time_ago_with_tooltip(key.created_at)}
-
- = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
+%tr
+ %td
+ = link_to path_to_key(key, is_admin) do
+ %strong= key.title
+ %td
+ %span
+ (#{key.fingerprint})
+ %td
+ %span.cgray
+ added #{time_ago_with_tooltip(key.created_at)}
+ %td
+ = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
new file mode 100644
index 00000000000..8bac22a2e1a
--- /dev/null
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -0,0 +1,22 @@
+- is_admin = defined?(admin) ? true : false
+.row
+ .col-md-4
+ .panel.panel-default
+ .panel-heading
+ SSH Key
+ %ul.well-list
+ %li
+ %span.light Title:
+ %strong= @key.title
+ %li
+ %span.light Created on:
+ %strong= @key.created_at.stamp("Aug 21, 2011")
+
+ .col-md-8
+ %p
+ %span.light Fingerprint:
+ %strong= @key.fingerprint
+ %pre.well-pre
+ = @key.key
+ .pull-right
+ = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
new file mode 100644
index 00000000000..ef0075aad3b
--- /dev/null
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -0,0 +1,19 @@
+- is_admin = defined?(admin) ? true : false
+.panel.panel-default
+ - if @keys.any?
+ %table.table
+ %thead.panel-heading
+ %tr
+ %th Title
+ %th Fingerprint
+ %th Added at
+ %th
+ %tbody
+ - @keys.each do |key|
+ = render 'profiles/keys/key', key: key, is_admin: is_admin
+ - else
+ .nothing-here-block
+ - if is_admin
+ User has no ssh keys
+ - else
+ There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index a322f82f236..809953960bb 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,5 +1,5 @@
%h3.page-title
- My SSH keys
+ My SSH keys (#{@keys.count})
.pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
%p.light
@@ -9,14 +9,4 @@
= link_to "generate it", help_page_path("ssh", "ssh")
%hr
-
-.panel.panel-default
- .panel-heading
- SSH Keys (#{@keys.count})
- %ul.well-list#keys-table
- = render @keys
- - if @keys.blank?
- %li
- .nothing-here-block There are no SSH keys with access to your account.
-
-
+= render 'key_table'
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index c4fc1bb269c..cfd53298962 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,22 +1 @@
-.row
- .col-md-4
- .panel.panel-default
- .panel-heading
- SSH Key
- %ul.well-list
- %li
- %span.light Title:
- %strong= @key.title
- %li
- %span.light Created on:
- %strong= @key.created_at.stamp("Aug 21, 2011")
-
- .col-md-8
- %p
- %span.light Fingerprint:
- %strong= @key.fingerprint
- %pre.well-pre
- = @key.key
-
-.pull-right
- = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
+= render "key_details"
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index a044fad8fa3..96fe91b9b20 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -17,6 +17,13 @@
.radio
= label_tag nil, class: '' do
+ = radio_button_tag :notification_level, Notification::N_MENTION, @notification.mention?, class: 'trigger-submit'
+ .level-title
+ Mention
+ %p You will receive notifications only for comments where you was @mentioned
+
+ .radio
+ = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
.level-title
Participating
diff --git a/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb
index 04b5cf4827d..e664ac2a52a 100644
--- a/app/views/profiles/update.js.erb
+++ b/app/views/profiles/update.js.erb
@@ -1,6 +1,6 @@
// Remove body class for any previous theme, re-add current one
-$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color')
-$('body').addClass('<%= app_theme %>')
+$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color light_theme dark_theme')
+$('body').addClass('<%= app_theme %> <%= theme_type %>')
// Re-render the header to reflect the new theme
$('header').html('<%= escape_javascript(render("layouts/head_panel", title: "Profile")) %>')
diff --git a/app/views/projects/_issuable_filter.html.haml b/app/views/projects/_issuable_filter.html.haml
deleted file mode 100644
index b3e5efd938f..00000000000
--- a/app/views/projects/_issuable_filter.html.haml
+++ /dev/null
@@ -1,72 +0,0 @@
-.issues-filters
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light author:
- - if @author.present?
- %strong= @author.name
- - elsif params[:author_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(author_id: nil) do
- Any
- = link_to project_filter_path(author_id: 0) do
- Unassigned
- - @authors.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(author_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index b02f52a5aff..9e2e214b3e8 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -52,10 +52,11 @@
- else
%span.light No open milestones available.
&nbsp;
- = link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank
+ - if can? current_user, :admin_milestone, issuable.project
+ = link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank
.form-group
= f.label :label_ids, class: 'control-label' do
- %i.icon-tag
+ %i.fa.fa-tag
Labels
.col-sm-10
- if issuable.project.labels.any?
@@ -64,9 +65,15 @@
- else
%span.light No labels yet.
&nbsp;
- = link_to 'Create new label', new_project_label_path(issuable.project), target: :blank
+ - if can? current_user, :admin_label, issuable.project
+ = link_to 'Create new label', new_project_label_path(issuable.project), target: :blank
.form-actions
+ - if !issuable.project.empty_repo? && contribution_guide_url(issuable.project) && !issuable.persisted?
+ %p
+ Please review the
+ %strong #{link_to 'guidelines for contribution', contribution_guide_url(issuable.project)}
+ to this repository.
- if issuable.new_record?
= f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else
diff --git a/app/views/projects/_issues_nav.html.haml b/app/views/projects/_issues_nav.html.haml
index 18628eb6207..4e2ef3202f9 100644
--- a/app/views/projects/_issues_nav.html.haml
+++ b/app/views/projects/_issues_nav.html.haml
@@ -2,15 +2,22 @@
- if project_nav_tab? :issues
= nav_link(controller: :issues) do
= link_to project_issues_path(@project), class: "tab" do
+ %i.fa.fa-exclamation-circle
Issues
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to project_merge_requests_path(@project), class: "tab" do
+ %i.fa.fa-tasks
Merge Requests
= nav_link(controller: :milestones) do
- = link_to 'Milestones', project_milestones_path(@project), class: "tab"
+ = link_to project_milestones_path(@project), class: "tab" do
+ %i.fa.fa-clock-o
+ Milestones
= nav_link(controller: :labels) do
- = link_to 'Labels', project_labels_path(@project), class: "tab"
+ = link_to project_labels_path(@project), class: "tab" do
+ %i.fa.fa-tags
+ Labels
+
- if current_controller?(:milestones)
%li.pull-right
diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml
index 2008f8c558d..64eda0bf286 100644
--- a/app/views/projects/_settings_nav.html.haml
+++ b/app/views/projects/_settings_nav.html.haml
@@ -1,25 +1,31 @@
-%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20.project-settings-nav
+%ul.project-settings-nav.sidebar-subnav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), class: "stat-tab tab " do
%i.fa.fa-pencil-square-o
- Project
+ %span
+ Project
= nav_link(controller: [:team_members, :teams]) do
= link_to project_team_index_path(@project), class: "team-tab tab" do
%i.fa.fa-users
- Members
+ %span
+ Members
= nav_link(controller: :deploy_keys) do
= link_to project_deploy_keys_path(@project) do
%i.fa.fa-key
- Deploy Keys
+ %span
+ Deploy Keys
= nav_link(controller: :hooks) do
= link_to project_hooks_path(@project) do
%i.fa.fa-link
- Web Hooks
+ %span
+ Web Hooks
= nav_link(controller: :services) do
= link_to project_services_path(@project) do
%i.fa.fa-cogs
- Services
+ %span
+ Services
= nav_link(controller: :protected_branches) do
= link_to project_protected_branches_path(@project) do
%i.fa.fa-lock
- Protected branches
+ %span
+ Protected branches
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 812d88a8730..f428ae41ef4 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -1,11 +1,5 @@
.btn-group.tree-btn-group
- -# only show edit link for text files
- - if @blob.text?
- - if allowed_tree_edit?
- = link_to 'Edit', project_edit_tree_path(@project, @id),
- class: 'btn btn-small'
- - else
- %span.btn.btn-small.disabled Edit
+ = edit_blob_link(@project, @ref, @path)
= link_to 'Raw', project_raw_path(@project, @id),
class: 'btn btn-small', target: '_blank'
-# only show normal/blame view links for text files
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 492dff437b5..68f3b08b8c8 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -16,7 +16,7 @@
= link_to title, '#'
%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs
- - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
+ - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
%div#tree-content-holder.tree-content-holder
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index d57659065a8..f279e3c37cd 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -7,5 +7,5 @@
%p= pluralize(commits.count, 'commit')
.col-md-10
%ul.bordered-list
- = render commits, project: @project
+ = render commits, project: project
%hr.lists-separator
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 5717c24c274..b80639763c8 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -11,11 +11,9 @@
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
- %li.active
- commits
%div{id: dom_id(@project)}
- #commits-list= render "commits"
+ #commits-list= render "commits", project: @project
.clear
= spinner
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 334ea1ba82f..48d4c33ce85 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,15 +2,9 @@
.col-md-8
= render 'projects/diffs/stats', diffs: diffs
.col-md-4
- %ul.nav.nav-tabs
- %li.pull-right{class: params[:view] == 'parallel' ? 'active' : ''}
- - params_copy = params.dup
- - params_copy[:view] = 'parallel'
- = link_to "Side-by-side Diff", url_for(params_copy), {id: "commit-diff-viewtype"}
- %li.pull-right{class: params[:view] != 'parallel' ? 'active' : ''}
- - params_copy[:view] = 'inline'
- = link_to "Inline Diff", url_for(params_copy), {id: "commit-diff-viewtype"}
-
+ .btn-group.pull-right
+ = inline_diff_btn
+ = parallel_diff_btn
- if show_diff_size_warning?(diffs)
= render 'projects/diffs/warning', diffs: diffs
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index bf7770ceedf..0c5f2ad1f3a 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -4,7 +4,7 @@
.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
.diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
- if diff_file.deleted_file
- %span= diff_file.old_path
+ %span="#{diff_file.old_path} deleted"
.diff-btn-group
- if @commit.parent_ids.present?
@@ -30,9 +30,9 @@
&nbsp;
- if @merge_request && @merge_request.source_project
- = link_to project_edit_tree_path(@merge_request.source_project, tree_join(@merge_request.source_branch, diff_file.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do
- Edit
- &nbsp;
+ = edit_blob_link(@merge_request.source_project,
+ @merge_request.source_branch, diff_file.new_path,
+ after: '&nbsp;', from_merge_request_id: @merge_request.id)
= view_file_btn(@commit.id, diff_file, project)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b85cf7d8d37..f2bb56b5664 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -136,6 +136,8 @@
.col-sm-9
.form-group
.input-group
+ .input-group-addon
+ #{URI.join(root_url, @project.namespace.path)}/
= f.text_field :path, class: 'form-control'
%span.input-group-addon .git
%ul
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
new file mode 100644
index 00000000000..ec03f375d6b
--- /dev/null
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -0,0 +1,37 @@
+- content_for :note_actions do
+ - if can?(current_user, :modify_issue, @issue)
+ - if @issue.closed?
+ = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue'
+ - else
+ = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
+.row
+ .col-md-9
+ .participants
+ %cite.cgray
+ = pluralize(@issue.participants.count, 'participant')
+ - @issue.participants.each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
+
+ .voting_notes#notes= render "projects/notes/notes_with_form"
+ .col-md-3.hidden-sm.hidden-xs
+ %div
+ .clearfix
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @issue)
+ %hr
+ .clearfix
+ .votes-holder
+ %h6 Votes
+ #votes= render 'votes/votes_block', votable: @issue
+ %hr
+ .context
+ %cite.cgray
+ = render partial: 'issue_context', locals: { issue: @issue }
+
+ - if @issue.labels.any?
+ %hr
+ %h6 Labels
+ .issue-show-labels
+ - @issue.labels.each do |label|
+ = link_to project_issues_path(@project, label_name: label.name) do
+ %p= render_colored_label(label)
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 64a28d8da49..2a7b44955cd 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,12 +1,6 @@
%div.issue-form-holder
%h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
%hr
- - if @repository.exists? && !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
- - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
- .row
- .col-sm-10.col-sm-offset-2
- .alert.alert-info
- = "Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe
= form_for [@project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
= render 'projects/issuable_form', f: f, issuable: @issue
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index 648f459dc9e..98777a58f9d 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -1,25 +1,24 @@
= form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
- .row
- .col-sm-6
- %strong.append-right-10
- Assignee:
+ %div.prepend-top-20
+ %p
+ Assignee:
- - if can?(current_user, :modify_issue, @issue)
- = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
- - elsif issue.assignee
- = link_to_member(@project, @issue.assignee)
- - else
- None
+ - if can?(current_user, :modify_issue, @issue)
+ = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
+ - elsif issue.assignee
+ = link_to_member(@project, @issue.assignee)
+ - else
+ None
- .col-sm-6.text-right
- %strong.append-right-10
- Milestone:
- - if can?(current_user, :modify_issue, @issue)
- = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
- = hidden_field_tag :issue_context
- = f.submit class: 'btn'
- - elsif issue.milestone
- = link_to project_milestone_path(@project, @issue.milestone) do
- = @issue.milestone.title
- - else
- None
+ %div.prepend-top-20
+ %p
+ Milestone:
+ - if can?(current_user, :modify_issue, @issue)
+ = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
+ = hidden_field_tag :issue_context
+ = f.submit class: 'btn'
+ - elsif issue.milestone
+ = link_to project_milestone_path(@project, @issue.milestone) do
+ = @issue.milestone.title
+ - else
+ None
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 15c84c7ced2..010ca3b68b3 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,7 +1,7 @@
.append-bottom-10
.check-all-holder
= check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
- = render 'projects/issuable_filter'
+ = render 'shared/issuable_filter'
.clearfix
.issues_bulk_update.hide
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 8db6241f21f..0d00d6bfded 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,9 +1,4 @@
= render "projects/issues_nav"
-.row
- .fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/project_filter', project_entities_path: project_issues_path(@project),
- labels: true, redirect: 'issues', entity: 'issue'
- .col-md-9.issues-holder
- = render "issues"
+
+.issues-holder
+ = render "issues"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 01a1fabda26..b21a394ebeb 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,7 +1,14 @@
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: issue_box_class(@issue) }
+ - if @issue.closed?
+ Closed
+ - else
+ Open
Issue ##{@issue.iid}
+ %small.creator
+ &middot; created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
- %span.pull-right.issue-btn-group
+ .pull-right
- if can?(current_user, :write_issue, @project)
= link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
%i.fa.fa-plus
@@ -12,68 +19,19 @@
- else
= link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
- = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do
+ = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped issuable-edit" do
%i.fa.fa-pencil-square-o
Edit
-.clearfix
- .votes-holder
- #votes= render 'votes/votes_block', votable: @issue
-
- .back-link
- = link_to project_issues_path(@project) do
- &larr; To issues list
- %span.milestone-nav-link
- - if @issue.milestone
- |
- %span.light Milestone
- = link_to project_milestone_path(@project, @issue.milestone) do
- = @issue.milestone.title
-
-.issue-box{ class: issue_box_class(@issue) }
- .state.clearfix
- .state-label
- - if @issue.closed?
- Closed
- - else
- Open
-
- .cross-project-ref
- %i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
- = cross_project_reference(@project, @issue)
-
- .creator
- Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
-
- %h4.title
- = gfm escape_once(@issue.title)
-
+%hr
+%h3.issue-title
+ = gfm escape_once(@issue.title)
+%div
- if @issue.description.present?
.description
.wiki
= preserve do
= markdown(@issue.description, parse_tasks: true)
- .context
- %cite.cgray
- = render partial: 'issue_context', locals: { issue: @issue }
-
-
-- content_for :note_actions do
- - if can?(current_user, :modify_issue, @issue)
- - if @issue.closed?
- = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue'
- - else
- = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
-
-.participants
- %cite.cgray
- = pluralize(@issue.participants.count, 'participant')
- - @issue.participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
-
- .issue-show-labels.pull-right
- - @issue.labels.each do |label|
- = link_to project_issues_path(@project, label_name: label.name) do
- = render_colored_label(label)
-.voting_notes#notes= render "projects/notes/notes_with_form"
+%hr
+= render "projects/issues/discussion"
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 5199e9fc61f..6e50667b084 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -3,7 +3,7 @@
:plain
$("##{dom_id(@issue)}").fadeOut();
- elsif params[:issue_context]
- $('.issue-box .context').effect('highlight');
+ $('.context').effect('highlight');
- if @issue.milestone
$('.milestone-nav-link').replaceWith("<span class='milestone-nav-link'>| <span class='light'>Milestone</span> #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))}</span>")
- else
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
new file mode 100644
index 00000000000..6bb5c465596
--- /dev/null
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -0,0 +1,31 @@
+- content_for :note_actions do
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ - if @merge_request.open?
+ = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
+ - if @merge_request.closed?
+ = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
+
+.row
+ .col-md-9
+ = render "projects/merge_requests/show/participants"
+ = render "projects/notes/notes_with_form"
+ .col-md-3.hidden-sm.hidden-xs
+ .clearfix
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+ %hr
+ .votes-holder.hidden-sm.hidden-xs
+ %h6 Votes
+ #votes= render 'votes/votes_block', votable: @merge_request
+ %hr
+ .context
+ %cite.cgray
+ = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
+
+ - if @merge_request.labels.any?
+ %hr
+ %h6 Labels
+ .merge-request-show-labels
+ - @merge_request.labels.each do |label|
+ = link_to project_merge_requests_path(@project, label_name: label.name) do
+ %p= render_colored_label(label)
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 76813e688b5..ac374532ffd 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -9,74 +9,103 @@
%span.pull-right
= link_to 'Change branches', new_project_merge_request_path(@project)
-= form_for [@project, @merge_request], html: { class: "merge-request-form gfm-form" } do |f|
- .panel.panel-default
-
- .panel-body
- .form-group
- .light
- = f.label :title do
- Title *
- = f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true
- .form-group
- .light
- = f.label :description, "Description"
+= form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal gfm-form" } do |f|
+ .merge-request-form-info
+ .form-group
+ = f.label :title, class: 'control-label' do
+ %strong Title *
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, autofocus: true, class: 'form-control pad js-gfm-input', required: true
+ .form-group.issuable-description
+ = f.label :description, 'Description', class: 'control-label'
+ .col-sm-10
= render layout: 'projects/md_preview' do
- = render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
- .clearfix.hint
- .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+
+ .col-sm-12-hint
+ .pull-left
+ Parsed with
+ #{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .pull-right
+ Attach images (JPG, PNG, GIF) by dragging &amp; dropping
+ or #{link_to 'selecting them', '#', class: 'markdown-selector'}.
+
+ .clearfix
.error-alert
- .form-group
- .issue-assignee
- = f.label :assignee_id do
- %i.fa.fa-user
- Assign to
- %div
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
- &nbsp;
- = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
- .form-group
- .issue-milestone
- = f.label :milestone_id do
- %i.fa.fa-clock-o
- Milestone
- %div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
- .form-group
- = f.label :label_ids do
- %i.fa.fa-tag
- Labels
- %div
- = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
+ %hr
+ .form-group
+ .issue-assignee
+ = f.label :assignee_id, class: 'control-label' do
+ %i.fa.fa-user
+ Assign to
+ .col-sm-10
+ = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
+ &nbsp;
+ = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
+ .form-group
+ .issue-milestone
+ = f.label :milestone_id, class: 'control-label' do
+ %i.fa.fa-clock-o
+ Milestone
+ .col-sm-10
+ - if milestone_options(@merge_request).present?
+ = f.select(:milestone_id, milestone_options(@merge_request), {include_blank: 'Select milestone'}, {class: 'select2'})
+ - else
+ %span.light No open milestones available.
+ &nbsp;
+ - if can? current_user, :admin_milestone, @merge_request.target_project
+ = link_to 'Create new milestone', new_project_milestone_path(@merge_request.target_project), target: :blank
+ .form-group
+ = f.label :label_ids, class: 'control-label' do
+ %i.fa.fa-tag
+ Labels
+ .col-sm-10
+ - if @merge_request.target_project.labels.any?
+ = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, {selected: @merge_request.label_ids}, multiple: true, class: 'select2'
+ - else
+ %span.light No labels yet.
+ &nbsp;
+ - if can? current_user, :admin_label, @merge_request.target_project
+ = link_to 'Create new label', new_project_label_path(@merge_request.target_project), target: :blank
- .panel-footer
+ .form-actions
- if contribution_guide_url(@target_project)
%p
Please review the
- %strong #{link_to "guidelines for contribution", contribution_guide_url(@target_project)}
+ %strong #{link_to 'guidelines for contribution', contribution_guide_url(@target_project)}
to this repository.
= f.hidden_field :source_project_id
+ = f.hidden_field :source_branch
= f.hidden_field :target_project_id
= f.hidden_field :target_branch
- = f.hidden_field :source_branch
- = f.submit 'Submit merge request', class: "btn btn-create"
+ = f.submit 'Submit merge request', class: 'btn btn-create'
-.mr-compare
- = render "projects/commits/commit_list"
-
- %h4 Changes
- - if @diffs.present?
- = render "projects/diffs/diffs", diffs: @diffs, project: @project
- - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- .bs-callout.bs-callout-danger
- %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
- %p To preserve performance the line changes are not shown.
- - else
- .bs-callout.bs-callout-danger
- %h4 This comparison includes huge diff.
- %p To preserve performance the line changes are not shown.
+.mr-compare.merge-request
+ %ul.nav.nav-tabs.merge-request-tabs
+ %li.commits-tab{data: {action: 'commits'}}
+ = link_to url_for(params) do
+ %i.fa.fa-history
+ Commits
+ %span.badge= @commits.size
+ %li.diffs-tab{data: {action: 'diffs'}}
+ = link_to url_for(params) do
+ %i.fa.fa-list-alt
+ Changes
+ %span.badge= @diffs.size
+ .commits.tab-content
+ = render "projects/commits/commits", project: @project
+ .diffs.tab-content
+ - if @diffs.present?
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project
+ - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ .bs-callout.bs-callout-danger
+ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
+ %p To preserve performance the line changes are not shown.
+ - else
+ .bs-callout.bs-callout-danger
+ %h4 This comparison includes a huge diff.
+ %p To preserve performance the line changes are not shown.
:javascript
$('.assign-to-me-link').on('click', function(e){
@@ -85,3 +114,9 @@
});
window.project_image_path_upload = "#{upload_image_project_path @project}";
+
+:javascript
+ var merge_request
+ merge_request = new MergeRequest({
+ action: 'commits'
+ });
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 7b28dd5e7da..f8d2673335a 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,39 +1,68 @@
.merge-request
= render "projects/merge_requests/show/mr_title"
- = render "projects/merge_requests/show/how_to_merge"
+ %hr
= render "projects/merge_requests/show/mr_box"
+ %hr
+ .append-bottom-20
+ .slead
+ %span From
+ - if @merge_request.for_fork?
+ %strong.label-branch<
+ - if @merge_request.source_project
+ = link_to @merge_request.source_project_namespace, project_path(@merge_request.source_project)
+ - else
+ \ #{@merge_request.source_project_namespace}
+ \:#{@merge_request.source_branch}
+ %span into
+ %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
+ - else
+ %strong.label-branch #{@merge_request.source_branch}
+ %span into
+ %strong.label-branch #{@merge_request.target_branch}
+ - if @merge_request.open?
+ %span.pull-right
+ .btn-group
+ %a.btn.dropdown-toggle{ data: {toggle: :dropdown} }
+ %i.fa.fa-download
+ Download as
+ %span.caret
+ %ul.dropdown-menu
+ %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
+ %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
+
+ = render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/show/state_widget"
- = render "projects/merge_requests/show/commits"
- = render "projects/merge_requests/show/participants"
- if @commits.present?
- %ul.nav.nav-pills.merge-request-tabs
+ %ul.nav.nav-tabs.merge-request-tabs
%li.notes-tab{data: {action: 'notes'}}
= link_to project_merge_request_path(@project, @merge_request) do
- %i.fa.fa-comment
+ %i.fa.fa-comments
Discussion
%span.badge= @merge_request.mr_and_commit_notes.count
+ %li.commits-tab{data: {action: 'commits'}}
+ = link_to project_merge_request_path(@project, @merge_request), title: 'Commits' do
+ %i.fa.fa-history
+ Commits
+ %span.badge= @commits.size
%li.diffs-tab{data: {action: 'diffs'}}
= link_to diffs_project_merge_request_path(@project, @merge_request) do
%i.fa.fa-list-alt
Changes
%span.badge= @merge_request.diffs.size
- - content_for :note_actions do
- - if can?(current_user, :modify_merge_request, @merge_request)
- - if @merge_request.open?
- = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
- - if @merge_request.closed?
- = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
-
+ .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
+ = render "projects/merge_requests/discussion"
+ .commits.tab-content
+ = render "projects/merge_requests/show/commits"
.diffs.tab-content
- if current_page?(action: 'diffs')
= render "projects/merge_requests/show/diffs"
- .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
- = render "projects/notes/notes_with_form"
+
.mr-loading-status
= spinner
+
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index b93e0f9da3e..2654ea70990 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,23 +1,19 @@
= render "projects/issues_nav"
-.row
- .col-md-3.responsive-side
- = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
- labels: true, redirect: 'merge_requests', entity: 'merge_request'
- .col-md-9
- .append-bottom-10
- = render 'projects/issuable_filter'
- .panel.panel-default
- %ul.well-list.mr-list
- = render @merge_requests
- - if @merge_requests.blank?
- %li
- .nothing-here-block No merge requests to show
- - if @merge_requests.present?
- .pull-right
- %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
+.merge-requests-holder
+ .append-bottom-10
+ = render 'shared/issuable_filter'
+ .panel.panel-default
+ %ul.well-list.mr-list
+ = render @merge_requests
+ - if @merge_requests.blank?
+ %li
+ .nothing-here-block No merge requests to show
+ - if @merge_requests.present?
+ .pull-right
+ %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
- = paginate @merge_requests, theme: "gitlab"
+ = paginate @merge_requests, theme: "gitlab"
:javascript
$(merge_requestsPage);
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index a6587403871..3b7f283daf0 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,30 +1 @@
-- if @commits.present?
- .panel.panel-default
- .panel-heading
- %i.fa.fa-list
- Commits (#{@commits.count})
- .commits.mr-commits
- - if @commits.count > 8
- %ul.first-commits.well-list
- - @commits.first(8).each do |commit|
- = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
- %li.bottom
- 8 of #{@commits.count} commits displayed.
- %strong
- %a.show-all-commits Click here to show all
- - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- %ul.all-commits.hide.well-list
- - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
- %li
- other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
- - else
- %ul.all-commits.hide.well-list
- - @commits.each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
-
- - else
- %ul.well-list
- - @commits.each do |commit|
- = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
-
+= render "projects/commits/commits", project: @merge_request.source_project
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
index 089302e3588..5b6e64f0657 100644
--- a/app/views/projects/merge_requests/show/_context.html.haml
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -1,24 +1,23 @@
= form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
- .row
- .col-sm-6
- %strong.append-right-10
- Assignee:
+ %div.prepend-top-20
+ %p
+ Assignee:
- - if can?(current_user, :modify_merge_request, @merge_request)
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id)
- - elsif merge_request.assignee
- = link_to_member(@project, @merge_request.assignee)
- - else
- None
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id)
+ - elsif merge_request.assignee
+ = link_to_member(@project, @merge_request.assignee)
+ - else
+ None
- .col-sm-6.text-right
- %strong.append-right-10
- Milestone:
- - if can?(current_user, :modify_merge_request, @merge_request)
- = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
- = hidden_field_tag :merge_request_context
- = f.submit class: 'btn'
- - elsif merge_request.milestone
- = link_to merge_request.milestone.title, project_milestone_path
- - else
- None
+ %div.prepend-top-20
+ %p
+ Milestone:
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
+ = hidden_field_tag :merge_request_context
+ = f.submit class: 'btn'
+ - elsif merge_request.milestone
+ = link_to merge_request.milestone.title, project_milestone_path
+ - else
+ None
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index 4939ae03994..dd5f29e5389 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -13,25 +13,22 @@
.automerge_widget.can_be_merged.hide
.clearfix
= form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f|
- %h4
- You can accept this request automatically.
- .accept-merge-holder.clearfix
- .accept-group
- .pull-left
- = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
- - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
- .remove_branch_holder.pull-left
- = label_tag :should_remove_source_branch, class: "checkbox" do
- = check_box_tag :should_remove_source_branch
- Remove source-branch
- .js-toggle-container
- %label
- %i.fa.fa-edit
- = link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
- .js-toggle-content.hide
- = render 'shared/commit_message_container', params: params,
- text: @merge_request.merge_commit_message,
- rows: 14, hint: true
+ .accept-merge-holder.clearfix.js-toggle-container
+ .accept-action
+ = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
+ - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
+ .accept-control
+ = label_tag :should_remove_source_branch, class: "checkbox" do
+ = check_box_tag :should_remove_source_branch
+ Remove source-branch
+ .accept-control
+ = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
+ %i.fa.fa-edit
+ Modify commit message
+ .js-toggle-content.hide.prepend-top-20
+ = render 'shared/commit_message_container', params: params,
+ text: @merge_request.merge_commit_message,
+ rows: 14, hint: true
%hr
.light
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 866b236d827..ab1284547ad 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,29 +1,9 @@
-.issue-box{ class: issue_box_class(@merge_request) }
- .state.clearfix
- .state-label
- - if @merge_request.merged?
- Merged
- - elsif @merge_request.closed?
- Closed
- - else
- Open
-
- .cross-project-ref
- %i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
- = cross_project_reference(@project, @merge_request)
-
- .creator
- Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
-
- %h4.title
- = gfm escape_once(@merge_request.title)
+%h3.issue-title
+ = gfm escape_once(@merge_request.title)
+%div
- if @merge_request.description.present?
.description
.wiki
= preserve do
= markdown(@merge_request.description, parse_tasks: true)
-
- .context
- %cite.cgray
- = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 6fe765248e4..0f20eba382c 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,45 +1,22 @@
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: issue_box_class(@merge_request) }
+ - if @merge_request.merged?
+ Merged
+ - elsif @merge_request.closed?
+ Closed
+ - else
+ Open
= "Merge Request ##{@merge_request.iid}"
+ %small.creator
+ &middot;
+ created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
- %span.pull-right.issue-btn-group
+ .issue-btn-group.pull-right
- if can?(current_user, :modify_merge_request, @merge_request)
- if @merge_request.open?
- .btn-group.pull-left
- %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.fa.fa-download
- Download as
- %span.caret
- %ul.dropdown-menu
- %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
- %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
-
= link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
-
- = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do
+ = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do
%i.fa.fa-pencil-square-o
Edit
- if @merge_request.closed?
= link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
-
-.votes-holder.hidden-sm.hidden-xs
- #votes= render 'votes/votes_block', votable: @merge_request
-
-.back-link
- = link_to project_merge_requests_path(@project) do
- &larr; To merge requests
-
- %span.prepend-left-20
- %span From
- - if @merge_request.for_fork?
- %strong.label-branch<
- - if @merge_request.source_project
- = link_to @merge_request.source_project_namespace, project_path(@merge_request.source_project)
- - else
- \ #{@merge_request.source_project_namespace}
- \:#{@merge_request.source_branch}
- %span into
- %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
- - else
- %strong.label-branch #{@merge_request.source_branch}
- %span into
- %strong.label-branch #{@merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml
index b709c89cec2..15a97404cb0 100644
--- a/app/views/projects/merge_requests/show/_participants.html.haml
+++ b/app/views/projects/merge_requests/show/_participants.html.haml
@@ -2,8 +2,3 @@
%cite.cgray #{@merge_request.participants.count} participants
- @merge_request.participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
-
- .merge-request-show-labels.pull-right
- - @merge_request.labels.each do |label|
- = link_to project_merge_requests_path(@project, label_name: label.name) do
- = render_colored_label(label)
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index 6452cc6382d..6f4c5dd7a3b 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,2 +1,2 @@
- if params[:merge_request_context]
- $('.issue-box .context').effect('highlight');
+ $('.context').effect('highlight');
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 0db0b114d63..04a1b9243d5 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -7,27 +7,15 @@
%i.fa.fa-plus
New Milestone
- .row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if (params[:f] == "active" || !params[:f]))}
- = link_to project_milestones_path(@project, f: "active") do
- Active
- %li{class: ("active" if params[:f] == "closed")}
- = link_to project_milestones_path(@project, f: "closed") do
- Closed
- %li{class: ("active" if params[:f] == "all")}
- = link_to project_milestones_path(@project, f: "all") do
- All
- .col-md-9
- .panel.panel-default
- %ul.well-list
- = render @milestones
+= render 'shared/milestones_filter'
- - if @milestones.blank?
- %li
- .nothing-here-block No milestones to show
+.milestones
+ .panel.panel-default
+ %ul.well-list
+ = render @milestones
- = paginate @milestones, theme: "gitlab"
+ - if @milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
+
+ = paginate @milestones, theme: "gitlab"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index f08ccc1d570..031b5a31895 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,6 +1,15 @@
= render "projects/issues_nav"
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: issue_box_class(@milestone) }
+ - if @milestone.closed?
+ Closed
+ - elsif @milestone.expired?
+ Expired
+ - else
+ Open
Milestone ##{@milestone.iid}
+ %small.creator
+ = @milestone.expires_at
.pull-right
- if can?(current_user, :admin_milestone, @project)
= link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
@@ -11,47 +20,32 @@
- else
= link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success
%span All issues for this milestone are closed. You may close milestone now.
-.back-link
- = link_to project_milestones_path(@project) do
- &larr; To milestones list
-
-
-.issue-box{ class: issue_box_class(@milestone) }
- .state.clearfix
- .state-label
- - if @milestone.closed?
- Closed
- - elsif @milestone.expired?
- Expired
- - else
- Open
- .creator
- = @milestone.expires_at
-
- %h4.title
- = gfm escape_once(@milestone.title)
-
+%h3.issue-title
+ = gfm escape_once(@milestone.title)
+%div
- if @milestone.description.present?
.description
.wiki
= preserve do
= markdown @milestone.description
- .context
- %p
- Progress:
- #{@milestone.closed_items_count} closed
- &ndash;
- #{@milestone.open_items_count} open
- &nbsp;
- %span.light #{@milestone.percent_complete}% complete
- %span.pull-right= @milestone.expires_at
- .progress.progress-info
- .progress-bar{style: "width: #{@milestone.percent_complete}%;"}
+%hr
+.context
+ %p.lead
+ Progress:
+ #{@milestone.closed_items_count} closed
+ &ndash;
+ #{@milestone.open_items_count} open
+ &nbsp;
+ %span.light #{@milestone.percent_complete}% complete
+ %span.pull-right= @milestone.expires_at
+ .progress.progress-info
+ .progress-bar{style: "width: #{@milestone.percent_complete}%;"}
%ul.nav.nav-tabs
@@ -69,10 +63,10 @@
%span.badge= @users.count
.pull-right
- = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do
+ = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
New Issue
- = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped"
+ = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index e77ef84f51c..f320a2b505e 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -5,10 +5,13 @@
= form_for @project, html: { class: 'new_project form-horizontal' } do |f|
.form-group.project-name-holder
- = f.label :name, class: 'control-label' do
- %strong Project name
+ = f.label :path, class: 'control-label' do
+ %strong Project path
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control", tabindex: 1, autofocus: true
+ .input-group
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true
+ .input-group-addon
+ \.git
- if current_user.can_select_namespace?
.form-group
@@ -23,22 +26,6 @@
.col-sm-2
.col-sm-10
= link_to "#", class: 'js-toggle-button' do
- %i.fa.fa-pencil-square-o
- %span Customize repository name?
- .js-toggle-content.hide
- .form-group
- = f.label :path, class: 'control-label' do
- %span Repository name
- .col-sm-10
- .input-group
- = f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
-
- .js-toggle-container
- .form-group
- .col-sm-2
- .col-sm-10
- = link_to "#", class: 'js-toggle-button' do
%i.fa.fa-upload
%span Import existing repository?
.js-toggle-content.hide
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 354afd3e2c9..80e7342455b 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,7 +1,10 @@
-%li.timeline-entry{ id: dom_id(note), class: dom_class(note), data: { discussion: note.discussion_id } }
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
- = image_tag avatar_icon(note.author_email), class: "avatar s40"
+ - if note.system
+ %span.fa.fa-circle
+ - else
+ = image_tag avatar_icon(note.author_email), class: "avatar s40"
.timeline-content
.note-header
.note-actions
@@ -17,6 +20,8 @@
= link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
%i.fa.fa-trash-o.cred
Remove
+ - if note.system
+ = image_tag avatar_icon(note.author_email), class: "avatar s16"
= link_to_member(@project, note.author, avatar: false)
%span.author-username
= '@' + note.author.username
@@ -41,7 +46,7 @@
.note-edit-form
= form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f|
= render layout: 'projects/md_preview' do
- = f.text_area :note, class: 'note_text js-note-text markdown-area js-gfm-input turn-on'
+ = f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on'
.form-actions.clearfix
= f.submit 'Save changes', class: "btn btn-primary btn-save js-comment-button"
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
new file mode 100644
index 00000000000..e422799f55c
--- /dev/null
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -0,0 +1,34 @@
+- unless @branches.empty?
+ %br
+ %h4 Already Protected:
+ %table.table.protected-branches-list
+ %thead
+ %tr.no-border
+ %th Branch
+ %th Developers can push
+ %th Last commit
+ %th
+
+ %tbody
+ - @branches.each do |branch|
+ - @url = project_protected_branch_path(@project, branch)
+ %tr
+ %td
+ = link_to project_commits_path(@project, branch.name) do
+ %strong= branch.name
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info default
+ %td
+ = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
+ %td
+ - if commit = branch.commit
+ = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ &middot;
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ (branch was removed from repository)
+ %td
+ .pull-right
+ - if can? current_user, :admin_project, @project
+ = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 227a2f9a061..2164c874c74 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -22,29 +22,14 @@
= f.label :name, "Branch", class: 'control-label'
.col-sm-10
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
+ .form-group
+ = f.label :developers_can_push, class: 'control-label' do
+ Developers can push
+ .col-sm-10
+ .checkbox
+ = f.check_box :developers_can_push
+ %span.descr Allow developers to push to this branch
.form-actions
= f.submit 'Protect', class: "btn-create btn"
-- unless @branches.empty?
- %h5 Already Protected:
- %ul.bordered-list.protected-branches-list
- - @branches.each do |branch|
- %li
- %h4
- = link_to project_commits_path(@project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label.label-info default
- %span.label.label-success
- %i.fa.fa-lock
- .pull-right
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
+= render 'branches_list'
- - if commit = branch.commit
- = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- %span.light
- = gfm escape_once(truncate(commit.title, length: 40))
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 9b06ebe95a4..af6e4567c1b 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,3 +1,6 @@
+- if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+
= render "home_panel"
- readme = @repository.readme
diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml
index 10b0de98c04..6d083c5c516 100644
--- a/app/views/projects/transfer.js.haml
+++ b/app/views/projects/transfer.js.haml
@@ -1,7 +1,2 @@
-- if @project.errors[:namespace_id].present?
- :plain
- $("#tab-transfer .errors-holder").replaceWith(errorMessage('#{escape_javascript(@project.errors[:namespace_id].first)}'));
- $("#tab-transfer .form-actions input").removeAttr('disabled').removeClass('disabled');
-- else
- :plain
+:plain
location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index f37c086716d..111484c8316 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -19,13 +19,15 @@
%code [Link Title](page-slug)
\.
- .form-group
+ .form-group.wiki-content
= f.label :content, class: 'control-label'
.col-sm-10
- = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
- .col-sm-12.hint
- .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ = render layout: 'projects/md_preview' do
+ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
+ .col-sm-12.hint
+ .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
+ .pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+
.clearfix
.error-alert
.form-group
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index ee0b57fbe5a..d07a9e2b924 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,5 +1,19 @@
-.event_filter
+%ul.nav.nav-pills.event_filter
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
+
+ - if current_user
+ - if current_controller?(:dashboard)
+ %li.pull-right
+ = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
+ %i.fa.fa-rss
+ News Feed
+
+ - if current_controller?(:groups)
+ %li.pull-right
+ = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
+ %i.fa.fa-rss
+ News Feed
+%hr
diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml
deleted file mode 100644
index d366dd97a71..00000000000
--- a/app/views/shared/_filter.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-.side-filters
- = form_tag filter_path(entity), method: 'get' do
- - if current_user
- %fieldset.scope-filter
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:scope] == 'assigned-to-me')}
- = link_to filter_path(entity, scope: 'assigned-to-me') do
- Assigned to me
- %span.pull-right
- = assigned_entities_count(current_user, entity, @group)
- %li{class: ("active" if params[:scope] == 'authored')}
- = link_to filter_path(entity, scope: 'authored') do
- Created by me
- %span.pull-right
- = authored_entities_count(current_user, entity, @group)
- %li{class: ("active" if params[:scope] == 'all')}
- = link_to filter_path(entity, scope: 'all') do
- Everyone's
- %span.pull-right
- = authorized_entities_count(current_user, entity, @group)
-
- %fieldset.status-filter
- %legend State
- %ul.nav.nav-pills
- %li{class: ("active" if params[:state] == 'opened')}
- = link_to filter_path(entity, state: 'opened') do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to filter_path(entity, state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to filter_path(entity, state: 'all') do
- All
-
- %fieldset
- %legend Projects
- %ul.nav.nav-pills.nav-stacked.nav-small
- - @projects.each do |project|
- - unless entities_per_project(project, entity).zero?
- %li{class: ("active" if params[:project_id] == project.id.to_s)}
- = link_to filter_path(entity, project_id: project.id) do
- = project.name_with_namespace
- %small.pull-right= entities_per_project(project, entity)
-
- %fieldset
- - if params[:state].present? || params[:project_id].present?
- = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do
- %i.fa.fa-times
- %strong Clear filter
-
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 93294e42505..5875f71bac2 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -1,9 +1,26 @@
+- if @group.persisted?
+ .form-group
+ = f.label :name, class: 'control-label' do
+ Group name
+ .col-sm-10
+ = f.text_field :name, placeholder: 'open-source', class: 'form-control'
+
.form-group
- = f.label :name, class: 'control-label' do
- Group name
+ = f.label :path, class: 'control-label' do
+ Group path
.col-sm-10
- = f.text_field :name, placeholder: 'Example Group', class: 'form-control',
- autofocus: local_assigns[:autofocus] || false
+ .input-group
+ .input-group-addon
+ = root_url
+ = f.text_field :path, placeholder: 'open-source', class: 'form-control',
+ autofocus: local_assigns[:autofocus] || false
+ - if @group.persisted?
+ .bs-callout.bs-callout-danger
+ %ul
+ %li Changing group path can have unintended side effects.
+ %li Renaming group path will rename directory for all related projects
+ %li It will change web url for access group and group projects.
+ %li It will change the git path to repositories under this group.
.form-group.group-description-holder
= f.label :description, 'Details', class: 'control-label'
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml
new file mode 100644
index 00000000000..4f683258fac
--- /dev/null
+++ b/app/views/shared/_issuable_filter.html.haml
@@ -0,0 +1,112 @@
+.issues-filters
+ .pull-left.append-right-20
+ %ul.nav.nav-pills.nav-compact
+ %li{class: ("active" if params[:state] == 'opened')}
+ = link_to page_filter_path(state: 'opened') do
+ %i.fa.fa-exclamation-circle
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed') do
+ %i.fa.fa-check-circle
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to page_filter_path(state: 'all') do
+ %i.fa.fa-compass
+ All
+
+ .dropdown.inline.assignee-filter
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light assignee:
+ - if @assignee.present?
+ %strong= @assignee.name
+ - elsif params[:assignee_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(assignee_id: nil) do
+ Any
+ = link_to page_filter_path(assignee_id: 0) do
+ Unassigned
+ - @assignees.sort_by(&:name).each do |user|
+ %li
+ = link_to page_filter_path(assignee_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10.author-filter
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light author:
+ - if @author.present?
+ %strong= @author.name
+ - elsif params[:author_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(author_id: nil) do
+ Any
+ = link_to page_filter_path(author_id: 0) do
+ Unassigned
+ - @authors.sort_by(&:name).each do |user|
+ %li
+ = link_to page_filter_path(author_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10.milestone-filter
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-clock-o
+ %span.light milestone:
+ - if @milestone.present?
+ %strong= @milestone.title
+ - elsif params[:milestone_id] == "0"
+ None (backlog)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(milestone_id: nil) do
+ Any
+ = link_to page_filter_path(milestone_id: 0) do
+ None (backlog)
+ - @milestones.each do |milestone|
+ %li
+ = link_to page_filter_path(milestone_id: milestone.id) do
+ %strong= milestone.title
+ %small.light= milestone.expires_at
+
+ - if @project
+ .dropdown.inline.prepend-left-10.labels-filter
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-tags
+ %span.light label:
+ - if params[:label_name].present?
+ %strong= params[:label_name]
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(label_name: nil) do
+ Any
+ - if @project.labels.any?
+ - @project.labels.order_by_name.each do |label|
+ %li
+ = link_to page_filter_path(label_name: label.name) do
+ = render_colored_label(label)
+ - else
+ %li
+ = link_to generate_project_labels_path(@project, redirect: request.original_url), method: :post do
+ %i.fa.fa-plus-circle
+ Create default labels
+
+ .pull-right
+ = render 'shared/sort_dropdown'
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
new file mode 100644
index 00000000000..8c2fd166922
--- /dev/null
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -0,0 +1,16 @@
+.fixed.sidebar-expand-button.hidden-lg.hidden-md
+ %i.fa.fa-list.fa-2x
+.responsive-side.milestones-filters.append-bottom-10
+ %ul.nav.nav-pills.nav-compact
+ %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
+ = link_to milestones_filter_path(state: 'opened') do
+ %i.fa.fa-exclamation-circle
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to milestones_filter_path(state: 'closed') do
+ %i.fa.fa-check-circle
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to milestones_filter_path(state: 'all') do
+ %i.fa.fa-compass
+ All
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index e70eb4d01b9..8e6f802fd3b 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,14 +1,8 @@
- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key
- .no-ssh-key-message
- .container
- You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
- .pull-right.hidden-xs
- = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
- |
- = link_to 'Remind later', '#', class: 'hide-no-ssh-message'
- .links-xs.visible-xs
- = link_to "Add key", new_profile_key_path
- |
- = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
- |
- = link_to 'Later', '#', class: 'hide-no-ssh-message'
+ .no-ssh-key-message.alert.alert-warning.hidden-xs
+ You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
+
+ .pull-right
+ = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put
+ |
+ = link_to 'Remind later', '#', class: 'hide-no-ssh-message'
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
new file mode 100644
index 00000000000..0eba1fe075f
--- /dev/null
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -0,0 +1,8 @@
+- if outdated_browser?
+ - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
+ .browser-alert
+ GitLab may not work properly because you are using an outdated web browser.
+ %br
+ Please install a
+ = link_to 'supported web browser', link
+ for a better experience.
diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml
deleted file mode 100644
index ea6a49e1501..00000000000
--- a/app/views/shared/_project_filter.html.haml
+++ /dev/null
@@ -1,64 +0,0 @@
-.side-filters
- = form_tag project_entities_path, method: 'get' do
- - if current_user
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:scope] == 'all')}
- = link_to project_filter_path(scope: 'all') do
- Everyone's
- %span.pull-right
- = authorized_entities_count(current_user, entity, @project)
- %li{class: ("active" if params[:scope] == 'assigned-to-me')}
- = link_to project_filter_path(scope: 'assigned-to-me') do
- Assigned to me
- %span.pull-right
- = assigned_entities_count(current_user, entity, @project)
- %li{class: ("active" if params[:scope] == 'created-by-me')}
- = link_to project_filter_path(scope: 'created-by-me') do
- Created by me
- %span.pull-right
- = authored_entities_count(current_user, entity, @project)
-
- %fieldset
- %legend State
- %ul.nav.nav-pills
- %li{class: ("active" if params[:state] == 'opened')}
- = link_to project_filter_path(state: 'opened') do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to project_filter_path(state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to project_filter_path(state: 'all') do
- All
-
- - if defined?(labels)
- %fieldset
- %legend
- Labels
- %small.pull-right
- = link_to project_labels_path(@project), class: 'light' do
- %i.fa.fa-pencil-square-o
- %ul.nav.nav-pills.nav-stacked.nav-small.labels-filter
- - @project.labels.order_by_name.each do |label|
- %li{class: label_filter_class(label.name)}
- = link_to labels_filter_path(label.name) do
- = render_colored_label(label)
- - if selected_label?(label.name)
- .pull-right
- %i.fa.fa-times
-
- - if @project.labels.empty?
- .light-well
- Create first label at
- = link_to 'labels page', project_labels_path(@project)
- %br
- or #{link_to 'generate', generate_project_labels_path(@project, redirect: redirect), method: :post} default set of labels
-
- %fieldset
- - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
- = link_to project_entities_path, class: 'cgray pull-right' do
- %i.fa.fa-times
- %strong Clear filter
-
-
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 54f59245690..93ed9b67336 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -8,15 +8,15 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to project_filter_path(sort: 'newest') do
+ = link_to page_filter_path(sort: 'newest') do
= sort_title_recently_created
- = link_to project_filter_path(sort: 'oldest') do
+ = link_to page_filter_path(sort: 'oldest') do
= sort_title_oldest_created
- = link_to project_filter_path(sort: 'recently_updated') do
+ = link_to page_filter_path(sort: 'recently_updated') do
= sort_title_recently_updated
- = link_to project_filter_path(sort: 'last_updated') do
+ = link_to page_filter_path(sort: 'last_updated') do
= sort_title_oldest_updated
- = link_to project_filter_path(sort: 'milestone_due_soon') do
+ = link_to page_filter_path(sort: 'milestone_due_soon') do
Milestone due soon
- = link_to project_filter_path(sort: 'milestone_due_later') do
+ = link_to page_filter_path(sort: 'milestone_due_later') do
Milestone due later
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
index b7216a88765..8fe30b23635 100644
--- a/app/views/users/show.atom.builder
+++ b/app/views/users/show.atom.builder
@@ -1,5 +1,5 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Activity feed for #{@user.name}"
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
index cc0a7f25664..64d39c4d3f7 100644
--- a/app/workers/project_service_worker.rb
+++ b/app/workers/project_service_worker.rb
@@ -4,6 +4,7 @@ class ProjectServiceWorker
sidekiq_options queue: :project_web_hook
def perform(hook_id, data)
+ data = data.with_indifferent_access
Service.find(hook_id).execute(data)
end
end
diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb
index 9f9b9b1df5f..73085c046bd 100644
--- a/app/workers/project_web_hook_worker.rb
+++ b/app/workers/project_web_hook_worker.rb
@@ -4,6 +4,7 @@ class ProjectWebHookWorker
sidekiq_options queue: :project_web_hook
def perform(hook_id, data)
- WebHook.find(hook_id).execute data
+ data = data.with_indifferent_access
+ WebHook.find(hook_id).execute(data)
end
end
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index a7ee3c59822..b6340287569 100644
--- a/config/initializers/6_rack_profiler.rb
+++ b/config/initializers/6_rack_profiler.rb
@@ -3,4 +3,6 @@ if Rails.env == 'development'
# initialization is skipped so trigger it
Rack::MiniProfilerRails.initialize!(Rails.application)
+ Rack::MiniProfiler.config.position = 'right'
+ Rack::MiniProfiler.config.start_hidden = true
end
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
new file mode 100644
index 00000000000..b2db3a7ea7e
--- /dev/null
+++ b/config/initializers/doorkeeper.rb
@@ -0,0 +1,91 @@
+Doorkeeper.configure do
+ # Change the ORM that doorkeeper will use.
+ # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
+ orm :active_record
+
+ # This block will be called to check whether the resource owner is authenticated or not.
+ resource_owner_authenticator do
+ # Put your resource owner authentication logic here.
+ # Example implementation:
+ current_user || redirect_to(new_user_session_url)
+ end
+
+ # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
+ # admin_authenticator do
+ # # Put your admin authentication logic here.
+ # # Example implementation:
+ # Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url)
+ # end
+
+ # Authorization Code expiration time (default 10 minutes).
+ # authorization_code_expires_in 10.minutes
+
+ # Access token expiration time (default 2 hours).
+ # If you want to disable expiration, set this to nil.
+ # access_token_expires_in 2.hours
+
+ # Reuse access token for the same resource owner within an application (disabled by default)
+ # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
+ # reuse_access_token
+
+ # Issue access tokens with refresh token (disabled by default)
+ use_refresh_token
+
+ # Provide support for an owner to be assigned to each registered application (disabled by default)
+ # Optional parameter :confirmation => true (default false) if you want to enforce ownership of
+ # a registered application
+ # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
+ enable_application_owner :confirmation => true
+
+ # Define access token scopes for your provider
+ # For more information go to
+ # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
+ default_scopes :api
+ #optional_scopes :write, :update
+
+ # Change the way client credentials are retrieved from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:client_id` and `:client_secret` params from the `params` object.
+ # Check out the wiki for more information on customization
+ # client_credentials :from_basic, :from_params
+
+ # Change the way access token is authenticated from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:access_token` or `:bearer_token` params from the `params` object.
+ # Check out the wiki for more information on customization
+ access_token_methods :from_access_token_param, :from_bearer_authorization, :from_bearer_param
+
+ # Change the native redirect uri for client apps
+ # When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider
+ # The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
+ # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
+ #
+ native_redirect_uri nil#'urn:ietf:wg:oauth:2.0:oob'
+
+ # Specify what grant flows are enabled in array of Strings. The valid
+ # strings and the flows they enable are:
+ #
+ # "authorization_code" => Authorization Code Grant Flow
+ # "implicit" => Implicit Grant Flow
+ # "password" => Resource Owner Password Credentials Grant Flow
+ # "client_credentials" => Client Credentials Grant Flow
+ #
+ # If not specified, Doorkeeper enables all the four grant flows.
+ #
+ # grant_flows %w(authorization_code implicit password client_credentials)
+
+ # Under some circumstances you might want to have applications auto-approved,
+ # so that the user skips the authorization step.
+ # For example if dealing with trusted a application.
+ # skip_authorization do |resource_owner, client|
+ # client.superapp? or resource_owner.admin?
+ # end
+
+ # WWW-Authenticate Realm (default "Doorkeeper").
+ # realm "Doorkeeper"
+
+ # Allow dynamic query parameters (disabled by default)
+ # Some applications require dynamic query parameters on their request_uri
+ # set to true if you want this to be allowed
+ # wildcard_redirect_uri false
+end
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
new file mode 100644
index 00000000000..c5b6b75e7f6
--- /dev/null
+++ b/config/locales/doorkeeper.en.yml
@@ -0,0 +1,73 @@
+en:
+ activerecord:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ mongoid:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ mongo_mapper:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ doorkeeper:
+ errors:
+ messages:
+ # Common error messages
+ invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
+ invalid_redirect_uri: 'The redirect uri included is not valid.'
+ unauthorized_client: 'The client is not authorized to perform this request using this method.'
+ access_denied: 'The resource owner or authorization server denied the request.'
+ invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
+ server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
+ temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
+
+ #configuration error messages
+ credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
+ resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.'
+
+ # Access grant errors
+ unsupported_response_type: 'The authorization server does not support this response type.'
+
+ # Access token errors
+ invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
+ invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
+
+ # Password Access token errors
+ invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found'
+
+ invalid_token:
+ revoked: "The access token was revoked"
+ expired: "The access token expired"
+ unknown: "The access token is invalid"
+ scopes:
+ api: Access your API
+
+ flash:
+ applications:
+ create:
+ notice: 'Application created.'
+ destroy:
+ notice: 'Application deleted.'
+ update:
+ notice: 'Application updated.'
+ authorized_applications:
+ destroy:
+ notice: 'Application revoked.'
diff --git a/config/routes.rb b/config/routes.rb
index 533e044ca4c..d36540024aa 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,6 +2,11 @@ require 'sidekiq/web'
require 'api/api'
Gitlab::Application.routes.draw do
+ use_doorkeeper do
+ controllers :applications => 'oauth/applications',
+ :authorized_applications => 'oauth/authorized_applications',
+ :authorizations => 'oauth/authorizations'
+ end
#
# Search
#
@@ -75,6 +80,7 @@ Gitlab::Application.routes.draw do
#
namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
+ resources :keys, only: [:show, :destroy]
member do
put :team_update
put :block
@@ -113,6 +119,7 @@ Gitlab::Application.routes.draw do
member do
get :history
get :design
+ get :applications
put :reset_private_token
put :update_username
@@ -197,6 +204,7 @@ Gitlab::Application.routes.draw do
resources :raw, only: [:show], constraints: {id: /.+/}
resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do
+ # Cannot be GET to differentiate from GET paths that end in preview.
post :preview, on: :member
end
resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new'
@@ -211,7 +219,8 @@ Gitlab::Application.routes.draw do
end
end
- match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
+ get '/compare/:from...:to' => 'compare#show', :as => 'compare',
+ :constraints => {from: /.+/, to: /.+/}
resources :snippets, constraints: {id: /\d+/} do
member do
@@ -255,7 +264,7 @@ Gitlab::Application.routes.draw do
resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+ resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :refs, only: [] do
collection do
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index c263dd232af..b697f58d4ef 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -16,14 +16,13 @@ Gitlab::Seeder.quiet do
(1..5).each do |i|
begin
- User.seed(:id, [
- id: i + 10,
- username: "user#{i}",
- name: "User #{i}",
- email: "user#{i}@example.com",
- confirmed_at: DateTime.now,
- password: '12345678'
- ])
+ User.seed do |s|
+ s.username = "user#{i}"
+ s.name = "User #{i}"
+ s.email = "user#{i}@example.com"
+ s.confirmed_at = DateTime.now
+ s.password = '12345678'
+ end
print '.'
rescue ActiveRecord::RecordNotSaved
print 'F'
diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb
index 6fe63637dfe..a85b0426cec 100644
--- a/db/migrate/20141121161704_add_identity_table.rb
+++ b/db/migrate/20141121161704_add_identity_table.rb
@@ -14,6 +14,10 @@ SELECT provider, extern_uid, id FROM users
WHERE provider IS NOT NULL
eos
+ if index_exists?(:users, ["extern_uid", "provider"])
+ remove_index :users, ["extern_uid", "provider"]
+ end
+
remove_column :users, :extern_uid
remove_column :users, :provider
end
@@ -34,5 +38,9 @@ eos
end
drop_table :identities
+
+ unless index_exists?(:users, ["extern_uid", "provider"])
+ add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
+ end
end
end
diff --git a/db/migrate/20141216155758_create_doorkeeper_tables.rb b/db/migrate/20141216155758_create_doorkeeper_tables.rb
new file mode 100644
index 00000000000..af5aa7d8b73
--- /dev/null
+++ b/db/migrate/20141216155758_create_doorkeeper_tables.rb
@@ -0,0 +1,42 @@
+class CreateDoorkeeperTables < ActiveRecord::Migration
+ def change
+ create_table :oauth_applications do |t|
+ t.string :name, null: false
+ t.string :uid, null: false
+ t.string :secret, null: false
+ t.text :redirect_uri, null: false
+ t.string :scopes, null: false, default: ''
+ t.timestamps
+ end
+
+ add_index :oauth_applications, :uid, unique: true
+
+ create_table :oauth_access_grants do |t|
+ t.integer :resource_owner_id, null: false
+ t.integer :application_id, null: false
+ t.string :token, null: false
+ t.integer :expires_in, null: false
+ t.text :redirect_uri, null: false
+ t.datetime :created_at, null: false
+ t.datetime :revoked_at
+ t.string :scopes
+ end
+
+ add_index :oauth_access_grants, :token, unique: true
+
+ create_table :oauth_access_tokens do |t|
+ t.integer :resource_owner_id
+ t.integer :application_id
+ t.string :token, null: false
+ t.string :refresh_token
+ t.integer :expires_in
+ t.datetime :revoked_at
+ t.datetime :created_at, null: false
+ t.string :scopes
+ end
+
+ add_index :oauth_access_tokens, :token, unique: true
+ add_index :oauth_access_tokens, :resource_owner_id
+ add_index :oauth_access_tokens, :refresh_token, unique: true
+ end
+end
diff --git a/db/migrate/20141217125223_add_owner_to_application.rb b/db/migrate/20141217125223_add_owner_to_application.rb
new file mode 100644
index 00000000000..7d5e6d07d0f
--- /dev/null
+++ b/db/migrate/20141217125223_add_owner_to_application.rb
@@ -0,0 +1,7 @@
+class AddOwnerToApplication < ActiveRecord::Migration
+ def change
+ add_column :oauth_applications, :owner_id, :integer, null: true
+ add_column :oauth_applications, :owner_type, :string, null: true
+ add_index :oauth_applications, [:owner_id, :owner_type]
+ end
+end \ No newline at end of file
diff --git a/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb b/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb
new file mode 100644
index 00000000000..70e7272f7f3
--- /dev/null
+++ b/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb
@@ -0,0 +1,5 @@
+class AddDevelopersCanPushToProtectedBranches < ActiveRecord::Migration
+ def change
+ add_column :protected_branches, :developers_can_push, :boolean, default: false, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b8335c5841b..cb945e71665 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: 20141205134006) do
+ActiveRecord::Schema.define(version: 20141226080412) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -249,6 +249,49 @@ ActiveRecord::Schema.define(version: 20141205134006) do
add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
+ create_table "oauth_access_grants", force: true do |t|
+ t.integer "resource_owner_id", null: false
+ t.integer "application_id", null: false
+ t.string "token", null: false
+ t.integer "expires_in", null: false
+ t.text "redirect_uri", null: false
+ t.datetime "created_at", null: false
+ t.datetime "revoked_at"
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
+
+ create_table "oauth_access_tokens", force: true do |t|
+ t.integer "resource_owner_id"
+ t.integer "application_id"
+ t.string "token", null: false
+ t.string "refresh_token"
+ t.integer "expires_in"
+ t.datetime "revoked_at"
+ t.datetime "created_at", null: false
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree
+ add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree
+ add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
+
+ create_table "oauth_applications", force: true do |t|
+ t.string "name", null: false
+ t.string "uid", null: false
+ t.string "secret", null: false
+ t.text "redirect_uri", null: false
+ t.string "scopes", default: "", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "owner_id"
+ t.string "owner_type"
+ end
+
+ add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
+ add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
+
create_table "projects", force: true do |t|
t.string "name"
t.string "path"
@@ -279,10 +322,11 @@ ActiveRecord::Schema.define(version: 20141205134006) do
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
create_table "protected_branches", force: true do |t|
- t.integer "project_id", null: false
- t.string "name", null: false
+ t.integer "project_id", null: false
+ t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.boolean "developers_can_push", default: false, null: false
end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
@@ -367,7 +411,6 @@ ActiveRecord::Schema.define(version: 20141205134006) do
t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
- t.datetime "last_credential_check_at"
t.string "avatar"
t.string "confirmation_token"
t.datetime "confirmed_at"
@@ -375,6 +418,7 @@ ActiveRecord::Schema.define(version: 20141205134006) do
t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false
+ t.datetime "last_credential_check_at"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 896224fe930..3c8f8ad3d03 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -23,6 +23,7 @@
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Operations](operations/README.md) Keeping GitLab up and running
## Contributor documentation
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 6b379b02d28..8aae4f6b1bb 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -19,6 +19,8 @@ GET /groups
]
```
+You can search for groups by name or path with: `/groups?search=Rails`
+
## Details of a group
Get all details of a group.
diff --git a/doc/api/notes.md b/doc/api/notes.md
index b5256ac803e..c22e493562a 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -78,6 +78,21 @@ Parameters:
- `issue_id` (required) - The ID of an issue
- `body` (required) - The content of a note
+### Modify existing issue note
+
+Modify existing note of an issue.
+
+```
+PUT /projects/:id/issues/:issue_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `issue_id` (required) - The ID of an issue
+- `note_id` (required) - The ID of a note
+- `body` (required) - The content of a note
+
## Snippets
### List all snippet notes
@@ -137,7 +152,22 @@ POST /projects/:id/snippets/:snippet_id/notes
Parameters:
- `id` (required) - The ID of a project
-- `snippet_id` (required) - The ID of an snippet
+- `snippet_id` (required) - The ID of a snippet
+- `body` (required) - The content of a note
+
+### Modify existing snippet note
+
+Modify existing note of a snippet.
+
+```
+PUT /projects/:id/snippets/:snippet_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `snippet_id` (required) - The ID of a snippet
+- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
## Merge Requests
@@ -199,3 +229,18 @@ Parameters:
- `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of a merge request
- `body` (required) - The content of a note
+
+### Modify existing merge request note
+
+Modify existing note of a merge request.
+
+```
+PUT /projects/:id/merge_requests/:merge_request_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `merge_request_id` (required) - The ID of a merge request
+- `note_id` (required) - The ID of a note
+- `body` (required) - The content of a note
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 0055e2e476f..22d3c828a4b 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -11,6 +11,8 @@ GET /projects
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order
```json
[
@@ -628,6 +630,8 @@ GET /projects/search/:query
Parameters:
-- query (required) - A string contained in the project name
-- per_page (optional) - number of projects to return per page
-- page (optional) - the page to retrieve
+- `query` (required) - A string contained in the project name
+- `per_page` (optional) - number of projects to return per page
+- `page` (optional) - the page to retrieve
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 263259bc2f9..d987e11040b 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -54,7 +54,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake libkrb5-dev
Make sure you have the right version of Git installed
@@ -181,9 +181,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-5-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-6-stable gitlab
-**Note:** You can change `7-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `7-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -278,7 +278,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v2.2.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install[v2.4.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
@@ -294,9 +294,9 @@ GitLab Shell is an SSH access and repository management software developed speci
# When done you see 'Administrator account created:'
-**Note:** You can set the Administrator password by supplying it in environmental variable `GITLAB_ROOT_PASSWORD`, eg.:
+**Note:** You can set the Administrator/root password by supplying it in environmental variable `GITLAB_ROOT_PASSWORD` as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password.
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=newpassword
+ sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword
### Install Init Script
@@ -383,15 +383,17 @@ 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 an admin account for you. You can use it to log in:
+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:
root
5iveL!fe
-**Important Note:** Please go over to your profile page and immediately change the password, so nobody can access your GitLab by using this login information later on.
+**Important Note:** On login you'll be prompted to change the password.
**Enjoy!**
+You can use `sudo service gitlab start` and `sudo service gitlab stop` to start and stop GitLab.
+
## Advanced Setup Tips
### Using HTTPS
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 00adae58dfa..15b4fb622af 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -7,6 +7,7 @@ OmniAuth does not prevent standard GitLab authentication or LDAP (if configured)
- [Initial OmniAuth Configuration](#initial-omniauth-configuration)
- [Supported Providers](#supported-providers)
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
+- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
## Initial OmniAuth Configuration
diff --git a/doc/operations/README.md b/doc/operations/README.md
new file mode 100644
index 00000000000..f1456c6c8e2
--- /dev/null
+++ b/doc/operations/README.md
@@ -0,0 +1,4 @@
+# GitLab operations
+
+- [Sidekiq MemoryKiller](sidekiq_memory_killer.md)
+- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md)
diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md
new file mode 100644
index 00000000000..93521e976d5
--- /dev/null
+++ b/doc/operations/cleaning_up_redis_sessions.md
@@ -0,0 +1,52 @@
+# Cleaning up stale Redis sessions
+
+Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis.
+Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If
+you have been running a large GitLab server (thousands of users) since before
+GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis
+database after you upgrade to GitLab 7.3. You can also perform a cleanup while
+still running GitLab 7.2 or older, but in that case new stale sessions will
+start building up again after you clean up.
+
+In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte
+hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with
+GitLab 7.3.0, the keys are
+prefixed with 'session:gitlab:', so they would look like
+'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to
+remove the keys in the old format.
+
+First we define a shell function with the proper Redis connection details.
+
+```
+rcli() {
+ # This example works for Omnibus installations of GitLab 7.3 or newer. For an
+ # installation from source you will have to change the socket path and the
+ # path to redis-cli.
+ sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@"
+}
+
+# test the new shell function; the response should be PONG
+rcli ping
+```
+
+Now we do a search to see if there are any session keys in the old format for
+us to clean up.
+
+```
+# returns the number of old-format session keys in Redis
+rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l
+```
+
+If the number is larger than zero, you can proceed to expire the keys from
+Redis. If the number is zero there is nothing to clean up.
+
+```
+# Tell Redis to expire each matched key after 600 seconds.
+rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli
+# This will print '(integer) 1' for each key that gets expired.
+```
+
+Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis
+background save interval) your Redis database will be compacted. If you are
+still using GitLab 7.2, users who are not clicking around in GitLab during the
+10 minute expiry window will be signed out of GitLab.
diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md
new file mode 100644
index 00000000000..867b01b0d5a
--- /dev/null
+++ b/doc/operations/sidekiq_memory_killer.md
@@ -0,0 +1,38 @@
+# Sidekiq MemoryKiller
+
+The GitLab Rails application code suffers from memory leaks. For web requests
+this problem is made manageable using
+[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which
+restarts Unicorn worker processes in between requests when needed. The Sidekiq
+MemoryKiller applies the same approach to the Sidekiq processes used by GitLab
+to process background jobs.
+
+Unlike unicorn-worker-killer, which is enabled by default for all GitLab
+installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default
+_only_ for Omnibus packages. The reason for this is that the MemoryKiller
+relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab
+installations from source do not all use Runit or an equivalent.
+
+With the default settings, the MemoryKiller will cause a Sidekiq restart no
+more often than once every 15 minutes, with the restart causing about one
+minute of delay for incoming background jobs.
+
+## Configuring the MemoryKiller
+
+The MemoryKiller is controlled using environment variables.
+
+- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is
+ greater than 0, then after each Sidekiq job, the MemoryKiller will check the
+ RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq
+ process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a
+ delayed shutdown is triggered. The default value for Omnibus packages is set
+ [in the omnibus-gitlab
+ repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
+- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When
+ a shutdown is triggered, the Sidekiq process will keep working normally for
+ another 15 minutes.
+- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace
+ time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs.
+ Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
+ Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
+ restart Sidekiq.
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index e21384d21dc..c9928e11b2e 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -8,7 +8,6 @@ If a user is a GitLab administrator they receive all permissions.
## Project
-
| Action | Guest | Reporter | Developer | Master | Owner |
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
@@ -29,6 +28,7 @@ If a user is a GitLab administrator they receive all permissions.
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
| Enable/disable branch protection | | | | ✓ | ✓ |
+| Turn on/off prot. branch push for devs| | | | ✓ | ✓ |
| Rewrite/remove git tags | | | | ✓ | ✓ |
| Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ |
@@ -37,7 +37,7 @@ If a user is a GitLab administrator they receive all permissions.
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Force push to protected branches | | | | | |
-| Remove protected branches | | | | | |
+| Remove protected branches | | | | | |
## Group
@@ -49,4 +49,4 @@ If a user is a GitLab administrator they receive all permissions.
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
-Any user can remove himself from a group, unless he is the last Owner of the group.
+Any user can remove themselves from a group, unless they are the last Owner of the group.
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index 20a69a211dd..ec46af5fe3b 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -16,3 +16,4 @@ __Project integrations with external services for continuous integration and mor
- PivotalTracker
- Pushover
- Slack
+- TeamCity \ No newline at end of file
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 9e2f697bca6..770b7a70fe0 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -1,5 +1,8 @@
+# Rake tasks
+
- [Backup restore](backup_restore.md)
- [Cleanup](cleanup.md)
+- [Features](features.md)
- [Maintenance](maintenance.md) and self-checks
- [User management](user_management.md)
- [Web hooks](web_hooks.md)
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index 0700f24ab76..b31fd885404 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -100,9 +100,9 @@ List any major changes here, so the user is aware of them before starting to upg
- Web server changes
- File structure changes
-#### 1. Make backup
+#### 1. Stop server
-#### 2. Stop server
+#### 2. Make backup
#### 3. Do users need to update dependencies like `git`?
@@ -200,7 +200,7 @@ Add to your local `gitlab-ci/.git/config`:
# **4 workdays before release - Release RC1**
-### **1. Determine QA person
+### **1. Determine QA person**
Notify person of QA day.
@@ -215,6 +215,7 @@ It is important to do this as soon as possible, so we can catch any errors befor
### **3. Prepare the blog post**
- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
+- Make sure the blog post contains information about the GitLab CI release.
- Check the changelog of CE and EE for important changes.
- Also check the CI changelog
- Add a proposed tweet text to the blog post WIP MR description.
@@ -224,8 +225,8 @@ It is important to do this as soon as possible, so we can catch any errors befor
- Create WIP MR for adding MVP to MVP page on website
- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
- Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
-- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor)
-- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments
+- Assign to one reviewer who will fix spelling issues by editing the branch (either with a git client or by using the online editor)
+- Comment to the reviewer: '@person Please mention the whole team as soon as you are done (3 workdays before release at the latest)'
### **4. Create a regressions issue**
@@ -238,7 +239,7 @@ The release manager will comment here about the plans for patch releases.
Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately.
-### **4. Tweet**
+### **5. Tweet**
Tweet about the RC release:
@@ -246,6 +247,10 @@ Tweet about the RC release:
# **1 workdays before release - Preparation**
+### **0. Doublecheck blog post**
+
+Doublecheck the everyone has been mentioned in the blog post.
+
### **1. Pre QA merge**
Merge CE into EE before doing the QA.
@@ -265,7 +270,7 @@ Create an issue with description of a problem, if it is quick fix fix it yoursel
**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted,
create an issue about it in order to discuss the next steps after the release.
-# **22nd - Release CE, EE and CI**
+# **Workday before release - Create Omnibus tags and build packages**
**Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`**
@@ -302,20 +307,24 @@ Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org
This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
-### **4. Publish packages for new release**
+# **22nd - Release CE, EE and CI**
+
+### **1. Publish packages for new release**
Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository.
-### **5. Publish blog for new release**
+### **2. Publish blog for new release**
Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository.
-### **6. Tweet to blog**
+### **3. Tweet to blog**
Send out a tweet to share the good news with the world.
List the most important features and link to the blog post.
-Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>"
+Proposed tweet "Release of GitLab X.X & CI Y.Y! FEATURE, FEATURE and FEATURE <link-to-blogpost> #gitlab"
+
+Consider creating a post on Hacker News.
# **1 workday after release - Update GitLab.com**
diff --git a/doc/update/6.x-or-7.x-to-7.5.md b/doc/update/6.x-or-7.x-to-7.6.md
index c9b95c62611..883a654dcd8 100644
--- a/doc/update/6.x-or-7.x-to-7.5.md
+++ b/doc/update/6.x-or-7.x-to-7.6.md
@@ -1,6 +1,6 @@
-# From 6.x or 7.x to 7.5
+# From 6.x or 7.x to 7.6
-This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.5.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.6.
## Global issue numbers
@@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
For GitLab Community Edition:
```bash
-sudo -u git -H git checkout 7-5-stable
+sudo -u git -H git checkout 7-6-stable
```
OR
@@ -78,7 +78,7 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout 7-5-stable-ee
+sudo -u git -H git checkout 7-6-stable-ee
```
## 4. Install additional packages
@@ -119,7 +119,7 @@ sudo apt-get install pkg-config cmake
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.2.0
+sudo -u git -H git checkout v2.4.0
```
## 7. Install libs, migrations, etc.
@@ -154,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
-git diff 6-0-stable:config/gitlab.yml.example 7-5-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-6-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.2.0/config.yml.example but with your settings.
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.4.0/config.yml.example but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/lib/support/nginx/gitlab-ssl but with your settings.
* Copy rack attack middleware config
```bash
diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md
index deee73fe560..35cd437fdc4 100644
--- a/doc/update/7.5-to-7.6.md
+++ b/doc/update/7.5-to-7.6.md
@@ -1,7 +1,5 @@
# From 7.5 to 7.6
-**7.6 is not yet released. This is a preliminary upgrade guide.**
-
### 0. Stop server
sudo service gitlab stop
@@ -39,12 +37,14 @@ sudo -u git -H git checkout 7-6-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.2.0
+sudo -u git -H git checkout v2.4.0
```
### 4. Install libs, migrations, etc.
```bash
+sudo apt-get install libkrb5-dev
+
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres')
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 695c083d361..229689392b8 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -13,7 +13,7 @@ sudo service gitlab stop
git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
cd mysql-postgresql-converter
-mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
+mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production -p
python db_converter.py databasename.mysql databasename.psql
# Import the database dump as the application database user
@@ -94,7 +94,7 @@ sudo -u git -H mv tmp/backups/TIMESTAMP_gitlab_backup.tar tmp/backups/postgresql
# Create a separate database dump with PostgreSQL compatibility
cd tmp/backups/postgresql
-sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production
+sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p
# Clone the database converter
sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index 0a9f242d9ab..5016ee4baad 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -23,7 +23,7 @@ If you have local changes to your GitLab repository the script will stash them a
## 2. Run GitLab upgrade tool
-Note: GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+Note: GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX) while 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
# Starting with GitLab version 7.0 upgrader script has been moved to bin directory
cd /home/git/gitlab
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index c26d85e9955..8ef51b50b9d 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,3 +1,5 @@
+# Workflow
+
- [Workflow](workflow.md)
- [Project Features](project_features.md)
- [Authorization for merge requests](authorization_for_merge_requests.md)
@@ -6,3 +8,4 @@
- [GitLab Flow](gitlab_flow.md)
- [Notifications](notifications.md)
- [Migrating from SVN to GitLab](migrating_from_svn.md)
+- [Protected branches](protected_branches.md)
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index f8fd7c97e2a..1dbff60cbfd 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -1,6 +1,6 @@
![GitLab Flow](gitlab_flow.png)
-# Introduction
+## Introduction
Version management with git makes branching and merging much easier than older versioning systems such as SVN.
This allows a wide variety of branching strategies and workflows.
@@ -29,9 +29,9 @@ People have a hard time figuring out which branch they should develop on or depl
Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html)
We think there is still room for improvement and will detail a set of practices we call GitLab flow.
-# Git flow and its problems
+## Git flow and its problems
-[![Git Flow timeline by Vincent Driessen, used with persmission](gitdashflow.png)
+[![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
@@ -50,7 +50,7 @@ Frequently developers make a mistake and for example changes are only merged int
The root cause of these errors is that git flow is too complex for most of the use cases.
And doing releases doesn't automatically mean also doing hotfixes.
-# GitHub flow as a simpler alternative
+## GitHub flow as a simpler alternative
![Master branch with feature branches merged in](github_flow.png)
@@ -62,13 +62,13 @@ Merging everything into the master branch and deploying often means you minimize
But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues.
With GitLab flow we offer additional guidance for these questions.
-# Production branch with GitLab flow
+## Production branch with GitLab flow
![Master branch and production branch with arrow that indicate deployments](production_branch.png)
GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
This is possible for SaaS applications but are many cases where this is not possible.
-One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass AppStore validation.
+One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
In these cases you can make a production branch that reflects the deployed code.
You can deploy a new version by merging in master to the production branch.
@@ -78,7 +78,7 @@ This time is pretty accurate if you automatically deploy your production branch.
If you need a more exact time you can have your deployment script create a tag on each deployment.
This flow prevents the overhead of releasing, tagging and merging that is common to git flow.
-# Environment branches with GitLab flow
+## Environment branches with GitLab flow
![Multiple branches with the code cascading from one to another](environment_branches.png)
@@ -93,7 +93,7 @@ If master is good to go (it should be if you a practicing [continuous delivery](
If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/).
-# Release branches with GitLab flow
+## Release branches with GitLab flow
![Master and multiple release branches that vary in length with cherrypicks from master](release_branches.png)
@@ -109,7 +109,7 @@ Every time a bug-fix is included in a release branch the patch version is raised
Some projects also have a stable branch that points to the same commit as the latest released branch.
In this flow it is not common to have a production branch (or git flow master branch).
-# Merge/pull requests with GitLab flow
+## Merge/pull requests with GitLab flow
![Merge request with line comments](mr_inline_comments.png)
@@ -134,7 +134,7 @@ If the assigned person does not feel comfortable they can close the merge reques
In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md).
So if you want to merge it into a protected branch you assign it to someone with master authorizations.
-# Issues with GitLab flow
+## Issues with GitLab flow
![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png)
@@ -168,7 +168,7 @@ In this case it is no problem to reuse the same branch name since it was deleted
At any time there is at most one branch for every issue.
It is possible that one feature branch solves more than one issue.
-# Linking and closing issues from merge requests
+## Linking and closing issues from merge requests
![Merge request showing the linked issues that will be closed](close_issue_mr.png)
@@ -181,7 +181,7 @@ If you only want to make the reference without closing the issue you can also ju
If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue.
-# Squashing commits with rebase
+## Squashing commits with rebase
![Vim screen showing the rebase view](rebase.png)
@@ -189,7 +189,7 @@ With git you can use an interactive rebase (rebase -i) to squash multiple commit
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
-When you rebase you change the identifier (SHA1) of the commit and this is confusing.
+When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
If you do that the same change will be known under multiple identifiers and this can cause much confusion.
If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
@@ -207,7 +207,7 @@ If you revert a merge and you change your mind, revert the revert instead of mer
Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option.
Git management software will always create a merge commit when you accept a merge request.
-# Do not order commits with rebase
+## Do not order commits with rebase
![List of sequential merge commits](merge_commits.png)
@@ -231,8 +231,8 @@ The last reason for creating merge commits is having long lived branches that yo
Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
That's continuous building, and a Good Thing, but there's no integration, so it's not CI.".
-The solution to prevent many merge commits is to keep your feature branches shortlived, the vast majority should take less than one day of work.
-If your feature branches commenly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
+The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work.
+If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
As for the long running branches that take more than one day there are two strategies.
In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time.
In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release.
@@ -244,7 +244,7 @@ Developing software happen in small messy steps and it is OK to have your histor
You can use tools to view the network graphs of commits and understand the messy history that created your code.
If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
-# Voting on merge requests
+## Voting on merge requests
![Voting slider in GitLab](voting_slider.png)
@@ -252,7 +252,7 @@ It is common to voice approval or disapproval by using +1 or -1 emoticons.
In GitLab the +1 and -1 are aggregated and shown at the top of the merge request.
As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet.
-# Pushing and removing branches
+## Pushing and removing branches
![Remove checkbox for branch in merge requests](remove_checkbox.png)
@@ -266,7 +266,7 @@ This ensures that the branch overview in the repository management software show
This also ensures that when someone reopens the issue a new branch with the same name can be used without problem.
When you reopen an issue you need to create a new merge request.
-# Committing often and with the right message
+## Committing often and with the right message
![Good and bad commit message](good_commit.png)
@@ -282,7 +282,7 @@ Some words that are bad commit messages because they don't contain munch informa
The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number.
To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
-# Testing before merging
+## Testing before merging
![Merge requests showing the test states, red, yellow and green](ci_mr.png)
@@ -299,7 +299,7 @@ If there are no merge conflicts and the feature branches are short lived the ris
If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
If you have long lived feature branches that last for more than a few days you should make your issues smaller.
-# Merging in other code
+## Merging in other code
![Shell output showing git pull output](git_pull.png)
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
new file mode 100644
index 00000000000..805f7f8d35c
--- /dev/null
+++ b/doc/workflow/protected_branches.md
@@ -0,0 +1,33 @@
+# Protected branches
+
+Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
+
+To prevent people from messing with history or pushing code without review, we've created protected branches.
+
+A protected branch does three simple things:
+
+* it prevents pushes from everybody except users with Master permission
+* it prevents anyone from force pushing to the branch
+* it prevents anyone from deleting the branch
+
+You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
+
+To protect a branch, user needs to have at least a Master permission level, see [permissions document](permissions/permissions.md).
+
+![protected branches page](protected_branches/protected_branches1.png)
+
+Navigate to project settings page and select `protected branches`. From the `Branch` dropdown menu select the branch you want to protect.
+
+Some workflows, like [GitLab workflow](gitlab_flow.md), require all users with write access to submit a Merge request in order to get the code into a protected branch.
+
+Since Masters and Owners can already push to protected branches, that means Developers cannot push to protected branch and need to submit a Merge request.
+
+However, there are workflows where that is not needed and only protecting from force pushes and branch removal is useful.
+
+For those workflows, you can allow everyone with write access to push to a protected branch by selecting `Developers can push` check box.
+
+On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box.
+
+![Developers can push](protected_branches/protected_branches2.png)
+
+
diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png
new file mode 100644
index 00000000000..5c2a3de5f70
--- /dev/null
+++ b/doc/workflow/protected_branches/protected_branches1.png
Binary files differ
diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png
new file mode 100644
index 00000000000..2dca3541365
--- /dev/null
+++ b/doc/workflow/protected_branches/protected_branches2.png
Binary files differ
diff --git a/docker/Dockerfile b/docker/Dockerfile
index aea59916c7a..5d0880b8c88 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -2,16 +2,16 @@ FROM ubuntu:14.04
# Install required packages
RUN apt-get update -q \
- && DEBIAN_FRONTEND=noninteractive apt-get install -qy \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \
+ ca-certificates \
openssh-server \
- wget \
- && apt-get clean
+ wget
# Download & Install GitLab
# If the Omnibus package version below is outdated please contribute a merge request to update it.
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN TMP_FILE=$(mktemp); \
- wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.2-omnibus.5.2.1.ci-1_amd64.deb \
+ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.3-omnibus.5.2.1.ci-1_amd64.deb \
&& dpkg -i $TMP_FILE \
&& rm -f $TMP_FILE
diff --git a/features/admin/users.feature b/features/admin/users.feature
index 278f6a43e94..1a8720dd77e 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -35,3 +35,13 @@ Feature: Admin Users
And I see the secondary email
When I click remove secondary email
Then I should not see secondary email anymore
+
+ Scenario: Show user keys
+ Given user "Pete" with ssh keys
+ And I visit admin users page
+ And click on user "Pete"
+ Then I should see key list
+ And I click on the key title
+ Then I should see key details
+ And I click on remove key
+ Then I should see the key removed
diff --git a/features/explore/groups.feature b/features/explore/groups.feature
index b50a3e766c6..c11634bd74a 100644
--- a/features/explore/groups.feature
+++ b/features/explore/groups.feature
@@ -28,7 +28,6 @@ Feature: Explore Groups
Given group "TestGroup" has internal project "Internal"
When I sign in as a user
And I visit group "TestGroup" issues page
- And I change filter to Everyone's
Then I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -36,7 +35,6 @@ Feature: Explore Groups
Given group "TestGroup" has internal project "Internal"
When I sign in as a user
And I visit group "TestGroup" merge requests page
- And I change filter to Everyone's
Then I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -94,7 +92,6 @@ Feature: Explore Groups
Given group "TestGroup" has public project "Community"
When I sign in as a user
And I visit group "TestGroup" issues page
- And I change filter to Everyone's
Then I should see project "Community" items
And I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -104,7 +101,6 @@ Feature: Explore Groups
Given group "TestGroup" has public project "Community"
When I sign in as a user
And I visit group "TestGroup" merge requests page
- And I change filter to Everyone's
Then I should see project "Community" items
And I should see project "Internal" items
And I should not see project "Enterprise" items
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d7fa370fe2a..fd132e1cd80 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -71,6 +71,20 @@ Feature: Profile
And I click on my profile picture
Then I should see my user page
+ Scenario: I can manage application
+ Given I visit profile applications page
+ Then I click on new application button
+ And I should see application form
+ Then I fill application form out and submit
+ And I see application
+ Then I click edit
+ And I see edit application form
+ Then I change name of application and submit
+ And I see that application was changed
+ Then I visit profile applications page
+ And I click to remove application
+ Then I see that application is removed
+
@javascript
Scenario: I change my application theme
Given I visit profile design page
@@ -101,4 +115,4 @@ Feature: Profile
Scenario: I see the password strength indicator with success
Given I visit profile password page
When I try to set a strong password
- Then I should see the input field green \ No newline at end of file
+ Then I should see the input field green
diff --git a/features/project/service.feature b/features/project/service.feature
index ed9e03b428d..85939a5c9ca 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -66,3 +66,10 @@ Feature: Project Services
And I click Atlassian Bamboo CI service link
And I fill Atlassian Bamboo CI settings
Then I should see Atlassian Bamboo CI service settings saved
+
+ Scenario: Activate jetBrains TeamCity CI service
+ When I visit project "Shop" services page
+ And I click jetBrains TeamCity CI service link
+ And I fill jetBrains TeamCity CI settings
+ Then I should see jetBrains TeamCity CI service settings saved
+
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index b7d70881d56..6ea64f70092 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -50,6 +50,16 @@ Feature: Project Source Browse Files
And I click button "Edit"
Then I can edit code
+ Scenario: If the file is binary the edit link is hidden
+ Given I visit a binary file in the repo
+ Then I cannot see the edit button
+
+ Scenario: If I don't have edit permission the edit link is disabled
+ Given public project "Community"
+ And I visit project "Community" source page
+ And I click on ".gitignore" file in repo
+ Then The edit button is disabled
+
@javascript
Scenario: I can edit and commit file
Given I click on ".gitignore" file in repo
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index d69a87cd07e..4171398e568 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -22,7 +22,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
step 'submit form with new group info' do
- fill_in 'group_name', with: 'gitlab'
+ fill_in 'group_path', with: 'gitlab'
fill_in 'group_description', with: 'Group description'
click_button "Create group"
end
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 546c1bf2a12..e1383097248 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -82,4 +82,36 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
page.should have_content 'Account'
page.should have_content 'Personal projects limit'
end
+
+ step 'user "Pete" with ssh keys' do
+ user = create(:user, name: 'Pete')
+ create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
+ create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
+ end
+
+ step 'click on user "Pete"' do
+ click_link 'Pete'
+ end
+
+ step 'I should see key list' do
+ page.should have_content 'ssh-rsa Key2'
+ page.should have_content 'ssh-rsa Key1'
+ end
+
+ step 'I click on the key title' do
+ click_link 'ssh-rsa Key2'
+ end
+
+ step 'I should see key details' do
+ page.should have_content 'ssh-rsa Key2'
+ page.should have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
+ end
+
+ step 'I click on remove key' do
+ click_link 'Remove'
+ end
+
+ step 'I should see the key removed' do
+ page.should_not have_content 'ssh-rsa Key2'
+ end
end
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 2a5850d091b..b77113e3974 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -35,14 +35,20 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".scope-filter" do
- click_link 'Created by me'
+ within ".assignee-filter" do
+ click_link "Any"
+ end
+ within ".author-filter" do
+ click_link current_user.name
end
end
step 'I click "All" link' do
- within ".scope-filter" do
- click_link "Everyone's"
+ within ".author-filter" do
+ click_link "Any"
+ end
+ within ".assignee-filter" do
+ click_link "Any"
end
end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 75e53173d3f..6261c89924c 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -39,14 +39,20 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".scope-filter" do
- click_link 'Created by me'
+ within ".assignee-filter" do
+ click_link "Any"
+ end
+ within ".author-filter" do
+ click_link current_user.name
end
end
step 'I click "All" link' do
- within ".scope-filter" do
- click_link "Everyone's"
+ within ".author-filter" do
+ click_link "Any"
+ end
+ within ".assignee-filter" do
+ click_link "Any"
end
end
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 616a297db99..f09d751dba3 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -77,7 +77,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'submit form with new group "Samurai" info' do
- fill_in 'group_name', with: 'Samurai'
+ fill_in 'group_path', with: 'Samurai'
fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button "Create group"
end
@@ -89,17 +89,17 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I should see newly created group "Samurai"' do
page.should have_content "Samurai"
page.should have_content "Tokugawa Shogunate"
- page.should have_content "Currently you are only seeing events from the"
end
step 'I change group "Owned" name to "new-name"' do
fill_in 'group_name', with: 'new-name'
+ fill_in 'group_path', with: 'new-name'
click_button "Save group"
end
step 'I should see new group "Owned" name' do
within ".navbar-gitlab" do
- page.should have_content "group: new-name"
+ page.should have_content "new-name"
end
end
@@ -188,7 +188,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see group milestone with descriptions and expiry date' do
- page.should have_content('Lorem Ipsum is simply dummy text of the printing and typesetting industry')
page.should have_content('expires at Aug 20, 2114')
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 38aaadcd28d..29fc7e68dac 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -221,4 +221,54 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I should see groups I belong to' do
page.should have_css('.profile-groups-avatars', visible: true)
end
+
+ step 'I click on new application button' do
+ click_on 'New Application'
+ end
+
+ step 'I should see application form' do
+ page.should have_content "New application"
+ end
+
+ step 'I fill application form out and submit' do
+ fill_in :doorkeeper_application_name, with: 'test'
+ fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
+ click_on "Submit"
+ end
+
+ step 'I see application' do
+ page.should have_content "Application: test"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click edit' do
+ click_on "Edit"
+ end
+
+ step 'I see edit application form' do
+ page.should have_content "Edit application"
+ end
+
+ step 'I change name of application and submit' do
+ page.should have_content "Edit application"
+ fill_in :doorkeeper_application_name, with: 'test_changed'
+ click_on "Submit"
+ end
+
+ step 'I see that application was changed' do
+ page.should have_content "test_changed"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click to remove application' do
+ within '.oauth-applications' do
+ click_on "Destroy"
+ end
+ end
+
+ step "I see that application is removed" do
+ page.find(".oauth-applications").should_not have_content "test_changed"
+ end
end
diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb
index d1e87d40705..ea912e5b4da 100644
--- a/features/steps/profile/ssh_keys.rb
+++ b/features/steps/profile/ssh_keys.rb
@@ -37,9 +37,7 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
end
step 'I should not see "Work" ssh key' do
- within "#keys-table" do
- page.should_not have_content "Work"
- end
+ page.should_not have_content "Work"
end
step 'I have ssh key "ssh-rsa Work"' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index 935f313e298..d515ee1ac11 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -78,14 +78,14 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click side-by-side diff button' do
- click_link "Side-by-side Diff"
+ click_link "Side-by-side"
end
step 'I see side-by-side diff button' do
- page.should have_content "Side-by-side Diff"
+ page.should have_content "Side-by-side"
end
step 'I see inline diff button' do
- page.should have_content "Inline Diff"
+ page.should have_content "Inline"
end
end
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index e1062a6ce39..6b07b62f16f 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -3,7 +3,7 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
include SharedPaths
step 'fill project form with valid data' do
- fill_in 'project_name', with: 'Empty'
+ fill_in 'project_path', with: 'Empty'
click_button "Create project"
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index d5e060bdbe8..84f1ebc003b 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -57,9 +57,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click link "Close"' do
- within '.page-title' do
- click_link "Close"
- end
+ first(:css, '.close-mr-link').click
end
step 'I submit new merge request "Wiki Feature"' do
@@ -111,7 +109,11 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click on the commit in the merge request' do
- within '.mr-commits' do
+ within '.merge-request-tabs' do
+ click_link 'Commits'
+ end
+
+ within '.commits' do
click_link Commit.truncate_sha(sample_commit.id)
end
end
@@ -154,7 +156,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'merge request is mergeable' do
- page.should have_content 'You can accept this request automatically'
+ page.should have_button 'Accept Merge Request'
end
step 'I modify merge commit message' do
@@ -181,13 +183,11 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click link "Reopen"' do
- within '.page-title' do
- click_link "Reopen"
- end
+ first(:css, '.reopen-mr-link').click
end
step 'I should see reopened merge request "Bug NS-04"' do
- within '.state-label' do
+ within '.issue-box' do
page.should have_content "Open"
end
end
@@ -265,7 +265,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click Side-by-side Diff tab' do
- click_link 'Side-by-side Diff'
+ find('a', text: 'Side-by-side').trigger('click')
end
step 'I should see comments on the side-by-side diff page' do
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 7a0b47a8fe5..09e86447058 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -15,6 +15,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
page.should have_content 'Assembla'
page.should have_content 'Pushover'
page.should have_content 'Atlassian Bamboo'
+ page.should have_content 'JetBrains TeamCity'
end
step 'I click gitlab-ci service link' do
@@ -168,4 +169,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Build key').value.should == 'KEY'
find_field('Username').value.should == 'user'
end
+
+ step 'I click JetBrains TeamCity CI service link' do
+ click_link 'JetBrains TeamCity CI'
+ end
+
+ step 'I fill JetBrains TeamCity CI settings' do
+ check 'Active'
+ fill_in 'Teamcity url', with: 'http://teamcity.example.com'
+ fill_in 'Build type', with: 'GitlabTest_Build'
+ fill_in 'Username', with: 'user'
+ fill_in 'Password', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see JetBrains TeamCity CI service settings saved' do
+ find_field('Teamcity url').value.should == 'http://teamcity.example.com'
+ find_field('Build type').value.should == 'GitlabTest_Build'
+ find_field('Username').value.should == 'user'
+ end
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index ddd501d4f88..805e6ff0eac 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -48,6 +48,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_link 'Edit'
end
+ step 'I cannot see the edit button' do
+ page.should_not have_link 'edit'
+ end
+
+ step 'The edit button is disabled' do
+ page.should have_css '.disabled', text: 'Edit'
+ end
+
step 'I can edit code' do
set_new_content
evaluate_script('editor.getValue()').should == new_gitignore_content
diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb
index f41b59a6f2b..c229864bc83 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -2,7 +2,7 @@ module SharedActiveTab
include Spinach::DSL
def ensure_active_main_tab(content)
- find('.main-nav li.active').should have_content(content)
+ find('.nav-sidebar > li.active').should have_content(content)
end
def ensure_active_sub_tab(content)
@@ -10,11 +10,11 @@ module SharedActiveTab
end
def ensure_active_sub_nav(content)
- find('div.content ul.nav-stacked-menu li.active').should have_content(content)
+ find('.sidebar-subnav > li.active').should have_content(content)
end
step 'no other main tabs should be active' do
- page.should have_selector('.main-nav li.active', count: 1)
+ page.should have_selector('.nav-sidebar > li.active', count: 1)
end
step 'no other sub tabs should be active' do
@@ -22,7 +22,7 @@ module SharedActiveTab
end
step 'no other sub navs should be active' do
- page.should have_selector('div.content ul.nav-stacked-menu li.active', count: 1)
+ page.should have_selector('.sidebar-subnav > li.active', count: 1)
end
step 'the active main tab should be Home' do
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index a0150e90380..41db2612f26 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -2,7 +2,7 @@ module SharedIssuable
include Spinach::DSL
def edit_issuable
- find('.issue-btn-group').click_link 'Edit'
+ find(:css, '.issuable-edit').click
end
step 'I click link "Edit" for the merge request' do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 5f292255ce1..e657fceb704 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -1,6 +1,7 @@
module SharedPaths
include Spinach::DSL
include RepoHelpers
+ include DashboardHelper
step 'I visit new project page' do
visit new_project_path
@@ -71,11 +72,11 @@ module SharedPaths
end
step 'I visit dashboard issues page' do
- visit issues_dashboard_path
+ visit assigned_issues_dashboard_path
end
step 'I visit dashboard merge requests page' do
- visit merge_requests_dashboard_path
+ visit assigned_mrs_dashboard_path
end
step 'I visit dashboard search page' do
@@ -94,6 +95,10 @@ module SharedPaths
visit profile_path
end
+ step 'I visit profile applications page' do
+ visit applications_profile_path
+ end
+
step 'I visit profile password page' do
visit edit_profile_password_path
end
@@ -178,6 +183,11 @@ module SharedPaths
visit project_tree_path(@project, root_ref)
end
+ step 'I visit a binary file in the repo' do
+ visit project_blob_path(@project, File.join(
+ root_ref, 'files/images/logo-black.png'))
+ end
+
step "I visit my project's commits page" do
visit project_commits_path(@project, root_ref, {limit: 5})
end
@@ -380,6 +390,11 @@ module SharedPaths
visit project_path(project)
end
+ step 'I visit project "Community" source page' do
+ project = Project.find_by(name: 'Community')
+ visit project_tree_path(project, root_ref)
+ end
+
step 'I visit project "Internal" page' do
project = Project.find_by(name: "Internal")
visit project_path(project)
diff --git a/lib/api/api.rb b/lib/api/api.rb
index d26667ba3f7..cb46f477ff9 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -2,6 +2,7 @@ Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
module API
class API < Grape::API
+ include APIGuard
version 'v3', using: :path
rescue_from ActiveRecord::RecordNotFound do
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
new file mode 100644
index 00000000000..23975518181
--- /dev/null
+++ b/lib/api/api_guard.rb
@@ -0,0 +1,175 @@
+# Guard API with OAuth 2.0 Access Token
+
+require 'rack/oauth2'
+
+module APIGuard
+ extend ActiveSupport::Concern
+
+ included do |base|
+ # OAuth2 Resource Server Authentication
+ use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
+ # The authenticator only fetches the raw token string
+
+ # Must yield access token to store it in the env
+ request.access_token
+ end
+
+ helpers HelperMethods
+
+ install_error_responders(base)
+ end
+
+ # Helper Methods for Grape Endpoint
+ module HelperMethods
+ # Invokes the doorkeeper guard.
+ #
+ # If token is presented and valid, then it sets @current_user.
+ #
+ # If the token does not have sufficient scopes to cover the requred scopes,
+ # then it raises InsufficientScopeError.
+ #
+ # If the token is expired, then it raises ExpiredError.
+ #
+ # If the token is revoked, then it raises RevokedError.
+ #
+ # If the token is not found (nil), then it raises TokenNotFoundError.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def doorkeeper_guard!(scopes: [])
+ if (access_token = find_access_token).nil?
+ raise TokenNotFoundError
+
+ else
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
+
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+
+ end
+ end
+ end
+
+ def doorkeeper_guard(scopes: [])
+ if access_token = find_access_token
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
+
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+ end
+ end
+ end
+
+ def current_user
+ @current_user
+ end
+
+ private
+ def find_access_token
+ @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods)
+ end
+
+ def doorkeeper_request
+ @doorkeeper_request ||= ActionDispatch::Request.new(env)
+ end
+
+ def validate_access_token(access_token, scopes)
+ Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
+ end
+ end
+
+ module ClassMethods
+ # Installs the doorkeeper guard on the whole Grape API endpoint.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def guard_all!(scopes: [])
+ before do
+ guard! scopes: scopes
+ end
+ end
+
+ private
+ def install_error_responders(base)
+ error_classes = [ MissingTokenError, TokenNotFoundError,
+ ExpiredError, RevokedError, InsufficientScopeError]
+
+ base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
+ end
+
+ def oauth2_bearer_token_error_handler
+ Proc.new {|e|
+ response = case e
+ when MissingTokenError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
+
+ when TokenNotFoundError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Bad Access Token.")
+
+ when ExpiredError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token is expired. You can either do re-authorization or token refresh.")
+
+ when RevokedError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token was revoked. You have to re-authorize from the user.")
+
+ when InsufficientScopeError
+ # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
+ # does not include WWW-Authenticate header, which breaks the standard.
+ Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
+ :insufficient_scope,
+ Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
+ { :scope => e.scopes})
+ end
+
+ response.finish
+ }
+ end
+ end
+
+ #
+ # Exceptions
+ #
+
+ class MissingTokenError < StandardError; end
+
+ class TokenNotFoundError < StandardError; end
+
+ class ExpiredError < StandardError; end
+
+ class RevokedError < StandardError; end
+
+ class InsufficientScopeError < StandardError
+ attr_reader :scopes
+ def initialize(scopes)
+ @scopes = scopes
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 6ec1a753a69..b52d786e020 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -14,7 +14,8 @@ module API
# Example Request:
# GET /projects/:id/repository/branches
get ":id/repository/branches" do
- present user_project.repository.branches.sort_by(&:name), with: Entities::RepoObject, project: user_project
+ branches = user_project.repository.branches.sort_by(&:name)
+ present branches, with: Entities::RepoObject, project: user_project
end
# Get a single branch
@@ -26,7 +27,7 @@ module API
# GET /projects/:id/repository/branches/:branch
get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do
@branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
- not_found!("Branch does not exist") if @branch.nil?
+ not_found!("Branch") unless @branch
present @branch, with: Entities::RepoObject, project: user_project
end
@@ -43,7 +44,7 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
+ not_found!("Branch") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
user_project.protected_branches.create(name: @branch.name) unless protected_branch
@@ -63,7 +64,7 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
+ not_found!("Branch does not exist") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 6c5391b98c8..0de4e720ffe 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -108,7 +108,7 @@ module API
if note.save
present note, with: Entities::CommitNote
else
- not_found!
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 84e1d311781..e6e71bac367 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -35,7 +35,7 @@ module API
file_path = attrs.delete(:file_path)
commit = user_project.repository.commit(ref)
- not_found! "Commit" unless commit
+ not_found! 'Commit' unless commit
blob = user_project.repository.blob_at(commit.sha, file_path)
@@ -53,7 +53,7 @@ module API
commit_id: commit.id,
}
else
- render_api_error!('File not found', 404)
+ not_found! 'File'
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f0ab6938b1c..bda60b3b7d5 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -25,11 +25,14 @@ module API
# Example Request:
# GET /groups
get do
- if current_user.admin
- @groups = paginate Group
- else
- @groups = paginate current_user.groups
- end
+ @groups = if current_user.admin
+ Group.all
+ else
+ current_user.groups
+ end
+
+ @groups = @groups.search(params[:search]) if params[:search].present?
+ @groups = paginate @groups
present @groups, with: Entities::Group
end
@@ -51,7 +54,7 @@ module API
if @group.save
present @group, with: Entities::Group
else
- not_found!
+ render_api_error!("Failed to save group #{@group.errors.messages}", 400)
end
end
@@ -94,7 +97,7 @@ module API
if result
present group
else
- not_found!
+ render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 027fb20ec46..62c26ef76ce 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -11,7 +11,7 @@ module API
def current_user
private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
- @current_user ||= User.find_by(authentication_token: private_token)
+ @current_user ||= (User.find_by(authentication_token: private_token) || doorkeeper_guard)
unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
return nil
@@ -42,7 +42,7 @@ module API
def user_project
@project ||= find_project(params[:id])
- @project || not_found!
+ @project || not_found!("Project")
end
def find_project(id)
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index a365f1db00f..81038d05f12 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -233,7 +233,7 @@ module API
if note.save
present note, with: Entities::MRNote
else
- render_validation_error!(note)
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index a4fdb752d69..2ea49359df0 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -48,7 +48,7 @@ module API
if milestone.valid?
present milestone, with: Entities::Milestone
else
- not_found!
+ render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
end
end
@@ -72,7 +72,7 @@ module API
if milestone.valid?
present milestone, with: Entities::Milestone
else
- not_found!
+ render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 0ef9a3c4beb..3726be7c537 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -61,9 +61,42 @@ module API
if @note.valid?
present @note, with: Entities::Note
else
- not_found!
+ not_found!("Note #{@note.errors.messages}")
end
end
+
+ # Modify existing +noteable+ note
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # noteable_id (required) - The ID of an issue or snippet
+ # node_id (required) - The ID of a note
+ # body (required) - New content of a note
+ # Example Request:
+ # PUT /projects/:id/issues/:noteable_id/notes/:note_id
+ # PUT /projects/:id/snippets/:noteable_id/notes/:node_id
+ put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
+ required_attributes! [:body]
+
+ authorize! :admin_note, user_project.notes.find(params[:note_id])
+
+ opts = {
+ note: params[:body],
+ note_id: params[:note_id],
+ noteable_type: noteables_str.classify,
+ noteable_id: params[noteable_id_str]
+ }
+
+ @note = ::Notes::UpdateService.new(user_project, current_user,
+ opts).execute
+
+ if @note.valid?
+ present @note, with: Entities::Note
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
+
end
end
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 7d056b9bf58..be9850367b9 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -53,7 +53,7 @@ module API
if @hook.errors[:url].present?
error!("Invalid url given", 422)
end
- not_found!
+ not_found!("Project hook #{@hook.errors.messages}")
end
end
@@ -82,7 +82,7 @@ module API
if @hook.errors[:url].present?
error!("Invalid url given", 422)
end
- not_found!
+ not_found!("Project hook #{@hook.errors.messages}")
end
end
diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb
index 1595ed0bc36..8e32f124ea5 100644
--- a/lib/api/project_members.rb
+++ b/lib/api/project_members.rb
@@ -9,7 +9,7 @@ module API
if errors[:access_level].any?
error!(errors[:access_level], 422)
end
- not_found!
+ not_found!(errors)
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 7fcf97d1ad6..e1cc2348865 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -22,6 +22,15 @@ module API
# GET /projects
get do
@projects = current_user.authorized_projects
+ sort = params[:sort] == 'desc' ? 'desc' : 'asc'
+
+ @projects = case params["order_by"]
+ when 'id' then @projects.reorder("id #{sort}")
+ when 'name' then @projects.reorder("name #{sort}")
+ when 'created_at' then @projects.reorder("created_at #{sort}")
+ when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}")
+ else @projects
+ end
# If the archived parameter is passed, limit results accordingly
if params[:archived].present?
@@ -37,7 +46,17 @@ module API
# Example Request:
# GET /projects/owned
get '/owned' do
- @projects = paginate current_user.owned_projects
+ sort = params[:sort] == 'desc' ? 'desc' : 'asc'
+ @projects = current_user.owned_projects
+ @projects = case params["order_by"]
+ when 'id' then @projects.reorder("id #{sort}")
+ when 'name' then @projects.reorder("name #{sort}")
+ when 'created_at' then @projects.reorder("created_at #{sort}")
+ when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}")
+ else @projects
+ end
+
+ @projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -47,7 +66,17 @@ module API
# GET /projects/all
get '/all' do
authenticated_as_admin!
- @projects = paginate Project
+ sort = params[:sort] == 'desc' ? 'desc' : 'asc'
+
+ @projects = case params["order_by"]
+ when 'id' then Project.order("id #{sort}")
+ when 'name' then Project.order("name #{sort}")
+ when 'created_at' then Project.order("created_at #{sort}")
+ when 'last_activity_at' then Project.order("last_activity_at #{sort}")
+ else Project
+ end
+
+ @projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -198,7 +227,7 @@ module API
render_api_error!("Project already forked", 409)
end
else
- not_found!
+ not_found!("Source Project")
end
end
@@ -227,6 +256,16 @@ module API
ids = current_user.authorized_projects.map(&:id)
visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
+ sort = params[:sort] == 'desc' ? 'desc' : 'asc'
+
+ projects = case params["order_by"]
+ when 'id' then projects.order("id #{sort}")
+ when 'name' then projects.order("name #{sort}")
+ when 'created_at' then projects.order("created_at #{sort}")
+ when 'last_activity_at' then projects.order("last_activity_at #{sort}")
+ else projects
+ end
+
present paginate(projects), with: Entities::Project
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index a1a7721b288..03a556a2c55 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -133,7 +133,7 @@ module API
env['api.format'] = :binary
present data
else
- not_found!
+ not_found!('File')
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 875f8d8b3a3..d47ef61fd11 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -79,16 +79,8 @@ module Gitlab
oldrev, newrev, ref = change.split(' ')
action = if project.protected_branch?(branch_name(ref))
- # we dont allow force push to protected branch
- if forced_push?(project, oldrev, newrev)
- :force_push_code_to_protected_branches
- # and we dont allow remove of protected branch
- elsif newrev == Gitlab::Git::BLANK_SHA
- :remove_protected_branches
- else
- :push_code_to_protected_branches
- end
- elsif project.repository.tag_names.include?(tag_name(ref))
+ protected_branch_action(project, oldrev, newrev, branch_name(ref))
+ elsif protected_tag?(project, tag_name(ref))
# Prevent any changes to existing git tag unless user has permissions
:admin_project
else
@@ -108,6 +100,24 @@ module Gitlab
private
+ def protected_branch_action(project, oldrev, newrev, branch_name)
+ # we dont allow force push to protected branch
+ if forced_push?(project, oldrev, newrev)
+ :force_push_code_to_protected_branches
+ # and we dont allow remove of protected branch
+ elsif newrev == Gitlab::Git::BLANK_SHA
+ :remove_protected_branches
+ elsif project.developers_can_push_to_protected_branch?(branch_name)
+ :push_code
+ else
+ :push_code_to_protected_branches
+ end
+ end
+
+ def protected_tag?(project, tag_name)
+ project.repository.tag_names.include?(tag_name)
+ end
+
def user_allowed?(user)
Gitlab::UserAccess.allowed?(user)
end
diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb
index b7c50cb734d..a7c83a880f6 100644
--- a/lib/gitlab/theme.rb
+++ b/lib/gitlab/theme.rb
@@ -19,5 +19,19 @@ module Gitlab
return themes[id]
end
+
+ def self.type_css_class_by_id(id)
+ types = {
+ BASIC => 'light_theme',
+ MARS => 'dark_theme',
+ MODERN => 'dark_theme',
+ GRAY => 'dark_theme',
+ COLOR => 'dark_theme'
+ }
+
+ id ||= Gitlab.config.gitlab.default_theme
+
+ types[id]
+ end
end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 7ff23a7600a..43115915de1 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -585,10 +585,6 @@ namespace :gitlab do
def gitlab_shell_patch_version
Gitlab::Shell.version_required.split('.')[2].to_i
end
-
- def has_gitlab_shell3?
- gitlab_shell_version.try(:start_with?, "v3.")
- end
end
@@ -790,14 +786,14 @@ namespace :gitlab do
end
def sanitized_message(project)
- if sanitize
+ if should_sanitize?
"#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... "
else
"#{project.name_with_namespace.yellow} ... "
end
end
- def sanitize
+ def should_sanitize?
if ENV['SANITIZE'] == "true"
true
else
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index b557567bd04..37d6b416d22 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -12,7 +12,7 @@ describe "Admin::Hooks", feature: true do
describe "GET /admin/hooks" do
it "should be ok" do
visit admin_root_path
- within ".main-nav" do
+ within ".sidebar-wrapper" do
click_on "Hooks"
end
current_path.should == admin_hooks_path
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 746b6fc1ac9..de4f94fff2f 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -24,11 +24,12 @@ describe "User Feed", feature: true do
end
it "should have issue opened event" do
- body.should have_content("#{user.name} opened issue ##{issue.iid}")
+ expect(body).to have_content("#{safe_name} opened issue ##{issue.iid}")
end
it "should have issue comment event" do
- body.should have_content("#{user.name} commented on issue ##{issue.iid}")
+ expect(body).
+ to have_content("#{safe_name} commented on issue ##{issue.iid}")
end
end
end
@@ -40,4 +41,8 @@ describe "User Feed", feature: true do
def note_event(note, user)
EventCreateService.new.leave_note(note, user)
end
+
+ def safe_name
+ html_escape(user.name)
+ end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 66e87e57cbc..8561fd89ba7 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -129,6 +129,13 @@ describe Gitlab::GitAccess do
}
end
+ def self.updated_permissions_matrix
+ updated_permissions_matrix = permissions_matrix.dup
+ updated_permissions_matrix[:developer][:push_protected_branch] = true
+ updated_permissions_matrix[:developer][:push_all] = true
+ updated_permissions_matrix
+ end
+
permissions_matrix.keys.each do |role|
describe "#{role} access" do
before { protect_feature_branch }
@@ -143,5 +150,22 @@ describe Gitlab::GitAccess do
end
end
end
+
+ context "with enabled developers push to protected branches " do
+ updated_permissions_matrix.keys.each do |role|
+ describe "#{role} access" do
+ before { create(:protected_branch, name: 'feature', developers_can_push: true, project: project) }
+ before { project.team << [user, role] }
+
+ updated_permissions_matrix[role].each do |action, allowed|
+ context action do
+ subject { access.push_access_check(user, project, changes[action]) }
+
+ it { subject.allowed?.should allowed ? be_true : be_false }
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index e2f222c0d34..cc071342d7c 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -41,6 +41,7 @@ describe API, api: true do
describe ".current_user" do
it "should return nil for an invalid token" do
env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
+ self.class.any_instance.stub(:doorkeeper_guard){ false }
current_user.should be_nil
end
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
new file mode 100644
index 00000000000..ddef99d77af
--- /dev/null
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let!(:user) { create(:user) }
+ let!(:application) { Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "https://app.com", :owner => user) }
+ let!(:token) { Doorkeeper::AccessToken.create! :application_id => application.id, :resource_owner_id => user.id }
+
+
+ describe "when unauthenticated" do
+ it "returns authentication success" do
+ get api("/user"), :access_token => token.token
+ response.status.should == 200
+ end
+ end
+
+ describe "when token invalid" do
+ it "returns authentication error" do
+ get api("/user"), :access_token => "123a"
+ response.status.should == 401
+ end
+ end
+
+ describe "authorization by private token" do
+ it "returns authentication success" do
+ get api("/user", user)
+ response.status.should == 200
+ end
+ end
+end
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index cbbd1e7de5a..5921b3e0698 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -44,7 +44,7 @@ describe API::API, api: true do
it 'should fail on missing project access for the project to fork' do
post api("/projects/fork/#{project.id}", user3)
response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ json_response['message'].should == '404 Project Not Found'
end
it 'should fail if forked project exists in the user namespace' do
@@ -58,7 +58,7 @@ describe API::API, api: true do
it 'should fail if project to fork from does not exist' do
post api('/projects/fork/424242', user)
response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ json_response['message'].should == '404 Project Not Found'
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 8dfd2cd650e..95f82463367 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -91,7 +91,8 @@ describe API::API, api: true do
it "should not create group, duplicate" do
post api("/groups", admin), {name: "Duplicate Test", path: group2.path}
- response.status.should == 404
+ response.status.should == 400
+ response.message.should == "Bad Request"
end
it "should return 400 bad request error if name not given" do
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 7aa53787aed..429824e829a 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -131,4 +131,58 @@ describe API::API, api: true do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
end
end
+
+ describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do
+ context 'when noteable is an Issue' do
+ it 'should return modified note' do
+ put api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user), body: 'Hello!'
+ response.status.should == 200
+ json_response['body'].should == 'Hello!'
+ end
+
+ it 'should return a 404 error when note id not found' do
+ put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
+ body: 'Hello!'
+ response.status.should == 404
+ end
+
+ it 'should return a 400 bad request error if body not given' do
+ put api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user)
+ response.status.should == 400
+ end
+ end
+
+ context 'when noteable is a Snippet' do
+ it 'should return modified note' do
+ put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/#{snippet_note.id}", user), body: 'Hello!'
+ response.status.should == 200
+ json_response['body'].should == 'Hello!'
+ end
+
+ it 'should return a 404 error when note id not found' do
+ put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/123", user), body: "Hello!"
+ response.status.should == 404
+ end
+ end
+
+ context 'when noteable is a Merge Request' do
+ it 'should return modified note' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
+ "notes/#{merge_request_note.id}", user), body: 'Hello!'
+ response.status.should == 200
+ json_response['body'].should == 'Hello!'
+ end
+
+ it 'should return a 404 error when note id not found' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
+ "notes/123", user), body: "Hello!"
+ response.status.should == 404
+ end
+ end
+ end
+
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 2c4b68c10b6..79865f15f06 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -198,8 +198,6 @@ describe API::API, api: true do
it 'should respond with 400 on failure' do
post api("/projects/user/#{user.id}", admin)
response.status.should == 400
- json_response['message']['creator'].should == ['can\'t be blank']
- json_response['message']['namespace'].should == ['can\'t be blank']
json_response['message']['name'].should == [
'can\'t be blank',
'is too short (minimum is 0 characters)',
@@ -291,7 +289,7 @@ describe API::API, api: true do
it "should return a 404 error if not found" do
get api("/projects/42", user)
response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ json_response['message'].should == '404 Project Not Found'
end
it "should return a 404 error if user is not a member" do
@@ -342,7 +340,7 @@ describe API::API, api: true do
it "should return a 404 error if not found" do
get api("/projects/42/events", user)
response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ json_response['message'].should == '404 Project Not Found'
end
it "should return a 404 error if user is not a member" do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index e6505040317..f149f3f62a9 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -430,6 +430,26 @@ describe Projects::TreeController, "routing" do
end
end
+describe Projects::EditTreeController, 'routing' do
+ it 'to #show' do
+ get('/gitlab/gitlabhq/edit/master/app/models/project.rb').should(
+ route_to('projects/edit_tree#show',
+ project_id: 'gitlab/gitlabhq',
+ id: 'master/app/models/project.rb'))
+ get('/gitlab/gitlabhq/edit/master/app/models/project.rb/preview').should(
+ route_to('projects/edit_tree#show',
+ project_id: 'gitlab/gitlabhq',
+ id: 'master/app/models/project.rb/preview'))
+ end
+
+ it 'to #preview' do
+ post('/gitlab/gitlabhq/edit/master/app/models/project.rb/preview').should(
+ route_to('projects/edit_tree#preview',
+ project_id: 'gitlab/gitlabhq',
+ id: 'master/app/models/project.rb'))
+ end
+end
+
# project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/}
# POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/}
# project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/}
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 9f294152053..35c7aac94df 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -47,10 +47,10 @@ describe MergeRequests::RefreshService do
reload_mrs
end
- it { @merge_request.notes.should be_empty }
+ it { @merge_request.notes.last.note.should include('changed to merged') }
it { @merge_request.should be_merged }
it { @fork_merge_request.should be_merged }
- it { @fork_merge_request.notes.should be_empty }
+ it { @fork_merge_request.notes.last.note.should include('changed to merged') }
end
context 'push to fork repo source branch' do
@@ -61,7 +61,7 @@ describe MergeRequests::RefreshService do
it { @merge_request.notes.should be_empty }
it { @merge_request.should be_open }
- it { @fork_merge_request.notes.should_not be_empty }
+ it { @fork_merge_request.notes.last.note.should include('new commit') }
it { @fork_merge_request.should be_open }
end
@@ -84,7 +84,7 @@ describe MergeRequests::RefreshService do
reload_mrs
end
- it { @merge_request.notes.should be_empty }
+ it { @merge_request.notes.last.note.should include('changed to merged') }
it { @merge_request.should be_merged }
it { @fork_merge_request.should be_open }
it { @fork_merge_request.notes.should be_empty }
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f8377650e0a..e305536f7ee 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -116,6 +116,7 @@ describe NotificationService do
should_email(note.noteable.assignee_id)
should_not_email(note.author_id)
+ should_not_email(@u_mentioned.id)
should_not_email(@u_disabled.id)
should_not_email(@u_not_mentioned.id)
notification.new_note(note)
@@ -168,6 +169,12 @@ describe NotificationService do
notification.new_note(note)
end
+ it do
+ @u_committer.update_attributes(notification_level: Notification::N_MENTION)
+ should_not_email(@u_committer.id, note)
+ notification.new_note(note)
+ end
+
def should_email(user_id, n)
Notify.should_receive(:note_commit_email).with(user_id, n.id)
end
@@ -190,11 +197,18 @@ describe NotificationService do
it do
should_email(issue.assignee_id)
should_email(@u_watcher.id)
+ should_not_email(@u_mentioned.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.new_issue(issue, @u_disabled)
end
+ it do
+ issue.assignee.update_attributes(notification_level: Notification::N_MENTION)
+ should_not_email(issue.assignee_id)
+ notification.new_issue(issue, @u_disabled)
+ end
+
def should_email(user_id)
Notify.should_receive(:new_issue_email).with(user_id, issue.id)
end
@@ -391,7 +405,7 @@ describe NotificationService do
@u_watcher = create(:user, notification_level: Notification::N_WATCH)
@u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
@u_disabled = create(:user, notification_level: Notification::N_DISABLED)
- @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING)
+ @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION)
@u_committer = create(:user, username: 'committer')
@u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING)
diff --git a/vendor/assets/javascripts/chart-lib.min.js b/vendor/assets/javascripts/chart-lib.min.js
index 626e6c3cdb9..626e6c3cdb9 100755..100644
--- a/vendor/assets/javascripts/chart-lib.min.js
+++ b/vendor/assets/javascripts/chart-lib.min.js
diff --git a/vendor/plugins/.gitkeep b/vendor/plugins/.gitkeep
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/vendor/plugins/.gitkeep
+++ /dev/null